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