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