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