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_BACKGROUND_INITIAL;
18  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.CSS_LENGTH_INITIAL;
19  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.CSS_LENGTH_UNDEFINED_AS_EMPTY;
20  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.CSS_OUTLINE_WIDTH_UNIT_NOT_REQUIRED;
21  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.CSS_SET_NULL_THROWS;
22  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.CSS_SUPPORTS_BEHAVIOR_PROPERTY;
23  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.CSS_VERTICAL_ALIGN_SUPPORTS_AUTO;
24  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.CSS_ZINDEX_TYPE_INTEGER;
25  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_STYLE_SET_PROPERTY_IMPORTANT_IGNORES_CASE;
26  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_STYLE_UNSUPPORTED_PROPERTY_GETTER;
27  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_STYLE_WORD_SPACING_ACCEPTS_PERCENT;
28  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_STYLE_WRONG_INDEX_RETURNS_UNDEFINED;
29  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.CHROME;
30  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.EDGE;
31  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.FF;
32  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.IE;
33  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.ACCELERATOR;
34  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BACKGROUND;
35  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BACKGROUND_ATTACHMENT;
36  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BACKGROUND_COLOR;
37  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BACKGROUND_IMAGE;
38  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BACKGROUND_POSITION;
39  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BACKGROUND_REPEAT;
40  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BEHAVIOR;
41  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BORDER;
42  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BORDER_BOTTOM;
43  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BORDER_BOTTOM_COLOR;
44  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BORDER_BOTTOM_STYLE;
45  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BORDER_BOTTOM_WIDTH;
46  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BORDER_LEFT;
47  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BORDER_LEFT_COLOR;
48  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BORDER_LEFT_STYLE;
49  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BORDER_LEFT_WIDTH;
50  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BORDER_RIGHT;
51  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BORDER_RIGHT_COLOR;
52  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BORDER_RIGHT_STYLE;
53  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BORDER_RIGHT_WIDTH;
54  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BORDER_TOP;
55  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BORDER_TOP_COLOR;
56  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BORDER_TOP_STYLE;
57  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BORDER_TOP_WIDTH;
58  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BORDER_WIDTH;
59  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.BOTTOM;
60  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.COLOR;
61  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.DISPLAY;
62  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.FLOAT;
63  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.FONT;
64  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.FONT_FAMILY;
65  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.FONT_SIZE;
66  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.HEIGHT;
67  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.LEFT;
68  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.LETTER_SPACING;
69  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.LINE_HEIGHT;
70  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.MARGIN;
71  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.MARGIN_BOTTOM;
72  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.MARGIN_LEFT;
73  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.MARGIN_RIGHT;
74  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.MARGIN_TOP;
75  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.MAX_HEIGHT;
76  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.MAX_WIDTH;
77  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.MIN_HEIGHT;
78  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.MIN_WIDTH;
79  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.OPACITY;
80  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.ORPHANS;
81  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.OUTLINE;
82  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.OUTLINE_WIDTH;
83  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.PADDING;
84  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.PADDING_BOTTOM;
85  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.PADDING_LEFT;
86  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.PADDING_RIGHT;
87  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.PADDING_TOP;
88  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.PAGE;
89  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.POSITION;
90  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.RIGHT;
91  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.RUBY_ALIGN;
92  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.SIZE;
93  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.TEXT_INDENT;
94  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.TOP;
95  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.VERTICAL_ALIGN;
96  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.WIDOWS;
97  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.WIDTH;
98  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.WORD_SPACING;
99  import static com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition.Z_INDEX_;
100 import static org.apache.commons.lang3.StringUtils.defaultIfEmpty;
101 
102 import java.awt.Color;
103 import java.io.IOException;
104 import java.io.StringReader;
105 import java.text.MessageFormat;
106 import java.text.ParseException;
107 import java.util.ArrayList;
108 import java.util.Arrays;
109 import java.util.Collections;
110 import java.util.HashMap;
111 import java.util.HashSet;
112 import java.util.LinkedHashMap;
113 import java.util.List;
114 import java.util.Locale;
115 import java.util.Map;
116 import java.util.Set;
117 import java.util.regex.Matcher;
118 import java.util.regex.Pattern;
119 
120 import org.apache.commons.lang3.StringUtils;
121 import org.apache.commons.lang3.math.NumberUtils;
122 import org.apache.commons.logging.Log;
123 import org.apache.commons.logging.LogFactory;
124 import org.w3c.css.sac.ErrorHandler;
125 import org.w3c.css.sac.InputSource;
126 
127 import com.gargoylesoftware.htmlunit.BrowserVersion;
128 import com.gargoylesoftware.htmlunit.WebAssert;
129 import com.gargoylesoftware.htmlunit.css.StyleElement;
130 import com.gargoylesoftware.htmlunit.html.HtmlElement;
131 import com.gargoylesoftware.htmlunit.javascript.SimpleScriptable;
132 import com.gargoylesoftware.htmlunit.javascript.configuration.JsxClass;
133 import com.gargoylesoftware.htmlunit.javascript.configuration.JsxConstructor;
134 import com.gargoylesoftware.htmlunit.javascript.configuration.JsxFunction;
135 import com.gargoylesoftware.htmlunit.javascript.configuration.JsxGetter;
136 import com.gargoylesoftware.htmlunit.javascript.configuration.JsxSetter;
137 import com.gargoylesoftware.htmlunit.javascript.host.Element;
138 import com.gargoylesoftware.htmlunit.javascript.host.css.StyleAttributes.Definition;
139 import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLCanvasElement;
140 import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLElement;
141 import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLHtmlElement;
142 import com.steadystate.css.dom.CSSValueImpl;
143 import com.steadystate.css.parser.CSSOMParser;
144 import com.steadystate.css.parser.SACParserCSS3;
145 
146 import net.sourceforge.htmlunit.corejs.javascript.Context;
147 import net.sourceforge.htmlunit.corejs.javascript.ScriptRuntime;
148 import net.sourceforge.htmlunit.corejs.javascript.Scriptable;
149 import net.sourceforge.htmlunit.corejs.javascript.Undefined;
150 
151 /**
152  * A JavaScript object for {@code CSSStyleDeclaration}.
153  *
154  * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
155  * @author <a href="mailto:cse@dynabean.de">Christian Sell</a>
156  * @author Daniel Gredler
157  * @author Chris Erskine
158  * @author Ahmed Ashour
159  * @author Rodney Gitzel
160  * @author Sudhan Moghe
161  * @author Ronald Brill
162  * @author Frank Danek
163  */
164 @JsxClass
165 public class CSSStyleDeclaration extends SimpleScriptable {
166     private static final Pattern TO_INT_PATTERN = Pattern.compile("(\\d+).*");
167     private static final Pattern URL_PATTERN =
168         Pattern.compile("url\\(\\s*[\"']?(.*?)[\"']?\\s*\\)");
169     private static final Pattern POSITION_PATTERN =
170         Pattern.compile("(\\d+\\s*(%|px|cm|mm|in|pt|pc|em|ex))\\s*"
171                 + "(\\d+\\s*(%|px|cm|mm|in|pt|pc|em|ex)|top|bottom|center)");
172     private static final Pattern POSITION_PATTERN2 =
173         Pattern.compile("(left|right|center)\\s*(\\d+\\s*(%|px|cm|mm|in|pt|pc|em|ex)|top|bottom|center)");
174     private static final Pattern POSITION_PATTERN3 =
175         Pattern.compile("(top|bottom|center)\\s*(\\d+\\s*(%|px|cm|mm|in|pt|pc|em|ex)|left|right|center)");
176 
177     private static final Set<String> LENGTH_PROPERTIES_FFFF = new HashSet<>(Arrays.asList(
178             BORDER_TOP_WIDTH.getAttributeName(),
179             BORDER_LEFT_WIDTH.getAttributeName(),
180             BORDER_BOTTOM_WIDTH.getAttributeName(),
181             BORDER_RIGHT_WIDTH.getAttributeName(),
182             LETTER_SPACING.getAttributeName()));
183 
184     private static final Set<String> LENGTH_PROPERTIES_TTFF = new HashSet<>(Arrays.asList(
185             HEIGHT.getAttributeName(),
186             WIDTH.getAttributeName(),
187             TOP.getAttributeName(),
188             LEFT.getAttributeName(),
189             BOTTOM.getAttributeName(),
190             RIGHT.getAttributeName(),
191             MARGIN_TOP.getAttributeName(),
192             MARGIN_LEFT.getAttributeName(),
193             MARGIN_BOTTOM.getAttributeName(),
194             MARGIN_RIGHT.getAttributeName(),
195             MIN_HEIGHT.getAttributeName(),
196             MIN_WIDTH.getAttributeName()
197             ));
198 
199     private static final Set<String> LENGTH_PROPERTIES_FTFF = new HashSet<>(Arrays.asList(
200             FONT_SIZE.getAttributeName(),
201             TEXT_INDENT.getAttributeName(),
202             PADDING_TOP.getAttributeName(),
203             PADDING_LEFT.getAttributeName(),
204             PADDING_BOTTOM.getAttributeName(),
205             PADDING_RIGHT.getAttributeName(),
206             MAX_HEIGHT.getAttributeName(),
207             MAX_WIDTH.getAttributeName()
208             ));
209 
210     private static final Log LOG = LogFactory.getLog(CSSStyleDeclaration.class);
211     private static final Map<String, String> CSSColors_ = new HashMap<>();
212 
213     private static final Map<String, String> CamelizeCache_
214             = Collections.synchronizedMap(new HashMap<String, String>());
215 
216     /** Used to parse URLs. */
217     private static final MessageFormat URL_FORMAT = new MessageFormat("url({0})");
218 
219     /** The element to which this style belongs. */
220     private Element jsElement_;
221 
222     /** The wrapped CSSStyleDeclaration (if created from CSSStyleRule). */
223     private org.w3c.dom.css.CSSStyleDeclaration styleDeclaration_;
224 
225     static {
226         CSSColors_.put("aqua", "rgb(0, 255, 255)");
227         CSSColors_.put("black", "rgb(0, 0, 0)");
228         CSSColors_.put("blue", "rgb(0, 0, 255)");
229         CSSColors_.put("fuchsia", "rgb(255, 0, 255)");
230         CSSColors_.put("gray", "rgb(128, 128, 128)");
231         CSSColors_.put("green", "rgb(0, 128, 0)");
232         CSSColors_.put("lime", "rgb(0, 255, 0)");
233         CSSColors_.put("maroon", "rgb(128, 0, 0)");
234         CSSColors_.put("navy", "rgb(0, 0, 128)");
235         CSSColors_.put("olive", "rgb(128, 128, 0)");
236         CSSColors_.put("purple", "rgb(128, 0, 128)");
237         CSSColors_.put("red", "rgb(255, 0, 0)");
238         CSSColors_.put("silver", "rgb(192, 192, 192)");
239         CSSColors_.put("teal", "rgb(0, 128, 128)");
240         CSSColors_.put("white", "rgb(255, 255, 255)");
241         CSSColors_.put("yellow", "rgb(255, 255, 0)");
242     }
243 
244     /**
245      * Creates an instance.
246      */
247     @JsxConstructor({CHROME, FF, EDGE})
248     public CSSStyleDeclaration() {
249     }
250 
251     /**
252      * Creates an instance and sets its parent scope to the one of the provided element.
253      * @param element the element to which this style is bound
254      */
255     public CSSStyleDeclaration(final Element element) {
256         setParentScope(element.getParentScope());
257         setPrototype(getPrototype(getClass()));
258         initialize(element);
259     }
260 
261     /**
262      * Creates an instance which wraps the specified style declaration.
263      * @param parentScope the parent scope to use
264      * @param styleDeclaration the style declaration to wrap
265      */
266     CSSStyleDeclaration(final Scriptable parentScope, final org.w3c.dom.css.CSSStyleDeclaration styleDeclaration) {
267         setParentScope(parentScope);
268         setPrototype(getPrototype(getClass()));
269         styleDeclaration_ = styleDeclaration;
270     }
271 
272     /**
273      * Initializes the object.
274      * @param htmlElement the element that this style describes
275      */
276     void initialize(final Element element) {
277         // Initialize.
278         WebAssert.notNull("htmlElement", element);
279         jsElement_ = element;
280         setDomNode(element.getDomNodeOrNull(), false);
281 
282         // If an IE behavior was specified in the style, apply the behavior.
283         if (getBrowserVersion().hasFeature(CSS_SUPPORTS_BEHAVIOR_PROPERTY)
284             && element instanceof HTMLElement) {
285             final HTMLElement htmlElement = (HTMLElement) element;
286             final String behavior = getStyleAttribute(BEHAVIOR);
287             if (StringUtils.isNotBlank(behavior)) {
288                 try {
289                     final Object[] url = URL_FORMAT.parse(behavior);
290                     if (url.length > 0) {
291                         htmlElement.addBehavior((String) url[0]);
292                     }
293                 }
294                 catch (final ParseException e) {
295                     LOG.warn("Invalid behavior: '" + behavior + "'.");
296                 }
297             }
298         }
299     }
300 
301     /**
302      * IE makes unknown style properties accessible.
303      * @param name the name of the requested property
304      * @return the object value, {@link #NOT_FOUND} if nothing is found
305      */
306     @Override
307     protected Object getWithPreemption(final String name) {
308         if (getBrowserVersion().hasFeature(JS_STYLE_UNSUPPORTED_PROPERTY_GETTER) && null != jsElement_) {
309             final StyleElement element = getStyleElement(name);
310             if (element != null && element.getValue() != null) {
311                 return element.getValue();
312             }
313         }
314 
315         return NOT_FOUND;
316     }
317 
318     /**
319      * Returns the element to which this style belongs.
320      * @return the element to which this style belongs
321      */
322     protected Element getElement() {
323         return jsElement_;
324     }
325 
326     /**
327      * Returns the priority of the named style attribute, or an empty string if it is not found.
328      *
329      * @param name the name of the style attribute whose value is to be retrieved
330      * @return the named style attribute value, or an empty string if it is not found
331      */
332     protected String getStylePriority(final String name) {
333         if (styleDeclaration_ != null) {
334             return styleDeclaration_.getPropertyPriority(name);
335         }
336         final StyleElement element = getStyleElement(name);
337         if (element != null && element.getValue() != null) {
338             return element.getPriority();
339         }
340         return "";
341     }
342 
343     /**
344      * Determines the StyleElement for the given name.
345      *
346      * @param name the name of the requested StyleElement
347      * @return the StyleElement or null if not found
348      */
349     protected StyleElement getStyleElement(final String name) {
350         if (jsElement_ == null) {
351             return null;
352         }
353         return jsElement_.getDomNodeOrDie().getStyleElement(name);
354     }
355 
356     /**
357      * Determines the StyleElement for the given name.
358      * This ignores the case of the name.
359      *
360      * @param name the name of the requested StyleElement
361      * @return the StyleElement or null if not found
362      */
363     private StyleElement getStyleElementCaseInSensitive(final String name) {
364         if (jsElement_ == null) {
365             return null;
366         }
367         return jsElement_.getDomNodeOrDie().getStyleElementCaseInSensitive(name);
368     }
369 
370     /**
371      * <p>Returns the value of one of the two named style attributes. If both attributes exist,
372      * the value of the attribute that was declared last is returned. If only one of the
373      * attributes exists, its value is returned. If neither attribute exists, an empty string
374      * is returned.</p>
375      *
376      * <p>The second named attribute may be shorthand for a the actual desired property.
377      * The following formats are possible:</p>
378      * <ol>
379      *   <li><tt>top right bottom left</tt>: All values are explicit.</li>
380      *   <li><tt>top right bottom</tt>: Left is implicitly the same as right.</li>
381      *   <li><tt>top right</tt>: Left is implicitly the same as right, bottom is implicitly the same as top.</li>
382      *   <li><tt>top</tt>: Left, bottom and right are implicitly the same as top.</li>
383      * </ol>
384      *
385      * @param name1 the name of the first style attribute
386      * @param name2 the name of the second style attribute
387      * @return the value of one of the two named style attributes
388      */
389     private String getStyleAttribute(final Definition name1, final Definition name2) {
390         final String value;
391         if (styleDeclaration_ != null) {
392             final String value1 = styleDeclaration_.getPropertyValue(name1.getAttributeName());
393             final String value2 = styleDeclaration_.getPropertyValue(name2.getAttributeName());
394 
395             if ("".equals(value1) && "".equals(value2)) {
396                 return "";
397             }
398             if (!"".equals(value1) && "".equals(value2)) {
399                 return value1;
400             }
401             value = value2;
402         }
403         else {
404             final StyleElement element1 = getStyleElement(name1.getAttributeName());
405             final StyleElement element2 = getStyleElement(name2.getAttributeName());
406 
407             if (element2 == null) {
408                 if (element1 == null) {
409                     return "";
410                 }
411                 return element1.getValue();
412             }
413             if (element1 == null) {
414                 value = element2.getValue();
415             }
416             else {
417                 if (element1.compareTo(element2) > 0) {
418                     return element1.getValue();
419                 }
420                 value = element2.getValue();
421             }
422         }
423 
424         final String[] values = StringUtils.split(value);
425         if (name1.name().contains("TOP")) {
426             return values[0];
427         }
428         else if (name1.name().contains("RIGHT")) {
429             if (values.length > 1) {
430                 return values[1];
431             }
432             return values[0];
433         }
434         else if (name1.name().contains("BOTTOM")) {
435             if (values.length > 2) {
436                 return values[2];
437             }
438             return values[0];
439         }
440         else if (name1.name().contains("LEFT")) {
441             if (values.length > 3) {
442                 return values[3];
443             }
444             else if (values.length > 1) {
445                 return values[1];
446             }
447             else {
448                 return values[0];
449             }
450         }
451         else {
452             throw new IllegalStateException("Unsupported definitino: " + name1);
453         }
454     }
455 
456     /**
457      * Sets the specified style attribute.
458      * @param name the attribute name (camel-cased)
459      * @param newValue the attribute value
460      */
461     protected void setStyleAttribute(final String name, final String newValue) {
462         setStyleAttribute(name, newValue, "");
463     }
464 
465     /**
466      * Sets the specified style attribute.
467      * @param name the attribute name (camel-cased)
468      * @param newValue the attribute value
469      * @param important important value
470      */
471     protected void setStyleAttribute(final String name, String newValue, final String important) {
472         if (null == newValue || "null".equals(newValue)) {
473             newValue = "";
474         }
475         if (styleDeclaration_ != null) {
476             styleDeclaration_.setProperty(name, newValue, important);
477             return;
478         }
479 
480         jsElement_.getDomNodeOrDie().replaceStyleAttribute(name, newValue, important);
481     }
482 
483     /**
484      * Removes the specified style attribute, returning the value of the removed attribute.
485      * @param name the attribute name (delimiter-separated, not camel-cased)
486      */
487     private String removeStyleAttribute(final String name) {
488         if (null != styleDeclaration_) {
489             return styleDeclaration_.removeProperty(name);
490         }
491 
492         return jsElement_.getDomNodeOrDie().removeStyleAttribute(name);
493     }
494 
495     /**
496      * Returns a sorted map containing style elements, keyed on style element name. We use a
497      * {@link LinkedHashMap} map so that results are deterministic and are thus testable.
498      *
499      * @return a sorted map containing style elements, keyed on style element name
500      */
501     private Map<String, StyleElement> getStyleMap() {
502         if (jsElement_ == null) {
503             return Collections.emptyMap();
504         }
505         return jsElement_.getDomNodeOrDie().getStyleMap();
506     }
507 
508     /**
509      * Transforms the specified string from delimiter-separated (e.g. <tt>font-size</tt>)
510      * to camel-cased (e.g. <tt>fontSize</tt>).
511      * @param string the string to camelize
512      * @return the transformed string
513      * @see com.gargoylesoftware.htmlunit.javascript.host.dom.DOMStringMap#decamelize(String)
514      */
515     protected static final String camelize(final String string) {
516         if (string == null) {
517             return null;
518         }
519 
520         String result = CamelizeCache_.get(string);
521         if (null != result) {
522             return result;
523         }
524 
525         // not found in CamelizeCache_; convert and store in cache
526         final int pos = string.indexOf('-');
527         if (pos == -1 || pos == string.length() - 1) {
528             // cache also this strings for performance
529             CamelizeCache_.put(string, string);
530             return string;
531         }
532 
533         final StringBuilder builder = new StringBuilder(string);
534         builder.deleteCharAt(pos);
535         builder.setCharAt(pos, Character.toUpperCase(builder.charAt(pos)));
536 
537         int i = pos + 1;
538         while (i < builder.length() - 1) {
539             if (builder.charAt(i) == '-') {
540                 builder.deleteCharAt(i);
541                 builder.setCharAt(i, Character.toUpperCase(builder.charAt(i)));
542             }
543             i++;
544         }
545         result = builder.toString();
546         CamelizeCache_.put(string, result);
547 
548         return result;
549     }
550 
551     /**
552      * Gets the {@code accelerator} style attribute.
553      * @return the style attribute
554      */
555     @JsxGetter(IE)
556     public String getAccelerator() {
557         return defaultIfEmpty(getStyleAttribute(ACCELERATOR), "false");
558     }
559 
560     /**
561      * Sets the {@code accelerator} style attribute.
562      * @param accelerator the new attribute
563      */
564     @JsxSetter(IE)
565     public void setAccelerator(final String accelerator) {
566         setStyleAttribute(ACCELERATOR.getAttributeName(), accelerator);
567     }
568 
569     /**
570      * Gets the {@code backgroundAttachment} style attribute.
571      * @return the style attribute
572      */
573     @JsxGetter
574     public String getBackgroundAttachment() {
575         String value = getStyleAttribute(BACKGROUND_ATTACHMENT, false);
576         if (StringUtils.isBlank(value)) {
577             final String bg = getStyleAttribute(BACKGROUND);
578             if (StringUtils.isNotBlank(bg)) {
579                 value = findAttachment(bg);
580                 if (value == null) {
581                     if (getBrowserVersion().hasFeature(CSS_BACKGROUND_INITIAL)
582                             && getClass() == CSSStyleDeclaration.class) {
583                         return "initial";
584                     }
585                     return "scroll"; // default if shorthand is used
586                 }
587                 return value;
588             }
589             return "";
590         }
591 
592         return value;
593     }
594 
595     /**
596      * Sets the {@code backgroundAttachment} style attribute.
597      * @param backgroundAttachment the new attribute
598      */
599     @JsxSetter
600     public void setBackgroundAttachment(final String backgroundAttachment) {
601         setStyleAttribute(BACKGROUND_ATTACHMENT.getAttributeName(), backgroundAttachment);
602     }
603 
604     /**
605      * Gets the {@code backgroundColor} style attribute.
606      * @return the style attribute
607      */
608     @JsxGetter
609     public String getBackgroundColor() {
610         String value = getStyleAttribute(BACKGROUND_COLOR, false);
611         if (StringUtils.isBlank(value)) {
612             final String bg = getStyleAttribute(BACKGROUND, false);
613             if (StringUtils.isBlank(bg)) {
614                 return "";
615             }
616             value = findColor(bg);
617             if (value == null) {
618                 if (getBrowserVersion().hasFeature(CSS_BACKGROUND_INITIAL)) {
619                     if (getClass() == CSSStyleDeclaration.class) {
620                         return "initial";
621                     }
622                     return "rgba(0, 0, 0, 0)";
623                 }
624                 return "transparent"; // default if shorthand is used
625             }
626             return value;
627         }
628         if (StringUtils.isBlank(value)) {
629             return "";
630         }
631         return value;
632     }
633 
634     /**
635      * Sets the {@code backgroundColor} style attribute.
636      * @param backgroundColor the new attribute
637      */
638     @JsxSetter
639     public void setBackgroundColor(final String backgroundColor) {
640         setStyleAttribute(BACKGROUND_COLOR.getAttributeName(), backgroundColor);
641     }
642 
643     /**
644      * Gets the {@code backgroundImage} style attribute.
645      * @return the style attribute
646      */
647     @JsxGetter
648     public String getBackgroundImage() {
649         String value = getStyleAttribute(BACKGROUND_IMAGE, false);
650         if (StringUtils.isBlank(value)) {
651             final String bg = getStyleAttribute(BACKGROUND, false);
652             if (StringUtils.isNotBlank(bg)) {
653                 value = findImageUrl(bg);
654                 final boolean isComputed = getClass() != CSSStyleDeclaration.class;
655                 final boolean backgroundInitial = getBrowserVersion().hasFeature(CSS_BACKGROUND_INITIAL);
656                 if (value == null) {
657                     return backgroundInitial && !isComputed ? "initial" : "none";
658                 }
659                 if (isComputed) {
660                     try {
661                         value = value.substring(5, value.length() - 2);
662                         return "url(\"" + ((HtmlElement) jsElement_.getDomNodeOrDie()).getHtmlPageOrNull()
663                             .getFullyQualifiedUrl(value) + "\")";
664                     }
665                     catch (final Exception e) {
666                         // ignore
667                     }
668                 }
669                 return value;
670             }
671             return "";
672         }
673 
674         return value;
675     }
676 
677     /**
678      * Sets the {@code backgroundImage} style attribute.
679      * @param backgroundImage the new attribute
680      */
681     @JsxSetter
682     public void setBackgroundImage(final String backgroundImage) {
683         setStyleAttribute(BACKGROUND_IMAGE.getAttributeName(), backgroundImage);
684     }
685 
686     /**
687      * Gets the {@code backgroundPosition} style attribute.
688      * @return the style attribute
689      */
690     @JsxGetter
691     public String getBackgroundPosition() {
692         String value = getStyleAttribute(BACKGROUND_POSITION, false);
693         if (value == null) {
694             return null;
695         }
696         if (StringUtils.isBlank(value)) {
697             final String bg = getStyleAttribute(BACKGROUND, false);
698             if (bg == null) {
699                 return null;
700             }
701             if (StringUtils.isNotBlank(bg)) {
702                 value = findPosition(bg);
703                 final boolean isInitial = getBrowserVersion().hasFeature(CSS_BACKGROUND_INITIAL);
704                 final boolean isComputed = getClass() != CSSStyleDeclaration.class;
705                 if (value == null) {
706                     if (isInitial) {
707                         return isComputed ? "" : "initial";
708                     }
709                     return "0% 0%";
710                 }
711                 if (getBrowserVersion().hasFeature(CSS_ZINDEX_TYPE_INTEGER)) {
712                     final String[] values = value.split(" ");
713                     if ("center".equals(values[0])) {
714                         values[0] = "";
715                     }
716                     if ("center".equals(values[1])) {
717                         values[1] = "";
718                     }
719                     if (!isComputed || value.contains("top")) {
720                         return (values[0] + ' ' + values[1]).trim();
721                     }
722                 }
723                 if (isComputed) {
724                     final String[] values = value.split(" ");
725                     switch (values[0]) {
726                         case "left":
727                             values[0] = "0%";
728                             break;
729 
730                         case "center":
731                             values[0] = "50%";
732                             break;
733 
734                         case "right":
735                             values[0] = "100%";
736                             break;
737 
738                         default:
739                     }
740                     switch (values[1]) {
741                         case "top":
742                             values[1] = "0%";
743                             break;
744 
745                         case "center":
746                             values[1] = "50%";
747                             break;
748 
749                         case "bottom":
750                             values[1] = "100%";
751                             break;
752 
753                         default:
754                     }
755                     value = values[0] + ' ' + values[1];
756                 }
757                 return value;
758             }
759             return "";
760         }
761 
762         return value;
763     }
764 
765     /**
766      * Sets the {@code backgroundPosition} style attribute.
767      * @param backgroundPosition the new attribute
768      */
769     @JsxSetter
770     public void setBackgroundPosition(final String backgroundPosition) {
771         setStyleAttribute(BACKGROUND_POSITION.getAttributeName(), backgroundPosition);
772     }
773 
774     /**
775      * Gets the {@code backgroundRepeat} style attribute.
776      * @return the style attribute
777      */
778     @JsxGetter
779     public String getBackgroundRepeat() {
780         String value = getStyleAttribute(BACKGROUND_REPEAT, false);
781         if (StringUtils.isBlank(value)) {
782             final String bg = getStyleAttribute(BACKGROUND, false);
783             if (StringUtils.isNotBlank(bg)) {
784                 value = findRepeat(bg);
785                 if (value == null) {
786                     if (getBrowserVersion().hasFeature(CSS_BACKGROUND_INITIAL)
787                             && getClass() == CSSStyleDeclaration.class) {
788                         return "initial";
789                     }
790                     return "repeat"; // default if shorthand is used
791                 }
792                 return value;
793             }
794             return "";
795         }
796 
797         return value;
798     }
799 
800     /**
801      * Sets the {@code backgroundRepeat} style attribute.
802      * @param backgroundRepeat the new attribute
803      */
804     @JsxSetter
805     public void setBackgroundRepeat(final String backgroundRepeat) {
806         setStyleAttribute(BACKGROUND_REPEAT.getAttributeName(), backgroundRepeat);
807     }
808 
809     /**
810      * Gets the {@code borderBottomColor} style attribute.
811      * @return the style attribute
812      */
813     @JsxGetter
814     public String getBorderBottomColor() {
815         String value = getStyleAttribute(BORDER_BOTTOM_COLOR, false);
816         if (value.isEmpty()) {
817             value = findColor(getStyleAttribute(BORDER_BOTTOM, false));
818             if (value == null) {
819                 value = findColor(getStyleAttribute(BORDER, false));
820             }
821             if (value == null) {
822                 value = "";
823             }
824         }
825         return value;
826     }
827 
828     /**
829      * Sets the {@code borderBottomColor} style attribute.
830      * @param borderBottomColor the new attribute
831      */
832     @JsxSetter
833     public void setBorderBottomColor(final String borderBottomColor) {
834         setStyleAttribute(BORDER_BOTTOM_COLOR.getAttributeName(), borderBottomColor);
835     }
836 
837     /**
838      * Gets the {@code borderBottomStyle} style attribute.
839      * @return the style attribute
840      */
841     @JsxGetter
842     public String getBorderBottomStyle() {
843         String value = getStyleAttribute(BORDER_BOTTOM_STYLE, false);
844         if (value.isEmpty()) {
845             value = findBorderStyle(getStyleAttribute(BORDER_BOTTOM, false));
846             if (value == null) {
847                 value = findBorderStyle(getStyleAttribute(BORDER, false));
848             }
849             if (value == null) {
850                 value = "";
851             }
852         }
853         return value;
854     }
855 
856     /**
857      * Sets the {@code borderBottomStyle} style attribute.
858      * @param borderBottomStyle the new attribute
859      */
860     @JsxSetter
861     public void setBorderBottomStyle(final String borderBottomStyle) {
862         setStyleAttribute(BORDER_BOTTOM_STYLE.getAttributeName(), borderBottomStyle);
863     }
864 
865     /**
866      * Gets the {@code borderBottomWidth} style attribute.
867      * @return the style attribute
868      */
869     @JsxGetter
870     public String getBorderBottomWidth() {
871         return getBorderWidth(BORDER_BOTTOM_WIDTH, BORDER_BOTTOM);
872     }
873 
874     /**
875      * Sets the {@code borderBottomWidth} style attribute.
876      * @param borderBottomWidth the new attribute
877      */
878     @JsxSetter
879     public void setBorderBottomWidth(final Object borderBottomWidth) {
880         setStyleLengthAttribute(BORDER_BOTTOM_WIDTH.getAttributeName(), borderBottomWidth, "",
881                 false, false, false, false, false);
882     }
883 
884     /**
885      * Gets the {@code borderLeftColor} style attribute.
886      * @return the style attribute
887      */
888     @JsxGetter
889     public String getBorderLeftColor() {
890         String value = getStyleAttribute(BORDER_LEFT_COLOR, false);
891         if (value.isEmpty()) {
892             value = findColor(getStyleAttribute(BORDER_LEFT, false));
893             if (value == null) {
894                 value = findColor(getStyleAttribute(BORDER, false));
895             }
896             if (value == null) {
897                 value = "";
898             }
899         }
900         return value;
901     }
902 
903     /**
904      * Sets the {@code borderLeftColor} style attribute.
905      * @param borderLeftColor the new attribute
906      */
907     @JsxSetter
908     public void setBorderLeftColor(final String borderLeftColor) {
909         setStyleAttribute(BORDER_LEFT_COLOR.getAttributeName(), borderLeftColor);
910     }
911 
912     /**
913      * Gets the {@code borderLeftStyle} style attribute.
914      * @return the style attribute
915      */
916     @JsxGetter
917     public String getBorderLeftStyle() {
918         String value = getStyleAttribute(BORDER_LEFT_STYLE, false);
919         if (value.isEmpty()) {
920             value = findBorderStyle(getStyleAttribute(BORDER_LEFT, false));
921             if (value == null) {
922                 value = findBorderStyle(getStyleAttribute(BORDER, false));
923             }
924             if (value == null) {
925                 value = "";
926             }
927         }
928         return value;
929     }
930 
931     /**
932      * Sets the {@code borderLeftStyle} style attribute.
933      * @param borderLeftStyle the new attribute
934      */
935     @JsxSetter
936     public void setBorderLeftStyle(final String borderLeftStyle) {
937         setStyleAttribute(BORDER_LEFT_STYLE.getAttributeName(), borderLeftStyle);
938     }
939 
940     /**
941      * Gets the {@code borderLeftWidth} style attribute.
942      * @return the style attribute
943      */
944     @JsxGetter
945     public String getBorderLeftWidth() {
946         return getBorderWidth(BORDER_LEFT_WIDTH, BORDER_LEFT);
947     }
948 
949     /**
950      * Gets the border width for the specified side
951      * @param borderSideWidth the border side width Definition
952      * @param borderside the border side Definition
953      * @return the width, "" if not defined
954      */
955     private String getBorderWidth(final Definition borderSideWidth, final Definition borderSide) {
956         String value = getStyleAttribute(borderSideWidth, false);
957         if (value.isEmpty()) {
958             value = findBorderWidth(getStyleAttribute(borderSide, false));
959             if (value == null) {
960                 final String borderWidth = getStyleAttribute(BORDER_WIDTH, false);
961                 if (!StringUtils.isEmpty(borderWidth)) {
962                     final String[] values = StringUtils.split(borderWidth);
963                     int index = values.length;
964                     if (borderSideWidth.name().contains("TOP")) {
965                         index = 0;
966                     }
967                     else if (borderSideWidth.name().contains("RIGHT")) {
968                         index = 1;
969                     }
970                     else if (borderSideWidth.name().contains("BOTTOM")) {
971                         index = 2;
972                     }
973                     else if (borderSideWidth.name().contains("LEFT")) {
974                         index = 3;
975                     }
976                     if (index < values.length) {
977                         value = values[index];
978                     }
979                 }
980             }
981             if (value == null) {
982                 value = findBorderWidth(getStyleAttribute(BORDER, false));
983             }
984             if (value == null) {
985                 value = "";
986             }
987         }
988         return value;
989     }
990 
991     /**
992      * Sets the {@code borderLeftWidth} style attribute.
993      * @param borderLeftWidth the new attribute
994      */
995     @JsxSetter
996     public void setBorderLeftWidth(final Object borderLeftWidth) {
997         setStyleLengthAttribute(BORDER_LEFT_WIDTH.getAttributeName(), borderLeftWidth, "",
998                 false, false, false, false, false);
999     }
1000 
1001     /**
1002      * Gets the {@code borderRightColor} style attribute.
1003      * @return the style attribute
1004      */
1005     @JsxGetter
1006     public String getBorderRightColor() {
1007         String value = getStyleAttribute(BORDER_RIGHT_COLOR, false);
1008         if (value.isEmpty()) {
1009             value = findColor(getStyleAttribute(BORDER_RIGHT, false));
1010             if (value == null) {
1011                 value = findColor(getStyleAttribute(BORDER, false));
1012             }
1013             if (value == null) {
1014                 value = "";
1015             }
1016         }
1017         return value;
1018     }
1019 
1020     /**
1021      * Sets the {@code borderRightColor} style attribute.
1022      * @param borderRightColor the new attribute
1023      */
1024     @JsxSetter
1025     public void setBorderRightColor(final String borderRightColor) {
1026         setStyleAttribute(BORDER_RIGHT_COLOR.getAttributeName(), borderRightColor);
1027     }
1028 
1029     /**
1030      * Gets the {@code borderRightStyle} style attribute.
1031      * @return the style attribute
1032      */
1033     @JsxGetter
1034     public String getBorderRightStyle() {
1035         String value = getStyleAttribute(BORDER_RIGHT_STYLE, false);
1036         if (value.isEmpty()) {
1037             value = findBorderStyle(getStyleAttribute(BORDER_RIGHT, false));
1038             if (value == null) {
1039                 value = findBorderStyle(getStyleAttribute(BORDER, false));
1040             }
1041             if (value == null) {
1042                 value = "";
1043             }
1044         }
1045         return value;
1046     }
1047 
1048     /**
1049      * Sets the {@code borderRightStyle} style attribute.
1050      * @param borderRightStyle the new attribute
1051      */
1052     @JsxSetter
1053     public void setBorderRightStyle(final String borderRightStyle) {
1054         setStyleAttribute(BORDER_RIGHT_STYLE.getAttributeName(), borderRightStyle);
1055     }
1056 
1057     /**
1058      * Gets the {@code borderRightWidth} style attribute.
1059      * @return the style attribute
1060      */
1061     @JsxGetter
1062     public String getBorderRightWidth() {
1063         return getBorderWidth(BORDER_RIGHT_WIDTH, BORDER_RIGHT);
1064     }
1065 
1066     /**
1067      * Sets the {@code borderRightWidth} style attribute.
1068      * @param borderRightWidth the new attribute
1069      */
1070     @JsxSetter
1071     public void setBorderRightWidth(final Object borderRightWidth) {
1072         setStyleLengthAttribute(BORDER_RIGHT_WIDTH.getAttributeName(), borderRightWidth, "",
1073                 false, false, false, false, false);
1074     }
1075 
1076     /**
1077      * Sets the {@code borderTop} style attribute.
1078      * @param borderTop the new attribute
1079      */
1080     @JsxSetter
1081     public void setBorderTop(final String borderTop) {
1082         setStyleAttribute(BORDER_TOP.getAttributeName(), borderTop);
1083     }
1084 
1085     /**
1086      * Gets the {@code borderTopColor} style attribute.
1087      * @return the style attribute
1088      */
1089     @JsxGetter
1090     public String getBorderTopColor() {
1091         String value = getStyleAttribute(BORDER_TOP_COLOR, false);
1092         if (value.isEmpty()) {
1093             value = findColor(getStyleAttribute(BORDER_TOP, false));
1094             if (value == null) {
1095                 value = findColor(getStyleAttribute(BORDER, false));
1096             }
1097             if (value == null) {
1098                 value = "";
1099             }
1100         }
1101         return value;
1102     }
1103 
1104     /**
1105      * Sets the {@code borderTopColor} style attribute.
1106      * @param borderTopColor the new attribute
1107      */
1108     @JsxSetter
1109     public void setBorderTopColor(final String borderTopColor) {
1110         setStyleAttribute(BORDER_TOP_COLOR.getAttributeName(), borderTopColor);
1111     }
1112 
1113     /**
1114      * Gets the {@code borderTopStyle} style attribute.
1115      * @return the style attribute
1116      */
1117     @JsxGetter
1118     public String getBorderTopStyle() {
1119         String value = getStyleAttribute(BORDER_TOP_STYLE, false);
1120         if (value.isEmpty()) {
1121             value = findBorderStyle(getStyleAttribute(BORDER_TOP, false));
1122             if (value == null) {
1123                 value = findBorderStyle(getStyleAttribute(BORDER, false));
1124             }
1125             if (value == null) {
1126                 value = "";
1127             }
1128         }
1129         return value;
1130     }
1131 
1132     /**
1133      * Sets the {@code borderTopStyle} style attribute.
1134      * @param borderTopStyle the new attribute
1135      */
1136     @JsxSetter
1137     public void setBorderTopStyle(final String borderTopStyle) {
1138         setStyleAttribute(BORDER_TOP_STYLE.getAttributeName(), borderTopStyle);
1139     }
1140 
1141     /**
1142      * Gets the {@code borderTopWidth} style attribute.
1143      * @return the style attribute
1144      */
1145     @JsxGetter
1146     public String getBorderTopWidth() {
1147         return getBorderWidth(BORDER_TOP_WIDTH, BORDER_TOP);
1148     }
1149 
1150     /**
1151      * Sets the {@code borderTopWidth} style attribute.
1152      * @param borderTopWidth the new attribute
1153      */
1154     @JsxSetter
1155     public void setBorderTopWidth(final Object borderTopWidth) {
1156         setStyleLengthAttribute(BORDER_TOP_WIDTH.getAttributeName(), borderTopWidth, "",
1157                 false, false, false, false, false);
1158     }
1159 
1160     /**
1161      * Gets the {@code bottom} style attribute.
1162      * @return the style attribute
1163      */
1164     @JsxGetter
1165     public String getBottom() {
1166         return getStyleAttribute(BOTTOM);
1167     }
1168 
1169     /**
1170      * Sets the {@code bottom} style attribute.
1171      * @param bottom the new attribute
1172      */
1173     @JsxSetter
1174     public void setBottom(final Object bottom) {
1175         setStyleLengthAttribute(BOTTOM.getAttributeName(), bottom, "", true, true, false, false, false);
1176     }
1177 
1178     /**
1179      * Gets the {@code color} style attribute.
1180      * @return the style attribute
1181      */
1182     @JsxGetter
1183     public String getColor() {
1184         return getStyleAttribute(COLOR);
1185     }
1186 
1187     /**
1188      * Sets the {@code color} style attribute.
1189      * @param color the new attribute
1190      */
1191     @JsxSetter
1192     public void setColor(final String color) {
1193         setStyleAttribute(COLOR.getAttributeName(), color);
1194     }
1195 
1196     /**
1197      * Gets the {@code cssFloat} style attribute.
1198      * @return the style attribute
1199      */
1200     @JsxGetter
1201     public String getCssFloat() {
1202         return getStyleAttribute(FLOAT);
1203     }
1204 
1205     /**
1206      * Sets the {@code cssFloat} style attribute.
1207      * @param value the new attribute
1208      */
1209     @JsxSetter
1210     public void setCssFloat(final String value) {
1211         setStyleAttribute(FLOAT.getAttributeName(), value);
1212     }
1213 
1214     /**
1215      * Returns the actual text of the style.
1216      * @return the actual text of the style
1217      */
1218     @JsxGetter
1219     public String getCssText() {
1220         return jsElement_.getDomNodeOrDie().getAttribute("style");
1221     }
1222 
1223     /**
1224      * Sets the actual text of the style.
1225      * @param value the new text
1226      */
1227     @JsxSetter
1228     public void setCssText(final String value) {
1229         jsElement_.getDomNodeOrDie().setAttribute("style", value);
1230     }
1231 
1232     /**
1233      * Gets the {@code display} style attribute.
1234      * @return the style attribute
1235      */
1236     @JsxGetter
1237     public String getDisplay() {
1238         return getStyleAttribute(DISPLAY);
1239     }
1240 
1241     /**
1242      * Sets the {@code display} style attribute.
1243      * @param display the new attribute
1244      */
1245     @JsxSetter
1246     public void setDisplay(final String display) {
1247         setStyleAttribute(DISPLAY.getAttributeName(), display);
1248     }
1249 
1250     /**
1251      * Gets the {@code fontSize} style attribute.
1252      * @return the style attribute
1253      */
1254     @JsxGetter
1255     public String getFontSize() {
1256         return getStyleAttribute(FONT_SIZE);
1257     }
1258 
1259     /**
1260      * Sets the {@code fontSize} style attribute.
1261      * @param fontSize the new attribute
1262      */
1263     @JsxSetter
1264     public void setFontSize(final Object fontSize) {
1265         setStyleLengthAttribute(FONT_SIZE.getAttributeName(), fontSize, "", false, true, false, false, false);
1266         updateFont(getFont(), false);
1267     }
1268 
1269     /**
1270      * Gets the {@code lineHeight} style attribute.
1271      * @return the style attribute
1272      */
1273     @JsxGetter
1274     public String getLineHeight() {
1275         return getStyleAttribute(LINE_HEIGHT);
1276     }
1277 
1278     /**
1279      * Sets the {@code lineHeight} style attribute.
1280      * @param lineHeight the new attribute
1281      */
1282     @JsxSetter
1283     public void setLineHeight(final String lineHeight) {
1284         setStyleAttribute(LINE_HEIGHT.getAttributeName(), lineHeight);
1285         updateFont(getFont(), false);
1286     }
1287 
1288     /**
1289      * Gets the {@code fontFamily} style attribute.
1290      * @return the style attribute
1291      */
1292     @JsxGetter
1293     public String getFontFamily() {
1294         return getStyleAttribute(FONT_FAMILY);
1295     }
1296 
1297     /**
1298      * Sets the {@code fontFamily} style attribute.
1299      * @param fontFamily the new attribute
1300      */
1301     @JsxSetter
1302     public void setFontFamily(final String fontFamily) {
1303         setStyleAttribute(FONT_FAMILY.getAttributeName(), fontFamily);
1304         updateFont(getFont(), false);
1305     }
1306 
1307     private void updateFont(final String font, final boolean force) {
1308         final BrowserVersion browserVersion = getBrowserVersion();
1309         final String[] details = ComputedFont.getDetails(font, !browserVersion.hasFeature(CSS_SET_NULL_THROWS));
1310         if (details != null || force) {
1311             final StringBuilder newFont = new StringBuilder();
1312             newFont.append(getFontSize());
1313             String lineHeight = getLineHeight();
1314             final String defaultLineHeight = LINE_HEIGHT.getDefaultComputedValue(browserVersion);
1315             if (lineHeight.isEmpty()) {
1316                 lineHeight = defaultLineHeight;
1317             }
1318 
1319             if (browserVersion.hasFeature(CSS_ZINDEX_TYPE_INTEGER) || !lineHeight.equals(defaultLineHeight)) {
1320                 newFont.append('/');
1321                 if (!lineHeight.equals(defaultLineHeight)) {
1322                     newFont.append(lineHeight);
1323                 }
1324                 else {
1325                     newFont.append(LINE_HEIGHT.getDefaultComputedValue(browserVersion));
1326                 }
1327             }
1328 
1329             newFont.append(' ').append(getFontFamily());
1330             setStyleAttribute(FONT.getAttributeName(), newFont.toString());
1331         }
1332     }
1333 
1334     /**
1335      * Gets the {@code font} style attribute.
1336      * @return the style attribute
1337      */
1338     @JsxGetter
1339     public String getFont() {
1340         return getStyleAttribute(FONT);
1341     }
1342 
1343     /**
1344      * Sets the {@code font} style attribute.
1345      * @param font the new attribute
1346      */
1347     @JsxSetter
1348     public void setFont(final String font) {
1349         final String[] details = ComputedFont.getDetails(font, !getBrowserVersion().hasFeature(CSS_SET_NULL_THROWS));
1350         if (details != null) {
1351             setStyleAttribute(FONT_FAMILY.getAttributeName(), details[ComputedFont.FONT_FAMILY_INDEX]);
1352             final String fontSize = details[ComputedFont.FONT_SIZE_INDEX];
1353             if (details[ComputedFont.LINE_HEIGHT_INDEX] != null) {
1354                 setStyleAttribute(LINE_HEIGHT.getAttributeName(), details[ComputedFont.LINE_HEIGHT_INDEX]);
1355             }
1356             setStyleAttribute(FONT_SIZE.getAttributeName(), fontSize);
1357             updateFont(font, true);
1358         }
1359     }
1360 
1361     /**
1362      * Gets the {@code height} style attribute.
1363      * @return the style attribute
1364      */
1365     @JsxGetter
1366     public String getHeight() {
1367         return getStyleAttribute(HEIGHT);
1368     }
1369 
1370     /**
1371      * Sets the {@code height} style attribute.
1372      * @param height the new attribute
1373      */
1374     @JsxSetter
1375     public void setHeight(final Object height) {
1376         setStyleLengthAttribute(HEIGHT.getAttributeName(), height, "", true, true, false, false, false);
1377     }
1378 
1379     /**
1380      * Gets the {@code left} style attribute.
1381      * @return the style attribute
1382      */
1383     @JsxGetter
1384     public String getLeft() {
1385         return getStyleAttribute(LEFT);
1386     }
1387 
1388     /**
1389      * Sets the {@code left} style attribute.
1390      * @param left the new attribute
1391      */
1392     @JsxSetter
1393     public void setLeft(final Object left) {
1394         setStyleLengthAttribute(LEFT.getAttributeName(), left, "", true, true, false, false, false);
1395     }
1396 
1397     /**
1398      * Returns the {@code length} property.
1399      * @return the {@code length} property
1400      */
1401     @JsxGetter
1402     public int getLength() {
1403         return getStyleMap().size();
1404     }
1405 
1406     /**
1407      * Gets the {@code letterSpacing} style attribute.
1408      * @return the style attribute
1409      */
1410     @JsxGetter
1411     public String getLetterSpacing() {
1412         return getStyleAttribute(LETTER_SPACING);
1413     }
1414 
1415     /**
1416      * Sets the {@code letterSpacing} style attribute.
1417      * @param letterSpacing the new attribute
1418      */
1419     @JsxSetter
1420     public void setLetterSpacing(final Object letterSpacing) {
1421         setStyleLengthAttribute(LETTER_SPACING.getAttributeName(), letterSpacing, "",
1422                 false, false, false, false, false);
1423     }
1424 
1425     /**
1426      * Gets the {@code margin} style attribute.
1427      * @return the style attribute
1428      */
1429     @JsxGetter
1430     public String getMargin() {
1431         return getStyleAttribute(MARGIN);
1432     }
1433 
1434     /**
1435      * Sets the {@code margin} style attribute.
1436      * @param margin the new attribute
1437      */
1438     @JsxSetter
1439     public void setMargin(final String margin) {
1440         setStyleAttribute(MARGIN.getAttributeName(), margin);
1441     }
1442 
1443     /**
1444      * Gets the {@code marginBottom} style attribute.
1445      * @return the style attribute
1446      */
1447     @JsxGetter
1448     public String getMarginBottom() {
1449         return getStyleAttribute(MARGIN_BOTTOM, MARGIN);
1450     }
1451 
1452     /**
1453      * Sets the {@code marginBottom} style attribute.
1454      * @param marginBottom the new attribute
1455      */
1456     @JsxSetter
1457     public void setMarginBottom(final Object marginBottom) {
1458         setStyleLengthAttribute(MARGIN_BOTTOM.getAttributeName(), marginBottom, "", true, true, false, false, false);
1459     }
1460 
1461     /**
1462      * Gets the {@code marginLeft} style attribute.
1463      * @return the style attribute
1464      */
1465     @JsxGetter
1466     public String getMarginLeft() {
1467         return getStyleAttribute(MARGIN_LEFT, MARGIN);
1468     }
1469 
1470     /**
1471      * Sets the {@code marginLeft} style attribute.
1472      * @param marginLeft the new attribute
1473      */
1474     @JsxSetter
1475     public void setMarginLeft(final Object marginLeft) {
1476         setStyleLengthAttribute(MARGIN_LEFT.getAttributeName(), marginLeft, "", true, true, false, false, false);
1477     }
1478 
1479     /**
1480      * Gets the {@code marginRight} style attribute.
1481      * @return the style attribute
1482      */
1483     @JsxGetter
1484     public String getMarginRight() {
1485         return getStyleAttribute(MARGIN_RIGHT, MARGIN);
1486     }
1487 
1488     /**
1489      * Sets the {@code marginRight} style attribute.
1490      * @param marginRight the new attribute
1491      */
1492     @JsxSetter
1493     public void setMarginRight(final Object marginRight) {
1494         setStyleLengthAttribute(MARGIN_RIGHT.getAttributeName(), marginRight, "", true, true, false, false, false);
1495     }
1496 
1497     /**
1498      * Gets the {@code marginTop} style attribute.
1499      * @return the style attribute
1500      */
1501     @JsxGetter
1502     public String getMarginTop() {
1503         return getStyleAttribute(MARGIN_TOP, MARGIN);
1504     }
1505 
1506     /**
1507      * Sets the {@code marginTop} style attribute.
1508      * @param marginTop the new attribute
1509      */
1510     @JsxSetter
1511     public void setMarginTop(final Object marginTop) {
1512         setStyleLengthAttribute(MARGIN_TOP.getAttributeName(), marginTop, "", true, true, false, false, false);
1513     }
1514 
1515     /**
1516      * Gets the {@code maxHeight} style attribute.
1517      * @return the style attribute
1518      */
1519     @JsxGetter
1520     public String getMaxHeight() {
1521         return getStyleAttribute(MAX_HEIGHT);
1522     }
1523 
1524     /**
1525      * Sets the {@code maxHeight} style attribute.
1526      * @param maxHeight the new attribute
1527      */
1528     @JsxSetter
1529     public void setMaxHeight(final Object maxHeight) {
1530         setStyleLengthAttribute(MAX_HEIGHT.getAttributeName(), maxHeight, "", false, true, false, false, false);
1531     }
1532 
1533     /**
1534      * Gets the {@code maxWidth} style attribute.
1535      * @return the style attribute
1536      */
1537     @JsxGetter
1538     public String getMaxWidth() {
1539         return getStyleAttribute(MAX_WIDTH);
1540     }
1541 
1542     /**
1543      * Sets the {@code maxWidth} style attribute.
1544      * @param maxWidth the new attribute
1545      */
1546     @JsxSetter
1547     public void setMaxWidth(final Object maxWidth) {
1548         setStyleLengthAttribute(MAX_WIDTH.getAttributeName(), maxWidth, "", false, true, false, false, false);
1549     }
1550 
1551     /**
1552      * Gets the {@code minHeight} style attribute.
1553      * @return the style attribute
1554      */
1555     @JsxGetter
1556     public String getMinHeight() {
1557         return getStyleAttribute(MIN_HEIGHT);
1558     }
1559 
1560     /**
1561      * Sets the {@code minHeight} style attribute.
1562      * @param minHeight the new attribute
1563      */
1564     @JsxSetter
1565     public void setMinHeight(final Object minHeight) {
1566         setStyleLengthAttribute(MIN_HEIGHT.getAttributeName(), minHeight, "", true, true, false, false, false);
1567     }
1568 
1569     /**
1570      * Gets the {@code minWidth} style attribute.
1571      * @return the style attribute
1572      */
1573     @JsxGetter
1574     public String getMinWidth() {
1575         return getStyleAttribute(MIN_WIDTH);
1576     }
1577 
1578     /**
1579      * Sets the {@code minWidth} style attribute.
1580      * @param minWidth the new attribute
1581      */
1582     @JsxSetter
1583     public void setMinWidth(final Object minWidth) {
1584         setStyleLengthAttribute(MIN_WIDTH.getAttributeName(), minWidth, "", true, true, false, false, false);
1585     }
1586 
1587     /**
1588      * {@inheritDoc}
1589      */
1590     @Override
1591     public Object get(final String name, final Scriptable start) {
1592         if (this != start) {
1593             return super.get(name, start);
1594         }
1595 
1596         Scriptable prototype = getPrototype();
1597         while (prototype != null) {
1598             final Object value = prototype.get(name, start);
1599             if (value != Scriptable.NOT_FOUND) {
1600                 return value;
1601             }
1602             prototype = prototype.getPrototype();
1603         }
1604 
1605         final Definition style = StyleAttributes.getDefinition(name, getBrowserVersion());
1606         if (style != null) {
1607             return getStyleAttribute(style);
1608         }
1609 
1610         return super.get(name, start);
1611     }
1612 
1613     @Override
1614     public Object get(final int index, final Scriptable start) {
1615         if (index < 0) {
1616             return Undefined.instance;
1617         }
1618 
1619         final Map<String, StyleElement> style = getStyleMap();
1620         final int size = style.size();
1621         if (index >= size) {
1622             if (getBrowserVersion().hasFeature(JS_STYLE_WRONG_INDEX_RETURNS_UNDEFINED)) {
1623                 return Undefined.instance;
1624             }
1625             return "";
1626         }
1627         return style.keySet().toArray(new String[size])[index];
1628     }
1629 
1630     /**
1631      * Get the value for the style attribute.
1632      * @param definition the definition
1633      * @return the value
1634      */
1635     public final String getStyleAttribute(final Definition definition) {
1636         return getStyleAttribute(definition, true);
1637     }
1638 
1639     /**
1640      * Get the value for the style attribute.
1641      * @param definition the definition
1642      * @param getDefaultValueIfEmpty whether to get the default value if empty or not
1643      * @return the value
1644      */
1645     public String getStyleAttribute(final Definition definition, final boolean getDefaultValueIfEmpty) {
1646         return getStyleAttributeImpl(definition.getAttributeName());
1647     }
1648 
1649     private String getStyleAttributeImpl(final String string) {
1650         if (styleDeclaration_ != null) {
1651             return styleDeclaration_.getPropertyValue(string);
1652         }
1653         final StyleElement element = getStyleElement(string);
1654         if (element != null && element.getValue() != null) {
1655             final String value = element.getValue();
1656             if (!value.contains("url")
1657                     && getBrowserVersion().hasFeature(JS_STYLE_SET_PROPERTY_IMPORTANT_IGNORES_CASE)) {
1658                 return value.toLowerCase(Locale.ROOT);
1659             }
1660             return value;
1661         }
1662         return "";
1663     }
1664 
1665     @Override
1666     public void put(final String name, final Scriptable start, final Object value) {
1667         if (this != start) {
1668             super.put(name, start, value);
1669             return;
1670         }
1671 
1672         final Scriptable prototype = getPrototype();
1673         if (prototype != null && !"constructor".equals(name) && prototype.get(name, start) != Scriptable.NOT_FOUND) {
1674             prototype.put(name, start, value);
1675             return;
1676         }
1677 
1678         if (getDomNodeOrNull() != null) { // check if prototype or not
1679             final Definition style = StyleAttributes.getDefinition(name, getBrowserVersion());
1680             if (style != null) {
1681                 final String stringValue = Context.toString(value);
1682                 setStyleAttribute(style.getAttributeName(), stringValue);
1683                 return;
1684             }
1685         }
1686 
1687         super.put(name, start, value);
1688     }
1689 
1690     @Override
1691     public boolean has(final String name, final Scriptable start) {
1692         if (this != start) {
1693             return super.has(name, start);
1694         }
1695 
1696         final Definition style = StyleAttributes.getDefinition(name, getBrowserVersion());
1697         if (style != null) {
1698             return true;
1699         }
1700 
1701         return super.has(name, start);
1702     }
1703 
1704     @Override
1705     public Object[] getIds() {
1706         final List<Object> ids = new ArrayList<>();
1707         for (final Definition styleAttribute : StyleAttributes.getDefinitions(getBrowserVersion())) {
1708             ids.add(styleAttribute.getPropertyName());
1709         }
1710         final Object[] normalIds = super.getIds();
1711         for (final Object o : normalIds) {
1712             if (!ids.contains(o)) {
1713                 ids.add(o);
1714             }
1715         }
1716         return ids.toArray();
1717     }
1718 
1719     /**
1720      * Sets the {@code msImeAlign} style attribute.
1721      * @param msImeAlign the new attribute
1722      */
1723     @JsxSetter(IE)
1724     public void setMsImeAlign(final String msImeAlign) {
1725         setStyleAttribute(Definition.MS_IME_ALIGN.getAttributeName(), msImeAlign);
1726     }
1727 
1728     /**
1729      * Gets the {@code opacity} style attribute.
1730      * @return the style attribute
1731      */
1732     @JsxGetter
1733     public String getOpacity() {
1734         final String opacity = getStyleAttribute(OPACITY, false);
1735         if (opacity == null || opacity.isEmpty()) {
1736             return "";
1737         }
1738 
1739         final String trimedOpacity = opacity.trim();
1740         try {
1741             final double value = Double.parseDouble(trimedOpacity);
1742             if (value % 1 == 0) {
1743                 return Integer.toString((int) value);
1744             }
1745             return Double.toString(value);
1746         }
1747         catch (final NumberFormatException e) {
1748             // ignore wrong value
1749         }
1750         return "";
1751     }
1752 
1753     /**
1754      * Sets the {@code opacity} style attribute.
1755      * @param opacity the new attribute
1756      */
1757     @JsxSetter
1758     public void setOpacity(final Object opacity) {
1759         if (ScriptRuntime.NaNobj == opacity) {
1760             return;
1761         }
1762 
1763         final double doubleValue;
1764         if (opacity instanceof Number) {
1765             doubleValue = ((Number) opacity).doubleValue();
1766         }
1767         else {
1768             String valueString = Context.toString(opacity);
1769 
1770             if (valueString.isEmpty()) {
1771                 setStyleAttribute(OPACITY.getAttributeName(), valueString);
1772                 return;
1773             }
1774 
1775             valueString = valueString.trim();
1776             try {
1777                 doubleValue = Double.parseDouble(valueString);
1778             }
1779             catch (final NumberFormatException e) {
1780                 // ignore wrong value
1781                 return;
1782             }
1783         }
1784 
1785         if (Double.isNaN(doubleValue) || Double.isInfinite(doubleValue)) {
1786             return;
1787         }
1788         setStyleAttribute(OPACITY.getAttributeName(), Double.toString(doubleValue));
1789     }
1790 
1791     /**
1792      * Gets the {@code outline} style attribute.
1793      * @return the style attribute
1794      */
1795     @JsxGetter
1796     public String getOutline() {
1797         return getStyleAttribute(OUTLINE);
1798     }
1799 
1800     /**
1801      * Sets the {@code outline} style attribute.
1802      * @param outline the new attribute
1803      */
1804     @JsxSetter
1805     public void setOutline(final String outline) {
1806         setStyleAttribute(OUTLINE.getAttributeName(), outline);
1807     }
1808 
1809     /**
1810      * Gets the {@code outlineWidth} style attribute.
1811      * @return the style attribute
1812      */
1813     @JsxGetter
1814     public String getOutlineWidth() {
1815         return getStyleAttribute(OUTLINE_WIDTH);
1816     }
1817 
1818     /**
1819      * Sets the {@code outlineWidth} style attribute.
1820      * @param outlineWidth the new attribute
1821      */
1822     @JsxSetter
1823     public void setOutlineWidth(final Object outlineWidth) {
1824         final boolean requiresUnit = !getBrowserVersion().hasFeature(CSS_OUTLINE_WIDTH_UNIT_NOT_REQUIRED);
1825         setStyleLengthAttribute(OUTLINE_WIDTH.getAttributeName(), outlineWidth, "",
1826                 false, false, true, requiresUnit, false);
1827     }
1828 
1829     /**
1830      * Gets the {@code padding} style attribute.
1831      * @return the style attribute
1832      */
1833     @JsxGetter
1834     public String getPadding() {
1835         return getStyleAttribute(PADDING);
1836     }
1837 
1838     /**
1839      * Sets the {@code padding} style attribute.
1840      * @param padding the new attribute
1841      */
1842     @JsxSetter
1843     public void setPadding(final String padding) {
1844         setStyleAttribute(PADDING.getAttributeName(), padding);
1845     }
1846 
1847     /**
1848      * Gets the {@code paddingBottom} style attribute.
1849      * @return the style attribute
1850      */
1851     @JsxGetter
1852     public String getPaddingBottom() {
1853         return getStyleAttribute(PADDING_BOTTOM, PADDING);
1854     }
1855 
1856     /**
1857      * Sets the {@code paddingBottom} style attribute.
1858      * @param paddingBottom the new attribute
1859      */
1860     @JsxSetter
1861     public void setPaddingBottom(final Object paddingBottom) {
1862         setStyleLengthAttribute(PADDING_BOTTOM.getAttributeName(), paddingBottom, "", false, true, false, false, false);
1863     }
1864 
1865     /**
1866      * Gets the {@code paddingLeft} style attribute.
1867      * @return the style attribute
1868      */
1869     @JsxGetter
1870     public String getPaddingLeft() {
1871         return getStyleAttribute(PADDING_LEFT, PADDING);
1872     }
1873 
1874     /**
1875      * Sets the {@code paddingLeft} style attribute.
1876      * @param paddingLeft the new attribute
1877      */
1878     @JsxSetter
1879     public void setPaddingLeft(final Object paddingLeft) {
1880         setStyleLengthAttribute(PADDING_LEFT.getAttributeName(), paddingLeft, "", false, true, false, false, false);
1881     }
1882 
1883     /**
1884      * Gets the {@code paddingRight} style attribute.
1885      * @return the style attribute
1886      */
1887     @JsxGetter
1888     public String getPaddingRight() {
1889         return getStyleAttribute(PADDING_RIGHT, PADDING);
1890     }
1891 
1892     /**
1893      * Sets the {@code paddingRight} style attribute.
1894      * @param paddingRight the new attribute
1895      */
1896     @JsxSetter
1897     public void setPaddingRight(final Object paddingRight) {
1898         setStyleLengthAttribute(PADDING_RIGHT.getAttributeName(), paddingRight, "", false, true, false, false, false);
1899     }
1900 
1901     /**
1902      * Gets the {@code paddingTop} style attribute.
1903      * @return the style attribute
1904      */
1905     @JsxGetter
1906     public String getPaddingTop() {
1907         return getStyleAttribute(PADDING_TOP, PADDING);
1908     }
1909 
1910     /**
1911      * Sets the {@code paddingTop} style attribute.
1912      * @param paddingTop the new attribute
1913      */
1914     @JsxSetter
1915     public void setPaddingTop(final Object paddingTop) {
1916         setStyleLengthAttribute(PADDING_TOP.getAttributeName(), paddingTop, "", false, true, false, false, false);
1917     }
1918 
1919     /**
1920      * Gets the {@code page} style attribute.
1921      * @return the style attribute
1922      */
1923     @JsxGetter(CHROME)
1924     public String getPage() {
1925         return getStyleAttribute(PAGE);
1926     }
1927 
1928     /**
1929      * Sets the {@code page} style attribute.
1930      * @param page the new attribute
1931      */
1932     @JsxSetter(CHROME)
1933     public void setPage(final String page) {
1934         setStyleAttribute(PAGE.getAttributeName(), page);
1935     }
1936 
1937     /**
1938      * Gets the {@code pixelBottom} style attribute.
1939      * @return the style attribute
1940      */
1941     @JsxGetter(IE)
1942     public int getPixelBottom() {
1943         return pixelValue(getBottom());
1944     }
1945 
1946     /**
1947      * Sets the {@code pixelBottom} style attribute.
1948      * @param pixelBottom the new attribute
1949      */
1950     @JsxSetter(IE)
1951     public void setPixelBottom(final int pixelBottom) {
1952         setBottom(pixelBottom + "px");
1953     }
1954 
1955     /**
1956      * Gets the {@code pixelHeight} style attribute.
1957      * @return the style attribute
1958      */
1959     @JsxGetter(IE)
1960     public int getPixelHeight() {
1961         return pixelValue(getHeight());
1962     }
1963 
1964     /**
1965      * Sets the {@code pixelHeight} style attribute.
1966      * @param pixelHeight the new attribute
1967      */
1968     @JsxSetter(IE)
1969     public void setPixelHeight(final int pixelHeight) {
1970         setHeight(pixelHeight + "px");
1971     }
1972 
1973     /**
1974      * Gets the {@code pixelLeft} style attribute.
1975      * @return the style attribute
1976      */
1977     @JsxGetter(IE)
1978     public int getPixelLeft() {
1979         return pixelValue(getLeft());
1980     }
1981 
1982     /**
1983      * Sets the {@code pixelLeft} style attribute.
1984      * @param pixelLeft the new attribute
1985      */
1986     @JsxSetter(IE)
1987     public void setPixelLeft(final int pixelLeft) {
1988         setLeft(pixelLeft + "px");
1989     }
1990 
1991     /**
1992      * Gets the {@code pixelRight} style attribute.
1993      * @return the style attribute
1994      */
1995     @JsxGetter(IE)
1996     public int getPixelRight() {
1997         return pixelValue(getRight());
1998     }
1999 
2000     /**
2001      * Sets the {@code pixelRight} style attribute.
2002      * @param pixelRight the new attribute
2003      */
2004     @JsxSetter(IE)
2005     public void setPixelRight(final int pixelRight) {
2006         setRight(pixelRight + "px");
2007     }
2008 
2009     /**
2010      * Gets the {@code pixelTop} style attribute.
2011      * @return the style attribute
2012      */
2013     @JsxGetter(IE)
2014     public int getPixelTop() {
2015         return pixelValue(getTop());
2016     }
2017 
2018     /**
2019      * Sets the {@code pixelTop} style attribute.
2020      * @param pixelTop the new attribute
2021      */
2022     @JsxSetter(IE)
2023     public void setPixelTop(final int pixelTop) {
2024         setTop(pixelTop + "px");
2025     }
2026 
2027     /**
2028      * Gets the {@code pixelWidth} style attribute.
2029      * @return the style attribute
2030      */
2031     @JsxGetter(IE)
2032     public int getPixelWidth() {
2033         return pixelValue(getWidth());
2034     }
2035 
2036     /**
2037      * Sets the {@code pixelWidth} style attribute.
2038      * @param pixelWidth the new attribute
2039      */
2040     @JsxSetter(IE)
2041     public void setPixelWidth(final int pixelWidth) {
2042         setWidth(pixelWidth + "px");
2043     }
2044 
2045     /**
2046      * Gets the {@code posBottom} style attribute.
2047      * @return the style attribute
2048      */
2049     @JsxGetter(IE)
2050     public int getPosBottom() {
2051         return 0;
2052     }
2053 
2054     /**
2055      * Sets the {@code posBottom} style attribute.
2056      * @param posBottom the new attribute
2057      */
2058     @JsxSetter(IE)
2059     public void setPosBottom(final int posBottom) {
2060         // Empty.
2061     }
2062 
2063     /**
2064      * Gets the {@code posHeight} style attribute.
2065      * @return the style attribute
2066      */
2067     @JsxGetter(IE)
2068     public int getPosHeight() {
2069         return 0;
2070     }
2071 
2072     /**
2073      * Sets the {@code posHeight} style attribute.
2074      * @param posHeight the new attribute
2075      */
2076     @JsxSetter(IE)
2077     public void setPosHeight(final int posHeight) {
2078         // Empty.
2079     }
2080 
2081     /**
2082      * Gets the {@code posLeft} style attribute.
2083      * @return the style attribute
2084      */
2085     @JsxGetter(IE)
2086     public int getPosLeft() {
2087         return 0;
2088     }
2089 
2090     /**
2091      * Sets the {@code posLeft} style attribute.
2092      * @param posLeft the new attribute
2093      */
2094     @JsxSetter(IE)
2095     public void setPosLeft(final int posLeft) {
2096         // Empty.
2097     }
2098 
2099     /**
2100      * Gets the {@code posRight} style attribute.
2101      * @return the style attribute
2102      */
2103     @JsxGetter(IE)
2104     public int getPosRight() {
2105         return 0;
2106     }
2107 
2108     /**
2109      * Sets the {@code posRight} style attribute.
2110      * @param posRight the new attribute
2111      */
2112     @JsxSetter(IE)
2113     public void setPosRight(final int posRight) {
2114         // Empty.
2115     }
2116 
2117     /**
2118      * Gets the {@code posTop} style attribute.
2119      * @return the style attribute
2120      */
2121     @JsxGetter(IE)
2122     public int getPosTop() {
2123         return 0;
2124     }
2125 
2126     /**
2127      * Sets the {@code posTop} style attribute.
2128      * @param posTop the new attribute
2129      */
2130     @JsxSetter(IE)
2131     public void setPosTop(final int posTop) {
2132         // Empty.
2133     }
2134 
2135     /**
2136      * Gets the {@code posWidth} style attribute.
2137      * @return the style attribute
2138      */
2139     @JsxGetter(IE)
2140     public int getPosWidth() {
2141         return 0;
2142     }
2143 
2144     /**
2145      * Sets the {@code posWidth} style attribute.
2146      * @param posWidth the new attribute
2147      */
2148     @JsxSetter(IE)
2149     public void setPosWidth(final int posWidth) {
2150         // Empty.
2151     }
2152 
2153     /**
2154      * Gets the {@code right} style attribute.
2155      * @return the style attribute
2156      */
2157     @JsxGetter
2158     public String getRight() {
2159         return getStyleAttribute(RIGHT);
2160     }
2161 
2162     /**
2163      * Sets the {@code right} style attribute.
2164      * @param right the new attribute
2165      */
2166     @JsxSetter
2167     public void setRight(final Object right) {
2168         setStyleLengthAttribute(RIGHT.getAttributeName(), right, "", true, true, false, false, false);
2169     }
2170 
2171     /**
2172      * Gets the {@code rubyAlign} style attribute.
2173      * @return the style attribute
2174      */
2175     @JsxGetter({IE, FF})
2176     public String getRubyAlign() {
2177         return getStyleAttribute(RUBY_ALIGN);
2178     }
2179 
2180     /**
2181      * Sets the {@code rubyAlign} style attribute.
2182      * @param rubyAlign the new attribute
2183      */
2184     @JsxSetter({IE, FF})
2185     public void setRubyAlign(final String rubyAlign) {
2186         setStyleAttribute(RUBY_ALIGN.getAttributeName(), rubyAlign);
2187     }
2188 
2189     /**
2190      * Gets the {@code size} style attribute.
2191      * @return the style attribute
2192      */
2193     @JsxGetter(CHROME)
2194     public String getSize() {
2195         return getStyleAttribute(SIZE);
2196     }
2197 
2198     /**
2199      * Sets the {@code size} style attribute.
2200      * @param size the new attribute
2201      */
2202     @JsxSetter(CHROME)
2203     public void setSize(final String size) {
2204         setStyleAttribute(SIZE.getAttributeName(), size);
2205     }
2206 
2207     /**
2208      * Gets the {@code textDecorationBlink} style attribute.
2209      * @return the style attribute
2210      */
2211     @JsxGetter(IE)
2212     public boolean isTextDecorationBlink() {
2213         return false;
2214     }
2215 
2216     /**
2217      * Sets the {@code textDecorationBlink} style attribute.
2218      * @param textDecorationBlink the new attribute
2219      */
2220     @JsxSetter(IE)
2221     public void setTextDecorationBlink(final boolean textDecorationBlink) {
2222         // Empty.
2223     }
2224 
2225     /**
2226      * Gets the {@code textDecorationLineThrough} style attribute.
2227      * @return the style attribute
2228      */
2229     @JsxGetter(IE)
2230     public boolean isTextDecorationLineThrough() {
2231         return false;
2232     }
2233 
2234     /**
2235      * Sets the {@code textDecorationLineThrough} style attribute.
2236      * @param textDecorationLineThrough the new attribute
2237      */
2238     @JsxSetter(IE)
2239     public void setTextDecorationLineThrough(final boolean textDecorationLineThrough) {
2240         // Empty.
2241     }
2242 
2243     /**
2244      * Gets the {@code textDecorationNone} style attribute.
2245      * @return the style attribute
2246      */
2247     @JsxGetter(IE)
2248     public boolean isTextDecorationNone() {
2249         return false;
2250     }
2251 
2252     /**
2253      * Sets the {@code textDecorationNone} style attribute.
2254      * @param textDecorationNone the new attribute
2255      */
2256     @JsxSetter(IE)
2257     public void setTextDecorationNone(final boolean textDecorationNone) {
2258         // Empty.
2259     }
2260 
2261     /**
2262      * Gets the {@code textDecorationOverline} style attribute.
2263      * @return the style attribute
2264      */
2265     @JsxGetter(IE)
2266     public boolean isTextDecorationOverline() {
2267         return false;
2268     }
2269 
2270     /**
2271      * Sets the {@code textDecorationOverline} style attribute.
2272      * @param textDecorationOverline the new attribute
2273      */
2274     @JsxSetter(IE)
2275     public void setTextDecorationOverline(final boolean textDecorationOverline) {
2276         // Empty.
2277     }
2278 
2279     /**
2280      * Gets the {@code textDecorationUnderline} style attribute.
2281      * @return the style attribute
2282      */
2283     @JsxGetter(IE)
2284     public boolean getTextDecorationUnderline() {
2285         return false;
2286     }
2287 
2288     /**
2289      * Sets the {@code textDecorationUnderline} style attribute.
2290      * @param textDecorationUnderline the new attribute
2291      */
2292     @JsxSetter(IE)
2293     public void setTextDecorationUnderline(final boolean textDecorationUnderline) {
2294         // Empty.
2295     }
2296 
2297     /**
2298      * Gets the {@code textIndent} style attribute.
2299      * @return the style attribute
2300      */
2301     @JsxGetter
2302     public String getTextIndent() {
2303         return getStyleAttribute(TEXT_INDENT);
2304     }
2305 
2306     /**
2307      * Sets the {@code textIndent} style attribute.
2308      * @param textIndent the new attribute
2309      */
2310     @JsxSetter
2311     public void setTextIndent(final Object textIndent) {
2312         setStyleLengthAttribute(TEXT_INDENT.getAttributeName(), textIndent, "", false, true, false, false, false);
2313     }
2314 
2315     /**
2316      * Gets the {@code top} style attribute.
2317      * @return the style attribute
2318      */
2319     @JsxGetter
2320     public String getTop() {
2321         return getStyleAttribute(TOP);
2322     }
2323 
2324     /**
2325      * Sets the {@code top} style attribute.
2326      * @param top the new attribute
2327      */
2328     @JsxSetter
2329     public void setTop(final Object top) {
2330         setStyleLengthAttribute(TOP.getAttributeName(), top, "", true, true, false, false, false);
2331     }
2332 
2333     /**
2334      * Gets the {@code verticalAlign} style attribute.
2335      * @return the style attribute
2336      */
2337     @JsxGetter
2338     public String getVerticalAlign() {
2339         return getStyleAttribute(VERTICAL_ALIGN);
2340     }
2341 
2342     /**
2343      * Sets the {@code verticalAlign} style attribute.
2344      * @param verticalAlign the new attribute
2345      */
2346     @JsxSetter
2347     public void setVerticalAlign(final Object verticalAlign) {
2348         final boolean auto = getBrowserVersion().hasFeature(CSS_VERTICAL_ALIGN_SUPPORTS_AUTO);
2349         setStyleLengthAttribute(VERTICAL_ALIGN.getAttributeName(), verticalAlign, "", auto, true, false, false, false);
2350     }
2351 
2352     /**
2353      * Gets the {@code width} style attribute.
2354      * @return the style attribute
2355      */
2356     @JsxGetter
2357     public String getWidth() {
2358         return getStyleAttribute(WIDTH);
2359     }
2360 
2361     /**
2362      * Sets the {@code width} style attribute.
2363      * @param width the new attribute
2364      */
2365     @JsxSetter
2366     public void setWidth(final Object width) {
2367         setStyleLengthAttribute(WIDTH.getAttributeName(), width, "", true, true, false, false, false);
2368     }
2369 
2370     /**
2371      * Gets the {@code widows} style attribute.
2372      * @return the style attribute
2373      */
2374     @JsxGetter({CHROME, IE})
2375     public String getWidows() {
2376         return getStyleAttribute(WIDOWS);
2377     }
2378 
2379     /**
2380      * Sets the {@code widows} style attribute.
2381      * @param widows the new attribute
2382      */
2383     @JsxSetter({CHROME, IE})
2384     public void setWidows(final String widows) {
2385         if (getBrowserVersion().hasFeature(CSS_BACKGROUND_INITIAL)) {
2386             try {
2387                 if (Integer.parseInt(widows) <= 0) {
2388                     return;
2389                 }
2390             }
2391             catch (final NumberFormatException e) {
2392                 return;
2393             }
2394         }
2395         setStyleAttribute(WIDOWS.getAttributeName(), widows);
2396     }
2397 
2398     /**
2399      * Gets the {@code orphans} style attribute.
2400      * @return the style attribute
2401      */
2402     @JsxGetter({CHROME, IE})
2403     public String getOrphans() {
2404         return getStyleAttribute(ORPHANS);
2405     }
2406 
2407     /**
2408      * Sets the {@code orphans} style attribute.
2409      * @param orphans the new attribute
2410      */
2411     @JsxSetter({CHROME, IE})
2412     public void setOrphans(final String orphans) {
2413         if (getBrowserVersion().hasFeature(CSS_BACKGROUND_INITIAL)) {
2414             try {
2415                 if (Integer.parseInt(orphans) <= 0) {
2416                     return;
2417                 }
2418             }
2419             catch (final NumberFormatException e) {
2420                 return;
2421             }
2422         }
2423         setStyleAttribute(ORPHANS.getAttributeName(), orphans);
2424     }
2425 
2426     /**
2427      * Gets the {@code position} style attribute.
2428      * @return the style attribute
2429      */
2430     @JsxGetter
2431     public String getPosition() {
2432         return getStyleAttribute(POSITION);
2433     }
2434 
2435     /**
2436      * Sets the {@code position} style attribute.
2437      * @param position the new attribute
2438      */
2439     @JsxSetter
2440     public void setPosition(final String position) {
2441         if (position.isEmpty() || "static".equalsIgnoreCase(position) || "absolute".equalsIgnoreCase(position)
2442                 || "fixed".equalsIgnoreCase(position) || "relative".equalsIgnoreCase(position)
2443                 || "initial".equalsIgnoreCase(position) || "inherit".equalsIgnoreCase(position)) {
2444             setStyleAttribute(POSITION.getAttributeName(), position.toLowerCase(Locale.ROOT));
2445         }
2446     }
2447 
2448     /**
2449      * Gets the {@code wordSpacing} style attribute.
2450      * @return the style attribute
2451      */
2452     @JsxGetter
2453     public String getWordSpacing() {
2454         return getStyleAttribute(WORD_SPACING);
2455     }
2456 
2457     /**
2458      * Sets the {@code wordSpacing} style attribute.
2459      * @param wordSpacing the new attribute
2460      */
2461     @JsxSetter
2462     public void setWordSpacing(final Object wordSpacing) {
2463         setStyleLengthAttribute(WORD_SPACING.getAttributeName(), wordSpacing, "",
2464                 false, getBrowserVersion().hasFeature(JS_STYLE_WORD_SPACING_ACCEPTS_PERCENT), false, false, false);
2465     }
2466 
2467     /**
2468      * Gets the {@code zIndex} style attribute.
2469      * @return the style attribute
2470      */
2471     @JsxGetter
2472     public Object getZIndex() {
2473         final String value = getStyleAttribute(Z_INDEX_);
2474         if (getBrowserVersion().hasFeature(CSS_ZINDEX_TYPE_INTEGER)) {
2475             try {
2476                 return Integer.valueOf(value);
2477             }
2478             catch (final NumberFormatException e) {
2479                 return "";
2480             }
2481         }
2482 
2483         // zIndex is string
2484         try {
2485             Integer.parseInt(value);
2486             return value;
2487         }
2488         catch (final NumberFormatException e) {
2489             return "";
2490         }
2491     }
2492 
2493     /**
2494      * Sets the {@code zIndex} style attribute.
2495      * @param zIndex the new attribute
2496      */
2497     @JsxSetter
2498     public void setZIndex(final Object zIndex) {
2499         // empty
2500         if (zIndex == null || StringUtils.isEmpty(zIndex.toString())) {
2501             setStyleAttribute(Z_INDEX_.getAttributeName(), "");
2502             return;
2503         }
2504         // undefined
2505         if (Undefined.instance.equals(zIndex)) {
2506             return;
2507         }
2508 
2509         // string
2510         if (zIndex instanceof Number) {
2511             final Number number = (Number) zIndex;
2512             if (number.doubleValue() % 1 == 0) {
2513                 setStyleAttribute(Z_INDEX_.getAttributeName(), Integer.toString(number.intValue()));
2514             }
2515             return;
2516         }
2517         try {
2518             final int i = Integer.parseInt(zIndex.toString());
2519             setStyleAttribute(Z_INDEX_.getAttributeName(), Integer.toString(i));
2520         }
2521         catch (final NumberFormatException e) {
2522             // ignore
2523         }
2524     }
2525 
2526     /**
2527      * Gets the value of the specified property of the style.
2528      * @param name the style property name
2529      * @return empty string if nothing found
2530      */
2531     @JsxFunction
2532     public String getPropertyValue(final String name) {
2533         if (name != null && name.contains("-")) {
2534             final Object value = getProperty(this, camelize(name));
2535             if (value instanceof String) {
2536                 return (String) value;
2537             }
2538         }
2539         return getStyleAttributeImpl(name);
2540     }
2541 
2542     /**
2543      * Gets the CSS property value.
2544      * @param name the name of the property to retrieve
2545      * @return the value
2546      */
2547     @JsxFunction(FF)
2548     public CSSValue getPropertyCSSValue(final String name) {
2549         LOG.info("getPropertyCSSValue(" + name + "): getPropertyCSSValue support is experimental");
2550         // following is a hack, just to have basic support for getPropertyCSSValue
2551         // TODO: rework the whole CSS processing here! we should *always* parse the style!
2552         if (styleDeclaration_ == null) {
2553             final String uri = getDomNodeOrDie().getPage().getWebResponse().getWebRequest()
2554                     .getUrl().toExternalForm();
2555             final String styleAttribute = jsElement_.getDomNodeOrDie().getAttribute("style");
2556             final InputSource source = new InputSource(new StringReader(styleAttribute));
2557             source.setURI(uri);
2558             final ErrorHandler errorHandler = getWindow().getWebWindow().getWebClient().getCssErrorHandler();
2559             final CSSOMParser parser = new CSSOMParser(new SACParserCSS3());
2560             parser.setErrorHandler(errorHandler);
2561             try {
2562                 styleDeclaration_ = parser.parseStyleDeclaration(source);
2563             }
2564             catch (final IOException e) {
2565                 throw new RuntimeException(e);
2566             }
2567         }
2568         org.w3c.dom.css.CSSValue cssValue = styleDeclaration_.getPropertyCSSValue(name);
2569         if (cssValue == null) {
2570             final CSSValueImpl newValue = new CSSValueImpl();
2571             newValue.setFloatValue(CSSPrimitiveValue.CSS_PX, 0);
2572             cssValue = newValue;
2573         }
2574 
2575         // FF has spaces next to ","
2576         final String cssText = cssValue.getCssText();
2577         if (cssText.startsWith("rgb(")) {
2578             final String formatedCssText = StringUtils.replace(cssText, ",", ", ");
2579             cssValue.setCssText(formatedCssText);
2580         }
2581 
2582         return new CSSPrimitiveValue(jsElement_, (org.w3c.dom.css.CSSPrimitiveValue) cssValue);
2583     }
2584 
2585     /**
2586      * Gets the value of the specified property of the style.
2587      * @param name the style property name
2588      * @return empty string if nothing found
2589      */
2590     @JsxFunction
2591     public String getPropertyPriority(final String name) {
2592         return getStylePriority(name);
2593     }
2594 
2595     /**
2596      * Sets the value of the specified property.
2597      *
2598      * @param name the name of the attribute
2599      * @param value the value to assign to the attribute
2600      * @param important may be null
2601      */
2602     @JsxFunction
2603     public void setProperty(final String name, final Object value, final String important) {
2604         String imp = "";
2605         if (!StringUtils.isEmpty(important) && !"null".equals(important)) {
2606             if (getBrowserVersion().hasFeature(JS_STYLE_SET_PROPERTY_IMPORTANT_IGNORES_CASE)) {
2607                 if (!StyleElement.PRIORITY_IMPORTANT.equalsIgnoreCase(important)) {
2608                     return;
2609                 }
2610             }
2611             else {
2612                 if (!StyleElement.PRIORITY_IMPORTANT.equals(important)) {
2613                     return;
2614                 }
2615             }
2616             imp = StyleElement.PRIORITY_IMPORTANT;
2617         }
2618 
2619         if (LENGTH_PROPERTIES_FFFF.contains(name)) {
2620             setStyleLengthAttribute(name, value, imp, false, false, false, false,
2621                     getBrowserVersion().hasFeature(CSS_LENGTH_UNDEFINED_AS_EMPTY));
2622         }
2623         else if (LENGTH_PROPERTIES_TTFF.contains(name)) {
2624             setStyleLengthAttribute(name, value, imp, true, true, false, false,
2625                     getBrowserVersion().hasFeature(CSS_LENGTH_UNDEFINED_AS_EMPTY));
2626         }
2627         else if (LENGTH_PROPERTIES_FTFF.contains(name)) {
2628             setStyleLengthAttribute(name, value, imp, false, true, false, false,
2629                     getBrowserVersion().hasFeature(CSS_LENGTH_UNDEFINED_AS_EMPTY));
2630         }
2631         else if (OUTLINE_WIDTH.getAttributeName().equals(name)) {
2632             final boolean requiresUnit = !getBrowserVersion().hasFeature(CSS_OUTLINE_WIDTH_UNIT_NOT_REQUIRED);
2633             setStyleLengthAttribute(OUTLINE_WIDTH.getAttributeName(), value, imp, false, false, true, requiresUnit,
2634                     getBrowserVersion().hasFeature(CSS_LENGTH_UNDEFINED_AS_EMPTY));
2635         }
2636         else if (WORD_SPACING.getAttributeName().equals(name)) {
2637             setStyleLengthAttribute(WORD_SPACING.getAttributeName(), value, imp,
2638                     false, getBrowserVersion().hasFeature(JS_STYLE_WORD_SPACING_ACCEPTS_PERCENT), false, false,
2639                     getBrowserVersion().hasFeature(CSS_LENGTH_UNDEFINED_AS_EMPTY));
2640         }
2641         else if (VERTICAL_ALIGN.getAttributeName().equals(name)) {
2642             final boolean auto = getBrowserVersion().hasFeature(CSS_VERTICAL_ALIGN_SUPPORTS_AUTO);
2643             setStyleLengthAttribute(VERTICAL_ALIGN.getAttributeName(), value, imp, auto, true, false, false,
2644                     getBrowserVersion().hasFeature(CSS_LENGTH_UNDEFINED_AS_EMPTY));
2645         }
2646         else {
2647             setStyleAttribute(name, Context.toString(value), imp);
2648         }
2649     }
2650 
2651     /**
2652      * Removes the named property.
2653      * @param name the name of the property to remove
2654      * @return the value deleted
2655      */
2656     @JsxFunction
2657     public String removeProperty(final Object name) {
2658         return removeStyleAttribute(Context.toString(name));
2659     }
2660 
2661     /**
2662      * Returns the value of the specified attribute, or an empty string if it does not exist.
2663      * This method exists only in IE.
2664      *
2665      * @see <a href="http://msdn.microsoft.com/en-us/library/ms536429.aspx">MSDN Documentation</a>
2666      * @param name the name of the attribute
2667      * @param flag 0 for case insensitive, 1 (default) for case sensitive
2668      * @return the value of the specified attribute
2669      */
2670     @JsxFunction(IE)
2671     public Object getAttribute(final String name, final int flag) {
2672         // Case-insensitive.
2673         final StyleElement style = getStyleElementCaseInSensitive(name);
2674         if (null == style) {
2675             return "";
2676         }
2677         return style.getValue();
2678     }
2679 
2680     /**
2681      * Sets the value of the specified attribute. This method exists only in IE.
2682      *
2683      * @see <a href="http://msdn.microsoft.com/en-us/library/ms536739.aspx">MSDN Documentation</a>
2684      * @param name the name of the attribute
2685      * @param value the value to assign to the attribute
2686      * @param flag 0 for case insensitive, 1 (default) for case sensitive
2687      */
2688     @JsxFunction(IE)
2689     public void setAttribute(final String name, final String value, final Object flag) {
2690         // Case-insensitive.
2691         final StyleElement style = getStyleElementCaseInSensitive(name);
2692         if (null != style) {
2693             setStyleAttribute(style.getName(), value);
2694         }
2695     }
2696 
2697     /**
2698      * Removes the specified attribute. This method exists only in IE.
2699      *
2700      * @see <a href="http://msdn.microsoft.com/en-us/library/ms536696.aspx">MSDN Documentation</a>
2701      * @param name the name of the attribute
2702      * @param flag 0 for case insensitive, 1 (default) for case sensitive
2703      * @return {@code true} if the attribute was successfully removed, {@code false} otherwise
2704      */
2705     @JsxFunction(IE)
2706     public boolean removeAttribute(final String name, final Object flag) {
2707         // Case-insensitive.
2708         final StyleElement style = getStyleElementCaseInSensitive(name);
2709         if (style != null) {
2710             removeStyleAttribute(style.getName());
2711             return true;
2712         }
2713         return false;
2714     }
2715 
2716     /**
2717      * Searches for any color notation in the specified text.
2718      * @param text the string to search in
2719      * @return the string of the color if found, null otherwise
2720      */
2721     private static String findColor(final String text) {
2722         Color tmpColor = com.gargoylesoftware.htmlunit.util.StringUtils.findColorRGB(text);
2723         if (tmpColor != null) {
2724             return com.gargoylesoftware.htmlunit.util.StringUtils.formatColor(tmpColor);
2725         }
2726 
2727         final String[] tokens = StringUtils.split(text, ' ');
2728         for (final String token : tokens) {
2729             if (isColorKeyword(token)) {
2730                 return token;
2731             }
2732 
2733             tmpColor = com.gargoylesoftware.htmlunit.util.StringUtils.asColorHexadecimal(token);
2734             if (tmpColor != null) {
2735                 return com.gargoylesoftware.htmlunit.util.StringUtils.formatColor(tmpColor);
2736             }
2737         }
2738         return null;
2739     }
2740 
2741     /**
2742      * Searches for any URL notation in the specified text.
2743      * @param text the string to search in
2744      * @return the string of the URL if found, null otherwise
2745      */
2746     private static String findImageUrl(final String text) {
2747         final Matcher m = URL_PATTERN.matcher(text);
2748         if (m.find()) {
2749             return "url(\"" + m.group(1) + "\")";
2750         }
2751         return null;
2752     }
2753 
2754     /**
2755      * Searches for any position notation in the specified text.
2756      * @param text the string to search in
2757      * @return the string of the position if found, null otherwise
2758      */
2759     private static String findPosition(final String text) {
2760         Matcher m = POSITION_PATTERN.matcher(text);
2761         if (m.find()) {
2762             return m.group(1) + " " + m.group(3);
2763         }
2764         m = POSITION_PATTERN2.matcher(text);
2765         if (m.find()) {
2766             return m.group(1) + " " + m.group(2);
2767         }
2768         m = POSITION_PATTERN3.matcher(text);
2769         if (m.find()) {
2770             return m.group(2) + " " + m.group(1);
2771         }
2772         return null;
2773     }
2774 
2775     /**
2776      * Searches for any repeat notation in the specified text.
2777      * @param text the string to search in
2778      * @return the string of the repeat if found, null otherwise
2779      */
2780     private static String findRepeat(final String text) {
2781         if (text.contains("repeat-x")) {
2782             return "repeat-x";
2783         }
2784         if (text.contains("repeat-y")) {
2785             return "repeat-y";
2786         }
2787         if (text.contains("no-repeat")) {
2788             return "no-repeat";
2789         }
2790         if (text.contains("repeat")) {
2791             return "repeat";
2792         }
2793         return null;
2794     }
2795 
2796     /**
2797      * Searches for any attachment notation in the specified text.
2798      * @param text the string to search in
2799      * @return the string of the attachment if found, null otherwise
2800      */
2801     private static String findAttachment(final String text) {
2802         if (text.contains("scroll")) {
2803             return "scroll";
2804         }
2805         if (text.contains("fixed")) {
2806             return "fixed";
2807         }
2808         return null;
2809     }
2810 
2811     /**
2812      * Searches for a border style in the specified text.
2813      * @param text the string to search in
2814      * @return the border style if found, null otherwise
2815      */
2816     private static String findBorderStyle(final String text) {
2817         for (final String token : StringUtils.split(text, ' ')) {
2818             if (isBorderStyle(token)) {
2819                 return token;
2820             }
2821         }
2822         return null;
2823     }
2824 
2825     /**
2826      * Searches for a border width in the specified text.
2827      * @param text the string to search in
2828      * @return the border width if found, null otherwise
2829      */
2830     private static String findBorderWidth(final String text) {
2831         for (final String token : StringUtils.split(text, ' ')) {
2832             if (isBorderWidth(token)) {
2833                 return token;
2834             }
2835         }
2836         return null;
2837     }
2838 
2839     /**
2840      * Returns if the specified token is a reserved color keyword.
2841      * @param token the token to check
2842      * @return whether the token is a reserved color keyword or not
2843      */
2844     private static boolean isColorKeyword(final String token) {
2845         return CSSColors_.containsKey(token.toLowerCase(Locale.ROOT));
2846     }
2847 
2848     /**
2849      * Gets the RGB equivalent of a CSS color if the provided color is recognized.
2850      * @param color the color
2851      * @return the provided color if this is not a recognized color keyword, the RGB value
2852      * in the form "rgb(x, y, z)" otherwise
2853      */
2854     public static String toRGBColor(final String color) {
2855         final String rgbValue = CSSColors_.get(color.toLowerCase(Locale.ROOT));
2856         if (rgbValue != null) {
2857             return rgbValue;
2858         }
2859         return color;
2860     }
2861 
2862     /**
2863      * Returns if the specified token is a border style.
2864      * @param token the token to check
2865      * @return whether the token is a border style or not
2866      */
2867     private static boolean isBorderStyle(final String token) {
2868         return "none".equalsIgnoreCase(token) || "hidden".equalsIgnoreCase(token)
2869             || "dotted".equalsIgnoreCase(token) || "dashed".equalsIgnoreCase(token)
2870             || "solid".equalsIgnoreCase(token) || "double".equalsIgnoreCase(token)
2871             || "groove".equalsIgnoreCase(token) || "ridge".equalsIgnoreCase(token)
2872             || "inset".equalsIgnoreCase(token) || "outset".equalsIgnoreCase(token);
2873     }
2874 
2875     /**
2876      * Returns if the specified token is a border width.
2877      * @param token the token to check
2878      * @return whether the token is a border width or not
2879      */
2880     private static boolean isBorderWidth(final String token) {
2881         return "thin".equalsIgnoreCase(token) || "medium".equalsIgnoreCase(token)
2882             || "thick".equalsIgnoreCase(token) || isLength(token);
2883     }
2884 
2885     /**
2886      * Returns if the specified token is a length.
2887      * @param token the token to check
2888      * @return whether the token is a length or not
2889      */
2890     static boolean isLength(String token) {
2891         if (token.endsWith("em") || token.endsWith("ex") || token.endsWith("px") || token.endsWith("in")
2892             || token.endsWith("cm") || token.endsWith("mm") || token.endsWith("pt") || token.endsWith("pc")
2893             || token.endsWith("%")) {
2894 
2895             if (token.endsWith("%")) {
2896                 token = token.substring(0, token.length() - 1);
2897             }
2898             else {
2899                 token = token.substring(0, token.length() - 2);
2900             }
2901             try {
2902                 Double.parseDouble(token);
2903                 return true;
2904             }
2905             catch (final NumberFormatException e) {
2906                 // Ignore.
2907             }
2908         }
2909         return false;
2910     }
2911 
2912     /**
2913      * Converts the specified length CSS attribute value into an integer number of pixels. If the
2914      * specified CSS attribute value is a percentage, this method uses the specified value object
2915      * to recursively retrieve the base (parent) CSS attribute value.
2916      * @param element the element for which the CSS attribute value is to be retrieved
2917      * @param value the CSS attribute value which is to be retrieved
2918      * @return the integer number of pixels corresponding to the specified length CSS attribute value
2919      * @see #pixelValue(String)
2920      */
2921     protected static int pixelValue(final Element element, final CssValue value) {
2922         return pixelValue(element, value, false);
2923     }
2924 
2925     private static int pixelValue(final Element element, final CssValue value, final boolean percentMode) {
2926         final String s = value.get(element);
2927         if (s.endsWith("%") || (s.isEmpty() && element instanceof HTMLHtmlElement)) {
2928             final int i = NumberUtils.toInt(TO_INT_PATTERN.matcher(s).replaceAll("$1"), 100);
2929             final Element parent = element.getParentElement();
2930             final int absoluteValue = (parent == null)
2931                             ? value.getWindowDefaultValue() : pixelValue(parent, value, true);
2932             return (int) ((i / 100D) * absoluteValue);
2933         }
2934         if ("auto".equals(s)) {
2935             return value.getDefaultValue();
2936         }
2937         if (s.isEmpty()) {
2938             if (element instanceof HTMLCanvasElement) {
2939                 return value.getWindowDefaultValue();
2940             }
2941 
2942             // if the call was originated from a percent value we have to go up until
2943             // we can provide some kind of base value for percent calculation
2944             if (percentMode) {
2945                 final Element parent = element.getParentElement();
2946                 if (parent == null || parent instanceof HTMLHtmlElement) {
2947                     return value.getWindowDefaultValue();
2948                 }
2949                 return pixelValue(parent, value, true);
2950             }
2951 
2952             return 0;
2953         }
2954         return pixelValue(s);
2955     }
2956 
2957     /**
2958      * Converts the specified length string value into an integer number of pixels. This method does
2959      * <b>NOT</b> handle percentages correctly; use {@link #pixelValue(Element, CssValue)} if you
2960      * need percentage support).
2961      * @param value the length string value to convert to an integer number of pixels
2962      * @return the integer number of pixels corresponding to the specified length string value
2963      * @see <a href="http://htmlhelp.com/reference/css/units.html">CSS Units</a>
2964      * @see #pixelValue(Element, CssValue)
2965      */
2966     protected static int pixelValue(final String value) {
2967         int i = NumberUtils.toInt(TO_INT_PATTERN.matcher(value).replaceAll("$1"), 0);
2968         if (value.length() < 2) {
2969             return i;
2970         }
2971 
2972         if (value.endsWith("px")) {
2973             // nothing to do
2974         }
2975         else if (value.endsWith("em")) {
2976             i = i * 16;
2977         }
2978         else if (value.endsWith("%")) {
2979             i = i * 16 / 100;
2980         }
2981         else if (value.endsWith("ex")) {
2982             i = i * 10;
2983         }
2984         else if (value.endsWith("in")) {
2985             i = i * 150;
2986         }
2987         else if (value.endsWith("cm")) {
2988             i = i * 50;
2989         }
2990         else if (value.endsWith("mm")) {
2991             i = i * 5;
2992         }
2993         else if (value.endsWith("pt")) {
2994             i = i * 2;
2995         }
2996         else if (value.endsWith("pc")) {
2997             i = i * 24;
2998         }
2999         return i;
3000     }
3001 
3002     /**
3003      * Encapsulates the retrieval of a style attribute, given a DOM element from which to retrieve it.
3004      */
3005     protected abstract static class CssValue {
3006         private final int defaultValue_;
3007         private final int windowDefaultValue_;
3008 
3009         /**
3010          * C'tor.
3011          * @param defaultValue the default value
3012          * @param windowDefaultValue the default value for the window
3013          */
3014         public CssValue(final int defaultValue, final int windowDefaultValue) {
3015             defaultValue_ = defaultValue;
3016             windowDefaultValue_ = windowDefaultValue;
3017         }
3018 
3019         /**
3020          * Gets the default value.
3021          * @return the default value
3022          */
3023         public int getDefaultValue() {
3024             return defaultValue_;
3025         }
3026 
3027         /**
3028          * Gets the default size for the window.
3029          * @return the default value for the window
3030          */
3031         public int getWindowDefaultValue() {
3032             return windowDefaultValue_;
3033         }
3034 
3035         /**
3036          * Returns the CSS attribute value for the specified element.
3037          * @param element the element for which the CSS attribute value is to be retrieved
3038          * @return the CSS attribute value for the specified element
3039          */
3040         public final String get(final Element element) {
3041             final ComputedCSSStyleDeclaration style = element.getWindow().getComputedStyle(element, null);
3042             final String value = get(style);
3043             return value;
3044         }
3045 
3046         /**
3047          * Returns the CSS attribute value from the specified computed style.
3048          * @param style the computed style from which to retrieve the CSS attribute value
3049          * @return the CSS attribute value from the specified computed style
3050          */
3051         public abstract String get(ComputedCSSStyleDeclaration style);
3052     }
3053 
3054     /**
3055      * {@inheritDoc}
3056      */
3057     @Override
3058     public String toString() {
3059         if (jsElement_ == null) {
3060             return "CSSStyleDeclaration for 'null'"; // for instance on prototype
3061         }
3062         final String style = jsElement_.getDomNodeOrDie().getAttribute("style");
3063         return "CSSStyleDeclaration for '" + style + "'";
3064     }
3065 
3066     /**
3067      * Sets the style attribute which should be treated as an integer in pixels.
3068      * @param name the attribute name
3069      * @param value the attribute value
3070      * @param important important value
3071      * @param auto true if auto is supported
3072      * @param thinMedThick thin, medium, thick are supported
3073      * @param unitRequired unit is required
3074      * @param perecent true if percent is supported
3075      * @param undefinedAsEmpty true if undefined should be handled like an empty string
3076      */
3077     private void setStyleLengthAttribute(final String name, final Object value, final String important,
3078                 final boolean auto, final boolean percent, final boolean thinMedThick, final boolean unitRequired,
3079                 final boolean undefinedAsEmpty) {
3080 
3081         if (ScriptRuntime.NaNobj == value) {
3082             return;
3083         }
3084 
3085         final double doubleValue;
3086         String unit = "px";
3087         if (value instanceof Number) {
3088             if (unitRequired) {
3089                 return;
3090             }
3091             doubleValue = ((Number) value).doubleValue();
3092         }
3093         else {
3094             String valueString = Context.toString(value);
3095             if (undefinedAsEmpty && Undefined.instance == value) {
3096                 valueString = "";
3097             }
3098             else if (null == value) {
3099                 valueString = "";
3100             }
3101 
3102             if (StringUtils.isEmpty(valueString)) {
3103                 setStyleAttribute(name, valueString, important);
3104                 return;
3105             }
3106 
3107             if ((auto && "auto".equals(valueString))
3108                     || "initial".equals(valueString) && getBrowserVersion().hasFeature(CSS_LENGTH_INITIAL)
3109                     || "inherit".equals(valueString)) {
3110                 setStyleAttribute(name, valueString, important);
3111                 return;
3112             }
3113 
3114             if (thinMedThick && "thin".equals(valueString)
3115                     || "medium".equals(valueString)
3116                     || "thick".equals(valueString)) {
3117                 setStyleAttribute(name, valueString, important);
3118                 return;
3119             }
3120 
3121             if (percent && valueString.endsWith("%")) {
3122                 unit = valueString.substring(valueString.length() - 1);
3123                 valueString = valueString.substring(0, valueString.length() - 1);
3124             }
3125             else if (valueString.endsWith("px")
3126                 || valueString.endsWith("em")
3127                 || valueString.endsWith("ex")
3128                 || valueString.endsWith("px")
3129                 || valueString.endsWith("cm")
3130                 || valueString.endsWith("mm")
3131                 || valueString.endsWith("in")
3132                 || valueString.endsWith("pc")
3133                 || valueString.endsWith("pc")
3134                 || valueString.endsWith("ch")
3135                 || valueString.endsWith("vh")
3136                 || valueString.endsWith("vw")) {
3137                 unit = valueString.substring(valueString.length() - 2);
3138                 valueString = valueString.substring(0, valueString.length() - 2);
3139             }
3140             else if (valueString.endsWith("rem")
3141                 || valueString.endsWith("vmin")
3142                 || valueString.endsWith("vmax")) {
3143                 unit = valueString.substring(valueString.length() - 3);
3144                 valueString = valueString.substring(0, valueString.length() - 3);
3145             }
3146             else if (unitRequired) {
3147                 return;
3148             }
3149 
3150             doubleValue = Context.toNumber(valueString);
3151         }
3152 
3153         try {
3154             if (Double.isNaN(doubleValue) || Double.isInfinite(doubleValue)) {
3155                 return;
3156             }
3157 
3158             final String valueString;
3159             if (doubleValue % 1 == 0) {
3160                 valueString = Integer.toString((int) doubleValue) + unit;
3161             }
3162             else {
3163                 valueString = Double.toString(doubleValue) + unit;
3164             }
3165 
3166             setStyleAttribute(name, valueString, important);
3167         }
3168         catch (final Exception e) {
3169             //ignore
3170         }
3171     }
3172 }