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