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.apache.commons.lang3.StringUtils;
47  import org.htmlunit.BrowserVersion;
48  import org.htmlunit.BrowserVersionFeatures;
49  import org.htmlunit.Page;
50  import org.htmlunit.SgmlPage;
51  import org.htmlunit.WebWindow;
52  import org.htmlunit.css.CssPixelValueConverter.CssValue;
53  import org.htmlunit.css.StyleAttributes.Definition;
54  import org.htmlunit.cssparser.dom.AbstractCSSRuleImpl;
55  import org.htmlunit.cssparser.dom.CSSStyleDeclarationImpl;
56  import org.htmlunit.cssparser.dom.Property;
57  import org.htmlunit.cssparser.parser.selector.Selector;
58  import org.htmlunit.cssparser.parser.selector.SelectorSpecificity;
59  import org.htmlunit.html.BaseFrameElement;
60  import org.htmlunit.html.DomElement;
61  import org.htmlunit.html.DomNode;
62  import org.htmlunit.html.DomText;
63  import org.htmlunit.html.HtmlAbbreviated;
64  import org.htmlunit.html.HtmlAcronym;
65  import org.htmlunit.html.HtmlAddress;
66  import org.htmlunit.html.HtmlArticle;
67  import org.htmlunit.html.HtmlAside;
68  import org.htmlunit.html.HtmlBaseFont;
69  import org.htmlunit.html.HtmlBidirectionalIsolation;
70  import org.htmlunit.html.HtmlBidirectionalOverride;
71  import org.htmlunit.html.HtmlBig;
72  import org.htmlunit.html.HtmlBody;
73  import org.htmlunit.html.HtmlBold;
74  import org.htmlunit.html.HtmlButton;
75  import org.htmlunit.html.HtmlButtonInput;
76  import org.htmlunit.html.HtmlCanvas;
77  import org.htmlunit.html.HtmlCenter;
78  import org.htmlunit.html.HtmlCheckBoxInput;
79  import org.htmlunit.html.HtmlCitation;
80  import org.htmlunit.html.HtmlCode;
81  import org.htmlunit.html.HtmlData;
82  import org.htmlunit.html.HtmlDefinition;
83  import org.htmlunit.html.HtmlDefinitionDescription;
84  import org.htmlunit.html.HtmlDefinitionTerm;
85  import org.htmlunit.html.HtmlDivision;
86  import org.htmlunit.html.HtmlElement;
87  import org.htmlunit.html.HtmlElement.DisplayStyle;
88  import org.htmlunit.html.HtmlEmphasis;
89  import org.htmlunit.html.HtmlFigure;
90  import org.htmlunit.html.HtmlFigureCaption;
91  import org.htmlunit.html.HtmlFileInput;
92  import org.htmlunit.html.HtmlFooter;
93  import org.htmlunit.html.HtmlHeader;
94  import org.htmlunit.html.HtmlHeading1;
95  import org.htmlunit.html.HtmlHeading2;
96  import org.htmlunit.html.HtmlHeading3;
97  import org.htmlunit.html.HtmlHeading4;
98  import org.htmlunit.html.HtmlHeading5;
99  import org.htmlunit.html.HtmlHeading6;
100 import org.htmlunit.html.HtmlHiddenInput;
101 import org.htmlunit.html.HtmlImage;
102 import org.htmlunit.html.HtmlInlineFrame;
103 import org.htmlunit.html.HtmlInput;
104 import org.htmlunit.html.HtmlItalic;
105 import org.htmlunit.html.HtmlKeyboard;
106 import org.htmlunit.html.HtmlLayer;
107 import org.htmlunit.html.HtmlLegend;
108 import org.htmlunit.html.HtmlMain;
109 import org.htmlunit.html.HtmlMark;
110 import org.htmlunit.html.HtmlNav;
111 import org.htmlunit.html.HtmlNoBreak;
112 import org.htmlunit.html.HtmlNoEmbed;
113 import org.htmlunit.html.HtmlNoFrames;
114 import org.htmlunit.html.HtmlNoLayer;
115 import org.htmlunit.html.HtmlNoScript;
116 import org.htmlunit.html.HtmlOutput;
117 import org.htmlunit.html.HtmlPage;
118 import org.htmlunit.html.HtmlPasswordInput;
119 import org.htmlunit.html.HtmlPlainText;
120 import org.htmlunit.html.HtmlRadioButtonInput;
121 import org.htmlunit.html.HtmlRb;
122 import org.htmlunit.html.HtmlResetInput;
123 import org.htmlunit.html.HtmlRp;
124 import org.htmlunit.html.HtmlRt;
125 import org.htmlunit.html.HtmlRtc;
126 import org.htmlunit.html.HtmlRuby;
127 import org.htmlunit.html.HtmlS;
128 import org.htmlunit.html.HtmlSample;
129 import org.htmlunit.html.HtmlSection;
130 import org.htmlunit.html.HtmlSelect;
131 import org.htmlunit.html.HtmlSlot;
132 import org.htmlunit.html.HtmlSmall;
133 import org.htmlunit.html.HtmlSpan;
134 import org.htmlunit.html.HtmlStrike;
135 import org.htmlunit.html.HtmlStrong;
136 import org.htmlunit.html.HtmlSubmitInput;
137 import org.htmlunit.html.HtmlSubscript;
138 import org.htmlunit.html.HtmlSummary;
139 import org.htmlunit.html.HtmlSuperscript;
140 import org.htmlunit.html.HtmlTableCell;
141 import org.htmlunit.html.HtmlTableRow;
142 import org.htmlunit.html.HtmlTeletype;
143 import org.htmlunit.html.HtmlTextArea;
144 import org.htmlunit.html.HtmlTextInput;
145 import org.htmlunit.html.HtmlTime;
146 import org.htmlunit.html.HtmlUnderlined;
147 import org.htmlunit.html.HtmlUnknownElement;
148 import org.htmlunit.html.HtmlVariable;
149 import org.htmlunit.html.HtmlWordBreak;
150 import org.htmlunit.platform.Platform;
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 org.htmlunit.util.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.isEmpty(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.isEmpty(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.isEmpty(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.isEmpty(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.isEmpty(styleWidth)) && parent instanceof HtmlElement) {
1574             // hack: TODO find a way to specify default values for different tags
1575             if (element instanceof HtmlCanvas) {
1576                 return 300;
1577             }
1578 
1579             // Width not explicitly set.
1580             final String cssFloat = getCssFloat();
1581             final String position = getStyleAttribute(Definition.POSITION, true);
1582             if ("right".equals(cssFloat) || "left".equals(cssFloat)
1583                     || ABSOLUTE.equals(position) || FIXED.equals(position)) {
1584                 final BrowserVersion browserVersion = getDomElement().getPage().getWebClient().getBrowserVersion();
1585                 // We're floating; simplistic approximation: text content * pixels per character.
1586                 width = element.getVisibleText().length() * browserVersion.getPixesPerChar();
1587             }
1588             else if (BLOCK.equals(display)) {
1589                 final int windowWidth = element.getPage().getEnclosingWindow().getInnerWidth();
1590                 if (element instanceof HtmlBody) {
1591                     width = windowWidth - 16;
1592                 }
1593                 else {
1594                     // Block elements take up 100% of the parent's width.
1595                     width = CssPixelValueConverter.pixelValue((DomElement) parent,
1596                                         new CssPixelValueConverter.CssValue(0, windowWidth) {
1597                             @Override public String get(final ComputedCssStyleDeclaration style) {
1598                                 return style.getWidth();
1599                             }
1600                         }) - (getBorderHorizontal() + getPaddingHorizontal());
1601                 }
1602             }
1603             else if (element instanceof HtmlSubmitInput
1604                         || element instanceof HtmlResetInput
1605                         || element instanceof HtmlButtonInput
1606                         || element instanceof HtmlButton
1607                         || element instanceof HtmlFileInput) {
1608                 // use asNormalizedText() here because getVisibleText() returns an empty string
1609                 // for submit and reset buttons
1610                 final String text = element.asNormalizedText();
1611                 final BrowserVersion browserVersion = getDomElement().getPage().getWebClient().getBrowserVersion();
1612                 // default font for buttons is a bit smaller than the body font size
1613                 width = 10 + (int) (text.length() * browserVersion.getPixesPerChar() * 0.9);
1614             }
1615             else if (element instanceof HtmlTextInput || element instanceof HtmlPasswordInput) {
1616                 final BrowserVersion browserVersion = getDomElement().getPage().getWebClient().getBrowserVersion();
1617                 if (browserVersion.hasFeature(JS_CLIENTWIDTH_INPUT_TEXT_173)) {
1618                     return 173;
1619                 }
1620                 if (browserVersion.hasFeature(JS_CLIENTWIDTH_INPUT_TEXT_154)) {
1621                     return 154;
1622                 }
1623                 width = 161; // FF
1624             }
1625             else if (element instanceof HtmlRadioButtonInput || element instanceof HtmlCheckBoxInput) {
1626                 final BrowserVersion browserVersion = getDomElement().getPage().getWebClient().getBrowserVersion();
1627                 if (browserVersion.hasFeature(JS_CLIENTWIDTH_RADIO_CHECKBOX_10)) {
1628                     width = 10;
1629                 }
1630                 else if (browserVersion.hasFeature(JS_CLIENTWIDTH_RADIO_CHECKBOX_14)) {
1631                     width = 14;
1632                 }
1633                 else {
1634                     width = 13;
1635                 }
1636             }
1637             else if (element instanceof HtmlTextArea) {
1638                 width = 100; // wild guess
1639             }
1640             else if (element instanceof HtmlImage) {
1641                 width = ((HtmlImage) element).getWidthOrDefault();
1642             }
1643             else {
1644                 // Inline elements take up however much space is required by their children.
1645                 width = getContentWidth();
1646             }
1647         }
1648         else if (AUTO.equals(styleWidth)) {
1649             width = element.getPage().getEnclosingWindow().getInnerWidth();
1650         }
1651         else {
1652             // Width explicitly set in the style attribute, or there was no parent to provide guidance.
1653             width = CssPixelValueConverter.pixelValue(element,
1654                     new CssPixelValueConverter.CssValue(0, element.getPage().getEnclosingWindow().getInnerWidth()) {
1655                     @Override public String get(final ComputedCssStyleDeclaration style) {
1656                         return style.getStyleAttribute(Definition.WIDTH, true);
1657                     }
1658                 });
1659         }
1660 
1661         return updateCachedWidth(width);
1662     }
1663 
1664     /**
1665      * Returns the total width of the element's children.
1666      * @return the total width of the element's children
1667      */
1668     public int getContentWidth() {
1669         int width = 0;
1670         final DomElement element = getDomElement();
1671         Iterable<DomNode> children = element.getChildren();
1672         if (element instanceof BaseFrameElement) {
1673             final Page enclosedPage = ((BaseFrameElement) element).getEnclosedPage();
1674             if (enclosedPage != null && enclosedPage.isHtmlPage()) {
1675                 children = ((DomNode) enclosedPage).getChildren();
1676             }
1677         }
1678         final WebWindow webWindow = element.getPage().getEnclosingWindow();
1679         for (final DomNode child : children) {
1680             if (child instanceof HtmlElement) {
1681                 final HtmlElement e = (HtmlElement) child;
1682                 final ComputedCssStyleDeclaration style = webWindow.getComputedStyle(e, null);
1683                 final int w = style.getCalculatedWidth(true, true);
1684                 width += w;
1685             }
1686             else if (child instanceof DomText) {
1687                 final BrowserVersion browserVersion = getDomElement().getPage().getWebClient().getBrowserVersion();
1688 
1689                 final DomNode parent = child.getParentNode();
1690                 if (parent instanceof HtmlElement) {
1691                     final ComputedCssStyleDeclaration style = webWindow.getComputedStyle((DomElement) parent, null);
1692                     final int height = browserVersion.getFontHeight(
1693                                         style.getStyleAttribute(Definition.FONT_SIZE, true));
1694                     width += child.getVisibleText().length() * (int) (height / 1.8f);
1695                 }
1696                 else {
1697                     width += child.getVisibleText().length() * browserVersion.getPixesPerChar();
1698                 }
1699             }
1700         }
1701         return width;
1702     }
1703 
1704     /**
1705      * @return the element's calculated height taking relevant CSS into account, but <b>not</b> the element's child
1706      *         elements
1707      */
1708     private int getEmptyHeight(final DomElement element) {
1709         final Integer cachedEmptyHeight = getCachedEmptyHeight();
1710         if (cachedEmptyHeight != null) {
1711             return cachedEmptyHeight.intValue();
1712         }
1713 
1714         if (!element.mayBeDisplayed()) {
1715             return updateCachedEmptyHeight(0);
1716         }
1717 
1718         final String display = getDisplay();
1719         if (NONE.equals(display)) {
1720             return updateCachedEmptyHeight(0);
1721         }
1722 
1723         final SgmlPage page = element.getPage();
1724         final WebWindow webWindow = page.getEnclosingWindow();
1725         final int windowHeight = webWindow.getInnerHeight();
1726 
1727         if (element instanceof HtmlBody) {
1728             if (page instanceof HtmlPage && ((HtmlPage) page).isQuirksMode()) {
1729                 return updateCachedEmptyHeight(windowHeight);
1730             }
1731 
1732             return updateCachedEmptyHeight(0);
1733         }
1734 
1735         final boolean isInline = INLINE.equals(display) && !(element instanceof HtmlInlineFrame);
1736         // height is ignored for inline elements
1737         final boolean explicitHeightSpecified = !isInline && !super.getHeight().isEmpty();
1738 
1739         int defaultHeight;
1740         if ((element instanceof HtmlAbbreviated
1741                 || element instanceof HtmlAcronym
1742                 || element instanceof HtmlAddress
1743                 || element instanceof HtmlArticle
1744                 || element instanceof HtmlAside
1745                 || element instanceof HtmlBaseFont
1746                 || element instanceof HtmlBidirectionalIsolation
1747                 || element instanceof HtmlBidirectionalOverride
1748                 || element instanceof HtmlBig
1749                 || element instanceof HtmlBold
1750                 || element instanceof HtmlCenter
1751                 || element instanceof HtmlCitation
1752                 || element instanceof HtmlCode
1753                 || element instanceof HtmlDefinition
1754                 || element instanceof HtmlDefinitionDescription
1755                 || element instanceof HtmlDefinitionTerm
1756                 || element instanceof HtmlEmphasis
1757                 || element instanceof HtmlFigure
1758                 || element instanceof HtmlFigureCaption
1759                 || element instanceof HtmlFooter
1760                 || element instanceof HtmlHeader
1761                 || element instanceof HtmlItalic
1762                 || element instanceof HtmlKeyboard
1763                 || element instanceof HtmlLayer
1764                 || element instanceof HtmlMark
1765                 || element instanceof HtmlNav
1766                 || element instanceof HtmlNoBreak
1767                 || element instanceof HtmlNoEmbed
1768                 || element instanceof HtmlNoFrames
1769                 || element instanceof HtmlNoLayer
1770                 || element instanceof HtmlNoScript
1771                 || element instanceof HtmlPlainText
1772                 || element instanceof HtmlRp
1773                 || element instanceof HtmlRtc
1774                 || element instanceof HtmlS
1775                 || element instanceof HtmlSample
1776                 || element instanceof HtmlSection
1777                 || element instanceof HtmlSmall
1778                 || element instanceof HtmlStrike
1779                 || element instanceof HtmlStrong
1780                 || element instanceof HtmlSubscript
1781                 || element instanceof HtmlSummary
1782                 || element instanceof HtmlSuperscript
1783                 || element instanceof HtmlTeletype
1784                 || element instanceof HtmlUnderlined
1785                 || element instanceof HtmlUnknownElement
1786                 || element instanceof HtmlWordBreak
1787                 || element instanceof HtmlMain
1788                 || element instanceof HtmlVariable
1789 
1790                 || element instanceof HtmlDivision
1791                 || element instanceof HtmlData
1792                 || element instanceof HtmlTime
1793                 || element instanceof HtmlOutput
1794                 || element instanceof HtmlSlot
1795                 || element instanceof HtmlLegend)
1796                 && StringUtils.isBlank(element.getTextContent())) {
1797             defaultHeight = 0;
1798         }
1799         else if (element.getFirstChild() == null) {
1800             if (element instanceof HtmlRadioButtonInput || element instanceof HtmlCheckBoxInput) {
1801                 final BrowserVersion browser = webWindow.getWebClient().getBrowserVersion();
1802                 if (browser.hasFeature(JS_CLIENTHEIGHT_RADIO_CHECKBOX_10)) {
1803                     defaultHeight = 10;
1804                 }
1805                 else if (browser.hasFeature(JS_CLIENTHEIGHT_RADIO_CHECKBOX_14)) {
1806                     defaultHeight = 14;
1807                 }
1808                 else {
1809                     defaultHeight = 13;
1810                 }
1811             }
1812             else if (element instanceof HtmlButton) {
1813                 defaultHeight = 20;
1814             }
1815             else if (element instanceof HtmlInput && !(element instanceof HtmlHiddenInput)) {
1816                 final BrowserVersion browser = webWindow.getWebClient().getBrowserVersion();
1817                 if (browser.hasFeature(JS_CLIENTHEIGHT_INPUT_17)) {
1818                     defaultHeight = 17;
1819                 }
1820                 else if (browser.hasFeature(JS_CLIENTHEIGHT_INPUT_18)) {
1821                     defaultHeight = 18;
1822                 }
1823                 else {
1824                     defaultHeight = 20;
1825                 }
1826             }
1827             else if (element instanceof HtmlSelect) {
1828                 defaultHeight = 20;
1829             }
1830             else if (element instanceof HtmlTextArea) {
1831                 defaultHeight = 49;
1832             }
1833             else if (element instanceof HtmlInlineFrame) {
1834                 defaultHeight = 154;
1835             }
1836             else {
1837                 defaultHeight = 0;
1838             }
1839         }
1840         else if (element instanceof HtmlRb) {
1841             final BrowserVersion browser = webWindow.getWebClient().getBrowserVersion();
1842             if (browser.hasFeature(JS_CLIENTHEIGHT_RB_17)) {
1843                 defaultHeight = 17;
1844             }
1845             else {
1846                 defaultHeight = 0;
1847             }
1848         }
1849         else if (element instanceof HtmlRt) {
1850             final BrowserVersion browser = webWindow.getWebClient().getBrowserVersion();
1851             if (browser.hasFeature(JS_CLIENTHEIGHT_RT_9)) {
1852                 defaultHeight = 9;
1853             }
1854             else {
1855                 defaultHeight = 0;
1856             }
1857         }
1858         else if (element instanceof HtmlRuby) {
1859             final BrowserVersion browser = webWindow.getWebClient().getBrowserVersion();
1860             if (browser.hasFeature(JS_CLIENTHEIGHT_RUBY_17)) {
1861                 defaultHeight = 17;
1862             }
1863             else {
1864                 defaultHeight = 0;
1865             }
1866         }
1867         else {
1868             final String fontSize;
1869 
1870             boolean isHeading = false;
1871             if (element instanceof HtmlHeading1) {
1872                 isHeading = true;
1873                 final String value = getStyleAttribute(Definition.FONT_SIZE, false);
1874                 if (value.isEmpty()) {
1875                     fontSize = "32px";
1876                 }
1877                 else {
1878                     fontSize = getStyleAttribute(Definition.FONT_SIZE, true);
1879                 }
1880             }
1881             else if (element instanceof HtmlHeading2) {
1882                 isHeading = true;
1883                 final String value = getStyleAttribute(Definition.FONT_SIZE, false);
1884                 if (value.isEmpty()) {
1885                     fontSize = "24px";
1886                 }
1887                 else {
1888                     fontSize = getStyleAttribute(Definition.FONT_SIZE, true);
1889                 }
1890             }
1891             else if (element instanceof HtmlHeading3) {
1892                 isHeading = true;
1893                 final String value = getStyleAttribute(Definition.FONT_SIZE, false);
1894                 if (value.isEmpty()) {
1895                     fontSize = "19px";
1896                 }
1897                 else {
1898                     fontSize = getStyleAttribute(Definition.FONT_SIZE, true);
1899                 }
1900             }
1901             else if (element instanceof HtmlHeading4) {
1902                 isHeading = true;
1903                 final String value = getStyleAttribute(Definition.FONT_SIZE, false);
1904                 if (value.isEmpty()) {
1905                     fontSize = "16px";
1906                 }
1907                 else {
1908                     fontSize = getStyleAttribute(Definition.FONT_SIZE, true);
1909                 }
1910             }
1911             else if (element instanceof HtmlHeading5) {
1912                 isHeading = true;
1913                 final String value = getStyleAttribute(Definition.FONT_SIZE, false);
1914                 if (value.isEmpty()) {
1915                     fontSize = "13px";
1916                 }
1917                 else {
1918                     fontSize = getStyleAttribute(Definition.FONT_SIZE, true);
1919                 }
1920             }
1921             else if (element instanceof HtmlHeading6) {
1922                 isHeading = true;
1923                 final String value = getStyleAttribute(Definition.FONT_SIZE, false);
1924                 if (value.isEmpty()) {
1925                     fontSize = "11px";
1926                 }
1927                 else {
1928                     fontSize = getStyleAttribute(Definition.FONT_SIZE, true);
1929                 }
1930             }
1931             else {
1932                 fontSize = getStyleAttribute(Definition.FONT_SIZE, true);
1933             }
1934 
1935             defaultHeight = webWindow.getWebClient().getBrowserVersion().getFontHeight(fontSize);
1936 
1937             if (isHeading
1938                     || element instanceof HtmlDivision
1939                     || element instanceof HtmlSpan) {
1940                 String width = getStyleAttribute(Definition.WIDTH, false);
1941 
1942                 // maybe we are enclosed something that forces a width
1943                 DomNode parent = getDomElement().getParentNode();
1944                 final WebWindow win = parent.getPage().getEnclosingWindow();
1945                 while (width.isEmpty() && parent != null) {
1946                     if (parent instanceof DomElement) {
1947                         final ComputedCssStyleDeclaration computedCss = win.getComputedStyle((DomElement) parent, null);
1948                         width = computedCss.getStyleAttribute(Definition.WIDTH, false);
1949                     }
1950                     parent = parent.getParentNode();
1951                     if (parent instanceof Page) {
1952                         break;
1953                     }
1954                 }
1955                 final int pixelWidth = CssPixelValueConverter.pixelValue(width);
1956                 final String content = element.getVisibleText();
1957 
1958                 if (pixelWidth > 0
1959                         && !width.isEmpty()
1960                         && StringUtils.isNotBlank(content)) {
1961                     final int lineCount = Platform.getFontUtil().countLines(content, pixelWidth, fontSize);
1962                     defaultHeight *= lineCount;
1963                 }
1964                 else {
1965                     if (element instanceof HtmlSpan && StringUtils.isEmpty(content)) {
1966                         defaultHeight = 0;
1967                     }
1968                     else {
1969                         defaultHeight *= StringUtils.countMatches(content, '\n') + 1;
1970                     }
1971                 }
1972 
1973                 final String styleHeight = getStyleAttribute(Definition.HEIGHT, true);
1974                 if (styleHeight.endsWith("%")) {
1975                     if (page instanceof HtmlPage && !((HtmlPage) page).isQuirksMode()) {
1976                         return defaultHeight;
1977                     }
1978                 }
1979             }
1980         }
1981 
1982         final int defaultWindowHeight = element instanceof HtmlCanvas ? 150 : windowHeight;
1983 
1984         int height = CssPixelValueConverter.pixelValue(element,
1985                 new CssPixelValueConverter.CssValue(defaultHeight, defaultWindowHeight) {
1986                 @Override public String get(final ComputedCssStyleDeclaration style) {
1987                     final DomElement elem = style.getDomElement();
1988                     if (elem instanceof HtmlBody) {
1989                         return String.valueOf(elem.getPage().getEnclosingWindow().getInnerHeight());
1990                     }
1991                     // height is ignored for inline elements
1992                     if (isInline) {
1993                         return "";
1994                     }
1995                     return style.getStyleAttribute(Definition.HEIGHT, true);
1996                 }
1997             });
1998 
1999         if (height == 0 && !explicitHeightSpecified) {
2000             height = defaultHeight;
2001         }
2002 
2003         return updateCachedEmptyHeight(height);
2004     }
2005 
2006     /**
2007      * Returns the total height of the element's children.
2008      * @return the total height of the element's children
2009      */
2010     public int getContentHeight() {
2011         // There are two different kinds of elements that might determine the content height:
2012         //  - elements with position:static or position:relative (elements that flow and build on each other)
2013         //  - elements with position:absolute (independent elements)
2014 
2015         final DomNode node = getDomElement();
2016         if (!node.mayBeDisplayed()) {
2017             return 0;
2018         }
2019 
2020         ComputedCssStyleDeclaration lastFlowing = null;
2021         final Set<ComputedCssStyleDeclaration> styles = new HashSet<>();
2022 
2023         if (node instanceof HtmlTableRow) {
2024             final HtmlTableRow row = (HtmlTableRow) node;
2025             for (final HtmlTableCell cell : row.getCellIterator()) {
2026                 if (cell.mayBeDisplayed()) {
2027                     final ComputedCssStyleDeclaration style =
2028                             cell.getPage().getEnclosingWindow().getComputedStyle(cell, null);
2029                     styles.add(style);
2030                 }
2031             }
2032         }
2033         else {
2034             for (final DomNode child : node.getChildren()) {
2035                 if (child.mayBeDisplayed()) {
2036                     if (child instanceof HtmlElement) {
2037                         final HtmlElement e = (HtmlElement) child;
2038                         final ComputedCssStyleDeclaration style =
2039                                 e.getPage().getEnclosingWindow().getComputedStyle(e, null);
2040                         final String position = style.getPositionWithInheritance();
2041                         if (STATIC.equals(position) || RELATIVE.equals(position)) {
2042                             lastFlowing = style;
2043                         }
2044                         else if (ABSOLUTE.equals(position) || FIXED.equals(position)) {
2045                             styles.add(style);
2046                         }
2047                     }
2048                 }
2049             }
2050 
2051             if (lastFlowing != null) {
2052                 styles.add(lastFlowing);
2053             }
2054         }
2055 
2056         int max = 0;
2057         for (final ComputedCssStyleDeclaration style : styles) {
2058             final int h = style.getTop(true, false, false) + style.getCalculatedHeight(true, true);
2059             if (h > max) {
2060                 max = h;
2061             }
2062         }
2063         return max;
2064     }
2065 
2066     /**
2067      * Returns {@code true} if the element is scrollable along the specified axis.
2068      * @param horizontal if {@code true}, the caller is interested in scrollability along the x-axis;
2069      *        if {@code false}, the caller is interested in scrollability along the y-axis
2070      * @return {@code true} if the element is scrollable along the specified axis
2071      */
2072     public boolean isScrollable(final boolean horizontal) {
2073         return isScrollable(getDomElement(), horizontal, false);
2074     }
2075 
2076     /**
2077      * @param ignoreSize whether to consider the content/calculated width/height
2078      */
2079     private boolean isScrollable(final DomElement element, final boolean horizontal, final boolean ignoreSize) {
2080         final boolean scrollable;
2081 
2082         String overflow;
2083         if (horizontal) {
2084             overflow = getStyleAttribute(Definition.OVERFLOW_X_, false);
2085             if (StringUtils.isEmpty(overflow)) {
2086                 overflow = getStyleAttribute(Definition.OVERFLOW_X, false);
2087             }
2088             // fall back to default
2089             if (StringUtils.isEmpty(overflow)) {
2090                 overflow = getStyleAttribute(Definition.OVERFLOW, true);
2091             }
2092             scrollable = (element instanceof HtmlBody || SCROLL.equals(overflow) || AUTO.equals(overflow))
2093                 && (ignoreSize || getContentWidth() > getCalculatedWidth());
2094         }
2095         else {
2096             overflow = getStyleAttribute(Definition.OVERFLOW_Y_, false);
2097             if (StringUtils.isEmpty(overflow)) {
2098                 overflow = getStyleAttribute(Definition.OVERFLOW_Y, false);
2099             }
2100             // fall back to default
2101             if (StringUtils.isEmpty(overflow)) {
2102                 overflow = getStyleAttribute(Definition.OVERFLOW, true);
2103             }
2104 
2105             scrollable = (element instanceof HtmlBody || SCROLL.equals(overflow) || AUTO.equals(overflow))
2106                 && (ignoreSize || getContentHeight() > getEmptyHeight(element));
2107         }
2108         return scrollable;
2109     }
2110 
2111     private int getBorderHorizontal() {
2112         final Integer borderHorizontal = getCachedBorderHorizontal();
2113         if (borderHorizontal != null) {
2114             return borderHorizontal.intValue();
2115         }
2116 
2117         final int border = NONE.equals(getDisplay()) ? 0 : getBorderLeftValue() + getBorderRightValue();
2118         return updateCachedBorderHorizontal(border);
2119     }
2120 
2121     private int getBorderVertical() {
2122         final Integer borderVertical = getCachedBorderVertical();
2123         if (borderVertical != null) {
2124             return borderVertical.intValue();
2125         }
2126 
2127         final int border = NONE.equals(getDisplay()) ? 0 : getBorderTopValue() + getBorderBottomValue();
2128         return updateCachedBorderVertical(border);
2129     }
2130 
2131     /**
2132      * Gets the size of the left border of the element.
2133      * @return the value in pixels
2134      */
2135     public int getBorderLeftValue() {
2136         return CssPixelValueConverter.pixelValue(getBorderLeftWidth());
2137     }
2138 
2139     /**
2140      * Gets the size of the right border of the element.
2141      * @return the value in pixels
2142      */
2143     public int getBorderRightValue() {
2144         return CssPixelValueConverter.pixelValue(getBorderRightWidth());
2145     }
2146 
2147     /**
2148      * Gets the size of the top border of the element.
2149      * @return the value in pixels
2150      */
2151     public int getBorderTopValue() {
2152         return CssPixelValueConverter.pixelValue(getBorderTopWidth());
2153     }
2154 
2155     /**
2156      * Gets the size of the bottom border of the element.
2157      * @return the value in pixels
2158      */
2159     public int getBorderBottomValue() {
2160         return CssPixelValueConverter.pixelValue(getBorderBottomWidth());
2161     }
2162 
2163     private int getPaddingHorizontal() {
2164         final Integer paddingHorizontal = getCachedPaddingHorizontal();
2165         if (paddingHorizontal != null) {
2166             return paddingHorizontal.intValue();
2167         }
2168 
2169         final int padding = NONE.equals(getDisplay()) ? 0 : getPaddingLeftValue() + getPaddingRightValue();
2170         return updateCachedPaddingHorizontal(padding);
2171     }
2172 
2173     private int getPaddingVertical() {
2174         final Integer paddingVertical = getCachedPaddingVertical();
2175         if (paddingVertical != null) {
2176             return paddingVertical.intValue();
2177         }
2178 
2179         final int padding = NONE.equals(getDisplay()) ? 0 : getPaddingTopValue() + getPaddingBottomValue();
2180         return updateCachedPaddingVertical(padding);
2181     }
2182 
2183     /**
2184      * Gets the left padding of the element.
2185      * @return the value in pixels
2186      */
2187     public int getPaddingLeftValue() {
2188         return CssPixelValueConverter.pixelValue(getPaddingLeft());
2189     }
2190 
2191     /**
2192      * Gets the right padding of the element.
2193      * @return the value in pixels
2194      */
2195     public int getPaddingRightValue() {
2196         return CssPixelValueConverter.pixelValue(getPaddingRight());
2197     }
2198 
2199     /**
2200      * Gets the top padding of the element.
2201      * @return the value in pixels
2202      */
2203     public int getPaddingTopValue() {
2204         return CssPixelValueConverter.pixelValue(getPaddingTop());
2205     }
2206 
2207     /**
2208      * Gets the bottom padding of the element.
2209      * @return the value in pixels
2210      */
2211     public int getPaddingBottomValue() {
2212         return CssPixelValueConverter.pixelValue(getPaddingBottom());
2213     }
2214 
2215     /**
2216      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span>
2217      * @return the cached width
2218      */
2219     public Integer getCachedWidth() {
2220         return width_;
2221     }
2222 
2223     /**
2224      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span>
2225      * @param width the new value
2226      * @return the param width
2227      */
2228     public int updateCachedWidth(final int width) {
2229         width_ = Integer.valueOf(width);
2230         return width;
2231     }
2232 
2233     /**
2234      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span>
2235      * @return the cached height
2236      */
2237     public Integer getCachedHeight() {
2238         return height_;
2239     }
2240 
2241     /**
2242      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span>
2243      * @param height the new value
2244      * @return the param height
2245      */
2246     public int updateCachedHeight(final int height) {
2247         height_ = Integer.valueOf(height);
2248         return height;
2249     }
2250 
2251     /**
2252      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span>
2253      * @return the cached emptyHeight
2254      */
2255     public Integer getCachedEmptyHeight() {
2256         return emptyHeight_;
2257     }
2258 
2259     /**
2260      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span>
2261      * @param emptyHeight the new value
2262      * @return the param emptyHeight
2263      */
2264     public int updateCachedEmptyHeight(final int emptyHeight) {
2265         emptyHeight_ = Integer.valueOf(emptyHeight);
2266         return emptyHeight;
2267     }
2268 
2269     /**
2270      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span>
2271      * @return the cached top
2272      */
2273     public Integer getCachedTop() {
2274         return top_;
2275     }
2276 
2277     /**
2278      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span>
2279      * @param top the new value
2280      */
2281     public void setCachedTop(final Integer top) {
2282         top_ = top;
2283     }
2284 
2285     /**
2286      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span>
2287      * @return the cached padding horizontal
2288      */
2289     public Integer getCachedPaddingHorizontal() {
2290         return paddingHorizontal_;
2291     }
2292 
2293     /**
2294      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span>
2295      * @param paddingHorizontal the new value
2296      * @return the param paddingHorizontal
2297      */
2298     public int updateCachedPaddingHorizontal(final int paddingHorizontal) {
2299         paddingHorizontal_ = Integer.valueOf(paddingHorizontal);
2300         return paddingHorizontal;
2301     }
2302 
2303     /**
2304      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span>
2305      * @return the cached padding vertical
2306      */
2307     public Integer getCachedPaddingVertical() {
2308         return paddingVertical_;
2309     }
2310 
2311     /**
2312      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span>
2313      * @param paddingVertical the new value
2314      * @return the param paddingVertical
2315      */
2316     public int updateCachedPaddingVertical(final int paddingVertical) {
2317         paddingVertical_ = Integer.valueOf(paddingVertical);
2318         return paddingVertical;
2319     }
2320 
2321     /**
2322      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span>
2323      * @return the cached border horizontal
2324      */
2325     public Integer getCachedBorderHorizontal() {
2326         return borderHorizontal_;
2327     }
2328 
2329     /**
2330      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span>
2331      * @param borderHorizontal the new value
2332      * @return the param borderHorizontal
2333      */
2334     public int updateCachedBorderHorizontal(final int borderHorizontal) {
2335         borderHorizontal_ = Integer.valueOf(borderHorizontal);
2336         return borderHorizontal;
2337     }
2338 
2339     /**
2340      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span>
2341      * @return the cached border vertical
2342      */
2343     public Integer getCachedBorderVertical() {
2344         return borderVertical_;
2345     }
2346 
2347     /**
2348      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span>
2349      * @param borderVertical the new value
2350      * @return the param borderVertical
2351      */
2352     public int updateCachedBorderVertical(final int borderVertical) {
2353         borderVertical_ = Integer.valueOf(borderVertical);
2354         return borderVertical;
2355     }
2356 
2357     /**
2358      * Makes a local, "computed", modification to this CSS style.
2359      *
2360      * @param declaration the style declaration
2361      * @param selector the selector determining that the style applies to this element
2362      */
2363     public void applyStyleFromSelector(final CSSStyleDeclarationImpl declaration, final Selector selector) {
2364         final SelectorSpecificity specificity = selector.getSelectorSpecificity();
2365         for (final Property prop : declaration.getProperties()) {
2366             final String name = prop.getName();
2367             final String value = declaration.getPropertyValue(name);
2368             final String priority = declaration.getPropertyPriority(name);
2369             applyLocalStyleAttribute(name, value, priority, specificity);
2370         }
2371     }
2372 
2373     private void applyLocalStyleAttribute(final String name, final String newValue, final String priority,
2374             final SelectorSpecificity specificity) {
2375         if (!StyleElement.PRIORITY_IMPORTANT.equals(priority)) {
2376             final StyleElement existingElement = localModifications_.get(name);
2377             if (existingElement != null) {
2378                 if (existingElement.isImportant()) {
2379                     return; // can't override a !important rule by a normal rule. Ignore it!
2380                 }
2381                 else if (specificity.compareTo(existingElement.getSpecificity()) < 0) {
2382                     return; // can't override a rule with a rule having higher specificity
2383                 }
2384             }
2385         }
2386         final StyleElement element = new StyleElement(name, newValue, priority, specificity);
2387         localModifications_.put(name, element);
2388     }
2389 
2390     /**
2391      * Makes a local, "computed", modification to this CSS style that won't override other
2392      * style attributes of the same name. This method should be used to set default values
2393      * for style attributes.
2394      *
2395      * @param name the name of the style attribute to set
2396      * @param newValue the value of the style attribute to set
2397      */
2398     public void setDefaultLocalStyleAttribute(final String name, final String newValue) {
2399         final StyleElement element = new StyleElement(name, newValue, "", SelectorSpecificity.DEFAULT_STYLE_ATTRIBUTE);
2400         localModifications_.put(name, element);
2401     }
2402 
2403     /**
2404      * {@inheritDoc}
2405      */
2406     @Override
2407     public boolean hasFeature(final BrowserVersionFeatures property) {
2408         return getDomElement().hasFeature(property);
2409     }
2410 
2411     /**
2412      * {@inheritDoc}
2413      */
2414     @Override
2415     public BrowserVersion getBrowserVersion() {
2416         return getDomElement().getPage().getWebClient().getBrowserVersion();
2417     }
2418 
2419     /**
2420      * {@inheritDoc}
2421      */
2422     @Override
2423     public boolean isComputed() {
2424         return true;
2425     }
2426 
2427     /**
2428      * {@inheritDoc}
2429      */
2430     @Override
2431     public String toString() {
2432         return "ComputedCssStyleDeclaration for '" + getDomElement() + "'";
2433     }
2434 
2435     private String defaultIfEmpty(final String str, final StyleAttributes.Definition definition) {
2436         return defaultIfEmpty(str, definition, false);
2437     }
2438 
2439     private String defaultIfEmpty(final String str, final StyleAttributes.Definition definition,
2440             final boolean isPixel) {
2441         if (!getDomElement().isAttachedToPage()) {
2442             return EMPTY_FINAL;
2443         }
2444         if (str == null || str.isEmpty()) {
2445             return definition.getDefaultComputedValue(getBrowserVersion());
2446         }
2447         if (isPixel) {
2448             return pixelString(str);
2449         }
2450         return str;
2451     }
2452 
2453     /**
2454      * @param toReturnIfEmptyOrDefault the value to return if empty or equals the {@code defaultValue}
2455      * @param defaultValue the default value of the string
2456      * @return the string, or {@code toReturnIfEmptyOrDefault}
2457      */
2458     private String defaultIfEmpty(final String str, final String toReturnIfEmptyOrDefault, final String defaultValue) {
2459         if (!getDomElement().isAttachedToPage()) {
2460             return EMPTY_FINAL;
2461         }
2462         if (str == null || str.isEmpty() || str.equals(defaultValue)) {
2463             return toReturnIfEmptyOrDefault;
2464         }
2465         return str;
2466     }
2467 
2468     /**
2469      * Returns the specified length value as a pixel length value.
2470      * This method does <b>NOT</b> handle percentages correctly;
2471      * use {@link CssPixelValueConverter#pixelValue(DomElement, CssValue)} if you need percentage support).
2472      * @param value the length value to convert to a pixel length value
2473      * @return the specified length value as a pixel length value
2474      * @see CssPixelValueConverter#pixelString(DomElement, CssValue)
2475      */
2476     private static String pixelString(final String value) {
2477         if (EMPTY_FINAL == value || value.endsWith("px")) {
2478             return value;
2479         }
2480         return CssPixelValueConverter.pixelValue(value) + "px";
2481     }
2482 }