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