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