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