View Javadoc
1   /*
2    * Copyright (c) 2002-2017 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    * http://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 com.gargoylesoftware.htmlunit.javascript.host.css;
16  
17  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.CSS_COMPUTED_BLOCK_IF_NOT_ATTACHED;
18  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.CSS_COMPUTED_NO_Z_INDEX;
19  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_CLIENTHIGHT_INPUT_17;
20  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_CLIENTWIDTH_INPUT_TEXT_143;
21  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_CLIENTWIDTH_INPUT_TEXT_169;
22  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.FF;
23  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.ACCELERATOR;
24  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.AZIMUTH;
25  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BACKGROUND_ATTACHMENT;
26  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BACKGROUND_COLOR;
27  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BACKGROUND_IMAGE;
28  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BACKGROUND_POSITION;
29  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BACKGROUND_REPEAT;
30  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BORDER_BOTTOM_COLOR;
31  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BORDER_BOTTOM_STYLE;
32  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BORDER_BOTTOM_WIDTH;
33  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BORDER_COLLAPSE;
34  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BORDER_LEFT_COLOR;
35  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BORDER_LEFT_STYLE;
36  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BORDER_SPACING;
37  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BOX_SIZING;
38  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.CAPTION_SIDE;
39  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.COLOR;
40  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.CSS_FLOAT;
41  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.CURSOR;
42  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.DIRECTION;
43  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.DISPLAY;
44  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.ELEVATION;
45  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.EMPTY_CELLS;
46  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.FONT;
47  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.FONT_FAMILY;
48  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.FONT_SIZE;
49  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.FONT_STRETCH;
50  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.FONT_STYLE;
51  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.FONT_VARIANT;
52  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.FONT_WEIGHT;
53  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.HEIGHT;
54  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.LEFT;
55  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.LETTER_SPACING;
56  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.LINE_HEIGHT;
57  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.LIST_STYLE;
58  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.LIST_STYLE_IMAGE;
59  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.LIST_STYLE_POSITION;
60  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.LIST_STYLE_TYPE;
61  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.MARGIN;
62  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.MARGIN_LEFT;
63  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.MARGIN_RIGHT;
64  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.ORPHANS;
65  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.OVERFLOW;
66  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.PADDING;
67  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.PITCH;
68  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.PITCH_RANGE;
69  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.POSITION;
70  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.QUOTES;
71  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.RICHNESS;
72  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.SPEAK;
73  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.SPEAK_HEADER;
74  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.SPEAK_NUMERAL;
75  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.SPEAK_PUNCTUATION;
76  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.SPEECH_RATE;
77  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.STRESS;
78  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.TEXT_ALIGN;
79  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.TEXT_INDENT;
80  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.TEXT_TRANSFORM;
81  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.TOP;
82  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.VISIBILITY;
83  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.VOICE_FAMILY;
84  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.VOLUME;
85  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.WHITE_SPACE;
86  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.WIDOWS;
87  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.WIDTH;
88  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.WORD_SPACING;
89  
90  import java.util.EnumSet;
91  import java.util.HashSet;
92  import java.util.Set;
93  import java.util.SortedMap;
94  import java.util.TreeMap;
95  
96  import org.apache.commons.lang3.StringUtils;
97  import org.w3c.css.sac.Selector;
98  
99  import com.gargoylesoftware.htmlunit.BrowserVersion;
100 import com.gargoylesoftware.htmlunit.Page;
101 import com.gargoylesoftware.htmlunit.css.SelectorSpecificity;
102 import com.gargoylesoftware.htmlunit.css.StyleElement;
103 import com.gargoylesoftware.htmlunit.html.BaseFrameElement;
104 import com.gargoylesoftware.htmlunit.html.DomElement;
105 import com.gargoylesoftware.htmlunit.html.DomNode;
106 import com.gargoylesoftware.htmlunit.html.HtmlButton;
107 import com.gargoylesoftware.htmlunit.html.HtmlButtonInput;
108 import com.gargoylesoftware.htmlunit.html.HtmlCheckBoxInput;
109 import com.gargoylesoftware.htmlunit.html.HtmlDivision;
110 import com.gargoylesoftware.htmlunit.html.HtmlElement;
111 import com.gargoylesoftware.htmlunit.html.HtmlFileInput;
112 import com.gargoylesoftware.htmlunit.html.HtmlHiddenInput;
113 import com.gargoylesoftware.htmlunit.html.HtmlInlineFrame;
114 import com.gargoylesoftware.htmlunit.html.HtmlInput;
115 import com.gargoylesoftware.htmlunit.html.HtmlPage;
116 import com.gargoylesoftware.htmlunit.html.HtmlPasswordInput;
117 import com.gargoylesoftware.htmlunit.html.HtmlRadioButtonInput;
118 import com.gargoylesoftware.htmlunit.html.HtmlResetInput;
119 import com.gargoylesoftware.htmlunit.html.HtmlSelect;
120 import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput;
121 import com.gargoylesoftware.htmlunit.html.HtmlTableRow;
122 import com.gargoylesoftware.htmlunit.html.HtmlTextArea;
123 import com.gargoylesoftware.htmlunit.html.HtmlTextInput;
124 import com.gargoylesoftware.htmlunit.javascript.configuration.JsxClass;
125 import com.gargoylesoftware.htmlunit.javascript.host.Element;
126 import com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition;
127 import com.gargoylesoftware.htmlunit.javascript.host.dom.Text;
128 import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLBodyElement;
129 import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLCanvasElement;
130 import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLElement;
131 
132 import net.sourceforge.htmlunit.corejs.javascript.Context;
133 
134 /**
135  * An object for a CSSStyleDeclaration, which is computed.
136  *
137  * @see com.gargoylesoftware.htmlunit.javascript.host.Window#getComputedStyle(Object, String)
138  *
139  * @author Ahmed Ashour
140  * @author Marc Guillemot
141  * @author Ronald Brill
142  * @author Frank Danek
143  */
144 @JsxClass(isJSObject = false, value = FF)
145 public class ComputedCSSStyleDeclaration extends CSSStyleDeclaration {
146 
147     /** Denotes a value which should be returned as is. */
148     private static final String EMPTY_FINAL = new String("");
149 
150     /** The number of (horizontal) pixels to assume that each character occupies. */
151     private static final int PIXELS_PER_CHAR = 10;
152 
153     /** The set of 'inheritable' definitions. */
154     private static final Set<Definition> INHERITABLE_DEFINITIONS = EnumSet.of(
155         AZIMUTH,
156         BORDER_COLLAPSE,
157         BORDER_SPACING,
158         CAPTION_SIDE,
159         COLOR,
160         CURSOR,
161         DIRECTION,
162         ELEVATION,
163         EMPTY_CELLS,
164         FONT_FAMILY,
165         FONT_SIZE,
166         FONT_STYLE,
167         FONT_VARIANT,
168         FONT_WEIGHT,
169         FONT,
170         LETTER_SPACING,
171         LINE_HEIGHT,
172         LIST_STYLE_IMAGE,
173         LIST_STYLE_POSITION,
174         LIST_STYLE_TYPE,
175         LIST_STYLE,
176         ORPHANS,
177         PITCH_RANGE,
178         PITCH,
179         QUOTES,
180         RICHNESS,
181         SPEAK_HEADER,
182         SPEAK_NUMERAL,
183         SPEAK_PUNCTUATION,
184         SPEAK,
185         SPEECH_RATE,
186         STRESS,
187         TEXT_ALIGN,
188         TEXT_INDENT,
189         TEXT_TRANSFORM,
190         VISIBILITY,
191         VOICE_FAMILY,
192         VOICE_FAMILY,
193         VOLUME,
194         WHITE_SPACE,
195         WIDOWS,
196         WORD_SPACING);
197 
198     /**
199      * Local modifications maintained here rather than in the element. We use a sorted
200      * map so that results are deterministic and thus easily testable.
201      */
202     private final SortedMap<String, StyleElement> localModifications_ = new TreeMap<>();
203 
204     /** The computed, cached width of the element to which this computed style belongs (no padding, borders, etc). */
205     private Integer width_;
206 
207     /**
208      * The computed, cached height of the element to which this computed style belongs (no padding, borders, etc),
209      * taking child elements into account.
210      */
211     private Integer height_;
212 
213     /**
214      * The computed, cached height of the element to which this computed style belongs (no padding, borders, etc),
215      * <b>not</b> taking child elements into account.
216      */
217     private Integer height2_;
218 
219     /** The computed, cached horizontal padding (left + right) of the element to which this computed style belongs. */
220     private Integer paddingHorizontal_;
221 
222     /** The computed, cached vertical padding (top + bottom) of the element to which this computed style belongs. */
223     private Integer paddingVertical_;
224 
225     /** The computed, cached horizontal border (left + right) of the element to which this computed style belongs. */
226     private Integer borderHorizontal_;
227 
228     /** The computed, cached vertical border (top + bottom) of the element to which this computed style belongs. */
229     private Integer borderVertical_;
230 
231     /** The computed, cached top of the element to which this computed style belongs. */
232     private Integer top_;
233 
234     /**
235      * Creates an instance.
236      */
237     public ComputedCSSStyleDeclaration() {
238     }
239 
240     /**
241      * Creates an instance.
242      *
243      * @param style the original Style
244      */
245     public ComputedCSSStyleDeclaration(final CSSStyleDeclaration style) {
246         super(style.getElement());
247         getElement().setDefaults(this);
248     }
249 
250     /**
251      * {@inheritDoc}
252      *
253      * This method does nothing as the object is read-only.
254      */
255     @Override
256     protected void setStyleAttribute(final String name, final String newValue) {
257         // Empty.
258     }
259 
260     /**
261      * Makes a local, "computed", modification to this CSS style.
262      *
263      * @param declaration the style declaration
264      * @param selector the selector determining that the style applies to this element
265      */
266     public void applyStyleFromSelector(final org.w3c.dom.css.CSSStyleDeclaration declaration, final Selector selector) {
267         final BrowserVersion browserVersion = getBrowserVersion();
268         final SelectorSpecificity specificity = new SelectorSpecificity(selector);
269         for (int k = 0; k < declaration.getLength(); k++) {
270             final String name = declaration.item(k);
271             final String value = declaration.getPropertyValue(name);
272             final String priority = declaration.getPropertyPriority(name);
273             if (!"z-index".equals(name) || !browserVersion.hasFeature(CSS_COMPUTED_NO_Z_INDEX)) {
274                 applyLocalStyleAttribute(name, value, priority, specificity);
275             }
276         }
277     }
278 
279     private void applyLocalStyleAttribute(final String name, final String newValue, final String priority,
280             final SelectorSpecificity specificity) {
281         if (!StyleElement.PRIORITY_IMPORTANT.equals(priority)) {
282             final StyleElement existingElement = localModifications_.get(name);
283             if (existingElement != null) {
284                 if (StyleElement.PRIORITY_IMPORTANT.equals(existingElement.getPriority())) {
285                     return; // can't override a !important rule by a normal rule. Ignore it!
286                 }
287                 else if (specificity.compareTo(existingElement.getSpecificity()) < 0) {
288                     return; // can't override a rule with a rule having higher specificity
289                 }
290             }
291         }
292         final StyleElement element = new StyleElement(name, newValue, priority, specificity);
293         localModifications_.put(name, element);
294     }
295 
296     /**
297      * Makes a local, "computed", modification to this CSS style that won't override other
298      * style attributes of the same name. This method should be used to set default values
299      * for style attributes.
300      *
301      * @param name the name of the style attribute to set
302      * @param newValue the value of the style attribute to set
303      */
304     public void setDefaultLocalStyleAttribute(final String name, final String newValue) {
305         final StyleElement element = new StyleElement(name, newValue);
306         localModifications_.put(name, element);
307     }
308 
309     @Override
310     protected StyleElement getStyleElement(final String name) {
311         final StyleElement existent = super.getStyleElement(name);
312 
313         if (localModifications_ != null) {
314             final StyleElement localStyleMod = localModifications_.get(name);
315             if (localStyleMod == null) {
316                 return existent;
317             }
318 
319             if (existent == null) {
320                 // Local modifications represent either default style elements or style elements
321                 // defined in stylesheets; either way, they shouldn't overwrite any style
322                 // elements derived directly from the HTML element's "style" attribute.
323                 return localStyleMod;
324             }
325 
326             // replace if !IMPORTANT
327             if (StyleElement.PRIORITY_IMPORTANT.equals(localStyleMod.getPriority())) {
328                 if (StyleElement.PRIORITY_IMPORTANT.equals(existent.getPriority())) {
329                     if (existent.getSpecificity().compareTo(localStyleMod.getSpecificity()) < 0) {
330                         return localStyleMod;
331                     }
332                 }
333                 else {
334                     return localStyleMod;
335                 }
336             }
337         }
338         return existent;
339     }
340 
341     private String defaultIfEmpty(final String str, final StyleAttributes.Definition definition) {
342         return defaultIfEmpty(str, definition, false);
343     }
344 
345     private String defaultIfEmpty(final String str, final StyleAttributes.Definition definition,
346             final boolean isPixel) {
347         if (!getElement().getDomNodeOrDie().isAttachedToPage()
348                 && getBrowserVersion().hasFeature(CSS_COMPUTED_NO_Z_INDEX)) {
349             return EMPTY_FINAL;
350         }
351         if (str == null || str.isEmpty()) {
352             return definition.getDefaultComputedValue(getBrowserVersion());
353         }
354         if (isPixel) {
355             return pixelString(str);
356         }
357         return str;
358     }
359 
360     /**
361      * @param toReturnIfEmptyOrDefault the value to return if empty or equals the {@code defualtValue}
362      * @param defaultValue the default value of the string
363      * @return the string, or {@code toReturnIfEmptyOrDefault}
364      */
365     private String defaultIfEmpty(final String str, final String toReturnIfEmptyOrDefault, final String defaultValue) {
366         if (!getElement().getDomNodeOrDie().isAttachedToPage()
367                 && getBrowserVersion().hasFeature(CSS_COMPUTED_NO_Z_INDEX)) {
368             return EMPTY_FINAL;
369         }
370         if (str == null || str.isEmpty() || str.equals(defaultValue)) {
371             return toReturnIfEmptyOrDefault;
372         }
373         return str;
374     }
375 
376     /**
377      * {@inheritDoc}
378      */
379     @Override
380     public String getAccelerator() {
381         return defaultIfEmpty(getStyleAttribute(ACCELERATOR, false), ACCELERATOR);
382     }
383 
384     /**
385      * {@inheritDoc}
386      */
387     @Override
388     public String getBackgroundAttachment() {
389         return defaultIfEmpty(super.getBackgroundAttachment(), BACKGROUND_ATTACHMENT);
390     }
391 
392     /**
393      * {@inheritDoc}
394      */
395     @Override
396     public String getBackgroundColor() {
397         final String value = super.getBackgroundColor();
398         if (StringUtils.isEmpty(value)) {
399             return BACKGROUND_COLOR.getDefaultComputedValue(getBrowserVersion());
400         }
401         return toRGBColor(value);
402     }
403 
404     /**
405      * {@inheritDoc}
406      */
407     @Override
408     public String getBackgroundImage() {
409         return defaultIfEmpty(super.getBackgroundImage(), BACKGROUND_IMAGE);
410     }
411 
412     /**
413      * Gets the {@code backgroundPosition} style attribute.
414      * @return the style attribute
415      */
416     @Override
417     public String getBackgroundPosition() {
418         return defaultIfEmpty(super.getBackgroundPosition(), BACKGROUND_POSITION);
419     }
420 
421     /**
422      * {@inheritDoc}
423      */
424     @Override
425     public String getBackgroundRepeat() {
426         return defaultIfEmpty(super.getBackgroundRepeat(), BACKGROUND_REPEAT);
427     }
428 
429     /**
430      * {@inheritDoc}
431      */
432     @Override
433     public String getBorderBottomColor() {
434         return defaultIfEmpty(super.getBorderBottomColor(), BORDER_BOTTOM_COLOR);
435     }
436 
437     /**
438      * {@inheritDoc}
439      */
440     @Override
441     public String getBorderBottomStyle() {
442         return defaultIfEmpty(super.getBorderBottomStyle(), BORDER_BOTTOM_STYLE);
443     }
444 
445     /**
446      * {@inheritDoc}
447      */
448     @Override
449     public String getBorderBottomWidth() {
450         return pixelString(defaultIfEmpty(super.getBorderBottomWidth(), BORDER_BOTTOM_WIDTH));
451     }
452 
453     /**
454      * {@inheritDoc}
455      */
456     @Override
457     public String getBorderLeftColor() {
458         return defaultIfEmpty(super.getBorderLeftColor(), BORDER_LEFT_COLOR);
459     }
460 
461     /**
462      * {@inheritDoc}
463      */
464     @Override
465     public String getBorderLeftStyle() {
466         return defaultIfEmpty(super.getBorderLeftStyle(), BORDER_LEFT_STYLE);
467     }
468 
469     /**
470      * {@inheritDoc}
471      */
472     @Override
473     public String getBorderLeftWidth() {
474         return pixelString(defaultIfEmpty(super.getBorderLeftWidth(), "0px", null));
475     }
476 
477     /**
478      * {@inheritDoc}
479      */
480     @Override
481     public String getBorderRightColor() {
482         return defaultIfEmpty(super.getBorderRightColor(), "rgb(0, 0, 0)", null);
483     }
484 
485     /**
486      * {@inheritDoc}
487      */
488     @Override
489     public String getBorderRightStyle() {
490         return defaultIfEmpty(super.getBorderRightStyle(), "none", null);
491     }
492 
493     /**
494      * {@inheritDoc}
495      */
496     @Override
497     public String getBorderRightWidth() {
498         return pixelString(defaultIfEmpty(super.getBorderRightWidth(), "0px", null));
499     }
500 
501     /**
502      * {@inheritDoc}
503      */
504     @Override
505     public String getBorderTopColor() {
506         return defaultIfEmpty(super.getBorderTopColor(), "rgb(0, 0, 0)", null);
507     }
508 
509     /**
510      * {@inheritDoc}
511      */
512     @Override
513     public String getBorderTopStyle() {
514         return defaultIfEmpty(super.getBorderTopStyle(), "none", null);
515     }
516 
517     /**
518      * {@inheritDoc}
519      */
520     @Override
521     public String getBorderTopWidth() {
522         return pixelString(defaultIfEmpty(super.getBorderTopWidth(), "0px", null));
523     }
524 
525     /**
526      * {@inheritDoc}
527      */
528     @Override
529     public String getBottom() {
530         return defaultIfEmpty(super.getBottom(), "auto", null);
531     }
532 
533     /**
534      * {@inheritDoc}
535      */
536     @Override
537     public String getColor() {
538         final String value = defaultIfEmpty(super.getColor(), "rgb(0, 0, 0)", null);
539         return toRGBColor(value);
540     }
541 
542     /**
543      * {@inheritDoc}
544      */
545     @Override
546     public String getCssFloat() {
547         return defaultIfEmpty(super.getCssFloat(), CSS_FLOAT);
548     }
549 
550     /**
551      * {@inheritDoc}
552      */
553     @Override
554     public String getDisplay() {
555         return getDisplay(false);
556     }
557 
558     /**
559      * Returns the {@code display} attribute.
560      * @param ignoreBlockIfNotAttached is
561      * {@link com.gargoylesoftware.htmlunit.BrowserVersionFeatures#CSS_COMPUTED_BLOCK_IF_NOT_ATTACHED} ignored
562      * @return the {@code display} attribute
563      */
564     public String getDisplay(final boolean ignoreBlockIfNotAttached) {
565         // don't use defaultIfEmpty for performance
566         // (no need to calculate the default if not empty)
567         final Element elem = getElement();
568         final DomElement domElem = elem.getDomNodeOrDie();
569         boolean changeValueIfEmpty = false;
570         if (!domElem.isAttachedToPage()) {
571             final BrowserVersion browserVersion = getBrowserVersion();
572             if (browserVersion.hasFeature(CSS_COMPUTED_NO_Z_INDEX)) {
573                 return "";
574             }
575             if (!ignoreBlockIfNotAttached && browserVersion.hasFeature(CSS_COMPUTED_BLOCK_IF_NOT_ATTACHED)) {
576                 changeValueIfEmpty = true;
577             }
578         }
579         final String value = super.getStyleAttribute(DISPLAY, false);
580         if (StringUtils.isEmpty(value)) {
581             if (domElem instanceof HtmlElement) {
582                 final String defaultValue = ((HtmlElement) domElem).getDefaultStyleDisplay().value();
583                 if (changeValueIfEmpty) {
584                     switch (defaultValue) {
585                         case "inline":
586                         case "inline-block":
587                         case "table-caption":
588                         case "table-cell":
589                         case "table-column":
590                         case "table-column-group":
591                         case "table-footer-group":
592                         case "table-header-group":
593                         case "table-row":
594                         case "table-row-group":
595                         case "list-item":
596                         case "ruby":
597                             return "block";
598 
599                         default:
600                     }
601                 }
602                 return defaultValue;
603             }
604             return "";
605         }
606         return value;
607     }
608 
609     /**
610      * {@inheritDoc}
611      */
612     @Override
613     public String getFont() {
614         if (getBrowserVersion().hasFeature(CSS_COMPUTED_NO_Z_INDEX)
615                 && getElement().getDomNodeOrDie().isAttachedToPage()) {
616             return getStyleAttribute(FONT_STYLE, true) + ' ' + getStyleAttribute(FONT_VARIANT, true) + ' '
617                     + getStyleAttribute(FONT_WEIGHT, true) + ' ' + getStyleAttribute(FONT_STRETCH, true) + ' '
618                     + getFontSize() + ' ' + '/' + ' ' + getStyleAttribute(LINE_HEIGHT, true) + ' '
619                     + getStyleAttribute(FONT_FAMILY, true);
620         }
621         return "";
622     }
623 
624     /**
625      * {@inheritDoc}
626      */
627     @Override
628     public String getFontSize() {
629         String value = super.getFontSize();
630         if (!value.isEmpty()) {
631             value = pixelValue(value) + "px";
632         }
633         return value;
634     }
635 
636     /**
637      * {@inheritDoc}
638      */
639     @Override
640     public String getLineHeight() {
641         return defaultIfEmpty(super.getLineHeight(), LINE_HEIGHT);
642     }
643 
644     /**
645      * {@inheritDoc}
646      */
647     @Override
648     public String getFontFamily() {
649         return defaultIfEmpty(super.getFontFamily(), FONT_FAMILY);
650     }
651 
652     /**
653      * {@inheritDoc}
654      */
655     @Override
656     public String getHeight() {
657         final Element elem = getElement();
658         if (!elem.getDomNodeOrDie().isAttachedToPage()) {
659             if (getBrowserVersion().hasFeature(CSS_COMPUTED_NO_Z_INDEX)) {
660                 return "";
661             }
662             if (getStyleAttribute(HEIGHT, true).isEmpty()) {
663                 return "auto";
664             }
665         }
666         final int windowHeight = elem.getWindow().getWebWindow().getInnerHeight();
667         return pixelString(elem, new CssValue(0, windowHeight) {
668             @Override
669             public String get(final ComputedCSSStyleDeclaration style) {
670                 final String offsetHeight = ((HTMLElement) elem).getOffsetHeight() + "px";
671                 return defaultIfEmpty(style.getStyleAttribute(HEIGHT, true), offsetHeight, "auto");
672             }
673         });
674     }
675 
676     /**
677      * {@inheritDoc}
678      */
679     @Override
680     public String getLeft() {
681         final String superLeft = super.getLeft();
682         if (!superLeft.endsWith("%")) {
683             return defaultIfEmpty(superLeft, "auto", null);
684         }
685 
686         final Element elem = getElement();
687         return pixelString(elem, new CssValue(0, 0) {
688             @Override
689             public String get(final ComputedCSSStyleDeclaration style) {
690                 if (style.getElement() == elem) {
691                     return style.getStyleAttribute(LEFT, true);
692                 }
693                 return style.getStyleAttribute(WIDTH, true);
694             }
695         });
696     }
697 
698     /**
699      * {@inheritDoc}
700      */
701     @Override
702     public String getLetterSpacing() {
703         return defaultIfEmpty(super.getLetterSpacing(), "normal", null);
704     }
705 
706     /**
707      * {@inheritDoc}
708      */
709     @Override
710     public String getMargin() {
711         return defaultIfEmpty(super.getMargin(), MARGIN, true);
712     }
713 
714     /**
715      * {@inheritDoc}
716      */
717     @Override
718     public String getMarginBottom() {
719         return pixelString(defaultIfEmpty(super.getMarginBottom(), "0px", null));
720     }
721 
722     /**
723      * {@inheritDoc}
724      */
725     @Override
726     public String getMarginLeft() {
727         return getMarginX(super.getMarginLeft(), MARGIN_LEFT);
728     }
729 
730     /**
731      * {@inheritDoc}
732      */
733     @Override
734     public String getMarginRight() {
735         return getMarginX(super.getMarginRight(), MARGIN_RIGHT);
736     }
737 
738     private String getMarginX(final String superMarginX, final Definition definition) {
739         if (!superMarginX.endsWith("%")) {
740             return pixelString(defaultIfEmpty(superMarginX, "0px", null));
741         }
742         final Element elem = getElement();
743         if (!elem.getDomNodeOrDie().isAttachedToPage() && getBrowserVersion().hasFeature(CSS_COMPUTED_NO_Z_INDEX)) {
744             return "";
745         }
746 
747         final int windowWidth = elem.getWindow().getWebWindow().getInnerWidth();
748         return pixelString(elem, new CssValue(0, windowWidth) {
749             @Override
750             public String get(final ComputedCSSStyleDeclaration style) {
751                 if (style.getElement() == elem) {
752                     return style.getStyleAttribute(definition, true);
753                 }
754                 return style.getStyleAttribute(WIDTH, true);
755             }
756         });
757     }
758 
759     /**
760      * {@inheritDoc}
761      */
762     @Override
763     public String getMarginTop() {
764         return pixelString(defaultIfEmpty(super.getMarginTop(), "0px", null));
765     }
766 
767     /**
768      * {@inheritDoc}
769      */
770     @Override
771     public String getMaxHeight() {
772         return defaultIfEmpty(super.getMaxHeight(), "none", null);
773     }
774 
775     /**
776      * {@inheritDoc}
777      */
778     @Override
779     public String getMaxWidth() {
780         return defaultIfEmpty(super.getMaxWidth(), "none", null);
781     }
782 
783     /**
784      * {@inheritDoc}
785      */
786     @Override
787     public String getMinHeight() {
788         return defaultIfEmpty(super.getMinHeight(), "0px", null);
789     }
790 
791     /**
792      * {@inheritDoc}
793      */
794     @Override
795     public String getMinWidth() {
796         return defaultIfEmpty(super.getMinWidth(), "0px", null);
797     }
798 
799     /**
800      * {@inheritDoc}
801      */
802     @Override
803     public String getOpacity() {
804         return defaultIfEmpty(super.getOpacity(), "1", null);
805     }
806 
807     /**
808      * {@inheritDoc}
809      */
810     @Override
811     public String getOutlineWidth() {
812         return defaultIfEmpty(super.getOutlineWidth(), "0px", null);
813     }
814 
815     /**
816      * {@inheritDoc}
817      */
818     @Override
819     public String getPadding() {
820         return defaultIfEmpty(super.getPadding(), PADDING, true);
821     }
822 
823     /**
824      * {@inheritDoc}
825      */
826     @Override
827     public String getPaddingBottom() {
828         return pixelString(defaultIfEmpty(super.getPaddingBottom(), "0px", null));
829     }
830 
831     /**
832      * {@inheritDoc}
833      */
834     @Override
835     public String getPaddingLeft() {
836         return pixelString(defaultIfEmpty(super.getPaddingLeft(), "0px", null));
837     }
838 
839     /**
840      * {@inheritDoc}
841      */
842     @Override
843     public String getPaddingRight() {
844         return pixelString(defaultIfEmpty(super.getPaddingRight(), "0px", null));
845     }
846 
847     /**
848      * {@inheritDoc}
849      */
850     @Override
851     public String getPaddingTop() {
852         return pixelString(defaultIfEmpty(super.getPaddingTop(), "0px", null));
853     }
854 
855     /**
856      * {@inheritDoc}
857      */
858     @Override
859     public String getRight() {
860         return defaultIfEmpty(super.getRight(), "auto", null);
861     }
862 
863     /**
864      * {@inheritDoc}
865      */
866     @Override
867     public String getTextIndent() {
868         return defaultIfEmpty(super.getTextIndent(), "0px", null);
869     }
870 
871     /**
872      * {@inheritDoc}
873      */
874     @Override
875     public String getTop() {
876         final Element elem = getElement();
877         if (!elem.getDomNodeOrDie().isAttachedToPage()
878                 && getBrowserVersion().hasFeature(CSS_COMPUTED_NO_Z_INDEX)) {
879             return "";
880         }
881         final String superTop = super.getTop();
882         if (!superTop.endsWith("%")) {
883             return defaultIfEmpty(superTop, TOP);
884         }
885 
886         return pixelString(elem, new CssValue(0, 0) {
887             @Override
888             public String get(final ComputedCSSStyleDeclaration style) {
889                 if (style.getElement() == elem) {
890                     return style.getStyleAttribute(TOP, true);
891                 }
892                 return style.getStyleAttribute(HEIGHT, true);
893             }
894         });
895     }
896 
897     /**
898      * {@inheritDoc}
899      */
900     @Override
901     public String getVerticalAlign() {
902         return defaultIfEmpty(super.getVerticalAlign(), "baseline", null);
903     }
904 
905     /**
906      * {@inheritDoc}
907      */
908     @Override
909     public String getWidows() {
910         return defaultIfEmpty(super.getWidows(), WIDOWS);
911     }
912 
913     /**
914      * {@inheritDoc}
915      */
916     @Override
917     public String getOrphans() {
918         return defaultIfEmpty(super.getOrphans(), ORPHANS);
919     }
920 
921     /**
922      * {@inheritDoc}
923      */
924     @Override
925     public String getPosition() {
926         return defaultIfEmpty(super.getPosition(), POSITION);
927     }
928 
929     /**
930      * {@inheritDoc}
931      */
932     @Override
933     public String getWidth() {
934         if ("none".equals(getDisplay())) {
935             return "auto";
936         }
937 
938         final Element elem = getElement();
939         if (!elem.getDomNodeOrDie().isAttachedToPage()) {
940             if (getBrowserVersion().hasFeature(CSS_COMPUTED_NO_Z_INDEX)) {
941                 return "";
942             }
943             if (getStyleAttribute(WIDTH, true).isEmpty()) {
944                 return "auto";
945             }
946         }
947 
948         final int windowWidth = elem.getWindow().getWebWindow().getInnerWidth();
949         return pixelString(elem, new CssValue(0, windowWidth) {
950             @Override
951             public String get(final ComputedCSSStyleDeclaration style) {
952                 final String value = style.getStyleAttribute(WIDTH, true);
953                 if (StringUtils.isEmpty(value)) {
954                     if ("absolute".equals(getStyleAttribute(POSITION, true))) {
955                         final String content = getDomNodeOrDie().getTextContent();
956                         // do this only for small content
957                         // at least for empty div's this is more correct
958                         if (null != content && content.length() < 13) {
959                             return (content.length() * 7) + "px";
960                         }
961                     }
962 
963                     int windowDefaultValue = getWindowDefaultValue();
964                     if (elem instanceof HTMLBodyElement) {
965                         windowDefaultValue -= 16;
966                     }
967                     return windowDefaultValue + "px";
968                 }
969                 else if ("auto".equals(value)) {
970                     int windowDefaultValue = getWindowDefaultValue();
971                     if (elem instanceof HTMLBodyElement) {
972                         windowDefaultValue -= 16;
973                     }
974                     return windowDefaultValue + "px";
975                 }
976 
977                 return value;
978             }
979         });
980     }
981 
982     /**
983      * Returns the element's width in pixels, possibly including its padding and border.
984      * @param includeBorder whether or not to include the border width in the returned value
985      * @param includePadding whether or not to include the padding width in the returned value
986      * @return the element's width in pixels, possibly including its padding and border
987      */
988     public int getCalculatedWidth(final boolean includeBorder, final boolean includePadding) {
989         if (!getElement().getDomNodeOrNull().isAttachedToPage()) {
990             return 0;
991         }
992         int width = getCalculatedWidth();
993         if (!"border-box".equals(getStyleAttribute(BOX_SIZING))) {
994             if (includeBorder) {
995                 width += getBorderHorizontal();
996             }
997             else if (isScrollable(true, true) && !(getElement() instanceof HTMLBodyElement)
998                     && getElement().getDomNodeOrDie().isAttachedToPage()) {
999                 width -= 17;
1000             }
1001 
1002             if (includePadding) {
1003                 width += getPaddingHorizontal();
1004             }
1005         }
1006         return width;
1007     }
1008 
1009     private int getCalculatedWidth() {
1010         if (width_ != null) {
1011             return width_.intValue();
1012         }
1013 
1014         final Element element = getElement();
1015         final DomNode node = element.getDomNodeOrDie();
1016         if (!node.mayBeDisplayed()) {
1017             width_ = Integer.valueOf(0);
1018             return 0;
1019         }
1020 
1021         final String display = getDisplay();
1022         if ("none".equals(display)) {
1023             width_ = Integer.valueOf(0);
1024             return 0;
1025         }
1026 
1027         final int windowWidth = element.getWindow().getWebWindow().getInnerWidth();
1028 
1029         final int width;
1030         final String styleWidth = super.getWidth();
1031         final DomNode parent = node.getParentNode();
1032 
1033         // width is ignored for inline elements
1034         if (("inline".equals(display) || StringUtils.isEmpty(styleWidth)) && parent instanceof HtmlElement) {
1035             // hack: TODO find a way to specify default values for different tags
1036             if (element instanceof HTMLCanvasElement) {
1037                 return 300;
1038             }
1039 
1040             // Width not explicitly set.
1041             final String cssFloat = getCssFloat();
1042             if ("right".equals(cssFloat) || "left".equals(cssFloat)
1043                     || "absolute".equals(getStyleAttribute(POSITION, true))) {
1044                 // We're floating; simplistic approximation: text content * pixels per character.
1045                 width = node.getTextContent().length() * PIXELS_PER_CHAR;
1046             }
1047             else if ("block".equals(display)) {
1048                 if (element instanceof HTMLBodyElement) {
1049                     width = windowWidth - 16;
1050                 }
1051                 else {
1052                     // Block elements take up 100% of the parent's width.
1053                     final HTMLElement parentJS = (HTMLElement) parent.getScriptableObject();
1054                     width = pixelValue(parentJS, new CssValue(0, windowWidth) {
1055                         @Override public String get(final ComputedCSSStyleDeclaration style) {
1056                             return style.getWidth();
1057                         }
1058                     }) - (getBorderHorizontal() + getPaddingHorizontal());
1059                 }
1060             }
1061             else if (node instanceof HtmlSubmitInput || node instanceof HtmlResetInput
1062                         || node instanceof HtmlButtonInput || node instanceof HtmlButton
1063                         || node instanceof HtmlFileInput) {
1064                 final String text = node.asText();
1065                 // default font for buttons is a bit smaller than the body font size
1066                 width = 10 + (int) (text.length() * PIXELS_PER_CHAR * 0.9);
1067             }
1068             else if (node instanceof HtmlTextInput || node instanceof HtmlPasswordInput) {
1069                 final BrowserVersion browserVersion = getBrowserVersion();
1070                 if (browserVersion.hasFeature(JS_CLIENTWIDTH_INPUT_TEXT_143)) {
1071                     return 143;
1072                 }
1073                 if (browserVersion.hasFeature(JS_CLIENTWIDTH_INPUT_TEXT_169)) {
1074                     return 169;
1075                 }
1076                 width = 141; // FF45
1077             }
1078             else if (node instanceof HtmlRadioButtonInput || node instanceof HtmlCheckBoxInput) {
1079                 width = 13;
1080             }
1081             else if (node instanceof HtmlTextArea) {
1082                 width = 100; // wild guess
1083             }
1084             else {
1085                 // Inline elements take up however much space is required by their children.
1086                 width = getContentWidth();
1087             }
1088         }
1089         else if ("auto".equals(styleWidth)) {
1090             width = windowWidth;
1091         }
1092         else {
1093             // Width explicitly set in the style attribute, or there was no parent to provide guidance.
1094             width = pixelValue(element, new CssValue(0, windowWidth) {
1095                 @Override public String get(final ComputedCSSStyleDeclaration style) {
1096                     return style.getStyleAttribute(WIDTH, true);
1097                 }
1098             });
1099         }
1100 
1101         width_ = Integer.valueOf(width);
1102         return width;
1103     }
1104 
1105     /**
1106      * Returns the total width of the element's children.
1107      * @return the total width of the element's children
1108      */
1109     public int getContentWidth() {
1110         int width = 0;
1111         final DomNode domNode = getDomNodeOrDie();
1112         Iterable<DomNode> children = domNode.getChildren();
1113         if (domNode instanceof BaseFrameElement) {
1114             final Page enclosedPage = ((BaseFrameElement) domNode).getEnclosedPage();
1115             if (enclosedPage != null && enclosedPage.isHtmlPage()) {
1116                 final HtmlPage htmlPage = (HtmlPage) enclosedPage;
1117                 children = htmlPage.getChildren();
1118             }
1119         }
1120         for (final DomNode child : children) {
1121             if (child.getScriptableObject() instanceof HTMLElement) {
1122                 final HTMLElement e = (HTMLElement) child.getScriptableObject();
1123                 final ComputedCSSStyleDeclaration style = e.getWindow().getComputedStyle(e, null);
1124                 final int w = style.getCalculatedWidth(true, true);
1125                 width += w;
1126             }
1127             else if (child.getScriptableObject() instanceof Text) {
1128                 final DomNode parent = child.getParentNode();
1129                 if (parent instanceof HtmlElement) {
1130                     final HTMLElement e = (HTMLElement) child.getParentNode().getScriptableObject();
1131                     final ComputedCSSStyleDeclaration style = e.getWindow().getComputedStyle(e, null);
1132                     final int height = ComputedHeight.getHeight(getBrowserVersion(), style.getFontSize());
1133                     width += child.getTextContent().length() * (int) (height / 1.8f);
1134                 }
1135                 else {
1136                     width += child.getTextContent().length() * PIXELS_PER_CHAR;
1137                 }
1138             }
1139         }
1140         return width;
1141     }
1142 
1143     /**
1144      * Returns the element's height, possibly including its padding and border.
1145      * @param includeBorder whether or not to include the border height in the returned value
1146      * @param includePadding whether or not to include the padding height in the returned value
1147      * @return the element's height, possibly including its padding and border
1148      */
1149     public int getCalculatedHeight(final boolean includeBorder, final boolean includePadding) {
1150         if (!getElement().getDomNodeOrNull().isAttachedToPage()) {
1151             return 0;
1152         }
1153         int height = getCalculatedHeight();
1154         if (!"border-box".equals(getStyleAttribute(BOX_SIZING))) {
1155             if (includeBorder) {
1156                 height += getBorderVertical();
1157             }
1158             else if (isScrollable(false, true) && !(getElement() instanceof HTMLBodyElement)
1159                     && getElement().getDomNodeOrDie().isAttachedToPage()) {
1160                 height -= 17;
1161             }
1162 
1163             if (includePadding) {
1164                 height += getPaddingVertical();
1165             }
1166         }
1167         return height;
1168     }
1169 
1170     /**
1171      * Returns the element's calculated height, taking both relevant CSS and the element's children into account.
1172      * @return the element's calculated height, taking both relevant CSS and the element's children into account
1173      */
1174     private int getCalculatedHeight() {
1175         if (height_ != null) {
1176             return height_.intValue();
1177         }
1178 
1179         final int elementHeight = getEmptyHeight();
1180         if (elementHeight == 0) {
1181             height_ = Integer.valueOf(elementHeight);
1182             return elementHeight;
1183         }
1184 
1185         final int contentHeight = getContentHeight();
1186         final boolean explicitHeightSpecified = !super.getHeight().isEmpty();
1187 
1188         final int height;
1189         if (contentHeight > 0 && !explicitHeightSpecified) {
1190             height = contentHeight;
1191         }
1192         else {
1193             height = elementHeight;
1194         }
1195 
1196         height_ = Integer.valueOf(height);
1197         return height;
1198     }
1199 
1200     /**
1201      * Returns the element's calculated height taking relevant CSS into account, but <b>not</b> the element's child
1202      * elements.
1203      *
1204      * @return the element's calculated height taking relevant CSS into account, but <b>not</b> the element's child
1205      *         elements
1206      */
1207     private int getEmptyHeight() {
1208         if (height2_ != null) {
1209             return height2_.intValue();
1210         }
1211 
1212         final DomNode node = getElement().getDomNodeOrDie();
1213         if (!node.mayBeDisplayed()) {
1214             height2_ = Integer.valueOf(0);
1215             return 0;
1216         }
1217 
1218         if ("none".equals(getDisplay())) {
1219             height2_ = Integer.valueOf(0);
1220             return 0;
1221         }
1222 
1223         final Element elem = getElement();
1224         final int windowHeight = elem.getWindow().getWebWindow().getInnerHeight();
1225 
1226         if (elem instanceof HTMLBodyElement) {
1227             height2_ = windowHeight;
1228             return windowHeight;
1229         }
1230 
1231         final boolean explicitHeightSpecified = !super.getHeight().isEmpty();
1232 
1233         int defaultHeight;
1234         if (node instanceof HtmlDivision && node.getTextContent().trim().isEmpty()) {
1235             defaultHeight = 0;
1236         }
1237         else if (elem.getFirstChild() == null) {
1238             if (node instanceof HtmlRadioButtonInput || node instanceof HtmlCheckBoxInput) {
1239                 defaultHeight = 13;
1240             }
1241             else if (node instanceof HtmlButton) {
1242                 defaultHeight = 20;
1243             }
1244             else if (node instanceof HtmlInput && !(node instanceof HtmlHiddenInput)) {
1245                 if (getBrowserVersion().hasFeature(JS_CLIENTHIGHT_INPUT_17)) {
1246                     defaultHeight = 17;
1247                 }
1248                 else {
1249                     defaultHeight = 21;
1250                 }
1251             }
1252             else if (node instanceof HtmlSelect) {
1253                 defaultHeight = 20;
1254             }
1255             else if (node instanceof HtmlTextArea) {
1256                 defaultHeight = 49;
1257             }
1258             else if (node instanceof HtmlInlineFrame) {
1259                 defaultHeight = 154;
1260             }
1261             else {
1262                 defaultHeight = 0;
1263             }
1264         }
1265         else {
1266             defaultHeight = ComputedHeight.getHeight(getBrowserVersion(), getFontSize());
1267             if (node instanceof HtmlDivision) {
1268                 defaultHeight *= StringUtils.countMatches(node.asText(), '\n') + 1;
1269             }
1270         }
1271 
1272         final int defaultWindowHeight = elem instanceof HTMLCanvasElement ? 150 : windowHeight;
1273 
1274         int height = pixelValue(elem, new CssValue(defaultHeight, defaultWindowHeight) {
1275             @Override public String get(final ComputedCSSStyleDeclaration style) {
1276                 final Element element = style.getElement();
1277                 if (element instanceof HTMLBodyElement) {
1278                     return String.valueOf(element.getWindow().getWebWindow().getInnerHeight());
1279                 }
1280                 return style.getStyleAttribute(HEIGHT, true);
1281             }
1282         });
1283 
1284         if (height == 0 && !explicitHeightSpecified) {
1285             height = defaultHeight;
1286         }
1287 
1288         height2_ = Integer.valueOf(height);
1289         return height;
1290     }
1291 
1292     /**
1293      * Returns the total height of the element's children.
1294      * @return the total height of the element's children
1295      */
1296     public int getContentHeight() {
1297         // There are two different kinds of elements that might determine the content height:
1298         //  - elements with position:static or position:relative (elements that flow and build on each other)
1299         //  - elements with position:absolute (independent elements)
1300 
1301         final DomNode node = getElement().getDomNodeOrDie();
1302         if (!node.mayBeDisplayed()) {
1303             return 0;
1304         }
1305 
1306         ComputedCSSStyleDeclaration lastFlowing = null;
1307         final Set<ComputedCSSStyleDeclaration> styles = new HashSet<>();
1308         for (final DomNode child : node.getChildren()) {
1309             if (child.mayBeDisplayed()) {
1310                 final Object scriptObj = child.getScriptableObject();
1311                 if (scriptObj instanceof HTMLElement) {
1312                     final HTMLElement e = (HTMLElement) scriptObj;
1313                     final ComputedCSSStyleDeclaration style = e.getWindow().getComputedStyle(e, null);
1314                     final String pos = style.getPositionWithInheritance();
1315                     if ("static".equals(pos) || "relative".equals(pos)) {
1316                         lastFlowing = style;
1317                     }
1318                     else if ("absolute".equals(pos)) {
1319                         styles.add(style);
1320                     }
1321                 }
1322             }
1323         }
1324 
1325         if (lastFlowing != null) {
1326             styles.add(lastFlowing);
1327         }
1328 
1329         int max = 0;
1330         for (final ComputedCSSStyleDeclaration style : styles) {
1331             final int h = style.getTop(true, false, false) + style.getCalculatedHeight(true, true);
1332             if (h > max) {
1333                 max = h;
1334             }
1335         }
1336         return max;
1337     }
1338 
1339     /**
1340      * Returns {@code true} if the element is scrollable along the specified axis.
1341      * @param horizontal if {@code true}, the caller is interested in scrollability along the x-axis;
1342      *        if {@code false}, the caller is interested in scrollability along the y-axis
1343      * @return {@code true} if the element is scrollable along the specified axis
1344      */
1345     public boolean isScrollable(final boolean horizontal) {
1346         return isScrollable(horizontal, false);
1347     }
1348 
1349     /**
1350      * @param ignoreSize whether to consider the content/calculated width/height
1351      */
1352     private boolean isScrollable(final boolean horizontal, final boolean ignoreSize) {
1353         final boolean scrollable;
1354         final Element node = getElement();
1355         final String overflow = getStyleAttribute(OVERFLOW, true);
1356         if (horizontal) {
1357             // TODO: inherit, overflow-x
1358             scrollable = (node instanceof HTMLBodyElement || "scroll".equals(overflow) || "auto".equals(overflow))
1359                 && (ignoreSize || getContentWidth() > getCalculatedWidth());
1360         }
1361         else {
1362             // TODO: inherit, overflow-y
1363             scrollable = (node instanceof HTMLBodyElement || "scroll".equals(overflow) || "auto".equals(overflow))
1364                 && (ignoreSize || getContentHeight() > getEmptyHeight());
1365         }
1366         return scrollable;
1367     }
1368 
1369     /**
1370      * Returns the computed top (Y coordinate), relative to the node's parent's top edge.
1371      * @param includeMargin whether or not to take the margin into account in the calculation
1372      * @param includeBorder whether or not to take the border into account in the calculation
1373      * @param includePadding whether or not to take the padding into account in the calculation
1374      * @return the computed top (Y coordinate), relative to the node's parent's top edge
1375      */
1376     public int getTop(final boolean includeMargin, final boolean includeBorder, final boolean includePadding) {
1377         int top = 0;
1378         if (null == top_) {
1379             final String p = getPositionWithInheritance();
1380             if ("absolute".equals(p)) {
1381                 top = getTopForAbsolutePositionWithInheritance();
1382             }
1383             else {
1384                 // Calculate the vertical displacement caused by *previous* siblings.
1385                 DomNode prev = getElement().getDomNodeOrDie().getPreviousSibling();
1386                 boolean prevHadComputedTop = false;
1387                 while (prev != null && !prevHadComputedTop) {
1388                     if (prev instanceof HtmlElement) {
1389                         final HTMLElement e = (HTMLElement) ((HtmlElement) prev).getScriptableObject();
1390                         final ComputedCSSStyleDeclaration style = e.getWindow().getComputedStyle(e, null);
1391                         int prevTop = 0;
1392                         if (style.top_ == null) {
1393                             final String prevPosition = style.getPositionWithInheritance();
1394                             if ("absolute".equals(prevPosition)) {
1395                                 prevTop += style.getTopForAbsolutePositionWithInheritance();
1396                             }
1397                             else {
1398                                 if ("relative".equals(prevPosition)) {
1399                                     final String t = style.getTopWithInheritance();
1400                                     prevTop += pixelValue(t);
1401                                 }
1402                             }
1403                         }
1404                         else {
1405                             prevHadComputedTop = true;
1406                             prevTop += style.top_;
1407                         }
1408                         prevTop += style.getCalculatedHeight(true, true);
1409                         final int margin = pixelValue(style.getMarginTop());
1410                         prevTop += margin;
1411                         top += prevTop;
1412                     }
1413                     prev = prev.getPreviousSibling();
1414                 }
1415                 // If the position is relative, we also need to add the specified "top" displacement.
1416                 if ("relative".equals(p)) {
1417                     final String t = getTopWithInheritance();
1418                     top += pixelValue(t);
1419                 }
1420             }
1421             top_ = Integer.valueOf(top);
1422         }
1423         else {
1424             top = top_.intValue();
1425         }
1426 
1427         if (includeMargin) {
1428             final int margin = pixelValue(getMarginTop());
1429             top += margin;
1430         }
1431 
1432         if (includeBorder) {
1433             final int border = pixelValue(getBorderTopWidth());
1434             top += border;
1435         }
1436 
1437         if (includePadding) {
1438             final int padding = getPaddingTopValue();
1439             top += padding;
1440         }
1441 
1442         return top;
1443     }
1444 
1445     private int getTopForAbsolutePositionWithInheritance() {
1446         int top = 0;
1447         final String t = getTopWithInheritance();
1448 
1449         if (!"auto".equals(t)) {
1450             // No need to calculate displacement caused by sibling nodes.
1451             top = pixelValue(t);
1452         }
1453         else {
1454             final String b = getBottomWithInheritance();
1455 
1456             if (!"auto".equals(b)) {
1457                 // Estimate the vertical displacement caused by *all* siblings.
1458                 // This is very rough, and doesn't even take position or display types into account.
1459                 // It also doesn't take into account the fact that the parent's height may be hardcoded in CSS.
1460                 top = 0;
1461                 DomNode child = getElement().getDomNodeOrDie().getParentNode().getFirstChild();
1462                 while (child != null) {
1463                     if (child instanceof HtmlElement && child.mayBeDisplayed()) {
1464                         top += 20;
1465                     }
1466                     child = child.getNextSibling();
1467                 }
1468                 top -= pixelValue(b);
1469             }
1470         }
1471         return top;
1472     }
1473 
1474     /**
1475      * Returns the computed left (X coordinate), relative to the node's parent's left edge.
1476      * @param includeMargin whether or not to take the margin into account in the calculation
1477      * @param includeBorder whether or not to take the border into account in the calculation
1478      * @param includePadding whether or not to take the padding into account in the calculation
1479      * @return the computed left (X coordinate), relative to the node's parent's left edge
1480      */
1481     public int getLeft(final boolean includeMargin, final boolean includeBorder, final boolean includePadding) {
1482         final String p = getPositionWithInheritance();
1483         final String l = getLeftWithInheritance();
1484         final String r = getRightWithInheritance();
1485 
1486         int left;
1487         if ("absolute".equals(p) && !"auto".equals(l)) {
1488             // No need to calculate displacement caused by sibling nodes.
1489             left = pixelValue(l);
1490         }
1491         else if ("absolute".equals(p) && !"auto".equals(r)) {
1492             // Need to calculate the horizontal displacement caused by *all* siblings.
1493             final HTMLElement parent = (HTMLElement) getElement().getParentElement();
1494             final ComputedCSSStyleDeclaration style = parent.getWindow().getComputedStyle(parent, null);
1495             final int parentWidth = style.getCalculatedWidth(false, false);
1496             left = parentWidth - pixelValue(r);
1497         }
1498         else if ("fixed".equals(p) && !"auto".equals(r)) {
1499             final HTMLElement parent = (HTMLElement) getElement().getParentElement();
1500             final ComputedCSSStyleDeclaration style = getWindow().getComputedStyle(getElement(), null);
1501             final ComputedCSSStyleDeclaration parentStyle = parent.getWindow().getComputedStyle(parent, null);
1502             left = pixelValue(parentStyle.getWidth()) - pixelValue(style.getWidth()) - pixelValue(r);
1503         }
1504         else if ("fixed".equals(p) && "auto".equals(l)) {
1505             // Fixed to the location at which the browser puts it via normal element flowing.
1506             final HTMLElement parent = (HTMLElement) getElement().getParentElement();
1507             final ComputedCSSStyleDeclaration style = parent.getWindow().getComputedStyle(parent, null);
1508             left = pixelValue(style.getLeftWithInheritance());
1509         }
1510         else if ("static".equals(p)) {
1511             // We need to calculate the horizontal displacement caused by *previous* siblings.
1512             left = 0;
1513             for (DomNode n = getDomNodeOrDie(); n != null; n = n.getPreviousSibling()) {
1514                 if (n.getScriptableObject() instanceof HTMLElement) {
1515                     final HTMLElement e = (HTMLElement) n.getScriptableObject();
1516                     final ComputedCSSStyleDeclaration style = e.getWindow().getComputedStyle(e, null);
1517                     final String d = style.getDisplay();
1518                     if ("block".equals(d)) {
1519                         break;
1520                     }
1521                     else if (!"none".equals(d)) {
1522                         left += style.getCalculatedWidth(true, true);
1523                     }
1524                 }
1525                 else if (n.getScriptableObject() instanceof Text) {
1526                     left += n.getTextContent().length() * PIXELS_PER_CHAR;
1527                 }
1528                 if (n instanceof HtmlTableRow) {
1529                     break;
1530                 }
1531             }
1532         }
1533         else {
1534             // Just use the CSS specified value.
1535             left = pixelValue(l);
1536         }
1537 
1538         if (includeMargin) {
1539             final int margin = getMarginLeftValue();
1540             left += margin;
1541         }
1542 
1543         if (includeBorder) {
1544             final int border = pixelValue(getBorderLeftWidth());
1545             left += border;
1546         }
1547 
1548         if (includePadding) {
1549             final int padding = getPaddingLeftValue();
1550             left += padding;
1551         }
1552 
1553         return left;
1554     }
1555 
1556     /**
1557      * Returns the CSS {@code position} attribute, replacing inherited values with the actual parent values.
1558      * @return the CSS {@code position} attribute, replacing inherited values with the actual parent values
1559      */
1560     public String getPositionWithInheritance() {
1561         String p = getStyleAttribute(POSITION, true);
1562         if ("inherit".equals(p)) {
1563             final HTMLElement parent = (HTMLElement) getElement().getParentElement();
1564             if (parent == null) {
1565                 p = "static";
1566             }
1567             else {
1568                 final ComputedCSSStyleDeclaration style = parent.getWindow().getComputedStyle(parent, null);
1569                 p = style.getPositionWithInheritance();
1570             }
1571         }
1572         return p;
1573     }
1574 
1575     /**
1576      * Returns the CSS {@code left} attribute, replacing inherited values with the actual parent values.
1577      * @return the CSS {@code left} attribute, replacing inherited values with the actual parent values
1578      */
1579     public String getLeftWithInheritance() {
1580         String left = getLeft();
1581         if ("inherit".equals(left)) {
1582             final HTMLElement parent = (HTMLElement) getElement().getParentElement();
1583             if (parent == null) {
1584                 left = "auto";
1585             }
1586             else {
1587                 final ComputedCSSStyleDeclaration style = parent.getWindow().getComputedStyle(parent, null);
1588                 left = style.getLeftWithInheritance();
1589             }
1590         }
1591         return left;
1592     }
1593 
1594     /**
1595      * Returns the CSS {@code right} attribute, replacing inherited values with the actual parent values.
1596      * @return the CSS {@code right} attribute, replacing inherited values with the actual parent values
1597      */
1598     public String getRightWithInheritance() {
1599         String right = getRight();
1600         if ("inherit".equals(right)) {
1601             final HTMLElement parent = (HTMLElement) getElement().getParentElement();
1602             if (parent == null) {
1603                 right = "auto";
1604             }
1605             else {
1606                 final ComputedCSSStyleDeclaration style = parent.getWindow().getComputedStyle(parent, null);
1607                 right = style.getRightWithInheritance();
1608             }
1609         }
1610         return right;
1611     }
1612 
1613     /**
1614      * Returns the CSS {@code top} attribute, replacing inherited values with the actual parent values.
1615      * @return the CSS {@code top} attribute, replacing inherited values with the actual parent values
1616      */
1617     public String getTopWithInheritance() {
1618         String top = getTop();
1619         if ("inherit".equals(top)) {
1620             final HTMLElement parent = (HTMLElement) getElement().getParentElement();
1621             if (parent == null) {
1622                 top = "auto";
1623             }
1624             else {
1625                 final ComputedCSSStyleDeclaration style = parent.getWindow().getComputedStyle(parent, null);
1626                 top = style.getTopWithInheritance();
1627             }
1628         }
1629         return top;
1630     }
1631 
1632     /**
1633      * Returns the CSS {@code bottom} attribute, replacing inherited values with the actual parent values.
1634      * @return the CSS {@code bottom} attribute, replacing inherited values with the actual parent values
1635      */
1636     public String getBottomWithInheritance() {
1637         String bottom = getBottom();
1638         if ("inherit".equals(bottom)) {
1639             final HTMLElement parent = (HTMLElement) getElement().getParentElement();
1640             if (parent == null) {
1641                 bottom = "auto";
1642             }
1643             else {
1644                 final ComputedCSSStyleDeclaration style = parent.getWindow().getComputedStyle(parent, null);
1645                 bottom = style.getBottomWithInheritance();
1646             }
1647         }
1648         return bottom;
1649     }
1650 
1651     /**
1652      * Gets the left margin of the element.
1653      * @return the value in pixels
1654      */
1655     public int getMarginLeftValue() {
1656         return pixelValue(getMarginLeft());
1657     }
1658 
1659     /**
1660      * Gets the right margin of the element.
1661      * @return the value in pixels
1662      */
1663     public int getMarginRightValue() {
1664         return pixelValue(getMarginRight());
1665     }
1666 
1667     /**
1668      * Gets the top margin of the element.
1669      * @return the value in pixels
1670      */
1671     public int getMarginTopValue() {
1672         return pixelValue(getMarginTop());
1673     }
1674 
1675     /**
1676      * Gets the bottom margin of the element.
1677      * @return the value in pixels
1678      */
1679     public int getMarginBottomValue() {
1680         return pixelValue(getMarginBottom());
1681     }
1682 
1683     /**
1684      * Gets the left padding of the element.
1685      * @return the value in pixels
1686      */
1687     public int getPaddingLeftValue() {
1688         return pixelValue(getPaddingLeft());
1689     }
1690 
1691     /**
1692      * Gets the right padding of the element.
1693      * @return the value in pixels
1694      */
1695     public int getPaddingRightValue() {
1696         return pixelValue(getPaddingRight());
1697     }
1698 
1699     /**
1700      * Gets the top padding of the element.
1701      * @return the value in pixels
1702      */
1703     public int getPaddingTopValue() {
1704         return pixelValue(getPaddingTop());
1705     }
1706 
1707     /**
1708      * Gets the bottom padding of the element.
1709      * @return the value in pixels
1710      */
1711     public int getPaddingBottomValue() {
1712         return pixelValue(getPaddingBottom());
1713     }
1714 
1715     private int getPaddingHorizontal() {
1716         if (paddingHorizontal_ == null) {
1717             paddingHorizontal_ =
1718                 Integer.valueOf("none".equals(getDisplay()) ? 0 : getPaddingLeftValue() + getPaddingRightValue());
1719         }
1720         return paddingHorizontal_.intValue();
1721     }
1722 
1723     private int getPaddingVertical() {
1724         if (paddingVertical_ == null) {
1725             paddingVertical_ =
1726                 Integer.valueOf("none".equals(getDisplay()) ? 0 : getPaddingTopValue() + getPaddingBottomValue());
1727         }
1728         return paddingVertical_.intValue();
1729     }
1730 
1731     /**
1732      * Gets the size of the left border of the element.
1733      * @return the value in pixels
1734      */
1735     public int getBorderLeftValue() {
1736         return pixelValue(getBorderLeftWidth());
1737     }
1738 
1739     /**
1740      * Gets the size of the right border of the element.
1741      * @return the value in pixels
1742      */
1743     public int getBorderRightValue() {
1744         return pixelValue(getBorderRightWidth());
1745     }
1746 
1747     /**
1748      * Gets the size of the top border of the element.
1749      * @return the value in pixels
1750      */
1751     public int getBorderTopValue() {
1752         return pixelValue(getBorderTopWidth());
1753     }
1754 
1755     /**
1756      * Gets the size of the bottom border of the element.
1757      * @return the value in pixels
1758      */
1759     public int getBorderBottomValue() {
1760         return pixelValue(getBorderBottomWidth());
1761     }
1762 
1763     private int getBorderHorizontal() {
1764         if (borderHorizontal_ == null) {
1765             borderHorizontal_ =
1766                 Integer.valueOf("none".equals(getDisplay()) ? 0 : getBorderLeftValue() + getBorderRightValue());
1767         }
1768         return borderHorizontal_.intValue();
1769     }
1770 
1771     private int getBorderVertical() {
1772         if (borderVertical_ == null) {
1773             borderVertical_ =
1774                 Integer.valueOf("none".equals(getDisplay()) ? 0 : getBorderTopValue() + getBorderBottomValue());
1775         }
1776         return borderVertical_.intValue();
1777     }
1778 
1779     /**
1780      * {@inheritDoc}
1781      */
1782     @Override
1783     public String getWordSpacing() {
1784         return defaultIfEmpty(super.getWordSpacing(), WORD_SPACING);
1785     }
1786 
1787     /**
1788      * {@inheritDoc}
1789      */
1790     @Override
1791     public String getStyleAttribute(final Definition style, final boolean getDefaultValueIfEmpty) {
1792         if (!getElement().getDomNodeOrDie().isAttachedToPage()
1793                 && getBrowserVersion().hasFeature(CSS_COMPUTED_NO_Z_INDEX)) {
1794             return EMPTY_FINAL;
1795         }
1796         String value = super.getStyleAttribute(style, getDefaultValueIfEmpty);
1797         if (value.isEmpty()) {
1798             final Element parent = getElement().getParentElement();
1799             if (INHERITABLE_DEFINITIONS.contains(style) && parent != null) {
1800                 value = getWindow().getComputedStyle(parent, null).getStyleAttribute(style, getDefaultValueIfEmpty);
1801             }
1802             else if (getDefaultValueIfEmpty) {
1803                 value = style.getDefaultComputedValue(getBrowserVersion());
1804             }
1805         }
1806 
1807         return value;
1808     }
1809 
1810     /**
1811      * {@inheritDoc}
1812      */
1813     @Override
1814     public Object getZIndex() {
1815         final Object response = super.getZIndex();
1816         if (response.toString().isEmpty()) {
1817             return "auto";
1818         }
1819         return response;
1820     }
1821 
1822     /**
1823      * {@inheritDoc}
1824      */
1825     @Override
1826     public String getPropertyValue(final String name) {
1827         // need to invoke the getter to take care of the default value
1828         final Object property = getProperty(this, camelize(name));
1829         if (property == NOT_FOUND) {
1830             return super.getPropertyValue(name);
1831         }
1832         return Context.toString(property);
1833     }
1834 
1835     /**
1836      * Returns the specified length value as a pixel length value, as long as we're not emulating IE.
1837      * This method does <b>NOT</b> handle percentages correctly; use {@link #pixelValue(Element, CssValue)}
1838      * if you need percentage support).
1839      * @param value the length value to convert to a pixel length value
1840      * @return the specified length value as a pixel length value
1841      * @see #pixelString(Element, CSSStyleDeclaration.CssValue)
1842      */
1843     protected String pixelString(final String value) {
1844         if (value == EMPTY_FINAL || value.endsWith("px")) {
1845             return value;
1846         }
1847         return pixelValue(value) + "px";
1848     }
1849 
1850     /**
1851      * Returns the specified length CSS attribute value value as a pixel length value, as long as
1852      * we're not emulating IE. If the specified CSS attribute value is a percentage, this method
1853      * uses the specified value object to recursively retrieve the base (parent) CSS attribute value.
1854      * @param element the element for which the CSS attribute value is to be retrieved
1855      * @param value the CSS attribute value which is to be retrieved
1856      * @return the specified length CSS attribute value as a pixel length value
1857      * @see #pixelString(String)
1858      */
1859     protected String pixelString(final Element element, final CssValue value) {
1860         final String s = value.get(element);
1861         if (s.endsWith("px")) {
1862             return s;
1863         }
1864         return pixelValue(element, value) + "px";
1865     }
1866 
1867 }