View Javadoc
1   /*
2    * Copyright (c) 2002-2025 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.javascript.host.css;
16  
17  import static org.htmlunit.BrowserVersionFeatures.CSS_BACKGROUND_INITIAL;
18  import static org.htmlunit.BrowserVersionFeatures.JS_STYLE_WORD_SPACING_ACCEPTS_PERCENT;
19  import static org.htmlunit.css.CssStyleSheet.ABSOLUTE;
20  import static org.htmlunit.css.CssStyleSheet.AUTO;
21  import static org.htmlunit.css.CssStyleSheet.FIXED;
22  import static org.htmlunit.css.CssStyleSheet.INHERIT;
23  import static org.htmlunit.css.CssStyleSheet.INITIAL;
24  import static org.htmlunit.css.CssStyleSheet.RELATIVE;
25  import static org.htmlunit.css.CssStyleSheet.STATIC;
26  import static org.htmlunit.javascript.configuration.SupportedBrowser.CHROME;
27  import static org.htmlunit.javascript.configuration.SupportedBrowser.EDGE;
28  
29  import java.util.ArrayList;
30  import java.util.Arrays;
31  import java.util.Collections;
32  import java.util.HashSet;
33  import java.util.LinkedHashMap;
34  import java.util.List;
35  import java.util.Locale;
36  import java.util.Map;
37  import java.util.Set;
38  
39  import org.apache.commons.lang3.ArrayUtils;
40  import org.apache.commons.lang3.StringUtils;
41  import org.htmlunit.BrowserVersion;
42  import org.htmlunit.corejs.javascript.Scriptable;
43  import org.htmlunit.corejs.javascript.ScriptableObject;
44  import org.htmlunit.css.AbstractCssStyleDeclaration;
45  import org.htmlunit.css.StyleAttributes;
46  import org.htmlunit.css.StyleAttributes.Definition;
47  import org.htmlunit.css.StyleElement;
48  import org.htmlunit.css.WrappedCssStyleDeclaration;
49  import org.htmlunit.cssparser.dom.AbstractCSSRuleImpl;
50  import org.htmlunit.javascript.HtmlUnitScriptable;
51  import org.htmlunit.javascript.JavaScriptEngine;
52  import org.htmlunit.javascript.configuration.JsxClass;
53  import org.htmlunit.javascript.configuration.JsxConstructor;
54  import org.htmlunit.javascript.configuration.JsxFunction;
55  import org.htmlunit.javascript.configuration.JsxGetter;
56  import org.htmlunit.javascript.configuration.JsxSetter;
57  import org.htmlunit.javascript.configuration.JsxSymbol;
58  import org.htmlunit.javascript.host.Element;
59  
60  /**
61   * A JavaScript object for {@code CSSStyleDeclaration}.
62   *
63   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
64   * @author <a href="mailto:cse@dynabean.de">Christian Sell</a>
65   * @author Daniel Gredler
66   * @author Chris Erskine
67   * @author Ahmed Ashour
68   * @author Rodney Gitzel
69   * @author Sudhan Moghe
70   * @author Ronald Brill
71   * @author Frank Danek
72   * @author Dennis Duysak
73   * @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration">MDN doc</a>
74   */
75  @JsxClass
76  public class CSSStyleDeclaration extends HtmlUnitScriptable {
77  
78      private static final Set<String> LENGTH_PROPERTIES_FFFF = new HashSet<>(Arrays.asList(
79              Definition.BORDER_TOP_WIDTH.getAttributeName(),
80              Definition.BORDER_LEFT_WIDTH.getAttributeName(),
81              Definition.BORDER_BOTTOM_WIDTH.getAttributeName(),
82              Definition.BORDER_RIGHT_WIDTH.getAttributeName(),
83              Definition.LETTER_SPACING.getAttributeName()));
84  
85      private static final Set<String> LENGTH_PROPERTIES_TTFF = new HashSet<>(Arrays.asList(
86              Definition.HEIGHT.getAttributeName(),
87              Definition.WIDTH.getAttributeName(),
88              Definition.TOP.getAttributeName(),
89              Definition.LEFT.getAttributeName(),
90              Definition.BOTTOM.getAttributeName(),
91              Definition.RIGHT.getAttributeName(),
92              Definition.MARGIN_TOP.getAttributeName(),
93              Definition.MARGIN_LEFT.getAttributeName(),
94              Definition.MARGIN_BOTTOM.getAttributeName(),
95              Definition.MARGIN_RIGHT.getAttributeName(),
96              Definition.MIN_HEIGHT.getAttributeName(),
97              Definition.MIN_WIDTH.getAttributeName()
98              ));
99  
100     private static final Set<String> LENGTH_PROPERTIES_FTFF = new HashSet<>(Arrays.asList(
101             Definition.FONT_SIZE.getAttributeName(),
102             Definition.TEXT_INDENT.getAttributeName(),
103             Definition.PADDING_TOP.getAttributeName(),
104             Definition.PADDING_LEFT.getAttributeName(),
105             Definition.PADDING_BOTTOM.getAttributeName(),
106             Definition.PADDING_RIGHT.getAttributeName(),
107             Definition.MAX_HEIGHT.getAttributeName(),
108             Definition.MAX_WIDTH.getAttributeName()
109             ));
110 
111     private static final String[] THIN_MED_THICK = {"thin", "medium", "thick"};
112     private static final String[] ALIGN_KEYWORDS =
113         {"baseline", "sub", "super", "text-top", "text-bottom", "middle", "top", "bottom",
114          "inherit", "initial", "revert", "unset"};
115     private static final String[] FONT_SIZES =
116         {"xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large", "xxx-large",
117          "smaller", "larger"};
118 
119     // private static final Log LOG = LogFactory.getLog(CSSStyleDeclaration.class);
120 
121     /** The wrapped CSSStyleDeclaration */
122     private AbstractCssStyleDeclaration styleDeclaration_;
123 
124     /**
125      * Creates an instance.
126      */
127     public CSSStyleDeclaration() {
128         super();
129     }
130 
131     /**
132      * JavaScript constructor.
133      *
134      * @param type the event type
135      * @param details the event details (optional)
136      */
137     @JsxConstructor
138     public void jsConstructor(final String type, final ScriptableObject details) {
139         throw JavaScriptEngine.typeError("CSSStyleDeclaration ctor is not available");
140     }
141 
142     /**
143      * Creates an instance and sets its parent scope to the one of the provided element.
144      * @param element the element to which this style is bound
145      * @param styleDeclaration the style declaration to be based on
146      */
147     public CSSStyleDeclaration(final Element element, final AbstractCssStyleDeclaration styleDeclaration) {
148         super();
149         setParentScope(element.getParentScope());
150         setPrototype(getPrototype(getClass()));
151 
152         setDomNode(element.getDomNodeOrNull(), false);
153 
154         if (styleDeclaration == null) {
155             throw new IllegalStateException("styleDeclaration can't be null");
156         }
157         styleDeclaration_ = styleDeclaration;
158     }
159 
160     /**
161      * Creates an instance which wraps the specified style declaration.
162      * @param parentStyleSheet the parent {@link CSSStyleSheet} to use
163      * @param styleDeclaration the style declaration to wrap
164      */
165     CSSStyleDeclaration(final CSSStyleSheet parentStyleSheet, final WrappedCssStyleDeclaration styleDeclaration) {
166         super();
167         setParentScope(parentStyleSheet);
168         setPrototype(getPrototype(getClass()));
169 
170         if (styleDeclaration == null) {
171             throw new IllegalStateException("styleDeclaration can't be null");
172         }
173         styleDeclaration_ = styleDeclaration;
174     }
175 
176     protected AbstractCssStyleDeclaration getCssStyleDeclaration() {
177         return styleDeclaration_;
178     }
179 
180     /**
181      * Returns the priority of the named style attribute, or an empty string if it is not found.
182      *
183      * @param name the name of the style attribute whose value is to be retrieved
184      * @return the named style attribute value, or an empty string if it is not found
185      */
186     protected String getStylePriority(final String name) {
187         return styleDeclaration_.getStylePriority(name);
188     }
189 
190     /**
191      * Sets the specified style attribute.
192      * @param name the attribute name (camel-cased)
193      * @param newValue the attribute value
194      */
195     protected void setStyleAttribute(final String name, final String newValue) {
196         setStyleAttribute(name, newValue, "");
197     }
198 
199     /**
200      * Sets the specified style attribute.
201      * @param name the attribute name (camel-cased)
202      * @param newValue the attribute value
203      * @param important important value
204      */
205     protected void setStyleAttribute(final String name, String newValue, final String important) {
206         if (null == newValue || "null".equals(newValue)) {
207             newValue = "";
208         }
209 
210         styleDeclaration_.setStyleAttribute(name, newValue, important);
211     }
212 
213     /**
214      * Removes the specified style attribute, returning the value of the removed attribute.
215      * @param name the attribute name (delimiter-separated, not camel-cased)
216      */
217     private String removeStyleAttribute(final String name) {
218         if (styleDeclaration_ == null) {
219             return null;
220         }
221         return styleDeclaration_.removeStyleAttribute(name);
222     }
223 
224     /**
225      * Returns a sorted map containing style elements, keyed on style element name. We use a
226      * {@link LinkedHashMap} map so that results are deterministic and are thus testable.
227      *
228      * @return a sorted map containing style elements, keyed on style element name
229      */
230     private Map<String, StyleElement> getStyleMap() {
231         if (styleDeclaration_ == null) {
232             return Collections.emptyMap();
233         }
234         return styleDeclaration_.getStyleMap();
235     }
236 
237     /**
238      * Gets the {@code backgroundAttachment} style attribute.
239      * @return the style attribute
240      */
241     @JsxGetter
242     public String getBackgroundAttachment() {
243         if (styleDeclaration_ == null) {
244             return null; // prototype
245         }
246         return styleDeclaration_.getBackgroundAttachment();
247     }
248 
249     /**
250      * Sets the {@code backgroundAttachment} style attribute.
251      * @param backgroundAttachment the new attribute
252      */
253     @JsxSetter
254     public void setBackgroundAttachment(final String backgroundAttachment) {
255         setStyleAttribute(Definition.BACKGROUND_ATTACHMENT.getAttributeName(), backgroundAttachment);
256     }
257 
258     /**
259      * Gets the {@code backgroundColor} style attribute.
260      * @return the style attribute
261      */
262     @JsxGetter
263     public String getBackgroundColor() {
264         if (styleDeclaration_ == null) {
265             return null; // prototype
266         }
267         return styleDeclaration_.getBackgroundColor();
268     }
269 
270     /**
271      * Sets the {@code backgroundColor} style attribute.
272      * @param backgroundColor the new attribute
273      */
274     @JsxSetter
275     public void setBackgroundColor(final String backgroundColor) {
276         setStyleAttribute(Definition.BACKGROUND_COLOR.getAttributeName(), backgroundColor);
277     }
278 
279     /**
280      * Gets the {@code backgroundImage} style attribute.
281      * @return the style attribute
282      */
283     @JsxGetter
284     public String getBackgroundImage() {
285         if (styleDeclaration_ == null) {
286             return null; // prototype
287         }
288         return styleDeclaration_.getBackgroundImage();
289     }
290 
291     /**
292      * Sets the {@code backgroundImage} style attribute.
293      * @param backgroundImage the new attribute
294      */
295     @JsxSetter
296     public void setBackgroundImage(final String backgroundImage) {
297         setStyleAttribute(Definition.BACKGROUND_IMAGE.getAttributeName(), backgroundImage);
298     }
299 
300     /**
301      * Gets the {@code backgroundPosition} style attribute.
302      * @return the style attribute
303      */
304     @JsxGetter
305     public String getBackgroundPosition() {
306         if (styleDeclaration_ == null) {
307             return null; // prototype
308         }
309         return styleDeclaration_.getBackgroundPosition();
310     }
311 
312     /**
313      * Sets the {@code backgroundPosition} style attribute.
314      * @param backgroundPosition the new attribute
315      */
316     @JsxSetter
317     public void setBackgroundPosition(final String backgroundPosition) {
318         setStyleAttribute(Definition.BACKGROUND_POSITION.getAttributeName(), backgroundPosition);
319     }
320 
321     /**
322      * Gets the {@code backgroundRepeat} style attribute.
323      * @return the style attribute
324      */
325     @JsxGetter
326     public String getBackgroundRepeat() {
327         if (styleDeclaration_ == null) {
328             return null; // prototype
329         }
330         return styleDeclaration_.getBackgroundRepeat();
331     }
332 
333     /**
334      * Sets the {@code backgroundRepeat} style attribute.
335      * @param backgroundRepeat the new attribute
336      */
337     @JsxSetter
338     public void setBackgroundRepeat(final String backgroundRepeat) {
339         setStyleAttribute(Definition.BACKGROUND_REPEAT.getAttributeName(), backgroundRepeat);
340     }
341 
342     /**
343      * Gets the {@code borderBottomColor} style attribute.
344      * @return the style attribute
345      */
346     @JsxGetter
347     public String getBorderBottomColor() {
348         if (styleDeclaration_ == null) {
349             return null; // prototype
350         }
351         return styleDeclaration_.getBorderBottomColor();
352     }
353 
354     /**
355      * Sets the {@code borderBottomColor} style attribute.
356      * @param borderBottomColor the new attribute
357      */
358     @JsxSetter
359     public void setBorderBottomColor(final String borderBottomColor) {
360         setStyleAttribute(Definition.BORDER_BOTTOM_COLOR.getAttributeName(), borderBottomColor);
361     }
362 
363     /**
364      * Gets the {@code borderBottomStyle} style attribute.
365      * @return the style attribute
366      */
367     @JsxGetter
368     public String getBorderBottomStyle() {
369         if (styleDeclaration_ == null) {
370             return null; // prototype
371         }
372         return styleDeclaration_.getBorderBottomStyle();
373     }
374 
375     /**
376      * Sets the {@code borderBottomStyle} style attribute.
377      * @param borderBottomStyle the new attribute
378      */
379     @JsxSetter
380     public void setBorderBottomStyle(final String borderBottomStyle) {
381         setStyleAttribute(Definition.BORDER_BOTTOM_STYLE.getAttributeName(), borderBottomStyle);
382     }
383 
384     /**
385      * Gets the {@code borderBottomWidth} style attribute.
386      * @return the style attribute
387      */
388     @JsxGetter
389     public String getBorderBottomWidth() {
390         if (styleDeclaration_ == null) {
391             return null; // prototype
392         }
393         return styleDeclaration_.getBorderBottomWidth();
394     }
395 
396     /**
397      * Sets the {@code borderBottomWidth} style attribute.
398      * @param borderBottomWidth the new attribute
399      */
400     @JsxSetter
401     public void setBorderBottomWidth(final Object borderBottomWidth) {
402         setStyleLengthAttribute(Definition.BORDER_BOTTOM_WIDTH.getAttributeName(), borderBottomWidth, "",
403                 false, false, false, null);
404     }
405 
406     /**
407      * Gets the {@code borderLeftColor} style attribute.
408      * @return the style attribute
409      */
410     @JsxGetter
411     public String getBorderLeftColor() {
412         if (styleDeclaration_ == null) {
413             return null; // prototype
414         }
415         return styleDeclaration_.getBorderLeftColor();
416     }
417 
418     /**
419      * Sets the {@code borderLeftColor} style attribute.
420      * @param borderLeftColor the new attribute
421      */
422     @JsxSetter
423     public void setBorderLeftColor(final String borderLeftColor) {
424         setStyleAttribute(Definition.BORDER_LEFT_COLOR.getAttributeName(), borderLeftColor);
425     }
426 
427     /**
428      * Gets the {@code borderLeftStyle} style attribute.
429      * @return the style attribute
430      */
431     @JsxGetter
432     public String getBorderLeftStyle() {
433         if (styleDeclaration_ == null) {
434             return null; // prototype
435         }
436         return styleDeclaration_.getBorderLeftStyle();
437     }
438 
439     /**
440      * Sets the {@code borderLeftStyle} style attribute.
441      * @param borderLeftStyle the new attribute
442      */
443     @JsxSetter
444     public void setBorderLeftStyle(final String borderLeftStyle) {
445         setStyleAttribute(Definition.BORDER_LEFT_STYLE.getAttributeName(), borderLeftStyle);
446     }
447 
448     /**
449      * Gets the {@code borderLeftWidth} style attribute.
450      * @return the style attribute
451      */
452     @JsxGetter
453     public String getBorderLeftWidth() {
454         if (styleDeclaration_ == null) {
455             return null; // prototype
456         }
457         return styleDeclaration_.getBorderLeftWidth();
458     }
459 
460     /**
461      * Sets the {@code borderLeftWidth} style attribute.
462      * @param borderLeftWidth the new attribute
463      */
464     @JsxSetter
465     public void setBorderLeftWidth(final Object borderLeftWidth) {
466         setStyleLengthAttribute(Definition.BORDER_LEFT_WIDTH.getAttributeName(), borderLeftWidth, "",
467                 false, false, false, null);
468     }
469 
470     /**
471      * Gets the {@code borderRightColor} style attribute.
472      * @return the style attribute
473      */
474     @JsxGetter
475     public String getBorderRightColor() {
476         if (styleDeclaration_ == null) {
477             return null; // prototype
478         }
479         return styleDeclaration_.getBorderRightColor();
480     }
481 
482     /**
483      * Sets the {@code borderRightColor} style attribute.
484      * @param borderRightColor the new attribute
485      */
486     @JsxSetter
487     public void setBorderRightColor(final String borderRightColor) {
488         setStyleAttribute(Definition.BORDER_RIGHT_COLOR.getAttributeName(), borderRightColor);
489     }
490 
491     /**
492      * Gets the {@code borderRightStyle} style attribute.
493      * @return the style attribute
494      */
495     @JsxGetter
496     public String getBorderRightStyle() {
497         if (styleDeclaration_ == null) {
498             return null; // prototype
499         }
500         return styleDeclaration_.getBorderRightStyle();
501     }
502 
503     /**
504      * Sets the {@code borderRightStyle} style attribute.
505      * @param borderRightStyle the new attribute
506      */
507     @JsxSetter
508     public void setBorderRightStyle(final String borderRightStyle) {
509         setStyleAttribute(Definition.BORDER_RIGHT_STYLE.getAttributeName(), borderRightStyle);
510     }
511 
512     /**
513      * Gets the {@code borderRightWidth} style attribute.
514      * @return the style attribute
515      */
516     @JsxGetter
517     public String getBorderRightWidth() {
518         if (styleDeclaration_ == null) {
519             return null; // prototype
520         }
521         return styleDeclaration_.getBorderRightWidth();
522     }
523 
524     /**
525      * Sets the {@code borderRightWidth} style attribute.
526      * @param borderRightWidth the new attribute
527      */
528     @JsxSetter
529     public void setBorderRightWidth(final Object borderRightWidth) {
530         setStyleLengthAttribute(Definition.BORDER_RIGHT_WIDTH.getAttributeName(), borderRightWidth, "",
531                 false, false, false, null);
532     }
533 
534     /**
535      * Gets the {@code borderTop} style attribute.
536      * @return the style attribute
537      */
538     @JsxGetter
539     public String getBorderTop() {
540         if (styleDeclaration_ == null) {
541             return null; // prototype
542         }
543         return styleDeclaration_.getBorderTop();
544     }
545 
546     /**
547      * Sets the {@code borderTop} style attribute.
548      * @param borderTop the new attribute
549      */
550     @JsxSetter
551     public void setBorderTop(final String borderTop) {
552         setStyleAttribute(Definition.BORDER_TOP.getAttributeName(), borderTop);
553     }
554 
555     /**
556      * Gets the {@code borderTopColor} style attribute.
557      * @return the style attribute
558      */
559     @JsxGetter
560     public String getBorderTopColor() {
561         if (styleDeclaration_ == null) {
562             return null; // prototype
563         }
564         return styleDeclaration_.getBorderTopColor();
565     }
566 
567     /**
568      * Sets the {@code borderTopColor} style attribute.
569      * @param borderTopColor the new attribute
570      */
571     @JsxSetter
572     public void setBorderTopColor(final String borderTopColor) {
573         setStyleAttribute(Definition.BORDER_TOP_COLOR.getAttributeName(), borderTopColor);
574     }
575 
576     /**
577      * Gets the {@code borderTopStyle} style attribute.
578      * @return the style attribute
579      */
580     @JsxGetter
581     public String getBorderTopStyle() {
582         if (styleDeclaration_ == null) {
583             return null; // prototype
584         }
585         return styleDeclaration_.getBorderTopStyle();
586     }
587 
588     /**
589      * Sets the {@code borderTopStyle} style attribute.
590      * @param borderTopStyle the new attribute
591      */
592     @JsxSetter
593     public void setBorderTopStyle(final String borderTopStyle) {
594         setStyleAttribute(Definition.BORDER_TOP_STYLE.getAttributeName(), borderTopStyle);
595     }
596 
597     /**
598      * Gets the {@code borderTopWidth} style attribute.
599      * @return the style attribute
600      */
601     @JsxGetter
602     public String getBorderTopWidth() {
603         if (styleDeclaration_ == null) {
604             return null; // prototype
605         }
606         return styleDeclaration_.getBorderTopWidth();
607     }
608 
609     /**
610      * Sets the {@code borderTopWidth} style attribute.
611      * @param borderTopWidth the new attribute
612      */
613     @JsxSetter
614     public void setBorderTopWidth(final Object borderTopWidth) {
615         setStyleLengthAttribute(Definition.BORDER_TOP_WIDTH.getAttributeName(), borderTopWidth, "",
616                 false, false, false, null);
617     }
618 
619     /**
620      * Gets the {@code bottom} style attribute.
621      * @return the style attribute
622      */
623     @JsxGetter
624     public String getBottom() {
625         if (styleDeclaration_ == null) {
626             return null; // prototype
627         }
628         return styleDeclaration_.getBottom();
629     }
630 
631     /**
632      * Sets the {@code bottom} style attribute.
633      * @param bottom the new attribute
634      */
635     @JsxSetter
636     public void setBottom(final Object bottom) {
637         setStyleLengthAttribute(Definition.BOTTOM.getAttributeName(), bottom, "", true, true, false, null);
638     }
639 
640     /**
641      * Gets the {@code color} style attribute.
642      * @return the style attribute
643      */
644     @JsxGetter
645     public String getColor() {
646         if (styleDeclaration_ == null) {
647             return null; // prototype
648         }
649         return styleDeclaration_.getColor();
650     }
651 
652     /**
653      * Sets the {@code color} style attribute.
654      * @param color the new attribute
655      */
656     @JsxSetter
657     public void setColor(final String color) {
658         setStyleAttribute(Definition.COLOR.getAttributeName(), color);
659     }
660 
661     /**
662      * Gets the {@code cssFloat} style attribute.
663      * @return the style attribute
664      */
665     @JsxGetter
666     public String getCssFloat() {
667         if (styleDeclaration_ == null) {
668             return null; // prototype
669         }
670         return styleDeclaration_.getCssFloat();
671     }
672 
673     /**
674      * Sets the {@code cssFloat} style attribute.
675      * @param value the new attribute
676      */
677     @JsxSetter
678     public void setCssFloat(final String value) {
679         setStyleAttribute(Definition.FLOAT.getAttributeName(), value);
680     }
681 
682     /**
683      * Returns the actual text of the style.
684      * @return the actual text of the style
685      */
686     @JsxGetter
687     public String getCssText() {
688         if (styleDeclaration_ == null) {
689             return null; // prototype
690         }
691         return styleDeclaration_.getCssText();
692     }
693 
694     /**
695      * Sets the actual text of the style.
696      * @param value the new text
697      */
698     @JsxSetter
699     public void setCssText(final String value) {
700         String fixedValue = value;
701         if (fixedValue == null || "null".equals(fixedValue)) {
702             fixedValue = "";
703         }
704 
705         styleDeclaration_.setCssText(fixedValue);
706     }
707 
708     /**
709      * Gets the {@code display} style attribute.
710      * @return the style attribute
711      */
712     @JsxGetter
713     public String getDisplay() {
714         if (styleDeclaration_ == null) {
715             return null; // prototype
716         }
717         return styleDeclaration_.getDisplay();
718     }
719 
720     /**
721      * Sets the {@code display} style attribute.
722      * @param display the new attribute
723      */
724     @JsxSetter
725     public void setDisplay(final String display) {
726         setStyleAttribute(Definition.DISPLAY.getAttributeName(), display);
727     }
728 
729     /**
730      * Gets the {@code fontSize} style attribute.
731      * @return the style attribute
732      */
733     @JsxGetter
734     public String getFontSize() {
735         if (styleDeclaration_ == null) {
736             return null; // prototype
737         }
738         return styleDeclaration_.getFontSize();
739     }
740 
741     /**
742      * Sets the {@code fontSize} style attribute.
743      * @param fontSize the new attribute
744      */
745     @JsxSetter
746     public void setFontSize(final Object fontSize) {
747         setStyleLengthAttribute(Definition.FONT_SIZE.getAttributeName(), fontSize, "", false, true, false, FONT_SIZES);
748         updateFont(getFont(), false);
749     }
750 
751     /**
752      * Gets the {@code lineHeight} style attribute.
753      * @return the style attribute
754      */
755     @JsxGetter
756     public String getLineHeight() {
757         if (styleDeclaration_ == null) {
758             return null; // prototype
759         }
760         return styleDeclaration_.getLineHeight();
761     }
762 
763     /**
764      * Sets the {@code lineHeight} style attribute.
765      * @param lineHeight the new attribute
766      */
767     @JsxSetter
768     public void setLineHeight(final String lineHeight) {
769         setStyleAttribute(Definition.LINE_HEIGHT.getAttributeName(), lineHeight);
770         updateFont(getFont(), false);
771     }
772 
773     /**
774      * Gets the {@code fontFamily} style attribute.
775      * @return the style attribute
776      */
777     @JsxGetter
778     public String getFontFamily() {
779         if (styleDeclaration_ == null) {
780             return null; // prototype
781         }
782         return styleDeclaration_.getFontFamily();
783     }
784 
785     /**
786      * Sets the {@code fontFamily} style attribute.
787      * @param fontFamily the new attribute
788      */
789     @JsxSetter
790     public void setFontFamily(final String fontFamily) {
791         setStyleAttribute(Definition.FONT_FAMILY.getAttributeName(), fontFamily);
792         updateFont(getFont(), false);
793     }
794 
795     private void updateFont(final String font, final boolean force) {
796         final BrowserVersion browserVersion = getBrowserVersion();
797         final String[] details = ComputedFont.getDetails(font);
798         if (details != null || force) {
799             final StringBuilder newFont = new StringBuilder();
800             newFont.append(getFontSize());
801             String lineHeight = getLineHeight();
802             final String defaultLineHeight = Definition.LINE_HEIGHT.getDefaultComputedValue(browserVersion);
803             if (lineHeight.isEmpty()) {
804                 lineHeight = defaultLineHeight;
805             }
806 
807             if (!lineHeight.equals(defaultLineHeight)) {
808                 newFont.append('/');
809                 if (lineHeight.equals(defaultLineHeight)) {
810                     newFont.append(Definition.LINE_HEIGHT.getDefaultComputedValue(browserVersion));
811                 }
812                 else {
813                     newFont.append(lineHeight);
814                 }
815             }
816 
817             newFont.append(' ').append(getFontFamily());
818             setStyleAttribute(Definition.FONT.getAttributeName(), newFont.toString());
819         }
820     }
821 
822     /**
823      * Gets the {@code font} style attribute.
824      * @return the style attribute
825      */
826     @JsxGetter
827     public String getFont() {
828         if (styleDeclaration_ == null) {
829             return null; // prototype
830         }
831         return styleDeclaration_.getFont();
832     }
833 
834     /**
835      * Sets the {@code font} style attribute.
836      * @param font the new attribute
837      */
838     @JsxSetter
839     public void setFont(final String font) {
840         final String[] details = ComputedFont.getDetails(font);
841         if (details != null) {
842             setStyleAttribute(Definition.FONT_FAMILY.getAttributeName(), details[ComputedFont.FONT_FAMILY_INDEX]);
843             final String fontSize = details[ComputedFont.FONT_SIZE_INDEX];
844             if (details[ComputedFont.LINE_HEIGHT_INDEX] != null) {
845                 setStyleAttribute(Definition.LINE_HEIGHT.getAttributeName(), details[ComputedFont.LINE_HEIGHT_INDEX]);
846             }
847             setStyleAttribute(Definition.FONT_SIZE.getAttributeName(), fontSize);
848             updateFont(font, true);
849         }
850     }
851 
852     /**
853      * Gets the {@code height} style attribute.
854      * @return the style attribute
855      */
856     @JsxGetter
857     public String getHeight() {
858         if (styleDeclaration_ == null) {
859             return null; // prototype
860         }
861         return styleDeclaration_.getHeight();
862     }
863 
864     /**
865      * Sets the {@code height} style attribute.
866      * @param height the new attribute
867      */
868     @JsxSetter
869     public void setHeight(final Object height) {
870         setStyleLengthAttribute(Definition.HEIGHT.getAttributeName(), height, "", true, true, false, null);
871     }
872 
873     /**
874      * Gets the {@code left} style attribute.
875      * @return the style attribute
876      */
877     @JsxGetter
878     public String getLeft() {
879         if (styleDeclaration_ == null) {
880             return null; // prototype
881         }
882         return styleDeclaration_.getLeft();
883     }
884 
885     /**
886      * Sets the {@code left} style attribute.
887      * @param left the new attribute
888      */
889     @JsxSetter
890     public void setLeft(final Object left) {
891         setStyleLengthAttribute(Definition.LEFT.getAttributeName(), left, "", true, true, false, null);
892     }
893 
894     /**
895      * Returns the {@code length} property.
896      * @return the {@code length} property
897      */
898     @JsxGetter
899     public int getLength() {
900         if (styleDeclaration_ == null) {
901             return 0; // prototype
902         }
903         return styleDeclaration_.getLength();
904     }
905 
906     /**
907      * @param index the index
908      * @return a CSS property name from a CSSStyleDeclaration by index.
909      */
910     @JsxFunction
911     public String item(final int index) {
912         if (styleDeclaration_ == null) {
913             return null; // prototype
914         }
915 
916         return styleDeclaration_.item(index);
917     }
918 
919     /**
920      * Returns an Iterator allowing to go through all keys contained in this object.
921      * @return a NativeArrayIterator
922      */
923     @JsxSymbol(symbolName = "iterator")
924     public Scriptable values() {
925         return JavaScriptEngine.newArrayIteratorTypeValues(getParentScope(), this);
926     }
927 
928     /**
929      * Gets the {@code letterSpacing} style attribute.
930      * @return the style attribute
931      */
932     @JsxGetter
933     public String getLetterSpacing() {
934         if (styleDeclaration_ == null) {
935             return null; // prototype
936         }
937         return styleDeclaration_.getLetterSpacing();
938     }
939 
940     /**
941      * Sets the {@code letterSpacing} style attribute.
942      * @param letterSpacing the new attribute
943      */
944     @JsxSetter
945     public void setLetterSpacing(final Object letterSpacing) {
946         setStyleLengthAttribute(Definition.LETTER_SPACING.getAttributeName(), letterSpacing, "",
947                 false, false, false, null);
948     }
949 
950     /**
951      * Gets the {@code margin} style attribute.
952      * @return the style attribute
953      */
954     @JsxGetter
955     public String getMargin() {
956         if (styleDeclaration_ == null) {
957             return null; // prototype
958         }
959         return styleDeclaration_.getMargin();
960     }
961 
962     /**
963      * Sets the {@code margin} style attribute.
964      * @param margin the new attribute
965      */
966     @JsxSetter
967     public void setMargin(final String margin) {
968         setStyleAttribute(Definition.MARGIN.getAttributeName(), margin);
969     }
970 
971     /**
972      * Gets the {@code marginBottom} style attribute.
973      * @return the style attribute
974      */
975     @JsxGetter
976     public String getMarginBottom() {
977         if (styleDeclaration_ == null) {
978             return null; // prototype
979         }
980         return styleDeclaration_.getMarginBottom();
981     }
982 
983     /**
984      * Sets the {@code marginBottom} style attribute.
985      * @param marginBottom the new attribute
986      */
987     @JsxSetter
988     public void setMarginBottom(final Object marginBottom) {
989         setStyleLengthAttribute(Definition.MARGIN_BOTTOM.getAttributeName(), marginBottom, "", true, true, false, null);
990     }
991 
992     /**
993      * Gets the {@code marginLeft} style attribute.
994      * @return the style attribute
995      */
996     @JsxGetter
997     public String getMarginLeft() {
998         if (styleDeclaration_ == null) {
999             return null; // prototype
1000         }
1001         return styleDeclaration_.getMarginLeft();
1002     }
1003 
1004     /**
1005      * Sets the {@code marginLeft} style attribute.
1006      * @param marginLeft the new attribute
1007      */
1008     @JsxSetter
1009     public void setMarginLeft(final Object marginLeft) {
1010         setStyleLengthAttribute(Definition.MARGIN_LEFT.getAttributeName(), marginLeft, "", true, true, false, null);
1011     }
1012 
1013     /**
1014      * Gets the {@code marginRight} style attribute.
1015      * @return the style attribute
1016      */
1017     @JsxGetter
1018     public String getMarginRight() {
1019         if (styleDeclaration_ == null) {
1020             return null; // prototype
1021         }
1022         return styleDeclaration_.getMarginRight();
1023     }
1024 
1025     /**
1026      * Sets the {@code marginRight} style attribute.
1027      * @param marginRight the new attribute
1028      */
1029     @JsxSetter
1030     public void setMarginRight(final Object marginRight) {
1031         setStyleLengthAttribute(Definition.MARGIN_RIGHT.getAttributeName(), marginRight, "", true, true, false, null);
1032     }
1033 
1034     /**
1035      * Gets the {@code marginTop} style attribute.
1036      * @return the style attribute
1037      */
1038     @JsxGetter
1039     public String getMarginTop() {
1040         if (styleDeclaration_ == null) {
1041             return null; // prototype
1042         }
1043         return styleDeclaration_.getMarginTop();
1044     }
1045 
1046     /**
1047      * Sets the {@code marginTop} style attribute.
1048      * @param marginTop the new attribute
1049      */
1050     @JsxSetter
1051     public void setMarginTop(final Object marginTop) {
1052         setStyleLengthAttribute(Definition.MARGIN_TOP.getAttributeName(), marginTop, "", true, true, false, null);
1053     }
1054 
1055     /**
1056      * Gets the {@code maxHeight} style attribute.
1057      * @return the style attribute
1058      */
1059     @JsxGetter
1060     public String getMaxHeight() {
1061         if (styleDeclaration_ == null) {
1062             return null; // prototype
1063         }
1064         return styleDeclaration_.getMaxHeight();
1065     }
1066 
1067     /**
1068      * Sets the {@code maxHeight} style attribute.
1069      * @param maxHeight the new attribute
1070      */
1071     @JsxSetter
1072     public void setMaxHeight(final Object maxHeight) {
1073         setStyleLengthAttribute(Definition.MAX_HEIGHT.getAttributeName(), maxHeight, "", false, true, false, null);
1074     }
1075 
1076     /**
1077      * Gets the {@code maxWidth} style attribute.
1078      * @return the style attribute
1079      */
1080     @JsxGetter
1081     public String getMaxWidth() {
1082         if (styleDeclaration_ == null) {
1083             return null; // prototype
1084         }
1085         return styleDeclaration_.getMaxWidth();
1086     }
1087 
1088     /**
1089      * Sets the {@code maxWidth} style attribute.
1090      * @param maxWidth the new attribute
1091      */
1092     @JsxSetter
1093     public void setMaxWidth(final Object maxWidth) {
1094         setStyleLengthAttribute(Definition.MAX_WIDTH.getAttributeName(), maxWidth, "", false, true, false, null);
1095     }
1096 
1097     /**
1098      * Gets the {@code minHeight} style attribute.
1099      * @return the style attribute
1100      */
1101     @JsxGetter
1102     public String getMinHeight() {
1103         if (styleDeclaration_ == null) {
1104             return null; // prototype
1105         }
1106         return styleDeclaration_.getMinHeight();
1107     }
1108 
1109     /**
1110      * Sets the {@code minHeight} style attribute.
1111      * @param minHeight the new attribute
1112      */
1113     @JsxSetter
1114     public void setMinHeight(final Object minHeight) {
1115         setStyleLengthAttribute(Definition.MIN_HEIGHT.getAttributeName(), minHeight, "", true, true, false, null);
1116     }
1117 
1118     /**
1119      * Gets the {@code minWidth} style attribute.
1120      * @return the style attribute
1121      */
1122     @JsxGetter
1123     public String getMinWidth() {
1124         if (styleDeclaration_ == null) {
1125             return null; // prototype
1126         }
1127         return styleDeclaration_.getMinWidth();
1128     }
1129 
1130     /**
1131      * Sets the {@code minWidth} style attribute.
1132      * @param minWidth the new attribute
1133      */
1134     @JsxSetter
1135     public void setMinWidth(final Object minWidth) {
1136         setStyleLengthAttribute(Definition.MIN_WIDTH.getAttributeName(), minWidth, "", true, true, false, null);
1137     }
1138 
1139     /**
1140      * {@inheritDoc}
1141      */
1142     @Override
1143     public Object get(final String name, final Scriptable start) {
1144         if (this != start) {
1145             return super.get(name, start);
1146         }
1147 
1148         Scriptable prototype = getPrototype();
1149         while (prototype != null) {
1150             Object value = prototype.get(name, start);
1151             if (value != Scriptable.NOT_FOUND) {
1152                 return value;
1153             }
1154 
1155             final String camel = org.htmlunit.util.StringUtils.cssCamelize(name);
1156             if (!name.equals(camel)) {
1157                 value = prototype.get(camel, start);
1158                 if (value != Scriptable.NOT_FOUND) {
1159                     return value;
1160                 }
1161             }
1162             prototype = prototype.getPrototype();
1163         }
1164 
1165         final Definition style = StyleAttributes.getDefinition(name, getBrowserVersion());
1166         if (style != null) {
1167             return getStyleAttribute(style);
1168         }
1169 
1170         return super.get(name, start);
1171     }
1172 
1173     @Override
1174     public Object get(final int index, final Scriptable start) {
1175         if (index < 0) {
1176             return JavaScriptEngine.UNDEFINED;
1177         }
1178 
1179         final Map<String, StyleElement> style = getStyleMap();
1180         final int size = style.size();
1181         if (index >= size) {
1182             return JavaScriptEngine.UNDEFINED;
1183         }
1184         return style.keySet().toArray(new String[0])[index];
1185     }
1186 
1187     /**
1188      * Get the value for the style attribute.
1189      * @param definition the definition
1190      * @return the value
1191      */
1192     public final String getStyleAttribute(final Definition definition) {
1193         return getStyleAttribute(definition, true);
1194     }
1195 
1196     /**
1197      * Get the value for the style attribute.
1198      * This impl ignores the default getDefaultValueIfEmpty flag, but there is a overload
1199      * in {@link ComputedCSSStyleDeclaration}.
1200      * @param definition the definition
1201      * @param getDefaultValueIfEmpty whether to get the default value if empty or not
1202      * @return the value
1203      */
1204     public String getStyleAttribute(final Definition definition, final boolean getDefaultValueIfEmpty) {
1205         if (styleDeclaration_ == null) {
1206             return ""; // prototype
1207         }
1208         return styleDeclaration_.getStyleAttribute(definition, getDefaultValueIfEmpty);
1209     }
1210 
1211     @Override
1212     public void put(final String name, final Scriptable start, final Object value) {
1213         if (this != start) {
1214             super.put(name, start, value);
1215             return;
1216         }
1217 
1218         final Scriptable prototype = getPrototype();
1219         if (prototype != null && !"constructor".equals(name)) {
1220             if (prototype.get(name, start) != Scriptable.NOT_FOUND) {
1221                 prototype.put(name, start, value);
1222                 return;
1223             }
1224             final String camel = org.htmlunit.util.StringUtils.cssCamelize(name);
1225             if (!name.equals(camel) && prototype.get(camel, start) != Scriptable.NOT_FOUND) {
1226                 prototype.put(camel, start, value);
1227                 return;
1228             }
1229         }
1230 
1231         if (getDomNodeOrNull() != null) { // check if prototype or not
1232             final Definition style = StyleAttributes.getDefinition(name, getBrowserVersion());
1233             if (style != null) {
1234                 final String stringValue = JavaScriptEngine.toString(value);
1235                 setStyleAttribute(style.getAttributeName(), stringValue);
1236                 return;
1237             }
1238         }
1239 
1240         super.put(name, start, value);
1241     }
1242 
1243     @Override
1244     public boolean has(final String name, final Scriptable start) {
1245         if (this != start) {
1246             return super.has(name, start);
1247         }
1248 
1249         final BrowserVersion browserVersion = getBrowserVersion();
1250         if (browserVersion != null) {
1251             final Definition style = StyleAttributes.getDefinition(name, getBrowserVersion());
1252             if (style != null) {
1253                 return true;
1254             }
1255         }
1256 
1257         return super.has(name, start);
1258     }
1259 
1260     @Override
1261     public Object[] getIds() {
1262         final List<Object> ids = new ArrayList<>();
1263         for (final Definition styleAttribute : StyleAttributes.getDefinitions(getBrowserVersion())) {
1264             ids.add(styleAttribute.getPropertyName());
1265         }
1266         final Object[] normalIds = super.getIds();
1267         for (final Object o : normalIds) {
1268             if (!ids.contains(o)) {
1269                 ids.add(o);
1270             }
1271         }
1272         return ids.toArray();
1273     }
1274 
1275     /**
1276      * Gets the {@code opacity} style attribute.
1277      * @return the style attribute
1278      */
1279     @JsxGetter
1280     public String getOpacity() {
1281         if (styleDeclaration_ == null) {
1282             return null; // prototype
1283         }
1284         return styleDeclaration_.getOpacity();
1285     }
1286 
1287     /**
1288      * Sets the {@code opacity} style attribute.
1289      * @param opacity the new attribute
1290      */
1291     @JsxSetter
1292     public void setOpacity(final Object opacity) {
1293         if (JavaScriptEngine.isNaN(opacity)) {
1294             return;
1295         }
1296 
1297         final double doubleValue;
1298         if (opacity instanceof Number) {
1299             doubleValue = ((Number) opacity).doubleValue();
1300         }
1301         else {
1302             String valueString = JavaScriptEngine.toString(opacity);
1303 
1304             if (valueString.isEmpty()) {
1305                 setStyleAttribute(Definition.OPACITY.getAttributeName(), valueString);
1306                 return;
1307             }
1308 
1309             valueString = valueString.trim();
1310             try {
1311                 doubleValue = Double.parseDouble(valueString);
1312             }
1313             catch (final NumberFormatException e) {
1314                 // ignore wrong value
1315                 return;
1316             }
1317         }
1318 
1319         if (Double.isNaN(doubleValue) || Double.isInfinite(doubleValue)) {
1320             return;
1321         }
1322         setStyleAttribute(Definition.OPACITY.getAttributeName(), Double.toString(doubleValue));
1323     }
1324 
1325     /**
1326      * Gets the {@code outline} style attribute.
1327      * @return the style attribute
1328      */
1329     @JsxGetter
1330     public String getOutline() {
1331         if (styleDeclaration_ == null) {
1332             return null; // prototype
1333         }
1334         return styleDeclaration_.getOutline();
1335     }
1336 
1337     /**
1338      * Sets the {@code outline} style attribute.
1339      * @param outline the new attribute
1340      */
1341     @JsxSetter
1342     public void setOutline(final String outline) {
1343         setStyleAttribute(Definition.OUTLINE.getAttributeName(), outline);
1344     }
1345 
1346     /**
1347      * Gets the {@code outlineWidth} style attribute.
1348      * @return the style attribute
1349      */
1350     @JsxGetter
1351     public String getOutlineWidth() {
1352         if (styleDeclaration_ == null) {
1353             return null; // prototype
1354         }
1355         return styleDeclaration_.getOutlineWidth();
1356     }
1357 
1358     /**
1359      * Sets the {@code outlineWidth} style attribute.
1360      * @param outlineWidth the new attribute
1361      */
1362     @JsxSetter
1363     public void setOutlineWidth(final Object outlineWidth) {
1364         setStyleLengthAttribute(Definition.OUTLINE_WIDTH.getAttributeName(), outlineWidth, "",
1365                 false, false, true, THIN_MED_THICK);
1366     }
1367 
1368     /**
1369      * Gets the {@code padding} style attribute.
1370      * @return the style attribute
1371      */
1372     @JsxGetter
1373     public String getPadding() {
1374         if (styleDeclaration_ == null) {
1375             return null; // prototype
1376         }
1377         return styleDeclaration_.getPadding();
1378     }
1379 
1380     /**
1381      * Sets the {@code padding} style attribute.
1382      * @param padding the new attribute
1383      */
1384     @JsxSetter
1385     public void setPadding(final String padding) {
1386         setStyleAttribute(Definition.PADDING.getAttributeName(), padding);
1387     }
1388 
1389     /**
1390      * Gets the {@code paddingBottom} style attribute.
1391      * @return the style attribute
1392      */
1393     @JsxGetter
1394     public String getPaddingBottom() {
1395         if (styleDeclaration_ == null) {
1396             return null; // prototype
1397         }
1398         return styleDeclaration_.getPaddingBottom();
1399     }
1400 
1401     /**
1402      * Sets the {@code paddingBottom} style attribute.
1403      * @param paddingBottom the new attribute
1404      */
1405     @JsxSetter
1406     public void setPaddingBottom(final Object paddingBottom) {
1407         setStyleLengthAttribute(Definition.PADDING_BOTTOM.getAttributeName(),
1408                 paddingBottom, "", false, true, false, null);
1409     }
1410 
1411     /**
1412      * Gets the {@code paddingLeft} style attribute.
1413      * @return the style attribute
1414      */
1415     @JsxGetter
1416     public String getPaddingLeft() {
1417         if (styleDeclaration_ == null) {
1418             return null; // prototype
1419         }
1420         return styleDeclaration_.getPaddingLeft();
1421     }
1422 
1423     /**
1424      * Sets the {@code paddingLeft} style attribute.
1425      * @param paddingLeft the new attribute
1426      */
1427     @JsxSetter
1428     public void setPaddingLeft(final Object paddingLeft) {
1429         setStyleLengthAttribute(Definition.PADDING_LEFT.getAttributeName(), paddingLeft, "", false, true, false, null);
1430     }
1431 
1432     /**
1433      * Gets the {@code paddingRight} style attribute.
1434      * @return the style attribute
1435      */
1436     @JsxGetter
1437     public String getPaddingRight() {
1438         if (styleDeclaration_ == null) {
1439             return null; // prototype
1440         }
1441         return styleDeclaration_.getPaddingRight();
1442     }
1443 
1444     /**
1445      * Sets the {@code paddingRight} style attribute.
1446      * @param paddingRight the new attribute
1447      */
1448     @JsxSetter
1449     public void setPaddingRight(final Object paddingRight) {
1450         setStyleLengthAttribute(Definition.PADDING_RIGHT.getAttributeName(),
1451                 paddingRight, "", false, true, false, null);
1452     }
1453 
1454     /**
1455      * Gets the {@code paddingTop} style attribute.
1456      * @return the style attribute
1457      */
1458     @JsxGetter
1459     public String getPaddingTop() {
1460         if (styleDeclaration_ == null) {
1461             return null; // prototype
1462         }
1463         return styleDeclaration_.getPaddingTop();
1464     }
1465 
1466     /**
1467      * Sets the {@code paddingTop} style attribute.
1468      * @param paddingTop the new attribute
1469      */
1470     @JsxSetter
1471     public void setPaddingTop(final Object paddingTop) {
1472         setStyleLengthAttribute(Definition.PADDING_TOP.getAttributeName(), paddingTop, "", false, true, false, null);
1473     }
1474 
1475     /**
1476      * Returns the CSSRule that is the parent of this style block or <code>null</code> if this CSSStyleDeclaration is
1477      * not attached to a CSSRule.
1478      * @return the CSSRule that is the parent of this style block or <code>null</code> if this CSSStyleDeclaration is
1479      *      not attached to a CSSRule
1480      */
1481     @JsxGetter
1482     public CSSRule getParentRule() {
1483         final AbstractCSSRuleImpl parentRule = styleDeclaration_.getParentRule();
1484         if (parentRule != null) {
1485             return CSSRule.create((CSSStyleSheet) getParentScope(), parentRule);
1486         }
1487         return null;
1488     }
1489 
1490     /**
1491      * Nothing.
1492      * @param parentRule ignored
1493      */
1494     @JsxSetter
1495     public void setParentRule(final CSSRule parentRule) {
1496         // nothing to do
1497     }
1498 
1499     /**
1500      * Gets the {@code right} style attribute.
1501      * @return the style attribute
1502      */
1503     @JsxGetter
1504     public String getRight() {
1505         if (styleDeclaration_ == null) {
1506             return null; // prototype
1507         }
1508         return styleDeclaration_.getRight();
1509     }
1510 
1511     /**
1512      * Sets the {@code right} style attribute.
1513      * @param right the new attribute
1514      */
1515     @JsxSetter
1516     public void setRight(final Object right) {
1517         setStyleLengthAttribute(Definition.RIGHT.getAttributeName(), right, "", true, true, false, null);
1518     }
1519 
1520     /**
1521      * Gets the {@code rubyAlign} style attribute.
1522      * @return the style attribute
1523      */
1524     @JsxGetter
1525     public String getRubyAlign() {
1526         if (styleDeclaration_ == null) {
1527             return null; // prototype
1528         }
1529         return styleDeclaration_.getRubyAlign();
1530     }
1531 
1532     /**
1533      * Sets the {@code rubyAlign} style attribute.
1534      * @param rubyAlign the new attribute
1535      */
1536     @JsxSetter
1537     public void setRubyAlign(final String rubyAlign) {
1538         setStyleAttribute(Definition.RUBY_ALIGN.getAttributeName(), rubyAlign);
1539     }
1540 
1541     /**
1542      * Gets the {@code size} style attribute.
1543      * @return the style attribute
1544      */
1545     @JsxGetter({CHROME, EDGE})
1546     public String getSize() {
1547         if (styleDeclaration_ == null) {
1548             return null; // prototype
1549         }
1550         return styleDeclaration_.getSize();
1551     }
1552 
1553     /**
1554      * Sets the {@code size} style attribute.
1555      * @param size the new attribute
1556      */
1557     @JsxSetter({CHROME, EDGE})
1558     public void setSize(final String size) {
1559         setStyleAttribute(Definition.SIZE.getAttributeName(), size);
1560     }
1561 
1562     /**
1563      * Gets the {@code textIndent} style attribute.
1564      * @return the style attribute
1565      */
1566     @JsxGetter
1567     public String getTextIndent() {
1568         if (styleDeclaration_ == null) {
1569             return null; // prototype
1570         }
1571         return styleDeclaration_.getTextIndent();
1572     }
1573 
1574     /**
1575      * Sets the {@code textIndent} style attribute.
1576      * @param textIndent the new attribute
1577      */
1578     @JsxSetter
1579     public void setTextIndent(final Object textIndent) {
1580         setStyleLengthAttribute(Definition.TEXT_INDENT.getAttributeName(), textIndent, "", false, true, false, null);
1581     }
1582 
1583     /**
1584      * Gets the {@code top} style attribute.
1585      * @return the style attribute
1586      */
1587     @JsxGetter
1588     public String getTop() {
1589         if (styleDeclaration_ == null) {
1590             return null; // prototype
1591         }
1592         return styleDeclaration_.getTop();
1593     }
1594 
1595     /**
1596      * Sets the {@code top} style attribute.
1597      * @param top the new attribute
1598      */
1599     @JsxSetter
1600     public void setTop(final Object top) {
1601         setStyleLengthAttribute(Definition.TOP.getAttributeName(), top, "", true, true, false, null);
1602     }
1603 
1604     /**
1605      * Gets the {@code verticalAlign} style attribute.
1606      * @return the style attribute
1607      */
1608     @JsxGetter
1609     public String getVerticalAlign() {
1610         if (styleDeclaration_ == null) {
1611             return null; // prototype
1612         }
1613         return styleDeclaration_.getVerticalAlign();
1614     }
1615 
1616     /**
1617      * Sets the {@code verticalAlign} style attribute.
1618      * @param verticalAlign the new attribute
1619      */
1620     @JsxSetter
1621     public void setVerticalAlign(final Object verticalAlign) {
1622         setStyleLengthAttribute(Definition.VERTICAL_ALIGN.getAttributeName(),
1623                 verticalAlign, "", false, true, false, ALIGN_KEYWORDS);
1624     }
1625 
1626     /**
1627      * Gets the {@code width} style attribute.
1628      * @return the style attribute
1629      */
1630     @JsxGetter
1631     public String getWidth() {
1632         if (styleDeclaration_ == null) {
1633             return null; // prototype
1634         }
1635         return styleDeclaration_.getWidth();
1636     }
1637 
1638     /**
1639      * Sets the {@code width} style attribute.
1640      * @param width the new attribute
1641      */
1642     @JsxSetter
1643     public void setWidth(final Object width) {
1644         setStyleLengthAttribute(Definition.WIDTH.getAttributeName(), width, "", true, true, false, null);
1645     }
1646 
1647     /**
1648      * Gets the {@code widows} style attribute.
1649      * @return the style attribute
1650      */
1651     @JsxGetter({CHROME, EDGE})
1652     public String getWidows() {
1653         if (styleDeclaration_ == null) {
1654             return null; // prototype
1655         }
1656         return styleDeclaration_.getWidows();
1657     }
1658 
1659     /**
1660      * Sets the {@code widows} style attribute.
1661      * @param widows the new attribute
1662      */
1663     @JsxSetter({CHROME, EDGE})
1664     public void setWidows(final String widows) {
1665         if (getBrowserVersion().hasFeature(CSS_BACKGROUND_INITIAL)) {
1666             try {
1667                 if (Integer.parseInt(widows) <= 0) {
1668                     return;
1669                 }
1670             }
1671             catch (final NumberFormatException e) {
1672                 return;
1673             }
1674         }
1675         setStyleAttribute(Definition.WIDOWS.getAttributeName(), widows);
1676     }
1677 
1678     /**
1679      * Gets the {@code orphans} style attribute.
1680      * @return the style attribute
1681      */
1682     @JsxGetter({CHROME, EDGE})
1683     public String getOrphans() {
1684         if (styleDeclaration_ == null) {
1685             return null; // prototype
1686         }
1687         return styleDeclaration_.getOrphans();
1688     }
1689 
1690     /**
1691      * Sets the {@code orphans} style attribute.
1692      * @param orphans the new attribute
1693      */
1694     @JsxSetter({CHROME, EDGE})
1695     public void setOrphans(final String orphans) {
1696         if (getBrowserVersion().hasFeature(CSS_BACKGROUND_INITIAL)) {
1697             try {
1698                 if (Integer.parseInt(orphans) <= 0) {
1699                     return;
1700                 }
1701             }
1702             catch (final NumberFormatException e) {
1703                 return;
1704             }
1705         }
1706         setStyleAttribute(Definition.ORPHANS.getAttributeName(), orphans);
1707     }
1708 
1709     /**
1710      * Gets the {@code position} style attribute.
1711      * @return the style attribute
1712      */
1713     @JsxGetter
1714     public String getPosition() {
1715         if (styleDeclaration_ == null) {
1716             return null; // prototype
1717         }
1718         return styleDeclaration_.getPosition();
1719     }
1720 
1721     /**
1722      * Sets the {@code position} style attribute.
1723      * @param position the new attribute
1724      */
1725     @JsxSetter
1726     public void setPosition(final String position) {
1727         if (position.isEmpty() || STATIC.equalsIgnoreCase(position) || ABSOLUTE.equalsIgnoreCase(position)
1728                 || FIXED.equalsIgnoreCase(position) || RELATIVE.equalsIgnoreCase(position)
1729                 || INITIAL.equalsIgnoreCase(position) || INHERIT.equalsIgnoreCase(position)) {
1730             setStyleAttribute(Definition.POSITION.getAttributeName(), position.toLowerCase(Locale.ROOT));
1731         }
1732     }
1733 
1734     /**
1735      * Gets the {@code wordSpacing} style attribute.
1736      * @return the style attribute
1737      */
1738     @JsxGetter
1739     public String getWordSpacing() {
1740         if (styleDeclaration_ == null) {
1741             return null; // prototype
1742         }
1743         return styleDeclaration_.getWordSpacing();
1744     }
1745 
1746     /**
1747      * Sets the {@code wordSpacing} style attribute.
1748      * @param wordSpacing the new attribute
1749      */
1750     @JsxSetter
1751     public void setWordSpacing(final Object wordSpacing) {
1752         setStyleLengthAttribute(Definition.WORD_SPACING.getAttributeName(), wordSpacing, "",
1753                 false, getBrowserVersion().hasFeature(JS_STYLE_WORD_SPACING_ACCEPTS_PERCENT), false, null);
1754     }
1755 
1756     /**
1757      * Gets the {@code zIndex} style attribute.
1758      * @return the style attribute
1759      */
1760     @JsxGetter
1761     public String getZIndex() {
1762         if (styleDeclaration_ == null) {
1763             return null; // prototype
1764         }
1765         return styleDeclaration_.getZIndex();
1766     }
1767 
1768     /**
1769      * Sets the {@code zIndex} style attribute.
1770      * @param zIndex the new attribute
1771      */
1772     @JsxSetter
1773     public void setZIndex(final Object zIndex) {
1774         // empty
1775         if (zIndex == null || StringUtils.isEmpty(zIndex.toString())) {
1776             setStyleAttribute(Definition.Z_INDEX_.getAttributeName(), "");
1777             return;
1778         }
1779         // undefined
1780         if (JavaScriptEngine.isUndefined(zIndex)) {
1781             return;
1782         }
1783 
1784         // string
1785         if (zIndex instanceof Number) {
1786             final Number number = (Number) zIndex;
1787             if (number.doubleValue() % 1 == 0) {
1788                 setStyleAttribute(Definition.Z_INDEX_.getAttributeName(), Integer.toString(number.intValue()));
1789             }
1790             return;
1791         }
1792         try {
1793             final int i = Integer.parseInt(zIndex.toString());
1794             setStyleAttribute(Definition.Z_INDEX_.getAttributeName(), Integer.toString(i));
1795         }
1796         catch (final NumberFormatException ignored) {
1797             // ignore
1798         }
1799     }
1800 
1801     /**
1802      * Gets the value of the specified property of the style.
1803      * @param name the style property name
1804      * @return empty string if nothing found
1805      */
1806     @JsxFunction
1807     public String getPropertyValue(final String name) {
1808         if (name != null && name.contains("-")) {
1809             final Object value = getProperty(this, org.htmlunit.util.StringUtils.cssCamelize(name));
1810             if (value instanceof String) {
1811                 return (String) value;
1812             }
1813         }
1814 
1815         return styleDeclaration_.getStyleAttribute(name);
1816     }
1817 
1818     /**
1819      * Gets the value of the specified property of the style.
1820      * @param name the style property name
1821      * @return empty string if nothing found
1822      */
1823     @JsxFunction
1824     public String getPropertyPriority(final String name) {
1825         return getStylePriority(name);
1826     }
1827 
1828     /**
1829      * Sets the value of the specified property.
1830      *
1831      * @param name the name of the attribute
1832      * @param value the value to assign to the attribute
1833      * @param important may be null
1834      */
1835     @JsxFunction
1836     public void setProperty(final String name, final Object value, final String important) {
1837         String imp = "";
1838         if (!StringUtils.isEmpty(important) && !"null".equals(important)) {
1839             if (!StyleElement.PRIORITY_IMPORTANT.equalsIgnoreCase(important)) {
1840                 return;
1841             }
1842             imp = StyleElement.PRIORITY_IMPORTANT;
1843         }
1844 
1845         if (LENGTH_PROPERTIES_FFFF.contains(name)) {
1846             setStyleLengthAttribute(name, value, imp, false, false, false, null);
1847         }
1848         else if (LENGTH_PROPERTIES_TTFF.contains(name)) {
1849             setStyleLengthAttribute(name, value, imp, true, true, false, null);
1850         }
1851         else if (LENGTH_PROPERTIES_FTFF.contains(name)) {
1852             setStyleLengthAttribute(name, value, imp, false, true, false, null);
1853         }
1854         else if (Definition.OUTLINE_WIDTH.getAttributeName().equals(name)) {
1855             setStyleLengthAttribute(Definition.OUTLINE_WIDTH.getAttributeName(),
1856                     value, imp, false, false, true, THIN_MED_THICK);
1857         }
1858         else if (Definition.WORD_SPACING.getAttributeName().equals(name)) {
1859             setStyleLengthAttribute(Definition.WORD_SPACING.getAttributeName(), value, imp,
1860                     false, getBrowserVersion().hasFeature(JS_STYLE_WORD_SPACING_ACCEPTS_PERCENT), false, null);
1861         }
1862         else if (Definition.VERTICAL_ALIGN.getAttributeName().equals(name)) {
1863             setStyleLengthAttribute(Definition.VERTICAL_ALIGN.getAttributeName(), value, imp, false, true, false, null);
1864         }
1865         else {
1866             setStyleAttribute(name, JavaScriptEngine.toString(value), imp);
1867         }
1868     }
1869 
1870     /**
1871      * Removes the named property.
1872      * @param name the name of the property to remove
1873      * @return the value deleted
1874      */
1875     @JsxFunction
1876     public String removeProperty(final Object name) {
1877         return removeStyleAttribute(JavaScriptEngine.toString(name));
1878     }
1879 
1880     /**
1881      * Returns if the specified token is a length.
1882      * @param token the token to check
1883      * @return whether the token is a length or not
1884      */
1885     static boolean isLength(String token) {
1886         if (token.endsWith("em") || token.endsWith("ex") || token.endsWith("px") || token.endsWith("in")
1887             || token.endsWith("cm") || token.endsWith("mm") || token.endsWith("pt") || token.endsWith("pc")
1888             || token.endsWith("%")) {
1889 
1890             if (token.endsWith("%")) {
1891                 token = token.substring(0, token.length() - 1);
1892             }
1893             else {
1894                 token = token.substring(0, token.length() - 2);
1895             }
1896             try {
1897                 Double.parseDouble(token);
1898                 return true;
1899             }
1900             catch (final NumberFormatException ignored) {
1901                 // ignore
1902             }
1903         }
1904         return false;
1905     }
1906 
1907     /**
1908      * {@inheritDoc}
1909      */
1910     @Override
1911     public String toString() {
1912         if (styleDeclaration_ == null) {
1913             return "CSSStyleDeclaration for 'null'"; // for instance on prototype
1914         }
1915 
1916         return "CSSStyleDeclaration for '" + styleDeclaration_ + "'";
1917     }
1918 
1919     /**
1920      * Sets the style attribute which should be treated as an integer in pixels.
1921      * @param name the attribute name
1922      * @param value the attribute value
1923      * @param important important value
1924      * @param auto true if auto is supported
1925      * @param percent true if percent is supported
1926      * @param unitRequired unit is required
1927      * @param validValues valid values
1928      */
1929     private void setStyleLengthAttribute(final String name, final Object value, final String important,
1930                 final boolean auto, final boolean percent, final boolean unitRequired, final String[] validValues) {
1931 
1932         if (JavaScriptEngine.isNaN(value)) {
1933             return;
1934         }
1935 
1936         final double doubleValue;
1937         String unit = "px";
1938         if (value instanceof Number) {
1939             if (unitRequired) {
1940                 return;
1941             }
1942             doubleValue = ((Number) value).doubleValue();
1943         }
1944         else {
1945             String valueString = JavaScriptEngine.toString(value);
1946             if (null == value) {
1947                 valueString = "";
1948             }
1949 
1950             if (StringUtils.isEmpty(valueString)) {
1951                 setStyleAttribute(name, valueString, important);
1952                 return;
1953             }
1954 
1955             if ((auto && AUTO.equals(valueString))
1956                     || INITIAL.equals(valueString)
1957                     || INHERIT.equals(valueString)) {
1958                 setStyleAttribute(name, valueString, important);
1959                 return;
1960             }
1961 
1962             if (validValues != null && ArrayUtils.contains(validValues, valueString)) {
1963                 setStyleAttribute(name, valueString, important);
1964                 return;
1965             }
1966 
1967             if (percent && valueString.endsWith("%")) {
1968                 unit = valueString.substring(valueString.length() - 1);
1969                 valueString = valueString.substring(0, valueString.length() - 1);
1970             }
1971             else if (valueString.endsWith("px")
1972                 || valueString.endsWith("em")
1973                 || valueString.endsWith("ex")
1974                 || valueString.endsWith("pt")
1975                 || valueString.endsWith("cm")
1976                 || valueString.endsWith("mm")
1977                 || valueString.endsWith("in")
1978                 || valueString.endsWith("pc")
1979                 || valueString.endsWith("ch")
1980                 || valueString.endsWith("vh")
1981                 || valueString.endsWith("vw")) {
1982                 unit = valueString.substring(valueString.length() - 2);
1983                 valueString = valueString.substring(0, valueString.length() - 2);
1984             }
1985             else if (valueString.endsWith("rem")
1986                 || valueString.endsWith("vmin")
1987                 || valueString.endsWith("vmax")) {
1988                 unit = valueString.substring(valueString.length() - 3);
1989                 valueString = valueString.substring(0, valueString.length() - 3);
1990             }
1991             else if (unitRequired) {
1992                 return;
1993             }
1994 
1995             if (!valueString.equals(valueString.trim())) {
1996                 // we have a unit but surrounding blanks
1997                 return;
1998             }
1999             doubleValue = JavaScriptEngine.toNumber(valueString);
2000         }
2001 
2002         try {
2003             if (Double.isNaN(doubleValue) || Double.isInfinite(doubleValue)) {
2004                 return;
2005             }
2006 
2007             final String valueString;
2008             if (doubleValue % 1 == 0) {
2009                 valueString = (int) doubleValue + unit;
2010             }
2011             else {
2012                 valueString = doubleValue + unit;
2013             }
2014 
2015             setStyleAttribute(name, valueString, important);
2016         }
2017         catch (final Exception ignored) {
2018             // ignore
2019         }
2020     }
2021 }