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