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