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