View Javadoc
1   /*
2    * Copyright (c) 2002-2025 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.css;
16  
17  import static org.htmlunit.BrowserVersionFeatures.JS_CLIENTHEIGHT_INPUT_17;
18  import static org.htmlunit.BrowserVersionFeatures.JS_CLIENTHEIGHT_INPUT_18;
19  import static org.htmlunit.BrowserVersionFeatures.JS_CLIENTHEIGHT_RADIO_CHECKBOX_10;
20  import static org.htmlunit.BrowserVersionFeatures.JS_CLIENTHEIGHT_RADIO_CHECKBOX_14;
21  import static org.htmlunit.BrowserVersionFeatures.JS_CLIENTHEIGHT_RB_17;
22  import static org.htmlunit.BrowserVersionFeatures.JS_CLIENTHEIGHT_RT_9;
23  import static org.htmlunit.BrowserVersionFeatures.JS_CLIENTHEIGHT_RUBY_17;
24  import static org.htmlunit.BrowserVersionFeatures.JS_CLIENTWIDTH_INPUT_TEXT_154;
25  import static org.htmlunit.BrowserVersionFeatures.JS_CLIENTWIDTH_INPUT_TEXT_173;
26  import static org.htmlunit.BrowserVersionFeatures.JS_CLIENTWIDTH_RADIO_CHECKBOX_10;
27  import static org.htmlunit.BrowserVersionFeatures.JS_CLIENTWIDTH_RADIO_CHECKBOX_14;
28  import static org.htmlunit.css.CssStyleSheet.ABSOLUTE;
29  import static org.htmlunit.css.CssStyleSheet.AUTO;
30  import static org.htmlunit.css.CssStyleSheet.BLOCK;
31  import static org.htmlunit.css.CssStyleSheet.FIXED;
32  import static org.htmlunit.css.CssStyleSheet.INHERIT;
33  import static org.htmlunit.css.CssStyleSheet.INLINE;
34  import static org.htmlunit.css.CssStyleSheet.NONE;
35  import static org.htmlunit.css.CssStyleSheet.RELATIVE;
36  import static org.htmlunit.css.CssStyleSheet.SCROLL;
37  import static org.htmlunit.css.CssStyleSheet.STATIC;
38  
39  import java.util.EnumSet;
40  import java.util.HashSet;
41  import java.util.Map;
42  import java.util.Set;
43  import java.util.SortedMap;
44  import java.util.TreeMap;
45  
46  import org.htmlunit.BrowserVersion;
47  import org.htmlunit.BrowserVersionFeatures;
48  import org.htmlunit.Page;
49  import org.htmlunit.SgmlPage;
50  import org.htmlunit.WebWindow;
51  import org.htmlunit.css.CssPixelValueConverter.CssValue;
52  import org.htmlunit.css.StyleAttributes.Definition;
53  import org.htmlunit.cssparser.dom.AbstractCSSRuleImpl;
54  import org.htmlunit.cssparser.dom.CSSStyleDeclarationImpl;
55  import org.htmlunit.cssparser.dom.Property;
56  import org.htmlunit.cssparser.parser.selector.Selector;
57  import org.htmlunit.cssparser.parser.selector.SelectorSpecificity;
58  import org.htmlunit.html.BaseFrameElement;
59  import org.htmlunit.html.DomElement;
60  import org.htmlunit.html.DomNode;
61  import org.htmlunit.html.DomText;
62  import org.htmlunit.html.HtmlAbbreviated;
63  import org.htmlunit.html.HtmlAcronym;
64  import org.htmlunit.html.HtmlAddress;
65  import org.htmlunit.html.HtmlArticle;
66  import org.htmlunit.html.HtmlAside;
67  import org.htmlunit.html.HtmlBaseFont;
68  import org.htmlunit.html.HtmlBidirectionalIsolation;
69  import org.htmlunit.html.HtmlBidirectionalOverride;
70  import org.htmlunit.html.HtmlBig;
71  import org.htmlunit.html.HtmlBody;
72  import org.htmlunit.html.HtmlBold;
73  import org.htmlunit.html.HtmlButton;
74  import org.htmlunit.html.HtmlButtonInput;
75  import org.htmlunit.html.HtmlCanvas;
76  import org.htmlunit.html.HtmlCenter;
77  import org.htmlunit.html.HtmlCheckBoxInput;
78  import org.htmlunit.html.HtmlCitation;
79  import org.htmlunit.html.HtmlCode;
80  import org.htmlunit.html.HtmlData;
81  import org.htmlunit.html.HtmlDefinition;
82  import org.htmlunit.html.HtmlDefinitionDescription;
83  import org.htmlunit.html.HtmlDefinitionTerm;
84  import org.htmlunit.html.HtmlDivision;
85  import org.htmlunit.html.HtmlElement;
86  import org.htmlunit.html.HtmlElement.DisplayStyle;
87  import org.htmlunit.html.HtmlEmphasis;
88  import org.htmlunit.html.HtmlFigure;
89  import org.htmlunit.html.HtmlFigureCaption;
90  import org.htmlunit.html.HtmlFileInput;
91  import org.htmlunit.html.HtmlFooter;
92  import org.htmlunit.html.HtmlHeader;
93  import org.htmlunit.html.HtmlHeading1;
94  import org.htmlunit.html.HtmlHeading2;
95  import org.htmlunit.html.HtmlHeading3;
96  import org.htmlunit.html.HtmlHeading4;
97  import org.htmlunit.html.HtmlHeading5;
98  import org.htmlunit.html.HtmlHeading6;
99  import org.htmlunit.html.HtmlHiddenInput;
100 import org.htmlunit.html.HtmlImage;
101 import org.htmlunit.html.HtmlInlineFrame;
102 import org.htmlunit.html.HtmlInput;
103 import org.htmlunit.html.HtmlItalic;
104 import org.htmlunit.html.HtmlKeyboard;
105 import org.htmlunit.html.HtmlLayer;
106 import org.htmlunit.html.HtmlLegend;
107 import org.htmlunit.html.HtmlMain;
108 import org.htmlunit.html.HtmlMark;
109 import org.htmlunit.html.HtmlNav;
110 import org.htmlunit.html.HtmlNoBreak;
111 import org.htmlunit.html.HtmlNoEmbed;
112 import org.htmlunit.html.HtmlNoFrames;
113 import org.htmlunit.html.HtmlNoLayer;
114 import org.htmlunit.html.HtmlNoScript;
115 import org.htmlunit.html.HtmlOutput;
116 import org.htmlunit.html.HtmlPage;
117 import org.htmlunit.html.HtmlPasswordInput;
118 import org.htmlunit.html.HtmlPlainText;
119 import org.htmlunit.html.HtmlRadioButtonInput;
120 import org.htmlunit.html.HtmlRb;
121 import org.htmlunit.html.HtmlResetInput;
122 import org.htmlunit.html.HtmlRp;
123 import org.htmlunit.html.HtmlRt;
124 import org.htmlunit.html.HtmlRtc;
125 import org.htmlunit.html.HtmlRuby;
126 import org.htmlunit.html.HtmlS;
127 import org.htmlunit.html.HtmlSample;
128 import org.htmlunit.html.HtmlSection;
129 import org.htmlunit.html.HtmlSelect;
130 import org.htmlunit.html.HtmlSlot;
131 import org.htmlunit.html.HtmlSmall;
132 import org.htmlunit.html.HtmlSpan;
133 import org.htmlunit.html.HtmlStrike;
134 import org.htmlunit.html.HtmlStrong;
135 import org.htmlunit.html.HtmlSubmitInput;
136 import org.htmlunit.html.HtmlSubscript;
137 import org.htmlunit.html.HtmlSummary;
138 import org.htmlunit.html.HtmlSuperscript;
139 import org.htmlunit.html.HtmlTableCell;
140 import org.htmlunit.html.HtmlTableRow;
141 import org.htmlunit.html.HtmlTeletype;
142 import org.htmlunit.html.HtmlTextArea;
143 import org.htmlunit.html.HtmlTextInput;
144 import org.htmlunit.html.HtmlTime;
145 import org.htmlunit.html.HtmlUnderlined;
146 import org.htmlunit.html.HtmlUnknownElement;
147 import org.htmlunit.html.HtmlVariable;
148 import org.htmlunit.html.HtmlWordBreak;
149 import org.htmlunit.platform.Platform;
150 import org.htmlunit.util.StringUtils;
151 
152 /**
153  * An object for a CSSStyleDeclaration, which is computed.
154  *
155  * @see org.htmlunit.javascript.host.Window#getComputedStyle(Object, String)
156  *
157  * @author Ahmed Ashour
158  * @author Marc Guillemot
159  * @author Ronald Brill
160  * @author Frank Danek
161  * @author Alex Gorbatovsky
162  * @author cd alexndr
163  */
164 @SuppressWarnings("PMD.AvoidDuplicateLiterals")
165 public class ComputedCssStyleDeclaration extends AbstractCssStyleDeclaration {
166 
167     /** The set of 'inheritable' definitions. */
168     private static final Set<Definition> INHERITABLE_DEFINITIONS = EnumSet.of(
169         Definition.BORDER_COLLAPSE,
170         Definition.BORDER_SPACING,
171         Definition.CAPTION_SIDE,
172         Definition.COLOR,
173         Definition.CURSOR,
174         Definition.DIRECTION,
175         Definition.EMPTY_CELLS,
176         Definition.FONT_FAMILY,
177         Definition.FONT_SIZE,
178         Definition.FONT_STYLE,
179         Definition.FONT_VARIANT,
180         Definition.FONT_WEIGHT,
181         Definition.FONT,
182         Definition.LETTER_SPACING,
183         Definition.LINE_HEIGHT,
184         Definition.LIST_STYLE_IMAGE,
185         Definition.LIST_STYLE_POSITION,
186         Definition.LIST_STYLE_TYPE,
187         Definition.LIST_STYLE,
188         Definition.ORPHANS,
189         Definition.QUOTES,
190         Definition.SPEAK,
191         Definition.TEXT_ALIGN,
192         Definition.TEXT_INDENT,
193         Definition.TEXT_TRANSFORM,
194         Definition.VISIBILITY,
195         Definition.WHITE_SPACE,
196         Definition.WIDOWS,
197         Definition.WORD_SPACING);
198 
199     /** Denotes a value which should be returned as is. */
200     public static final String EMPTY_FINAL = new String("");
201 
202     /** The computed, cached width of the element to which this computed style belongs (no padding, borders, etc.). */
203     private Integer width_;
204 
205     /**
206      * The computed, cached height of the element to which this computed style belongs (no padding, borders, etc.),
207      * taking child elements into account.
208      */
209     private Integer height_;
210 
211     /**
212      * The computed, cached height of the element to which this computed style belongs (no padding, borders, etc.),
213      * <b>not</b> taking child elements into account.
214      */
215     private Integer emptyHeight_;
216 
217     /** The computed, cached horizontal padding (left + right) of the element to which this computed style belongs. */
218     private Integer paddingHorizontal_;
219 
220     /** The computed, cached vertical padding (top + bottom) of the element to which this computed style belongs. */
221     private Integer paddingVertical_;
222 
223     /** The computed, cached horizontal border (left + right) of the element to which this computed style belongs. */
224     private Integer borderHorizontal_;
225 
226     /** The computed, cached vertical border (top + bottom) of the element to which this computed style belongs. */
227     private Integer borderVertical_;
228 
229     /** The computed, cached top of the element to which this computed style belongs. */
230     private Integer top_;
231 
232     /**
233      * Local modifications maintained here rather than in the element. We use a sorted
234      * map so that results are deterministic and thus easily testable.
235      */
236     private final SortedMap<String, StyleElement> localModifications_ = new TreeMap<>();
237 
238     /** The wrapped CSSStyleDeclaration */
239     private final ElementCssStyleDeclaration elementStyleDeclaration_;
240 
241     /**
242      * Ctor.
243      * @param styleDeclaration the {@link ElementCssStyleDeclaration} this is based on
244      */
245     public ComputedCssStyleDeclaration(final ElementCssStyleDeclaration styleDeclaration) {
246         super();
247         elementStyleDeclaration_ = styleDeclaration;
248         elementStyleDeclaration_.getDomElement().setDefaults(this);
249     }
250 
251     /**
252      * {@inheritDoc}
253      */
254     @Override
255     public String getStylePriority(final String name) {
256         return elementStyleDeclaration_.getStylePriority(name);
257     }
258 
259     /**
260      * {@inheritDoc}
261      */
262     @Override
263     public String getCssText() {
264         return elementStyleDeclaration_.getCssText();
265     }
266 
267     /**
268      * {@inheritDoc}
269      */
270     @Override
271     public String getStyleAttribute(final String name) {
272         final StyleElement element = getStyleElement(name);
273         if (element != null && element.getValue() != null) {
274             final String value = element.getValue();
275             if (!"content".equals(name)
276                     && !value.contains("url")) {
277                 return StringUtils.toRootLowerCase(value);
278             }
279             return value;
280         }
281         return "";
282     }
283 
284     /**
285      * {@inheritDoc}
286      */
287     @Override
288     public String getStyleAttribute(final Definition definition, final boolean getDefaultValueIfEmpty) {
289         final BrowserVersion browserVersion = getDomElement().getPage().getWebClient().getBrowserVersion();
290         final boolean isDefInheritable = INHERITABLE_DEFINITIONS.contains(definition);
291 
292         // to make the fuzzer happy the recursion was removed
293         final ComputedCssStyleDeclaration[] queue = {this};
294         String value = null;
295         while (queue[0] != null) {
296             value = getStyleAttributeWorker(definition, getDefaultValueIfEmpty,
297                         browserVersion, true, isDefInheritable, queue);
298         }
299 
300         return value;
301     }
302 
303     private static String getStyleAttributeWorker(final Definition definition,
304                 final boolean getDefaultValueIfEmpty, final BrowserVersion browserVersion,
305                 final boolean feature, final boolean isDefInheritable,
306                 final ComputedCssStyleDeclaration[] queue) {
307         final ComputedCssStyleDeclaration decl = queue[0];
308         queue[0] = null;
309 
310         final DomElement domElem = decl.getDomElement();
311         if (!domElem.isAttachedToPage() && feature) {
312             return EMPTY_FINAL;
313         }
314 
315         String value = decl.getStyleAttribute(definition.getAttributeName());
316         if (value.isEmpty()) {
317             final DomNode parent = domElem.getParentNode();
318             if (isDefInheritable && parent instanceof DomElement) {
319                 final WebWindow window = domElem.getPage().getEnclosingWindow();
320 
321                 queue[0] = window.getComputedStyle((DomElement) parent, null);
322             }
323             else if (getDefaultValueIfEmpty) {
324                 value = definition.getDefaultComputedValue(browserVersion);
325             }
326         }
327 
328         return value;
329     }
330 
331     /**
332      * @param toReturnIfEmptyOrDefault the value to return if empty or equals the {@code defaultValue}
333      * @param defaultValue the default value of the string
334      * @return the string, or {@code toReturnIfEmptyOrDefault}
335      */
336     private String getStyleAttribute(final Definition definition, final String toReturnIfEmptyOrDefault,
337             final String defaultValue) {
338         final DomElement domElement = getDomElement();
339 
340         if (!domElement.isAttachedToPage()) {
341             return EMPTY_FINAL;
342         }
343 
344         final boolean isDefInheritable = INHERITABLE_DEFINITIONS.contains(definition);
345 
346         // to make the fuzzer happy the recursion was removed
347         final BrowserVersion browserVersion = domElement.getPage().getWebClient().getBrowserVersion();
348         final ComputedCssStyleDeclaration[] queue = {this};
349         String value = null;
350         while (queue[0] != null) {
351             value = getStyleAttributeWorker(definition, false,
352                     browserVersion, true, isDefInheritable, queue);
353         }
354 
355         if (value == null || value.isEmpty() || value.equals(defaultValue)) {
356             return toReturnIfEmptyOrDefault;
357         }
358 
359         return value;
360     }
361 
362     /**
363      * {@inheritDoc}
364      */
365     @Override
366     public void setCssText(final String value) {
367         // read only
368     }
369 
370     /**
371      * {@inheritDoc}
372      */
373     @Override
374     public void setStyleAttribute(final String name, final String newValue, final String important) {
375         // read only
376     }
377 
378     /**
379      * {@inheritDoc}
380      */
381     @Override
382     public String removeStyleAttribute(final String name) {
383         // read only
384         return null;
385     }
386 
387     /**
388      * {@inheritDoc}
389      */
390     @Override
391     public int getLength() {
392         return elementStyleDeclaration_.getLength();
393     }
394 
395     /**
396      * @return the width
397      */
398     @Override
399     public String getWidth() {
400         if (NONE.equals(getDisplay())) {
401             return AUTO;
402         }
403 
404         final DomElement domElem = getDomElement();
405         if (!domElem.isAttachedToPage()) {
406             return "";
407         }
408 
409         final int windowWidth = domElem.getPage().getEnclosingWindow().getInnerWidth();
410         return CssPixelValueConverter.pixelString(domElem, new CssPixelValueConverter.CssValue(0, windowWidth) {
411             @Override
412             public String get(final ComputedCssStyleDeclaration style) {
413                 final String value = style.getStyleAttribute(Definition.WIDTH, true);
414                 if (StringUtils.isEmptyOrNull(value)) {
415                     final String position = getStyleAttribute(Definition.POSITION, true);
416                     if (ABSOLUTE.equals(position) || FIXED.equals(position)) {
417                         final String content = domElem.getVisibleText();
418                         // do this only for small content
419                         // at least for empty div's this is more correct
420                         if (null != content && content.length() < 13) {
421                             return (content.length() * 7) + "px";
422                         }
423                     }
424 
425                     int windowDefaultValue = getWindowDefaultValue();
426                     if (domElem instanceof HtmlBody) {
427                         windowDefaultValue -= 16;
428                     }
429                     return windowDefaultValue + "px";
430                 }
431                 else if (AUTO.equals(value)) {
432                     int windowDefaultValue = getWindowDefaultValue();
433                     if (domElem instanceof HtmlBody) {
434                         windowDefaultValue -= 16;
435                     }
436                     return windowDefaultValue + "px";
437                 }
438 
439                 return value;
440             }
441         });
442     }
443 
444     /**
445      * {@inheritDoc}
446      */
447     @Override
448     public String item(final int index) {
449         return elementStyleDeclaration_.item(index);
450     }
451 
452     /**
453      * {@inheritDoc}
454      */
455     @Override
456     public AbstractCSSRuleImpl getParentRule() {
457         return elementStyleDeclaration_.getParentRule();
458     }
459 
460     /**
461      * {@inheritDoc}
462      */
463     @Override
464     public StyleElement getStyleElement(final String name) {
465         final StyleElement existent = elementStyleDeclaration_.getStyleElement(name);
466 
467         final StyleElement localStyleMod = localModifications_.get(name);
468         if (localStyleMod == null) {
469             return existent;
470         }
471 
472         if (existent == null) {
473             // Local modifications represent either default style elements or style elements
474             // defined in stylesheets; either way, they shouldn't overwrite any style
475             // elements derived directly from the HTML element's "style" attribute.
476             return localStyleMod;
477         }
478 
479         // replace if !IMPORTANT
480         if (StyleElement.PRIORITY_IMPORTANT.equals(localStyleMod.getPriority())) {
481             if (existent.isImportant()) {
482                 if (existent.getSpecificity().compareTo(localStyleMod.getSpecificity()) < 0) {
483                     return localStyleMod;
484                 }
485             }
486             else {
487                 return localStyleMod;
488             }
489         }
490         return existent;
491     }
492 
493     /**
494      * {@inheritDoc}
495      */
496     @Override
497     public StyleElement getStyleElementCaseInSensitive(final String name) {
498         return elementStyleDeclaration_.getStyleElementCaseInSensitive(name);
499     }
500 
501     /**
502      * {@inheritDoc}
503      */
504     @Override
505     public Map<String, StyleElement> getStyleMap() {
506         return elementStyleDeclaration_.getStyleMap();
507     }
508 
509     /**
510      * @return the {@link DomElement} the backing {@link ElementCssStyleDeclaration}
511      *         is associated with
512      */
513     public DomElement getDomElement() {
514         return elementStyleDeclaration_.getDomElement();
515     }
516 
517     /**
518      * {@inheritDoc}
519      */
520     @Override
521     public String getBackgroundAttachment() {
522         return defaultIfEmpty(super.getBackgroundAttachment(), Definition.BACKGROUND_ATTACHMENT);
523     }
524 
525     /**
526      * {@inheritDoc}
527      */
528     @Override
529     public String getBackgroundColor() {
530         if (!getDomElement().isAttachedToPage()) {
531             return EMPTY_FINAL;
532         }
533 
534         final String value = super.getBackgroundColor();
535         if (StringUtils.isEmptyOrNull(value)) {
536             return Definition.BACKGROUND_COLOR.getDefaultComputedValue(getBrowserVersion());
537         }
538         return CssColors.toRGBColor(value);
539     }
540 
541     /**
542      * {@inheritDoc}
543      */
544     @Override
545     public String getBackgroundImage() {
546         return defaultIfEmpty(super.getBackgroundImage(), Definition.BACKGROUND_IMAGE);
547     }
548 
549     /**
550      * Gets the {@code backgroundPosition} style attribute.
551      * @return the style attribute
552      */
553     @Override
554     public String getBackgroundPosition() {
555         return defaultIfEmpty(super.getBackgroundPosition(), Definition.BACKGROUND_POSITION);
556     }
557 
558     /**
559      * {@inheritDoc}
560      */
561     @Override
562     public String getBackgroundRepeat() {
563         return defaultIfEmpty(super.getBackgroundRepeat(), Definition.BACKGROUND_REPEAT);
564     }
565 
566     /**
567      * {@inheritDoc}
568      */
569     @Override
570     public String getBlockSize() {
571         if (NONE.equals(getDisplay())) {
572             return defaultIfEmpty(super.getBlockSize(), Definition.BLOCK_SIZE);
573         }
574 
575         final DomElement domElem = getDomElement();
576         if (!domElem.isAttachedToPage()) {
577             return defaultIfEmpty(super.getBlockSize(), Definition.BLOCK_SIZE);
578         }
579 
580         return CssPixelValueConverter.pixelString(domElem, new CssPixelValueConverter.CssValue(0, 0) {
581             @Override
582             public String get(final ComputedCssStyleDeclaration style) {
583                 final String value = style.getStyleAttribute(Definition.HEIGHT, true);
584                 if (StringUtils.isEmptyOrNull(value)) {
585                     final String content = domElem.getVisibleText();
586                     // do this only for small content
587                     // at least for empty div's this is more correct
588                     if (null == content) {
589                         return getDefaultValue() + "px";
590                     }
591                     return getEmptyHeight(domElem) + "px";
592                 }
593                 return value;
594             }
595         });
596     }
597 
598     /**
599      * {@inheritDoc}
600      */
601     @Override
602     public String getBorderBottomColor() {
603         return defaultIfEmpty(super.getBorderBottomColor(), Definition.BORDER_BOTTOM_COLOR);
604     }
605 
606     /**
607      * {@inheritDoc}
608      */
609     @Override
610     public String getBorderBottomStyle() {
611         return defaultIfEmpty(super.getBorderBottomStyle(), Definition.BORDER_BOTTOM_STYLE);
612     }
613 
614     /**
615      * {@inheritDoc}
616      */
617     @Override
618     public String getBorderBottomWidth() {
619         return pixelString(defaultIfEmpty(super.getBorderBottomWidth(), Definition.BORDER_BOTTOM_WIDTH));
620     }
621 
622     /**
623      * {@inheritDoc}
624      */
625     @Override
626     public String getBorderLeftColor() {
627         return defaultIfEmpty(super.getBorderLeftColor(), Definition.BORDER_LEFT_COLOR);
628     }
629 
630     /**
631      * {@inheritDoc}
632      */
633     @Override
634     public String getBorderLeftStyle() {
635         return defaultIfEmpty(super.getBorderLeftStyle(), Definition.BORDER_LEFT_STYLE);
636     }
637 
638     /**
639      * {@inheritDoc}
640      */
641     @Override
642     public String getBorderLeftWidth() {
643         return pixelString(defaultIfEmpty(super.getBorderLeftWidth(), "0px", null));
644     }
645 
646     /**
647      * {@inheritDoc}
648      */
649     @Override
650     public String getBorderRightColor() {
651         return defaultIfEmpty(super.getBorderRightColor(), "rgb(0, 0, 0)", null);
652     }
653 
654     /**
655      * {@inheritDoc}
656      */
657     @Override
658     public String getBorderRightStyle() {
659         return defaultIfEmpty(super.getBorderRightStyle(), NONE, null);
660     }
661 
662     /**
663      * {@inheritDoc}
664      */
665     @Override
666     public String getBorderRightWidth() {
667         return pixelString(defaultIfEmpty(super.getBorderRightWidth(), "0px", null));
668     }
669 
670     /**
671      * {@inheritDoc}
672      */
673     @Override
674     public String getBorderTopColor() {
675         return defaultIfEmpty(super.getBorderTopColor(), "rgb(0, 0, 0)", null);
676     }
677 
678     /**
679      * {@inheritDoc}
680      */
681     @Override
682     public String getBorderTopStyle() {
683         return defaultIfEmpty(super.getBorderTopStyle(), NONE, null);
684     }
685 
686     /**
687      * {@inheritDoc}
688      */
689     @Override
690     public String getBorderTopWidth() {
691         return pixelString(defaultIfEmpty(super.getBorderTopWidth(), "0px", null));
692     }
693 
694     /**
695      * @return the bottom setting
696      */
697     @Override
698     public String getBottom() {
699         return getStyleAttribute(Definition.BOTTOM, AUTO, null);
700     }
701 
702     /**
703      * @return the color setting
704      */
705     @Override
706     public String getColor() {
707         final String value = getStyleAttribute(Definition.COLOR, "rgb(0, 0, 0)", null);
708         return CssColors.toRGBColor(value);
709     }
710 
711     /**
712      * {@inheritDoc}
713      */
714     @Override
715     public String getCssFloat() {
716         return defaultIfEmpty(super.getCssFloat(), Definition.CSS_FLOAT);
717     }
718 
719     /**
720      * @return the display setting
721      */
722     @Override
723     public String getDisplay() {
724         final DomElement domElem = getDomElement();
725         if (!domElem.isAttachedToPage()) {
726             return "";
727         }
728 
729         if (domElem instanceof HtmlElement) {
730             if (((HtmlElement) domElem).isHidden()) {
731                 return DisplayStyle.NONE.value();
732             }
733         }
734 
735         // don't use defaultIfEmpty for performance
736         // (no need to calculate the default if not empty)
737         final String value = getStyleAttribute(Definition.DISPLAY.getAttributeName());
738         if (StringUtils.isEmptyOrNull(value)) {
739             if (domElem instanceof HtmlElement) {
740                 return ((HtmlElement) domElem).getDefaultStyleDisplay().value();
741             }
742             return "";
743         }
744         return value;
745     }
746 
747     /**
748      * @return the font setting
749      */
750     @Override
751     public String getFont() {
752         final DomElement domElem = getDomElement();
753         if (domElem.isAttachedToPage()) {
754             return getStyleAttribute(Definition.FONT, true);
755         }
756         return "";
757     }
758 
759     /**
760      * @return the font family setting
761      */
762     @Override
763     public String getFontFamily() {
764         return getStyleAttribute(Definition.FONT_FAMILY, true);
765     }
766 
767     /**
768      * @return the font size setting
769      */
770     @Override
771     public String getFontSize() {
772         return getStyleAttribute(Definition.FONT_SIZE, true);
773     }
774 
775     /**
776      * {@inheritDoc}
777      */
778     @Override
779     public String getLineHeight() {
780         return defaultIfEmpty(super.getLineHeight(), Definition.LINE_HEIGHT);
781     }
782 
783     /**
784      * {@inheritDoc}
785      */
786     @Override
787     public String getHeight() {
788         if (NONE.equals(getDisplay())) {
789             return AUTO;
790         }
791 
792         final DomElement elem = getDomElement();
793         if (!elem.isAttachedToPage()) {
794             return "";
795         }
796 
797         final ComputedCssStyleDeclaration style = elem.getPage().getEnclosingWindow().getComputedStyle(elem, null);
798         final String styleValue = style.getStyleAttribute(Definition.HEIGHT, true);
799 
800         if (styleValue == null || styleValue.isEmpty() || AUTO.equals(styleValue) || styleValue.endsWith("%")) {
801             final String calculatedHeight = style.getCalculatedHeight(false, false) + "px";
802             return calculatedHeight;
803         }
804 
805         if (styleValue.endsWith("px")) {
806             return styleValue;
807         }
808 
809         return CssPixelValueConverter.pixelValue(styleValue) + "px";
810     }
811 
812     /**
813      * {@inheritDoc}
814      */
815     @Override
816     public String getLeft() {
817         if (NONE.equals(getDisplay())) {
818             return AUTO;
819         }
820 
821         final DomElement elem = getDomElement();
822         if (!elem.isAttachedToPage()) {
823             return "";
824         }
825 
826         final String superLeft = super.getLeft();
827         if (!superLeft.endsWith("%")) {
828             return defaultIfEmpty(superLeft, AUTO, null);
829         }
830 
831         return CssPixelValueConverter.pixelString(elem, new CssPixelValueConverter.CssValue(0, 0) {
832             @Override
833             public String get(final ComputedCssStyleDeclaration style) {
834                 if (style.getDomElement() == elem) {
835                     return style.getStyleAttribute(Definition.LEFT, true);
836                 }
837                 return style.getStyleAttribute(Definition.WIDTH, true);
838             }
839         });
840     }
841 
842     /**
843      * {@inheritDoc}
844      */
845     @Override
846     public String getLetterSpacing() {
847         return defaultIfEmpty(super.getLetterSpacing(), "normal", null);
848     }
849 
850     /**
851      * {@inheritDoc}
852      */
853     @Override
854     public String getMargin() {
855         return defaultIfEmpty(super.getMargin(), Definition.MARGIN, true);
856     }
857 
858     /**
859      * {@inheritDoc}
860      */
861     @Override
862     public String getMarginBottom() {
863         return pixelString(defaultIfEmpty(super.getMarginBottom(), "0px", null));
864     }
865 
866     /**
867      * {@inheritDoc}
868      */
869     @Override
870     public String getMarginLeft() {
871         return getMarginX(super.getMarginLeft(), Definition.MARGIN_LEFT);
872     }
873 
874     /**
875      * {@inheritDoc}
876      */
877     @Override
878     public String getMarginRight() {
879         return getMarginX(super.getMarginRight(), Definition.MARGIN_RIGHT);
880     }
881 
882     private String getMarginX(final String superMarginX, final Definition definition) {
883         if (!superMarginX.endsWith("%")) {
884             return pixelString(defaultIfEmpty(superMarginX, "0px", null));
885         }
886         final DomElement element = getDomElement();
887         if (!element.isAttachedToPage()) {
888             return "";
889         }
890 
891         final int windowWidth = element.getPage().getEnclosingWindow().getInnerWidth();
892         return CssPixelValueConverter
893                 .pixelString(element, new CssPixelValueConverter.CssValue(0, windowWidth) {
894                     @Override
895                     public String get(final ComputedCssStyleDeclaration style) {
896                         if (style.getDomElement() == element) {
897                             return style.getStyleAttribute(definition, true);
898                         }
899                         return style.getStyleAttribute(Definition.WIDTH, true);
900                     }
901                 });
902     }
903 
904     /**
905      * {@inheritDoc}
906      */
907     @Override
908     public String getMarginTop() {
909         return pixelString(defaultIfEmpty(super.getMarginTop(), "0px", null));
910     }
911 
912     /**
913      * {@inheritDoc}
914      */
915     @Override
916     public String getMaxHeight() {
917         return defaultIfEmpty(super.getMaxHeight(), NONE, null);
918     }
919 
920     /**
921      * {@inheritDoc}
922      */
923     @Override
924     public String getMaxWidth() {
925         return defaultIfEmpty(super.getMaxWidth(), NONE, null);
926     }
927 
928     /**
929      * {@inheritDoc}
930      */
931     @Override
932     public String getMinHeight() {
933         return defaultIfEmpty(super.getMinHeight(), "0px", null);
934     }
935 
936     /**
937      * {@inheritDoc}
938      */
939     @Override
940     public String getMinWidth() {
941         return defaultIfEmpty(super.getMinWidth(), "0px", null);
942     }
943 
944     /**
945      * {@inheritDoc}
946      */
947     @Override
948     public String getOpacity() {
949         return defaultIfEmpty(super.getOpacity(), "1", null);
950     }
951 
952     /**
953      * {@inheritDoc}
954      */
955     @Override
956     public String getOrphans() {
957         return defaultIfEmpty(super.getOrphans(), Definition.ORPHANS);
958     }
959 
960     /**
961      * {@inheritDoc}
962      */
963     @Override
964     public String getOutlineWidth() {
965         return defaultIfEmpty(super.getOutlineWidth(), "0px", null);
966     }
967 
968     /**
969      * {@inheritDoc}
970      */
971     @Override
972     public String getPadding() {
973         return defaultIfEmpty(super.getPadding(), Definition.PADDING, true);
974     }
975 
976     /**
977      * {@inheritDoc}
978      */
979     @Override
980     public String getPaddingBottom() {
981         return pixelString(defaultIfEmpty(super.getPaddingBottom(), "0px", null));
982     }
983 
984     /**
985      * {@inheritDoc}
986      */
987     @Override
988     public String getPaddingLeft() {
989         return pixelString(defaultIfEmpty(super.getPaddingLeft(), "0px", null));
990     }
991 
992     /**
993      * {@inheritDoc}
994      */
995     @Override
996     public String getPaddingRight() {
997         return pixelString(defaultIfEmpty(super.getPaddingRight(), "0px", null));
998     }
999 
1000     /**
1001      * {@inheritDoc}
1002      */
1003     @Override
1004     public String getPaddingTop() {
1005         return pixelString(defaultIfEmpty(super.getPaddingTop(), "0px", null));
1006     }
1007 
1008     /**
1009      * {@inheritDoc}
1010      */
1011     @Override
1012     public String getRight() {
1013         return defaultIfEmpty(super.getRight(), AUTO, null);
1014     }
1015 
1016     /**
1017      * {@inheritDoc}
1018      */
1019     @Override
1020     public String getTextIndent() {
1021         return defaultIfEmpty(super.getTextIndent(), "0px", null);
1022     }
1023 
1024     /**
1025      * {@inheritDoc}
1026      */
1027     @Override
1028     public String getTop() {
1029         if (NONE.equals(getDisplay())) {
1030             return AUTO;
1031         }
1032 
1033         final DomElement elem = getDomElement();
1034         if (!elem.isAttachedToPage()) {
1035             return "";
1036         }
1037 
1038         final String superTop = super.getTop();
1039         if (!superTop.endsWith("%")) {
1040             return defaultIfEmpty(superTop, Definition.TOP);
1041         }
1042 
1043         return CssPixelValueConverter.pixelString(elem, new CssPixelValueConverter.CssValue(0, 0) {
1044             @Override
1045             public String get(final ComputedCssStyleDeclaration style) {
1046                 if (style.getDomElement() == elem) {
1047                     return style.getStyleAttribute(Definition.TOP, true);
1048                 }
1049                 return style.getStyleAttribute(Definition.HEIGHT, true);
1050             }
1051         });
1052     }
1053 
1054     /**
1055      * Returns the computed top (Y coordinate), relative to the node's parent's top edge.
1056      * @param includeMargin whether or not to take the margin into account in the calculation
1057      * @param includeBorder whether or not to take the border into account in the calculation
1058      * @param includePadding whether or not to take the padding into account in the calculation
1059      * @return the computed top (Y coordinate), relative to the node's parent's top edge
1060      */
1061     public int getTop(final boolean includeMargin, final boolean includeBorder, final boolean includePadding) {
1062         Integer cachedTop = getCachedTop();
1063 
1064         int top = 0;
1065         if (null == cachedTop) {
1066             final String position = getPositionWithInheritance();
1067             if (ABSOLUTE.equals(position) || FIXED.equals(position)) {
1068                 top = getTopForAbsolutePositionWithInheritance();
1069             }
1070             else if (getDomElement() instanceof HtmlTableCell) {
1071                 top = 0;
1072             }
1073             else {
1074                 // Calculate the vertical displacement caused by *previous* siblings.
1075                 DomNode prev = getDomElement().getPreviousSibling();
1076                 boolean prevHadComputedTop = false;
1077                 while (prev != null && !prevHadComputedTop) {
1078                     if (prev instanceof HtmlElement) {
1079                         final ComputedCssStyleDeclaration style =
1080                                 prev.getPage().getEnclosingWindow().getComputedStyle((DomElement) prev, null);
1081 
1082                         // only previous block elements are counting
1083                         final String display = style.getDisplay();
1084                         if (isBlock(display)) {
1085                             int prevTop = 0;
1086                             final Integer eCachedTop = style.getCachedTop();
1087                             if (eCachedTop == null) {
1088                                 final String prevPosition = style.getPositionWithInheritance();
1089                                 if (ABSOLUTE.equals(prevPosition) || FIXED.equals(prevPosition)) {
1090                                     prevTop += style.getTopForAbsolutePositionWithInheritance();
1091                                 }
1092                                 else {
1093                                     if (RELATIVE.equals(prevPosition)) {
1094                                         final String t = style.getTopWithInheritance();
1095                                         prevTop += CssPixelValueConverter.pixelValue(t);
1096                                     }
1097                                 }
1098                             }
1099                             else {
1100                                 prevHadComputedTop = true;
1101                                 prevTop += eCachedTop.intValue();
1102                             }
1103                             prevTop += style.getCalculatedHeight(true, true);
1104                             final int margin = CssPixelValueConverter.pixelValue(style.getMarginTop());
1105                             prevTop += margin;
1106                             top += prevTop;
1107                         }
1108                     }
1109                     prev = prev.getPreviousSibling();
1110                 }
1111                 // If the position is relative, we also need to add the specified "top" displacement.
1112                 if (RELATIVE.equals(position)) {
1113                     final String t = getTopWithInheritance();
1114                     top += CssPixelValueConverter.pixelValue(t);
1115                 }
1116             }
1117             cachedTop = Integer.valueOf(top);
1118             setCachedTop(cachedTop);
1119         }
1120         else {
1121             top = cachedTop.intValue();
1122         }
1123 
1124         if (includeMargin) {
1125             final int margin = CssPixelValueConverter.pixelValue(getMarginTop());
1126             top += margin;
1127         }
1128 
1129         if (includeBorder) {
1130             final int border = CssPixelValueConverter.pixelValue(getBorderTopWidth());
1131             top += border;
1132         }
1133 
1134         if (includePadding) {
1135             final int padding = getPaddingTopValue();
1136             top += padding;
1137         }
1138 
1139         return top;
1140     }
1141 
1142     private static boolean isBlock(final String display) {
1143         return display != null
1144                 && !INLINE.equals(display)
1145                 && !NONE.equals(display);
1146     }
1147 
1148     /**
1149      * Returns the CSS {@code top} attribute, replacing inherited values with the actual parent values.
1150      * @return the CSS {@code top} attribute, replacing inherited values with the actual parent values
1151      */
1152     public String getTopWithInheritance() {
1153         String top = getTop();
1154         if (INHERIT.equals(top)) {
1155             final HtmlElement parent = (HtmlElement) getDomElement().getParentNode();
1156             if (parent == null) {
1157                 top = AUTO;
1158             }
1159             else {
1160                 final ComputedCssStyleDeclaration style =
1161                         parent.getPage().getEnclosingWindow().getComputedStyle(parent, null);
1162                 top = style.getTopWithInheritance();
1163             }
1164         }
1165         return top;
1166     }
1167 
1168     /**
1169      * Returns the CSS {@code bottom} attribute, replacing inherited values with the actual parent values.
1170      * @return the CSS {@code bottom} attribute, replacing inherited values with the actual parent values
1171      */
1172     public String getBottomWithInheritance() {
1173         String bottom = getBottom();
1174         if (INHERIT.equals(bottom)) {
1175             final DomNode parent = getDomElement().getParentNode();
1176             if (parent == null) {
1177                 bottom = AUTO;
1178             }
1179             else {
1180                 final ComputedCssStyleDeclaration style =
1181                         parent.getPage().getEnclosingWindow().getComputedStyle((DomElement) parent, null);
1182                 bottom = style.getBottomWithInheritance();
1183             }
1184         }
1185         return bottom;
1186     }
1187 
1188     /**
1189      * {@inheritDoc}
1190      */
1191     @Override
1192     public String getVerticalAlign() {
1193         return defaultIfEmpty(super.getVerticalAlign(), "baseline", null);
1194     }
1195 
1196     /**
1197      * {@inheritDoc}
1198      */
1199     @Override
1200     public String getWidows() {
1201         return defaultIfEmpty(super.getWidows(), Definition.WIDOWS);
1202     }
1203 
1204     /**
1205      * {@inheritDoc}
1206      */
1207     @Override
1208     public String getWordSpacing() {
1209         return defaultIfEmpty(super.getWordSpacing(), Definition.WORD_SPACING);
1210     }
1211 
1212     /**
1213      * {@inheritDoc}
1214      */
1215     @Override
1216     public String getZIndex() {
1217         if (!getDomElement().isAttachedToPage()) {
1218             return EMPTY_FINAL;
1219         }
1220 
1221         final String response = super.getZIndex();
1222         if (response.isEmpty()) {
1223             return AUTO;
1224         }
1225         return response;
1226     }
1227 
1228     /**
1229      * Gets the left margin of the element.
1230      * @return the value in pixels
1231      */
1232     public int getMarginLeftValue() {
1233         return CssPixelValueConverter.pixelValue(getMarginLeft());
1234     }
1235 
1236     /**
1237      * Gets the right margin of the element.
1238      * @return the value in pixels
1239      */
1240     public int getMarginRightValue() {
1241         return CssPixelValueConverter.pixelValue(getMarginRight());
1242     }
1243 
1244     /**
1245      * Gets the top margin of the element.
1246      * @return the value in pixels
1247      */
1248     public int getMarginTopValue() {
1249         return CssPixelValueConverter.pixelValue(getMarginTop());
1250     }
1251 
1252     /**
1253      * Gets the bottom margin of the element.
1254      * @return the value in pixels
1255      */
1256     public int getMarginBottomValue() {
1257         return CssPixelValueConverter.pixelValue(getMarginBottom());
1258     }
1259 
1260     /**
1261      * Returns the computed left (X coordinate), relative to the node's parent's left edge.
1262      * @param includeMargin whether or not to take the margin into account in the calculation
1263      * @param includeBorder whether or not to take the border into account in the calculation
1264      * @param includePadding whether or not to take the padding into account in the calculation
1265      * @return the computed left (X coordinate), relative to the node's parent's left edge
1266      */
1267     public int getLeft(final boolean includeMargin, final boolean includeBorder, final boolean includePadding) {
1268         final String p = getPositionWithInheritance();
1269         final String l = getLeftWithInheritance();
1270         final String r = getRightWithInheritance();
1271 
1272         int left;
1273         if ((ABSOLUTE.equals(p) || FIXED.equals(p)) && !AUTO.equals(l)) {
1274             // No need to calculate displacement caused by sibling nodes.
1275             left = CssPixelValueConverter.pixelValue(l);
1276         }
1277         else if ((ABSOLUTE.equals(p) || FIXED.equals(p)) && !AUTO.equals(r)) {
1278             // Need to calculate the horizontal displacement caused by *all* siblings.
1279             final DomNode parent = getDomElement().getParentNode();
1280             final int parentWidth;
1281             if (parent == null) {
1282                 parentWidth = getDomElement().getPage().getEnclosingWindow().getInnerWidth();
1283             }
1284             else if (parent instanceof Page) {
1285                 parentWidth = ((Page) parent).getEnclosingWindow().getInnerWidth();
1286             }
1287             else {
1288                 final ComputedCssStyleDeclaration parentStyle =
1289                         parent.getPage().getEnclosingWindow().getComputedStyle((DomElement) parent, null);
1290                 parentWidth = parentStyle.getCalculatedWidth(false, false);
1291             }
1292             left = parentWidth - CssPixelValueConverter.pixelValue(r);
1293         }
1294         else if (FIXED.equals(p) && !AUTO.equals(r)) {
1295             final DomElement e = getDomElement();
1296             final WebWindow win = e.getPage().getEnclosingWindow();
1297             final ComputedCssStyleDeclaration style = win.getComputedStyle(e, null);
1298 
1299             final DomNode parent = e.getParentNode();
1300             final int parentWidth;
1301             if (parent == null) {
1302                 parentWidth = win.getInnerWidth();
1303             }
1304             else {
1305                 final ComputedCssStyleDeclaration parentStyle = win.getComputedStyle((DomElement) parent, null);
1306                 parentWidth = CssPixelValueConverter.pixelValue(parentStyle.getWidth())
1307                                 - CssPixelValueConverter.pixelValue(style.getWidth());
1308             }
1309             left = parentWidth - CssPixelValueConverter.pixelValue(r);
1310         }
1311         else if (FIXED.equals(p) && AUTO.equals(l)) {
1312             // Fixed to the location at which the browser puts it via normal element flowing.
1313             final DomNode parent = getDomElement().getParentNode();
1314             if (parent == null || parent instanceof Page) {
1315                 left = 0;
1316             }
1317             else {
1318                 final ComputedCssStyleDeclaration style =
1319                         parent.getPage().getEnclosingWindow().getComputedStyle((DomElement) parent, null);
1320                 left = CssPixelValueConverter.pixelValue(style.getLeftWithInheritance());
1321             }
1322         }
1323         else if (STATIC.equals(p)) {
1324             // We need to calculate the horizontal displacement caused by *previous* siblings.
1325             left = 0;
1326             DomNode prev = getDomElement().getPreviousSibling();
1327             while (prev != null) {
1328                 if (prev instanceof HtmlElement) {
1329                     final ComputedCssStyleDeclaration style =
1330                             prev.getPage().getEnclosingWindow().getComputedStyle((DomElement) prev, null);
1331                     final String d = style.getDisplay();
1332                     if (isBlock(d)) {
1333                         break;
1334                     }
1335                     else if (!NONE.equals(d)) {
1336                         left += style.getCalculatedWidth(true, true);
1337                     }
1338                 }
1339                 else if (prev instanceof DomText) {
1340                     final String content = prev.getVisibleText();
1341                     if (content != null) {
1342                         left += content.trim().length()
1343                                 * getDomElement().getPage().getWebClient().getBrowserVersion().getPixesPerChar();
1344                     }
1345                 }
1346                 prev = prev.getPreviousSibling();
1347             }
1348         }
1349         else {
1350             // Just use the CSS specified value.
1351             left = CssPixelValueConverter.pixelValue(l);
1352         }
1353 
1354         if (includeMargin) {
1355             final int margin = getMarginLeftValue();
1356             left += margin;
1357         }
1358 
1359         if (includeBorder) {
1360             final int border = CssPixelValueConverter.pixelValue(getBorderLeftWidth());
1361             left += border;
1362         }
1363 
1364         if (includePadding) {
1365             final int padding = getPaddingLeftValue();
1366             left += padding;
1367         }
1368 
1369         return left;
1370     }
1371 
1372     /**
1373      * {@inheritDoc}
1374      */
1375     @Override
1376     public String getPosition() {
1377         return defaultIfEmpty(super.getPosition(), Definition.POSITION);
1378     }
1379 
1380     /**
1381      * Returns the CSS {@code position} attribute, replacing inherited values with the actual parent values.
1382      * @return the CSS {@code position} attribute, replacing inherited values with the actual parent values
1383      */
1384     public String getPositionWithInheritance() {
1385         String p = getStyleAttribute(Definition.POSITION, true);
1386         if (INHERIT.equals(p)) {
1387             final DomNode parent = getDomElement().getParentNode();
1388             if (parent == null) {
1389                 p = STATIC;
1390             }
1391             else {
1392                 final ComputedCssStyleDeclaration style =
1393                         parent.getPage().getEnclosingWindow().getComputedStyle((DomElement) parent, null);
1394                 p = style.getPositionWithInheritance();
1395             }
1396         }
1397         return p;
1398     }
1399 
1400     /**
1401      * Returns the CSS {@code left} attribute, replacing inherited values with the actual parent values.
1402      * @return the CSS {@code left} attribute, replacing inherited values with the actual parent values
1403      */
1404     public String getLeftWithInheritance() {
1405         String left = getLeft();
1406         if (INHERIT.equals(left)) {
1407             final DomNode parent = getDomElement().getParentNode();
1408             if (parent == null) {
1409                 left = AUTO;
1410             }
1411             else {
1412                 final ComputedCssStyleDeclaration style =
1413                         parent.getPage().getEnclosingWindow().getComputedStyle((DomElement) parent, null);
1414                 left = style.getLeftWithInheritance();
1415             }
1416         }
1417         return left;
1418     }
1419 
1420     /**
1421      * Returns the CSS {@code right} attribute, replacing inherited values with the actual parent values.
1422      * @return the CSS {@code right} attribute, replacing inherited values with the actual parent values
1423      */
1424     public String getRightWithInheritance() {
1425         String right = getRight();
1426         if (INHERIT.equals(right)) {
1427             final DomNode parent = getDomElement().getParentNode();
1428             if (parent == null) {
1429                 right = AUTO;
1430             }
1431             else {
1432                 final ComputedCssStyleDeclaration style =
1433                         parent.getPage().getEnclosingWindow().getComputedStyle((DomElement) parent, null);
1434                 right = style.getRightWithInheritance();
1435             }
1436         }
1437         return right;
1438     }
1439 
1440     private int getTopForAbsolutePositionWithInheritance() {
1441         final String t = getTopWithInheritance();
1442 
1443         if (!AUTO.equals(t)) {
1444             // No need to calculate displacement caused by sibling nodes.
1445             return CssPixelValueConverter.pixelValue(t);
1446         }
1447 
1448         final String b = getBottomWithInheritance();
1449         if (!AUTO.equals(b)) {
1450             // Estimate the vertical displacement caused by *all* siblings.
1451             // This is very rough, and doesn't even take position or display types into account.
1452             // It also doesn't take into account the fact that the parent's height may be hardcoded in CSS.
1453             int top = 0;
1454             DomNode child = getDomElement().getParentNode().getFirstChild();
1455             while (child != null) {
1456                 if (child instanceof HtmlElement && child.mayBeDisplayed()) {
1457                     top += 20;
1458                 }
1459                 child = child.getNextSibling();
1460             }
1461             top -= CssPixelValueConverter.pixelValue(b);
1462             return top;
1463         }
1464 
1465         return 0;
1466     }
1467 
1468     /**
1469      * Returns the element's height, possibly including its padding and border.
1470      * @param includeBorder whether or not to include the border height in the returned value
1471      * @param includePadding whether or not to include the padding height in the returned value
1472      * @return the element's height, possibly including its padding and border
1473      */
1474     public int getCalculatedHeight(final boolean includeBorder, final boolean includePadding) {
1475         final DomElement element = getDomElement();
1476 
1477         if (!element.isAttachedToPage()) {
1478             return 0;
1479         }
1480         int height = getCalculatedHeight(element);
1481         if (!"border-box".equals(getStyleAttribute(Definition.BOX_SIZING, true))) {
1482             if (includeBorder) {
1483                 height += getBorderVertical();
1484             }
1485             else if (isScrollable(element, true, true) && !(element instanceof HtmlBody)) {
1486                 height -= 17;
1487             }
1488 
1489             if (includePadding) {
1490                 height += getPaddingVertical();
1491             }
1492         }
1493         return height;
1494     }
1495 
1496     /**
1497      * Returns the element's calculated height, taking both relevant CSS and the element's children into account.
1498      * @return the element's calculated height, taking both relevant CSS and the element's children into account
1499      */
1500     private int getCalculatedHeight(final DomElement element) {
1501         final Integer cachedHeight = getCachedHeight();
1502         if (cachedHeight != null) {
1503             return cachedHeight.intValue();
1504         }
1505 
1506         if (element instanceof HtmlImage) {
1507             return updateCachedHeight(((HtmlImage) element).getHeightOrDefault());
1508         }
1509 
1510         final boolean isInline = INLINE.equals(getDisplay()) && !(element instanceof HtmlInlineFrame);
1511         // height is ignored for inline elements
1512         if (isInline || super.getHeight().isEmpty()) {
1513             final int contentHeight = getContentHeight();
1514             if (contentHeight > 0) {
1515                 return updateCachedHeight(contentHeight);
1516             }
1517         }
1518 
1519         return updateCachedHeight(getEmptyHeight(element));
1520     }
1521 
1522     /**
1523      * Returns the element's width in pixels, possibly including its padding and border.
1524      * @param includeBorder whether or not to include the border width in the returned value
1525      * @param includePadding whether or not to include the padding width in the returned value
1526      * @return the element's width in pixels, possibly including its padding and border
1527      */
1528     public int getCalculatedWidth(final boolean includeBorder, final boolean includePadding) {
1529         final DomElement element = getDomElement();
1530 
1531         if (!element.isAttachedToPage()) {
1532             return 0;
1533         }
1534         int width = getCalculatedWidth();
1535         if (!"border-box".equals(getStyleAttribute(Definition.BOX_SIZING, true))) {
1536             if (includeBorder) {
1537                 width += getBorderHorizontal();
1538             }
1539             else if (isScrollable(element, false, true) && !(element instanceof HtmlBody)) {
1540                 width -= 17;
1541             }
1542 
1543             if (includePadding) {
1544                 width += getPaddingHorizontal();
1545             }
1546         }
1547         return width;
1548     }
1549 
1550     private int getCalculatedWidth() {
1551         final Integer cachedWidth = getCachedWidth();
1552         if (cachedWidth != null) {
1553             return cachedWidth.intValue();
1554         }
1555 
1556         final DomElement element = getDomElement();
1557         if (!element.mayBeDisplayed()) {
1558             return updateCachedWidth(0);
1559         }
1560 
1561         final String display = getDisplay();
1562         if (NONE.equals(display)) {
1563             return updateCachedWidth(0);
1564         }
1565 
1566         final int width;
1567         final String styleWidth = getStyleAttribute(Definition.WIDTH, true);
1568         final DomNode parent = element.getParentNode();
1569 
1570         // width is ignored for inline elements
1571         if ((INLINE.equals(display) || StringUtils.isEmptyOrNull(styleWidth))
1572                 && parent instanceof HtmlElement) {
1573             // hack: TODO find a way to specify default values for different tags
1574             if (element instanceof HtmlCanvas) {
1575                 return 300;
1576             }
1577 
1578             // Width not explicitly set.
1579             final String cssFloat = getCssFloat();
1580             final String position = getStyleAttribute(Definition.POSITION, true);
1581             if ("right".equals(cssFloat) || "left".equals(cssFloat)
1582                     || ABSOLUTE.equals(position) || FIXED.equals(position)) {
1583                 final BrowserVersion browserVersion = getDomElement().getPage().getWebClient().getBrowserVersion();
1584                 // We're floating; simplistic approximation: text content * pixels per character.
1585                 width = element.getVisibleText().length() * browserVersion.getPixesPerChar();
1586             }
1587             else if (BLOCK.equals(display)) {
1588                 final int windowWidth = element.getPage().getEnclosingWindow().getInnerWidth();
1589                 if (element instanceof HtmlBody) {
1590                     width = windowWidth - 16;
1591                 }
1592                 else {
1593                     // Block elements take up 100% of the parent's width.
1594                     width = CssPixelValueConverter.pixelValue((DomElement) parent,
1595                                         new CssPixelValueConverter.CssValue(0, windowWidth) {
1596                             @Override public String get(final ComputedCssStyleDeclaration style) {
1597                                 return style.getWidth();
1598                             }
1599                         }) - (getBorderHorizontal() + getPaddingHorizontal());
1600                 }
1601             }
1602             else if (element instanceof HtmlSubmitInput
1603                         || element instanceof HtmlResetInput
1604                         || element instanceof HtmlButtonInput
1605                         || element instanceof HtmlButton
1606                         || element instanceof HtmlFileInput) {
1607                 // use asNormalizedText() here because getVisibleText() returns an empty string
1608                 // for submit and reset buttons
1609                 final String text = element.asNormalizedText();
1610                 final BrowserVersion browserVersion = getDomElement().getPage().getWebClient().getBrowserVersion();
1611                 // default font for buttons is a bit smaller than the body font size
1612                 width = 10 + (int) (text.length() * browserVersion.getPixesPerChar() * 0.9);
1613             }
1614             else if (element instanceof HtmlTextInput || element instanceof HtmlPasswordInput) {
1615                 final BrowserVersion browserVersion = getDomElement().getPage().getWebClient().getBrowserVersion();
1616                 if (browserVersion.hasFeature(JS_CLIENTWIDTH_INPUT_TEXT_173)) {
1617                     return 173;
1618                 }
1619                 if (browserVersion.hasFeature(JS_CLIENTWIDTH_INPUT_TEXT_154)) {
1620                     return 154;
1621                 }
1622                 width = 161; // FF
1623             }
1624             else if (element instanceof HtmlRadioButtonInput || element instanceof HtmlCheckBoxInput) {
1625                 final BrowserVersion browserVersion = getDomElement().getPage().getWebClient().getBrowserVersion();
1626                 if (browserVersion.hasFeature(JS_CLIENTWIDTH_RADIO_CHECKBOX_10)) {
1627                     width = 10;
1628                 }
1629                 else if (browserVersion.hasFeature(JS_CLIENTWIDTH_RADIO_CHECKBOX_14)) {
1630                     width = 14;
1631                 }
1632                 else {
1633                     width = 13;
1634                 }
1635             }
1636             else if (element instanceof HtmlTextArea) {
1637                 width = 100; // wild guess
1638             }
1639             else if (element instanceof HtmlImage) {
1640                 width = ((HtmlImage) element).getWidthOrDefault();
1641             }
1642             else {
1643                 // Inline elements take up however much space is required by their children.
1644                 width = getContentWidth();
1645             }
1646         }
1647         else if (AUTO.equals(styleWidth)) {
1648             width = element.getPage().getEnclosingWindow().getInnerWidth();
1649         }
1650         else {
1651             // Width explicitly set in the style attribute, or there was no parent to provide guidance.
1652             width = CssPixelValueConverter.pixelValue(element,
1653                     new CssPixelValueConverter.CssValue(0, element.getPage().getEnclosingWindow().getInnerWidth()) {
1654                     @Override public String get(final ComputedCssStyleDeclaration style) {
1655                         return style.getStyleAttribute(Definition.WIDTH, true);
1656                     }
1657                 });
1658         }
1659 
1660         return updateCachedWidth(width);
1661     }
1662 
1663     /**
1664      * Returns the total width of the element's children.
1665      * @return the total width of the element's children
1666      */
1667     public int getContentWidth() {
1668         int width = 0;
1669         final DomElement element = getDomElement();
1670         Iterable<DomNode> children = element.getChildren();
1671         if (element instanceof BaseFrameElement) {
1672             final Page enclosedPage = ((BaseFrameElement) element).getEnclosedPage();
1673             if (enclosedPage != null && enclosedPage.isHtmlPage()) {
1674                 children = ((DomNode) enclosedPage).getChildren();
1675             }
1676         }
1677         final WebWindow webWindow = element.getPage().getEnclosingWindow();
1678         for (final DomNode child : children) {
1679             if (child instanceof HtmlElement) {
1680                 final HtmlElement e = (HtmlElement) child;
1681                 final ComputedCssStyleDeclaration style = webWindow.getComputedStyle(e, null);
1682                 final int w = style.getCalculatedWidth(true, true);
1683                 width += w;
1684             }
1685             else if (child instanceof DomText) {
1686                 final BrowserVersion browserVersion = getDomElement().getPage().getWebClient().getBrowserVersion();
1687 
1688                 final DomNode parent = child.getParentNode();
1689                 if (parent instanceof HtmlElement) {
1690                     final ComputedCssStyleDeclaration style = webWindow.getComputedStyle((DomElement) parent, null);
1691                     final int height = browserVersion.getFontHeight(
1692                                         style.getStyleAttribute(Definition.FONT_SIZE, true));
1693                     width += child.getVisibleText().length() * (int) (height / 1.8f);
1694                 }
1695                 else {
1696                     width += child.getVisibleText().length() * browserVersion.getPixesPerChar();
1697                 }
1698             }
1699         }
1700         return width;
1701     }
1702 
1703     /**
1704      * @return the element's calculated height taking relevant CSS into account, but <b>not</b> the element's child
1705      *         elements
1706      */
1707     private int getEmptyHeight(final DomElement element) {
1708         final Integer cachedEmptyHeight = getCachedEmptyHeight();
1709         if (cachedEmptyHeight != null) {
1710             return cachedEmptyHeight.intValue();
1711         }
1712 
1713         if (!element.mayBeDisplayed()) {
1714             return updateCachedEmptyHeight(0);
1715         }
1716 
1717         final String display = getDisplay();
1718         if (NONE.equals(display)) {
1719             return updateCachedEmptyHeight(0);
1720         }
1721 
1722         final SgmlPage page = element.getPage();
1723         final WebWindow webWindow = page.getEnclosingWindow();
1724         final int windowHeight = webWindow.getInnerHeight();
1725 
1726         if (element instanceof HtmlBody) {
1727             if (page instanceof HtmlPage && ((HtmlPage) page).isQuirksMode()) {
1728                 return updateCachedEmptyHeight(windowHeight);
1729             }
1730 
1731             return updateCachedEmptyHeight(0);
1732         }
1733 
1734         final boolean isInline = INLINE.equals(display) && !(element instanceof HtmlInlineFrame);
1735         // height is ignored for inline elements
1736         final boolean explicitHeightSpecified = !isInline && !super.getHeight().isEmpty();
1737 
1738         int defaultHeight;
1739         if ((element instanceof HtmlAbbreviated
1740                 || element instanceof HtmlAcronym
1741                 || element instanceof HtmlAddress
1742                 || element instanceof HtmlArticle
1743                 || element instanceof HtmlAside
1744                 || element instanceof HtmlBaseFont
1745                 || element instanceof HtmlBidirectionalIsolation
1746                 || element instanceof HtmlBidirectionalOverride
1747                 || element instanceof HtmlBig
1748                 || element instanceof HtmlBold
1749                 || element instanceof HtmlCenter
1750                 || element instanceof HtmlCitation
1751                 || element instanceof HtmlCode
1752                 || element instanceof HtmlDefinition
1753                 || element instanceof HtmlDefinitionDescription
1754                 || element instanceof HtmlDefinitionTerm
1755                 || element instanceof HtmlEmphasis
1756                 || element instanceof HtmlFigure
1757                 || element instanceof HtmlFigureCaption
1758                 || element instanceof HtmlFooter
1759                 || element instanceof HtmlHeader
1760                 || element instanceof HtmlItalic
1761                 || element instanceof HtmlKeyboard
1762                 || element instanceof HtmlLayer
1763                 || element instanceof HtmlMark
1764                 || element instanceof HtmlNav
1765                 || element instanceof HtmlNoBreak
1766                 || element instanceof HtmlNoEmbed
1767                 || element instanceof HtmlNoFrames
1768                 || element instanceof HtmlNoLayer
1769                 || element instanceof HtmlNoScript
1770                 || element instanceof HtmlPlainText
1771                 || element instanceof HtmlRp
1772                 || element instanceof HtmlRtc
1773                 || element instanceof HtmlS
1774                 || element instanceof HtmlSample
1775                 || element instanceof HtmlSection
1776                 || element instanceof HtmlSmall
1777                 || element instanceof HtmlStrike
1778                 || element instanceof HtmlStrong
1779                 || element instanceof HtmlSubscript
1780                 || element instanceof HtmlSummary
1781                 || element instanceof HtmlSuperscript
1782                 || element instanceof HtmlTeletype
1783                 || element instanceof HtmlUnderlined
1784                 || element instanceof HtmlUnknownElement
1785                 || element instanceof HtmlWordBreak
1786                 || element instanceof HtmlMain
1787                 || element instanceof HtmlVariable
1788 
1789                 || element instanceof HtmlDivision
1790                 || element instanceof HtmlData
1791                 || element instanceof HtmlTime
1792                 || element instanceof HtmlOutput
1793                 || element instanceof HtmlSlot
1794                 || element instanceof HtmlLegend)
1795                 && StringUtils.isBlank(element.getTextContent())) {
1796             defaultHeight = 0;
1797         }
1798         else if (element.getFirstChild() == null) {
1799             if (element instanceof HtmlRadioButtonInput || element instanceof HtmlCheckBoxInput) {
1800                 final BrowserVersion browser = webWindow.getWebClient().getBrowserVersion();
1801                 if (browser.hasFeature(JS_CLIENTHEIGHT_RADIO_CHECKBOX_10)) {
1802                     defaultHeight = 10;
1803                 }
1804                 else if (browser.hasFeature(JS_CLIENTHEIGHT_RADIO_CHECKBOX_14)) {
1805                     defaultHeight = 14;
1806                 }
1807                 else {
1808                     defaultHeight = 13;
1809                 }
1810             }
1811             else if (element instanceof HtmlButton) {
1812                 defaultHeight = 20;
1813             }
1814             else if (element instanceof HtmlInput && !(element instanceof HtmlHiddenInput)) {
1815                 final BrowserVersion browser = webWindow.getWebClient().getBrowserVersion();
1816                 if (browser.hasFeature(JS_CLIENTHEIGHT_INPUT_17)) {
1817                     defaultHeight = 17;
1818                 }
1819                 else if (browser.hasFeature(JS_CLIENTHEIGHT_INPUT_18)) {
1820                     defaultHeight = 18;
1821                 }
1822                 else {
1823                     defaultHeight = 20;
1824                 }
1825             }
1826             else if (element instanceof HtmlSelect) {
1827                 defaultHeight = 20;
1828             }
1829             else if (element instanceof HtmlTextArea) {
1830                 defaultHeight = 49;
1831             }
1832             else if (element instanceof HtmlInlineFrame) {
1833                 defaultHeight = 154;
1834             }
1835             else {
1836                 defaultHeight = 0;
1837             }
1838         }
1839         else if (element instanceof HtmlRb) {
1840             final BrowserVersion browser = webWindow.getWebClient().getBrowserVersion();
1841             if (browser.hasFeature(JS_CLIENTHEIGHT_RB_17)) {
1842                 defaultHeight = 17;
1843             }
1844             else {
1845                 defaultHeight = 0;
1846             }
1847         }
1848         else if (element instanceof HtmlRt) {
1849             final BrowserVersion browser = webWindow.getWebClient().getBrowserVersion();
1850             if (browser.hasFeature(JS_CLIENTHEIGHT_RT_9)) {
1851                 defaultHeight = 9;
1852             }
1853             else {
1854                 defaultHeight = 0;
1855             }
1856         }
1857         else if (element instanceof HtmlRuby) {
1858             final BrowserVersion browser = webWindow.getWebClient().getBrowserVersion();
1859             if (browser.hasFeature(JS_CLIENTHEIGHT_RUBY_17)) {
1860                 defaultHeight = 17;
1861             }
1862             else {
1863                 defaultHeight = 0;
1864             }
1865         }
1866         else {
1867             final String fontSize;
1868 
1869             boolean isHeading = false;
1870             if (element instanceof HtmlHeading1) {
1871                 isHeading = true;
1872                 final String value = getStyleAttribute(Definition.FONT_SIZE, false);
1873                 if (value.isEmpty()) {
1874                     fontSize = "32px";
1875                 }
1876                 else {
1877                     fontSize = getStyleAttribute(Definition.FONT_SIZE, true);
1878                 }
1879             }
1880             else if (element instanceof HtmlHeading2) {
1881                 isHeading = true;
1882                 final String value = getStyleAttribute(Definition.FONT_SIZE, false);
1883                 if (value.isEmpty()) {
1884                     fontSize = "24px";
1885                 }
1886                 else {
1887                     fontSize = getStyleAttribute(Definition.FONT_SIZE, true);
1888                 }
1889             }
1890             else if (element instanceof HtmlHeading3) {
1891                 isHeading = true;
1892                 final String value = getStyleAttribute(Definition.FONT_SIZE, false);
1893                 if (value.isEmpty()) {
1894                     fontSize = "19px";
1895                 }
1896                 else {
1897                     fontSize = getStyleAttribute(Definition.FONT_SIZE, true);
1898                 }
1899             }
1900             else if (element instanceof HtmlHeading4) {
1901                 isHeading = true;
1902                 final String value = getStyleAttribute(Definition.FONT_SIZE, false);
1903                 if (value.isEmpty()) {
1904                     fontSize = "16px";
1905                 }
1906                 else {
1907                     fontSize = getStyleAttribute(Definition.FONT_SIZE, true);
1908                 }
1909             }
1910             else if (element instanceof HtmlHeading5) {
1911                 isHeading = true;
1912                 final String value = getStyleAttribute(Definition.FONT_SIZE, false);
1913                 if (value.isEmpty()) {
1914                     fontSize = "13px";
1915                 }
1916                 else {
1917                     fontSize = getStyleAttribute(Definition.FONT_SIZE, true);
1918                 }
1919             }
1920             else if (element instanceof HtmlHeading6) {
1921                 isHeading = true;
1922                 final String value = getStyleAttribute(Definition.FONT_SIZE, false);
1923                 if (value.isEmpty()) {
1924                     fontSize = "11px";
1925                 }
1926                 else {
1927                     fontSize = getStyleAttribute(Definition.FONT_SIZE, true);
1928                 }
1929             }
1930             else {
1931                 fontSize = getStyleAttribute(Definition.FONT_SIZE, true);
1932             }
1933 
1934             defaultHeight = webWindow.getWebClient().getBrowserVersion().getFontHeight(fontSize);
1935 
1936             if (isHeading
1937                     || element instanceof HtmlDivision
1938                     || element instanceof HtmlSpan) {
1939                 String width = getStyleAttribute(Definition.WIDTH, false);
1940 
1941                 // maybe we are enclosed something that forces a width
1942                 DomNode parent = getDomElement().getParentNode();
1943                 final WebWindow win = parent.getPage().getEnclosingWindow();
1944                 while (width.isEmpty() && parent != null) {
1945                     if (parent instanceof DomElement) {
1946                         final ComputedCssStyleDeclaration computedCss = win.getComputedStyle((DomElement) parent, null);
1947                         width = computedCss.getStyleAttribute(Definition.WIDTH, false);
1948                     }
1949                     parent = parent.getParentNode();
1950                     if (parent instanceof Page) {
1951                         break;
1952                     }
1953                 }
1954                 final int pixelWidth = CssPixelValueConverter.pixelValue(width);
1955                 final String content = element.getVisibleText();
1956 
1957                 if (pixelWidth > 0
1958                         && !width.isEmpty()
1959                         && StringUtils.isNotBlank(content)) {
1960                     final int lineCount = Platform.getFontUtil().countLines(content, pixelWidth, fontSize);
1961                     defaultHeight *= lineCount;
1962                 }
1963                 else {
1964                     if (element instanceof HtmlSpan && StringUtils.isEmptyOrNull(content)) {
1965                         defaultHeight = 0;
1966                     }
1967                     else {
1968                         defaultHeight *= org.apache.commons.lang3.StringUtils.countMatches(content, '\n') + 1;
1969                     }
1970                 }
1971 
1972                 final String styleHeight = getStyleAttribute(Definition.HEIGHT, true);
1973                 if (styleHeight.endsWith("%")) {
1974                     if (page instanceof HtmlPage && !((HtmlPage) page).isQuirksMode()) {
1975                         return defaultHeight;
1976                     }
1977                 }
1978             }
1979         }
1980 
1981         final int defaultWindowHeight = element instanceof HtmlCanvas ? 150 : windowHeight;
1982 
1983         int height = CssPixelValueConverter.pixelValue(element,
1984                 new CssPixelValueConverter.CssValue(defaultHeight, defaultWindowHeight) {
1985                 @Override public String get(final ComputedCssStyleDeclaration style) {
1986                     final DomElement elem = style.getDomElement();
1987                     if (elem instanceof HtmlBody) {
1988                         return String.valueOf(elem.getPage().getEnclosingWindow().getInnerHeight());
1989                     }
1990                     // height is ignored for inline elements
1991                     if (isInline) {
1992                         return "";
1993                     }
1994                     return style.getStyleAttribute(Definition.HEIGHT, true);
1995                 }
1996             });
1997 
1998         if (height == 0 && !explicitHeightSpecified) {
1999             height = defaultHeight;
2000         }
2001 
2002         return updateCachedEmptyHeight(height);
2003     }
2004 
2005     /**
2006      * Returns the total height of the element's children.
2007      * @return the total height of the element's children
2008      */
2009     public int getContentHeight() {
2010         // There are two different kinds of elements that might determine the content height:
2011         //  - elements with position:static or position:relative (elements that flow and build on each other)
2012         //  - elements with position:absolute (independent elements)
2013 
2014         final DomNode node = getDomElement();
2015         if (!node.mayBeDisplayed()) {
2016             return 0;
2017         }
2018 
2019         ComputedCssStyleDeclaration lastFlowing = null;
2020         final Set<ComputedCssStyleDeclaration> styles = new HashSet<>();
2021 
2022         if (node instanceof HtmlTableRow) {
2023             final HtmlTableRow row = (HtmlTableRow) node;
2024             for (final HtmlTableCell cell : row.getCellIterator()) {
2025                 if (cell.mayBeDisplayed()) {
2026                     final ComputedCssStyleDeclaration style =
2027                             cell.getPage().getEnclosingWindow().getComputedStyle(cell, null);
2028                     styles.add(style);
2029                 }
2030             }
2031         }
2032         else {
2033             for (final DomNode child : node.getChildren()) {
2034                 if (child.mayBeDisplayed()) {
2035                     if (child instanceof HtmlElement) {
2036                         final HtmlElement e = (HtmlElement) child;
2037                         final ComputedCssStyleDeclaration style =
2038                                 e.getPage().getEnclosingWindow().getComputedStyle(e, null);
2039                         final String position = style.getPositionWithInheritance();
2040                         if (STATIC.equals(position) || RELATIVE.equals(position)) {
2041                             lastFlowing = style;
2042                         }
2043                         else if (ABSOLUTE.equals(position) || FIXED.equals(position)) {
2044                             styles.add(style);
2045                         }
2046                     }
2047                 }
2048             }
2049 
2050             if (lastFlowing != null) {
2051                 styles.add(lastFlowing);
2052             }
2053         }
2054 
2055         int max = 0;
2056         for (final ComputedCssStyleDeclaration style : styles) {
2057             final int h = style.getTop(true, false, false) + style.getCalculatedHeight(true, true);
2058             if (h > max) {
2059                 max = h;
2060             }
2061         }
2062         return max;
2063     }
2064 
2065     /**
2066      * Returns {@code true} if the element is scrollable along the specified axis.
2067      * @param horizontal if {@code true}, the caller is interested in scrollability along the x-axis;
2068      *        if {@code false}, the caller is interested in scrollability along the y-axis
2069      * @return {@code true} if the element is scrollable along the specified axis
2070      */
2071     public boolean isScrollable(final boolean horizontal) {
2072         return isScrollable(getDomElement(), horizontal, false);
2073     }
2074 
2075     /**
2076      * @param ignoreSize whether to consider the content/calculated width/height
2077      */
2078     private boolean isScrollable(final DomElement element, final boolean horizontal, final boolean ignoreSize) {
2079         final boolean scrollable;
2080 
2081         String overflow;
2082         if (horizontal) {
2083             overflow = getStyleAttribute(Definition.OVERFLOW_X_, false);
2084             if (StringUtils.isEmptyOrNull(overflow)) {
2085                 overflow = getStyleAttribute(Definition.OVERFLOW_X, false);
2086             }
2087             // fall back to default
2088             if (StringUtils.isEmptyOrNull(overflow)) {
2089                 overflow = getStyleAttribute(Definition.OVERFLOW, true);
2090             }
2091             scrollable = (element instanceof HtmlBody || SCROLL.equals(overflow) || AUTO.equals(overflow))
2092                 && (ignoreSize || getContentWidth() > getCalculatedWidth());
2093         }
2094         else {
2095             overflow = getStyleAttribute(Definition.OVERFLOW_Y_, false);
2096             if (StringUtils.isEmptyOrNull(overflow)) {
2097                 overflow = getStyleAttribute(Definition.OVERFLOW_Y, false);
2098             }
2099             // fall back to default
2100             if (StringUtils.isEmptyOrNull(overflow)) {
2101                 overflow = getStyleAttribute(Definition.OVERFLOW, true);
2102             }
2103 
2104             scrollable = (element instanceof HtmlBody || SCROLL.equals(overflow) || AUTO.equals(overflow))
2105                 && (ignoreSize || getContentHeight() > getEmptyHeight(element));
2106         }
2107         return scrollable;
2108     }
2109 
2110     private int getBorderHorizontal() {
2111         final Integer borderHorizontal = getCachedBorderHorizontal();
2112         if (borderHorizontal != null) {
2113             return borderHorizontal.intValue();
2114         }
2115 
2116         final int border = NONE.equals(getDisplay()) ? 0 : getBorderLeftValue() + getBorderRightValue();
2117         return updateCachedBorderHorizontal(border);
2118     }
2119 
2120     private int getBorderVertical() {
2121         final Integer borderVertical = getCachedBorderVertical();
2122         if (borderVertical != null) {
2123             return borderVertical.intValue();
2124         }
2125 
2126         final int border = NONE.equals(getDisplay()) ? 0 : getBorderTopValue() + getBorderBottomValue();
2127         return updateCachedBorderVertical(border);
2128     }
2129 
2130     /**
2131      * Gets the size of the left border of the element.
2132      * @return the value in pixels
2133      */
2134     public int getBorderLeftValue() {
2135         return CssPixelValueConverter.pixelValue(getBorderLeftWidth());
2136     }
2137 
2138     /**
2139      * Gets the size of the right border of the element.
2140      * @return the value in pixels
2141      */
2142     public int getBorderRightValue() {
2143         return CssPixelValueConverter.pixelValue(getBorderRightWidth());
2144     }
2145 
2146     /**
2147      * Gets the size of the top border of the element.
2148      * @return the value in pixels
2149      */
2150     public int getBorderTopValue() {
2151         return CssPixelValueConverter.pixelValue(getBorderTopWidth());
2152     }
2153 
2154     /**
2155      * Gets the size of the bottom border of the element.
2156      * @return the value in pixels
2157      */
2158     public int getBorderBottomValue() {
2159         return CssPixelValueConverter.pixelValue(getBorderBottomWidth());
2160     }
2161 
2162     private int getPaddingHorizontal() {
2163         final Integer paddingHorizontal = getCachedPaddingHorizontal();
2164         if (paddingHorizontal != null) {
2165             return paddingHorizontal.intValue();
2166         }
2167 
2168         final int padding = NONE.equals(getDisplay()) ? 0 : getPaddingLeftValue() + getPaddingRightValue();
2169         return updateCachedPaddingHorizontal(padding);
2170     }
2171 
2172     private int getPaddingVertical() {
2173         final Integer paddingVertical = getCachedPaddingVertical();
2174         if (paddingVertical != null) {
2175             return paddingVertical.intValue();
2176         }
2177 
2178         final int padding = NONE.equals(getDisplay()) ? 0 : getPaddingTopValue() + getPaddingBottomValue();
2179         return updateCachedPaddingVertical(padding);
2180     }
2181 
2182     /**
2183      * Gets the left padding of the element.
2184      * @return the value in pixels
2185      */
2186     public int getPaddingLeftValue() {
2187         return CssPixelValueConverter.pixelValue(getPaddingLeft());
2188     }
2189 
2190     /**
2191      * Gets the right padding of the element.
2192      * @return the value in pixels
2193      */
2194     public int getPaddingRightValue() {
2195         return CssPixelValueConverter.pixelValue(getPaddingRight());
2196     }
2197 
2198     /**
2199      * Gets the top padding of the element.
2200      * @return the value in pixels
2201      */
2202     public int getPaddingTopValue() {
2203         return CssPixelValueConverter.pixelValue(getPaddingTop());
2204     }
2205 
2206     /**
2207      * Gets the bottom padding of the element.
2208      * @return the value in pixels
2209      */
2210     public int getPaddingBottomValue() {
2211         return CssPixelValueConverter.pixelValue(getPaddingBottom());
2212     }
2213 
2214     /**
2215      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span>
2216      * @return the cached width
2217      */
2218     public Integer getCachedWidth() {
2219         return width_;
2220     }
2221 
2222     /**
2223      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span>
2224      * @param width the new value
2225      * @return the param width
2226      */
2227     public int updateCachedWidth(final int width) {
2228         width_ = Integer.valueOf(width);
2229         return width;
2230     }
2231 
2232     /**
2233      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span>
2234      * @return the cached height
2235      */
2236     public Integer getCachedHeight() {
2237         return height_;
2238     }
2239 
2240     /**
2241      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span>
2242      * @param height the new value
2243      * @return the param height
2244      */
2245     public int updateCachedHeight(final int height) {
2246         height_ = Integer.valueOf(height);
2247         return height;
2248     }
2249 
2250     /**
2251      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span>
2252      * @return the cached emptyHeight
2253      */
2254     public Integer getCachedEmptyHeight() {
2255         return emptyHeight_;
2256     }
2257 
2258     /**
2259      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span>
2260      * @param emptyHeight the new value
2261      * @return the param emptyHeight
2262      */
2263     public int updateCachedEmptyHeight(final int emptyHeight) {
2264         emptyHeight_ = Integer.valueOf(emptyHeight);
2265         return emptyHeight;
2266     }
2267 
2268     /**
2269      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span>
2270      * @return the cached top
2271      */
2272     public Integer getCachedTop() {
2273         return top_;
2274     }
2275 
2276     /**
2277      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span>
2278      * @param top the new value
2279      */
2280     public void setCachedTop(final Integer top) {
2281         top_ = top;
2282     }
2283 
2284     /**
2285      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span>
2286      * @return the cached padding horizontal
2287      */
2288     public Integer getCachedPaddingHorizontal() {
2289         return paddingHorizontal_;
2290     }
2291 
2292     /**
2293      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span>
2294      * @param paddingHorizontal the new value
2295      * @return the param paddingHorizontal
2296      */
2297     public int updateCachedPaddingHorizontal(final int paddingHorizontal) {
2298         paddingHorizontal_ = Integer.valueOf(paddingHorizontal);
2299         return paddingHorizontal;
2300     }
2301 
2302     /**
2303      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span>
2304      * @return the cached padding vertical
2305      */
2306     public Integer getCachedPaddingVertical() {
2307         return paddingVertical_;
2308     }
2309 
2310     /**
2311      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span>
2312      * @param paddingVertical the new value
2313      * @return the param paddingVertical
2314      */
2315     public int updateCachedPaddingVertical(final int paddingVertical) {
2316         paddingVertical_ = Integer.valueOf(paddingVertical);
2317         return paddingVertical;
2318     }
2319 
2320     /**
2321      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span>
2322      * @return the cached border horizontal
2323      */
2324     public Integer getCachedBorderHorizontal() {
2325         return borderHorizontal_;
2326     }
2327 
2328     /**
2329      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span>
2330      * @param borderHorizontal the new value
2331      * @return the param borderHorizontal
2332      */
2333     public int updateCachedBorderHorizontal(final int borderHorizontal) {
2334         borderHorizontal_ = Integer.valueOf(borderHorizontal);
2335         return borderHorizontal;
2336     }
2337 
2338     /**
2339      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span>
2340      * @return the cached border vertical
2341      */
2342     public Integer getCachedBorderVertical() {
2343         return borderVertical_;
2344     }
2345 
2346     /**
2347      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span>
2348      * @param borderVertical the new value
2349      * @return the param borderVertical
2350      */
2351     public int updateCachedBorderVertical(final int borderVertical) {
2352         borderVertical_ = Integer.valueOf(borderVertical);
2353         return borderVertical;
2354     }
2355 
2356     /**
2357      * Makes a local, "computed", modification to this CSS style.
2358      *
2359      * @param declaration the style declaration
2360      * @param selector the selector determining that the style applies to this element
2361      */
2362     public void applyStyleFromSelector(final CSSStyleDeclarationImpl declaration, final Selector selector) {
2363         final SelectorSpecificity specificity = selector.getSelectorSpecificity();
2364         for (final Property prop : declaration.getProperties()) {
2365             final String name = prop.getName();
2366             final String value = declaration.getPropertyValue(name);
2367             final String priority = declaration.getPropertyPriority(name);
2368             applyLocalStyleAttribute(name, value, priority, specificity);
2369         }
2370     }
2371 
2372     private void applyLocalStyleAttribute(final String name, final String newValue, final String priority,
2373             final SelectorSpecificity specificity) {
2374         if (!StyleElement.PRIORITY_IMPORTANT.equals(priority)) {
2375             final StyleElement existingElement = localModifications_.get(name);
2376             if (existingElement != null) {
2377                 if (existingElement.isImportant()) {
2378                     return; // can't override a !important rule by a normal rule. Ignore it!
2379                 }
2380                 else if (specificity.compareTo(existingElement.getSpecificity()) < 0) {
2381                     return; // can't override a rule with a rule having higher specificity
2382                 }
2383             }
2384         }
2385         final StyleElement element = new StyleElement(name, newValue, priority, specificity);
2386         localModifications_.put(name, element);
2387     }
2388 
2389     /**
2390      * Makes a local, "computed", modification to this CSS style that won't override other
2391      * style attributes of the same name. This method should be used to set default values
2392      * for style attributes.
2393      *
2394      * @param name the name of the style attribute to set
2395      * @param newValue the value of the style attribute to set
2396      */
2397     public void setDefaultLocalStyleAttribute(final String name, final String newValue) {
2398         final StyleElement element = new StyleElement(name, newValue, "", SelectorSpecificity.DEFAULT_STYLE_ATTRIBUTE);
2399         localModifications_.put(name, element);
2400     }
2401 
2402     /**
2403      * {@inheritDoc}
2404      */
2405     @Override
2406     public boolean hasFeature(final BrowserVersionFeatures property) {
2407         return getDomElement().hasFeature(property);
2408     }
2409 
2410     /**
2411      * {@inheritDoc}
2412      */
2413     @Override
2414     public BrowserVersion getBrowserVersion() {
2415         return getDomElement().getPage().getWebClient().getBrowserVersion();
2416     }
2417 
2418     /**
2419      * {@inheritDoc}
2420      */
2421     @Override
2422     public boolean isComputed() {
2423         return true;
2424     }
2425 
2426     /**
2427      * {@inheritDoc}
2428      */
2429     @Override
2430     public String toString() {
2431         return "ComputedCssStyleDeclaration for '" + getDomElement() + "'";
2432     }
2433 
2434     private String defaultIfEmpty(final String str, final StyleAttributes.Definition definition) {
2435         return defaultIfEmpty(str, definition, false);
2436     }
2437 
2438     private String defaultIfEmpty(final String str, final StyleAttributes.Definition definition,
2439             final boolean isPixel) {
2440         if (!getDomElement().isAttachedToPage()) {
2441             return EMPTY_FINAL;
2442         }
2443         if (str == null || str.isEmpty()) {
2444             return definition.getDefaultComputedValue(getBrowserVersion());
2445         }
2446         if (isPixel) {
2447             return pixelString(str);
2448         }
2449         return str;
2450     }
2451 
2452     /**
2453      * @param toReturnIfEmptyOrDefault the value to return if empty or equals the {@code defaultValue}
2454      * @param defaultValue the default value of the string
2455      * @return the string, or {@code toReturnIfEmptyOrDefault}
2456      */
2457     private String defaultIfEmpty(final String str, final String toReturnIfEmptyOrDefault, final String defaultValue) {
2458         if (!getDomElement().isAttachedToPage()) {
2459             return EMPTY_FINAL;
2460         }
2461         if (str == null || str.isEmpty() || str.equals(defaultValue)) {
2462             return toReturnIfEmptyOrDefault;
2463         }
2464         return str;
2465     }
2466 
2467     /**
2468      * Returns the specified length value as a pixel length value.
2469      * This method does <b>NOT</b> handle percentages correctly;
2470      * use {@link CssPixelValueConverter#pixelValue(DomElement, CssValue)} if you need percentage support.
2471      * @param value the length value to convert to a pixel length value
2472      * @return the specified length value as a pixel length value
2473      * @see CssPixelValueConverter#pixelString(DomElement, CssValue)
2474      */
2475     private static String pixelString(final String value) {
2476         if (EMPTY_FINAL == value || value.endsWith("px")) {
2477             return value;
2478         }
2479         return CssPixelValueConverter.pixelValue(value) + "px";
2480     }
2481 }