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