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