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.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.cssparser.dom.DOMExceptionImpl;
51  import org.htmlunit.javascript.HtmlUnitScriptable;
52  import org.htmlunit.javascript.JavaScriptEngine;
53  import org.htmlunit.javascript.configuration.JsxClass;
54  import org.htmlunit.javascript.configuration.JsxConstructor;
55  import org.htmlunit.javascript.configuration.JsxFunction;
56  import org.htmlunit.javascript.configuration.JsxGetter;
57  import org.htmlunit.javascript.configuration.JsxSetter;
58  import org.htmlunit.javascript.configuration.JsxSymbol;
59  import org.htmlunit.javascript.host.Element;
60  import org.htmlunit.util.StringUtils;
61  
62  /**
63   * A JavaScript object for {@code CSSStyleDeclaration}.
64   *
65   * @author Mike Bowler
66   * @author Christian Sell
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('/').append(lineHeight);
837             }
838 
839             newFont.append(' ').append(getFontFamily());
840             setStyleAttribute(Definition.FONT.getAttributeName(), newFont.toString());
841         }
842     }
843 
844     /**
845      * Gets the {@code font} style attribute.
846      * @return the style attribute
847      */
848     @JsxGetter
849     public String getFont() {
850         if (styleDeclaration_ == null) {
851             return null; // prototype
852         }
853         return styleDeclaration_.getFont();
854     }
855 
856     /**
857      * Sets the {@code font} style attribute.
858      * @param font the new attribute
859      */
860     @JsxSetter
861     public void setFont(final String font) {
862         final String[] details = ComputedFont.getDetails(font);
863         if (details != null) {
864             setStyleAttribute(Definition.FONT_FAMILY.getAttributeName(), details[ComputedFont.FONT_FAMILY_INDEX]);
865             final String fontSize = details[ComputedFont.FONT_SIZE_INDEX];
866             if (details[ComputedFont.LINE_HEIGHT_INDEX] != null) {
867                 setStyleAttribute(Definition.LINE_HEIGHT.getAttributeName(), details[ComputedFont.LINE_HEIGHT_INDEX]);
868             }
869             setStyleAttribute(Definition.FONT_SIZE.getAttributeName(), fontSize);
870             updateFont(font, true);
871         }
872     }
873 
874     /**
875      * Gets the {@code height} style attribute.
876      * @return the style attribute
877      */
878     @JsxGetter
879     public String getHeight() {
880         if (styleDeclaration_ == null) {
881             return null; // prototype
882         }
883         return styleDeclaration_.getHeight();
884     }
885 
886     /**
887      * Sets the {@code height} style attribute.
888      * @param height the new attribute
889      */
890     @JsxSetter
891     public void setHeight(final Object height) {
892         setStyleLengthAttribute(Definition.HEIGHT.getAttributeName(), height, "", true, true, null);
893     }
894 
895     /**
896      * Gets the {@code left} style attribute.
897      * @return the style attribute
898      */
899     @JsxGetter
900     public String getLeft() {
901         if (styleDeclaration_ == null) {
902             return null; // prototype
903         }
904         return styleDeclaration_.getLeft();
905     }
906 
907     /**
908      * Sets the {@code left} style attribute.
909      * @param left the new attribute
910      */
911     @JsxSetter
912     public void setLeft(final Object left) {
913         setStyleLengthAttribute(Definition.LEFT.getAttributeName(), left, "", true, true, null);
914     }
915 
916     /**
917      * Returns the {@code length} property.
918      * @return the {@code length} property
919      */
920     @JsxGetter
921     public int getLength() {
922         if (styleDeclaration_ == null) {
923             return 0; // prototype
924         }
925         return styleDeclaration_.getLength();
926     }
927 
928     /**
929      * @param index the index
930      * @return a CSS property name from a CSSStyleDeclaration by index.
931      */
932     @JsxFunction
933     public String item(final int index) {
934         if (styleDeclaration_ == null) {
935             return null; // prototype
936         }
937 
938         return styleDeclaration_.item(index);
939     }
940 
941     /**
942      * Returns an Iterator allowing to go through all keys contained in this object.
943      * @return a NativeArrayIterator
944      */
945     @JsxSymbol(symbolName = "iterator")
946     public Scriptable values() {
947         return JavaScriptEngine.newArrayIteratorTypeValues(getParentScope(), this);
948     }
949 
950     /**
951      * Gets the {@code letterSpacing} style attribute.
952      * @return the style attribute
953      */
954     @JsxGetter
955     public String getLetterSpacing() {
956         if (styleDeclaration_ == null) {
957             return null; // prototype
958         }
959         return styleDeclaration_.getLetterSpacing();
960     }
961 
962     /**
963      * Sets the {@code letterSpacing} style attribute.
964      * @param letterSpacing the new attribute
965      */
966     @JsxSetter
967     public void setLetterSpacing(final Object letterSpacing) {
968         setStyleLengthAttribute(Definition.LETTER_SPACING.getAttributeName(), letterSpacing, "",
969                 false, getBrowserVersion().hasFeature(JS_STYLE_LETTER_SPACING_ACCEPTS_PERCENT), null);
970     }
971 
972     /**
973      * Gets the {@code margin} style attribute.
974      * @return the style attribute
975      */
976     @JsxGetter
977     public String getMargin() {
978         if (styleDeclaration_ == null) {
979             return null; // prototype
980         }
981         return styleDeclaration_.getMargin();
982     }
983 
984     /**
985      * Sets the {@code margin} style attribute.
986      * @param margin the new attribute
987      */
988     @JsxSetter
989     public void setMargin(final String margin) {
990         setStyleAttribute(Definition.MARGIN.getAttributeName(), margin);
991     }
992 
993     /**
994      * Gets the {@code marginBottom} style attribute.
995      * @return the style attribute
996      */
997     @JsxGetter
998     public String getMarginBottom() {
999         if (styleDeclaration_ == null) {
1000             return null; // prototype
1001         }
1002         return styleDeclaration_.getMarginBottom();
1003     }
1004 
1005     /**
1006      * Sets the {@code marginBottom} style attribute.
1007      * @param marginBottom the new attribute
1008      */
1009     @JsxSetter
1010     public void setMarginBottom(final Object marginBottom) {
1011         setStyleLengthAttribute(Definition.MARGIN_BOTTOM.getAttributeName(), marginBottom, "", true, true, null);
1012     }
1013 
1014     /**
1015      * Gets the {@code marginLeft} style attribute.
1016      * @return the style attribute
1017      */
1018     @JsxGetter
1019     public String getMarginLeft() {
1020         if (styleDeclaration_ == null) {
1021             return null; // prototype
1022         }
1023         return styleDeclaration_.getMarginLeft();
1024     }
1025 
1026     /**
1027      * Sets the {@code marginLeft} style attribute.
1028      * @param marginLeft the new attribute
1029      */
1030     @JsxSetter
1031     public void setMarginLeft(final Object marginLeft) {
1032         setStyleLengthAttribute(Definition.MARGIN_LEFT.getAttributeName(), marginLeft, "", true, true, null);
1033     }
1034 
1035     /**
1036      * Gets the {@code marginRight} style attribute.
1037      * @return the style attribute
1038      */
1039     @JsxGetter
1040     public String getMarginRight() {
1041         if (styleDeclaration_ == null) {
1042             return null; // prototype
1043         }
1044         return styleDeclaration_.getMarginRight();
1045     }
1046 
1047     /**
1048      * Sets the {@code marginRight} style attribute.
1049      * @param marginRight the new attribute
1050      */
1051     @JsxSetter
1052     public void setMarginRight(final Object marginRight) {
1053         setStyleLengthAttribute(Definition.MARGIN_RIGHT.getAttributeName(), marginRight, "", true, true, null);
1054     }
1055 
1056     /**
1057      * Gets the {@code marginTop} style attribute.
1058      * @return the style attribute
1059      */
1060     @JsxGetter
1061     public String getMarginTop() {
1062         if (styleDeclaration_ == null) {
1063             return null; // prototype
1064         }
1065         return styleDeclaration_.getMarginTop();
1066     }
1067 
1068     /**
1069      * Sets the {@code marginTop} style attribute.
1070      * @param marginTop the new attribute
1071      */
1072     @JsxSetter
1073     public void setMarginTop(final Object marginTop) {
1074         setStyleLengthAttribute(Definition.MARGIN_TOP.getAttributeName(), marginTop, "", true, true, null);
1075     }
1076 
1077     /**
1078      * Gets the {@code maxHeight} style attribute.
1079      * @return the style attribute
1080      */
1081     @JsxGetter
1082     public String getMaxHeight() {
1083         if (styleDeclaration_ == null) {
1084             return null; // prototype
1085         }
1086         return styleDeclaration_.getMaxHeight();
1087     }
1088 
1089     /**
1090      * Sets the {@code maxHeight} style attribute.
1091      * @param maxHeight the new attribute
1092      */
1093     @JsxSetter
1094     public void setMaxHeight(final Object maxHeight) {
1095         setStyleLengthAttribute(Definition.MAX_HEIGHT.getAttributeName(), maxHeight, "", false, true, null);
1096     }
1097 
1098     /**
1099      * Gets the {@code maxWidth} style attribute.
1100      * @return the style attribute
1101      */
1102     @JsxGetter
1103     public String getMaxWidth() {
1104         if (styleDeclaration_ == null) {
1105             return null; // prototype
1106         }
1107         return styleDeclaration_.getMaxWidth();
1108     }
1109 
1110     /**
1111      * Sets the {@code maxWidth} style attribute.
1112      * @param maxWidth the new attribute
1113      */
1114     @JsxSetter
1115     public void setMaxWidth(final Object maxWidth) {
1116         setStyleLengthAttribute(Definition.MAX_WIDTH.getAttributeName(), maxWidth, "", false, true, null);
1117     }
1118 
1119     /**
1120      * Gets the {@code minHeight} style attribute.
1121      * @return the style attribute
1122      */
1123     @JsxGetter
1124     public String getMinHeight() {
1125         if (styleDeclaration_ == null) {
1126             return null; // prototype
1127         }
1128         return styleDeclaration_.getMinHeight();
1129     }
1130 
1131     /**
1132      * Sets the {@code minHeight} style attribute.
1133      * @param minHeight the new attribute
1134      */
1135     @JsxSetter
1136     public void setMinHeight(final Object minHeight) {
1137         setStyleLengthAttribute(Definition.MIN_HEIGHT.getAttributeName(), minHeight, "", true, true, null);
1138     }
1139 
1140     /**
1141      * Gets the {@code minWidth} style attribute.
1142      * @return the style attribute
1143      */
1144     @JsxGetter
1145     public String getMinWidth() {
1146         if (styleDeclaration_ == null) {
1147             return null; // prototype
1148         }
1149         return styleDeclaration_.getMinWidth();
1150     }
1151 
1152     /**
1153      * Sets the {@code minWidth} style attribute.
1154      * @param minWidth the new attribute
1155      */
1156     @JsxSetter
1157     public void setMinWidth(final Object minWidth) {
1158         setStyleLengthAttribute(Definition.MIN_WIDTH.getAttributeName(), minWidth, "", true, true, null);
1159     }
1160 
1161     /**
1162      * {@inheritDoc}
1163      */
1164     @Override
1165     public Object get(final String name, final Scriptable start) {
1166         if (this != start) {
1167             return super.get(name, start);
1168         }
1169 
1170         Scriptable prototype = getPrototype();
1171         while (prototype != null) {
1172             Object value = prototype.get(name, start);
1173             if (value != Scriptable.NOT_FOUND) {
1174                 return value;
1175             }
1176 
1177             final String camel = StringUtils.cssCamelize(name);
1178             if (!name.equals(camel)) {
1179                 value = prototype.get(camel, start);
1180                 if (value != Scriptable.NOT_FOUND) {
1181                     return value;
1182                 }
1183             }
1184             prototype = prototype.getPrototype();
1185         }
1186 
1187         final Definition style = StyleAttributes.getDefinition(name, getBrowserVersion());
1188         if (style != null) {
1189             return getStyleAttribute(style);
1190         }
1191 
1192         return super.get(name, start);
1193     }
1194 
1195     @Override
1196     public Object get(final int index, final Scriptable start) {
1197         if (index < 0) {
1198             return JavaScriptEngine.UNDEFINED;
1199         }
1200 
1201         final Map<String, StyleElement> style = getStyleMap();
1202         final int size = style.size();
1203         if (index >= size) {
1204             return JavaScriptEngine.UNDEFINED;
1205         }
1206         return style.keySet().toArray(new String[0])[index];
1207     }
1208 
1209     /**
1210      * Get the value for the style attribute.
1211      * @param definition the definition
1212      * @return the value
1213      */
1214     public final String getStyleAttribute(final Definition definition) {
1215         return getStyleAttribute(definition, true);
1216     }
1217 
1218     /**
1219      * Get the value for the style attribute.
1220      * This impl ignores the default getDefaultValueIfEmpty flag, but there is an overload
1221      * in {@link ComputedCSSStyleDeclaration}.
1222      * @param definition the definition
1223      * @param getDefaultValueIfEmpty whether to get the default value if empty or not
1224      * @return the value
1225      */
1226     public String getStyleAttribute(final Definition definition, final boolean getDefaultValueIfEmpty) {
1227         if (styleDeclaration_ == null) {
1228             return ""; // prototype
1229         }
1230         return styleDeclaration_.getStyleAttribute(definition, getDefaultValueIfEmpty);
1231     }
1232 
1233     @Override
1234     public void put(final String name, final Scriptable start, final Object value) {
1235         if (this != start) {
1236             super.put(name, start, value);
1237             return;
1238         }
1239 
1240         final Scriptable prototype = getPrototype();
1241         if (prototype != null && !"constructor".equals(name)) {
1242             if (prototype.get(name, start) != Scriptable.NOT_FOUND) {
1243                 prototype.put(name, start, value);
1244                 return;
1245             }
1246             final String camel = StringUtils.cssCamelize(name);
1247             if (!name.equals(camel) && prototype.get(camel, start) != Scriptable.NOT_FOUND) {
1248                 prototype.put(camel, start, value);
1249                 return;
1250             }
1251         }
1252 
1253         if (getDomNodeOrNull() != null) { // check if prototype or not
1254             final Definition style = StyleAttributes.getDefinition(name, getBrowserVersion());
1255             if (style != null) {
1256                 final String stringValue = JavaScriptEngine.toString(value);
1257                 setStyleAttribute(style.getAttributeName(), stringValue);
1258                 return;
1259             }
1260         }
1261 
1262         super.put(name, start, value);
1263     }
1264 
1265     @Override
1266     public boolean has(final String name, final Scriptable start) {
1267         if (this != start) {
1268             return super.has(name, start);
1269         }
1270 
1271         final BrowserVersion browserVersion = getBrowserVersion();
1272         if (browserVersion != null) {
1273             final Definition style = StyleAttributes.getDefinition(name, getBrowserVersion());
1274             if (style != null) {
1275                 return true;
1276             }
1277         }
1278 
1279         return super.has(name, start);
1280     }
1281 
1282     @Override
1283     public Object[] getIds() {
1284         final List<Object> ids = new ArrayList<>();
1285         for (final Definition styleAttribute : StyleAttributes.getDefinitions(getBrowserVersion())) {
1286             ids.add(styleAttribute.getPropertyName());
1287         }
1288         final Object[] normalIds = super.getIds();
1289         for (final Object o : normalIds) {
1290             if (!ids.contains(o)) {
1291                 ids.add(o);
1292             }
1293         }
1294         return ids.toArray();
1295     }
1296 
1297     /**
1298      * Gets the {@code opacity} style attribute.
1299      * @return the style attribute
1300      */
1301     @JsxGetter
1302     public String getOpacity() {
1303         if (styleDeclaration_ == null) {
1304             return null; // prototype
1305         }
1306         return styleDeclaration_.getOpacity();
1307     }
1308 
1309     /**
1310      * Sets the {@code opacity} style attribute.
1311      * @param opacity the new attribute
1312      */
1313     @JsxSetter
1314     public void setOpacity(final Object opacity) {
1315         if (JavaScriptEngine.isNaN(opacity)) {
1316             return;
1317         }
1318 
1319         final double doubleValue;
1320         if (opacity instanceof Number) {
1321             doubleValue = ((Number) opacity).doubleValue();
1322         }
1323         else {
1324             String valueString = JavaScriptEngine.toString(opacity);
1325 
1326             if (valueString.isEmpty()) {
1327                 setStyleAttribute(Definition.OPACITY.getAttributeName(), valueString);
1328                 return;
1329             }
1330 
1331             valueString = valueString.trim();
1332             try {
1333                 doubleValue = Double.parseDouble(valueString);
1334             }
1335             catch (final NumberFormatException e) {
1336                 // ignore wrong value
1337                 return;
1338             }
1339         }
1340 
1341         if (Double.isNaN(doubleValue) || Double.isInfinite(doubleValue)) {
1342             return;
1343         }
1344         setStyleAttribute(Definition.OPACITY.getAttributeName(), Double.toString(doubleValue));
1345     }
1346 
1347     /**
1348      * Gets the {@code outline} style attribute.
1349      * @return the style attribute
1350      */
1351     @JsxGetter
1352     public String getOutline() {
1353         if (styleDeclaration_ == null) {
1354             return null; // prototype
1355         }
1356         return styleDeclaration_.getOutline();
1357     }
1358 
1359     /**
1360      * Sets the {@code outline} style attribute.
1361      * @param outline the new attribute
1362      */
1363     @JsxSetter
1364     public void setOutline(final String outline) {
1365         setStyleAttribute(Definition.OUTLINE.getAttributeName(), outline);
1366     }
1367 
1368     /**
1369      * Gets the {@code outlineWidth} style attribute.
1370      * @return the style attribute
1371      */
1372     @JsxGetter
1373     public String getOutlineWidth() {
1374         if (styleDeclaration_ == null) {
1375             return null; // prototype
1376         }
1377         return styleDeclaration_.getOutlineWidth();
1378     }
1379 
1380     /**
1381      * Sets the {@code outlineWidth} style attribute.
1382      * @param outlineWidth the new attribute
1383      */
1384     @JsxSetter
1385     public void setOutlineWidth(final Object outlineWidth) {
1386         setStyleLengthAttribute(Definition.OUTLINE_WIDTH.getAttributeName(), outlineWidth, "",
1387                 false, false, THIN_MED_THICK);
1388     }
1389 
1390     /**
1391      * Gets the {@code padding} style attribute.
1392      * @return the style attribute
1393      */
1394     @JsxGetter
1395     public String getPadding() {
1396         if (styleDeclaration_ == null) {
1397             return null; // prototype
1398         }
1399         return styleDeclaration_.getPadding();
1400     }
1401 
1402     /**
1403      * Sets the {@code padding} style attribute.
1404      * @param padding the new attribute
1405      */
1406     @JsxSetter
1407     public void setPadding(final String padding) {
1408         setStyleAttribute(Definition.PADDING.getAttributeName(), padding);
1409     }
1410 
1411     /**
1412      * Gets the {@code paddingBottom} style attribute.
1413      * @return the style attribute
1414      */
1415     @JsxGetter
1416     public String getPaddingBottom() {
1417         if (styleDeclaration_ == null) {
1418             return null; // prototype
1419         }
1420         return styleDeclaration_.getPaddingBottom();
1421     }
1422 
1423     /**
1424      * Sets the {@code paddingBottom} style attribute.
1425      * @param paddingBottom the new attribute
1426      */
1427     @JsxSetter
1428     public void setPaddingBottom(final Object paddingBottom) {
1429         setStyleLengthAttribute(Definition.PADDING_BOTTOM.getAttributeName(),
1430                 paddingBottom, "", false, true, null);
1431     }
1432 
1433     /**
1434      * Gets the {@code paddingLeft} style attribute.
1435      * @return the style attribute
1436      */
1437     @JsxGetter
1438     public String getPaddingLeft() {
1439         if (styleDeclaration_ == null) {
1440             return null; // prototype
1441         }
1442         return styleDeclaration_.getPaddingLeft();
1443     }
1444 
1445     /**
1446      * Sets the {@code paddingLeft} style attribute.
1447      * @param paddingLeft the new attribute
1448      */
1449     @JsxSetter
1450     public void setPaddingLeft(final Object paddingLeft) {
1451         setStyleLengthAttribute(Definition.PADDING_LEFT.getAttributeName(), paddingLeft, "", false, true, null);
1452     }
1453 
1454     /**
1455      * Gets the {@code paddingRight} style attribute.
1456      * @return the style attribute
1457      */
1458     @JsxGetter
1459     public String getPaddingRight() {
1460         if (styleDeclaration_ == null) {
1461             return null; // prototype
1462         }
1463         return styleDeclaration_.getPaddingRight();
1464     }
1465 
1466     /**
1467      * Sets the {@code paddingRight} style attribute.
1468      * @param paddingRight the new attribute
1469      */
1470     @JsxSetter
1471     public void setPaddingRight(final Object paddingRight) {
1472         setStyleLengthAttribute(Definition.PADDING_RIGHT.getAttributeName(),
1473                 paddingRight, "", false, true, null);
1474     }
1475 
1476     /**
1477      * Gets the {@code paddingTop} style attribute.
1478      * @return the style attribute
1479      */
1480     @JsxGetter
1481     public String getPaddingTop() {
1482         if (styleDeclaration_ == null) {
1483             return null; // prototype
1484         }
1485         return styleDeclaration_.getPaddingTop();
1486     }
1487 
1488     /**
1489      * Sets the {@code paddingTop} style attribute.
1490      * @param paddingTop the new attribute
1491      */
1492     @JsxSetter
1493     public void setPaddingTop(final Object paddingTop) {
1494         setStyleLengthAttribute(Definition.PADDING_TOP.getAttributeName(), paddingTop, "", false, true, null);
1495     }
1496 
1497     /**
1498      * Returns the CSSRule that is the parent of this style block or <code>null</code> if this CSSStyleDeclaration is
1499      * not attached to a CSSRule.
1500      * @return the CSSRule that is the parent of this style block or <code>null</code> if this CSSStyleDeclaration is
1501      *      not attached to a CSSRule
1502      */
1503     @JsxGetter
1504     public CSSRule getParentRule() {
1505         final AbstractCSSRuleImpl parentRule = styleDeclaration_.getParentRule();
1506         if (parentRule != null) {
1507             return CSSRule.create((CSSStyleSheet) getParentScope(), parentRule);
1508         }
1509         return null;
1510     }
1511 
1512     /**
1513      * Nothing.
1514      * @param parentRule ignored
1515      */
1516     @JsxSetter
1517     public void setParentRule(final CSSRule parentRule) {
1518         // nothing to do
1519     }
1520 
1521     /**
1522      * Gets the {@code right} style attribute.
1523      * @return the style attribute
1524      */
1525     @JsxGetter
1526     public String getRight() {
1527         if (styleDeclaration_ == null) {
1528             return null; // prototype
1529         }
1530         return styleDeclaration_.getRight();
1531     }
1532 
1533     /**
1534      * Sets the {@code right} style attribute.
1535      * @param right the new attribute
1536      */
1537     @JsxSetter
1538     public void setRight(final Object right) {
1539         setStyleLengthAttribute(Definition.RIGHT.getAttributeName(), right, "", true, true, null);
1540     }
1541 
1542     /**
1543      * Gets the {@code rubyAlign} style attribute.
1544      * @return the style attribute
1545      */
1546     @JsxGetter
1547     public String getRubyAlign() {
1548         if (styleDeclaration_ == null) {
1549             return null; // prototype
1550         }
1551         return styleDeclaration_.getRubyAlign();
1552     }
1553 
1554     /**
1555      * Sets the {@code rubyAlign} style attribute.
1556      * @param rubyAlign the new attribute
1557      */
1558     @JsxSetter
1559     public void setRubyAlign(final String rubyAlign) {
1560         setStyleAttribute(Definition.RUBY_ALIGN.getAttributeName(), rubyAlign);
1561     }
1562 
1563     /**
1564      * Gets the {@code size} style attribute.
1565      * @return the style attribute
1566      */
1567     @JsxGetter({CHROME, EDGE})
1568     public String getSize() {
1569         if (styleDeclaration_ == null) {
1570             return null; // prototype
1571         }
1572         return styleDeclaration_.getSize();
1573     }
1574 
1575     /**
1576      * Sets the {@code size} style attribute.
1577      * @param size the new attribute
1578      */
1579     @JsxSetter({CHROME, EDGE})
1580     public void setSize(final String size) {
1581         setStyleAttribute(Definition.SIZE.getAttributeName(), size);
1582     }
1583 
1584     /**
1585      * Gets the {@code textIndent} style attribute.
1586      * @return the style attribute
1587      */
1588     @JsxGetter
1589     public String getTextIndent() {
1590         if (styleDeclaration_ == null) {
1591             return null; // prototype
1592         }
1593         return styleDeclaration_.getTextIndent();
1594     }
1595 
1596     /**
1597      * Sets the {@code textIndent} style attribute.
1598      * @param textIndent the new attribute
1599      */
1600     @JsxSetter
1601     public void setTextIndent(final Object textIndent) {
1602         setStyleLengthAttribute(Definition.TEXT_INDENT.getAttributeName(), textIndent, "", false, true, null);
1603     }
1604 
1605     /**
1606      * Gets the {@code top} style attribute.
1607      * @return the style attribute
1608      */
1609     @JsxGetter
1610     public String getTop() {
1611         if (styleDeclaration_ == null) {
1612             return null; // prototype
1613         }
1614         return styleDeclaration_.getTop();
1615     }
1616 
1617     /**
1618      * Sets the {@code top} style attribute.
1619      * @param top the new attribute
1620      */
1621     @JsxSetter
1622     public void setTop(final Object top) {
1623         setStyleLengthAttribute(Definition.TOP.getAttributeName(), top, "", true, true, null);
1624     }
1625 
1626     /**
1627      * Gets the {@code verticalAlign} style attribute.
1628      * @return the style attribute
1629      */
1630     @JsxGetter
1631     public String getVerticalAlign() {
1632         if (styleDeclaration_ == null) {
1633             return null; // prototype
1634         }
1635         return styleDeclaration_.getVerticalAlign();
1636     }
1637 
1638     /**
1639      * Sets the {@code verticalAlign} style attribute.
1640      * @param verticalAlign the new attribute
1641      */
1642     @JsxSetter
1643     public void setVerticalAlign(final Object verticalAlign) {
1644         setStyleLengthAttribute(Definition.VERTICAL_ALIGN.getAttributeName(),
1645                 verticalAlign, "", false, true, ALIGN_KEYWORDS);
1646     }
1647 
1648     /**
1649      * Gets the {@code width} style attribute.
1650      * @return the style attribute
1651      */
1652     @JsxGetter
1653     public String getWidth() {
1654         if (styleDeclaration_ == null) {
1655             return null; // prototype
1656         }
1657         return styleDeclaration_.getWidth();
1658     }
1659 
1660     /**
1661      * Sets the {@code width} style attribute.
1662      * @param width the new attribute
1663      */
1664     @JsxSetter
1665     public void setWidth(final Object width) {
1666         setStyleLengthAttribute(Definition.WIDTH.getAttributeName(), width, "", true, true, null);
1667     }
1668 
1669     /**
1670      * Gets the {@code widows} style attribute.
1671      * @return the style attribute
1672      */
1673     @JsxGetter({CHROME, EDGE})
1674     public String getWidows() {
1675         if (styleDeclaration_ == null) {
1676             return null; // prototype
1677         }
1678         return styleDeclaration_.getWidows();
1679     }
1680 
1681     /**
1682      * Sets the {@code widows} style attribute.
1683      * @param widows the new attribute
1684      */
1685     @JsxSetter({CHROME, EDGE})
1686     public void setWidows(final String widows) {
1687         if (getBrowserVersion().hasFeature(CSS_BACKGROUND_INITIAL)) {
1688             try {
1689                 if (Integer.parseInt(widows) <= 0) {
1690                     return;
1691                 }
1692             }
1693             catch (final NumberFormatException e) {
1694                 return;
1695             }
1696         }
1697         setStyleAttribute(Definition.WIDOWS.getAttributeName(), widows);
1698     }
1699 
1700     /**
1701      * Gets the {@code orphans} style attribute.
1702      * @return the style attribute
1703      */
1704     @JsxGetter({CHROME, EDGE})
1705     public String getOrphans() {
1706         if (styleDeclaration_ == null) {
1707             return null; // prototype
1708         }
1709         return styleDeclaration_.getOrphans();
1710     }
1711 
1712     /**
1713      * Sets the {@code orphans} style attribute.
1714      * @param orphans the new attribute
1715      */
1716     @JsxSetter({CHROME, EDGE})
1717     public void setOrphans(final String orphans) {
1718         if (getBrowserVersion().hasFeature(CSS_BACKGROUND_INITIAL)) {
1719             try {
1720                 if (Integer.parseInt(orphans) <= 0) {
1721                     return;
1722                 }
1723             }
1724             catch (final NumberFormatException e) {
1725                 return;
1726             }
1727         }
1728         setStyleAttribute(Definition.ORPHANS.getAttributeName(), orphans);
1729     }
1730 
1731     /**
1732      * Gets the {@code position} style attribute.
1733      * @return the style attribute
1734      */
1735     @JsxGetter
1736     public String getPosition() {
1737         if (styleDeclaration_ == null) {
1738             return null; // prototype
1739         }
1740         return styleDeclaration_.getPosition();
1741     }
1742 
1743     /**
1744      * Sets the {@code position} style attribute.
1745      * @param position the new attribute
1746      */
1747     @JsxSetter
1748     public void setPosition(final String position) {
1749         if (position.isEmpty() || STATIC.equalsIgnoreCase(position) || ABSOLUTE.equalsIgnoreCase(position)
1750                 || FIXED.equalsIgnoreCase(position) || RELATIVE.equalsIgnoreCase(position)
1751                 || INITIAL.equalsIgnoreCase(position) || INHERIT.equalsIgnoreCase(position)) {
1752             setStyleAttribute(Definition.POSITION.getAttributeName(), position.toLowerCase(Locale.ROOT));
1753         }
1754     }
1755 
1756     /**
1757      * Gets the {@code wordSpacing} style attribute.
1758      * @return the style attribute
1759      */
1760     @JsxGetter
1761     public String getWordSpacing() {
1762         if (styleDeclaration_ == null) {
1763             return null; // prototype
1764         }
1765         return styleDeclaration_.getWordSpacing();
1766     }
1767 
1768     /**
1769      * Sets the {@code wordSpacing} style attribute.
1770      * @param wordSpacing the new attribute
1771      */
1772     @JsxSetter
1773     public void setWordSpacing(final Object wordSpacing) {
1774         setStyleLengthAttribute(Definition.WORD_SPACING.getAttributeName(), wordSpacing, "",
1775                 false, getBrowserVersion().hasFeature(JS_STYLE_WORD_SPACING_ACCEPTS_PERCENT), null);
1776     }
1777 
1778     /**
1779      * Gets the {@code zIndex} style attribute.
1780      * @return the style attribute
1781      */
1782     @JsxGetter
1783     public String getZIndex() {
1784         if (styleDeclaration_ == null) {
1785             return null; // prototype
1786         }
1787         return styleDeclaration_.getZIndex();
1788     }
1789 
1790     /**
1791      * Sets the {@code zIndex} style attribute.
1792      * @param zIndex the new attribute
1793      */
1794     @JsxSetter
1795     public void setZIndex(final Object zIndex) {
1796         // empty
1797         if (zIndex == null || StringUtils.isEmptyOrNull(zIndex.toString())) {
1798             setStyleAttribute(Definition.Z_INDEX_.getAttributeName(), "");
1799             return;
1800         }
1801         // undefined
1802         if (JavaScriptEngine.isUndefined(zIndex)) {
1803             return;
1804         }
1805 
1806         // string
1807         if (zIndex instanceof Number) {
1808             final Number number = (Number) zIndex;
1809             if (number.doubleValue() % 1 == 0) {
1810                 setStyleAttribute(Definition.Z_INDEX_.getAttributeName(), Integer.toString(number.intValue()));
1811             }
1812             return;
1813         }
1814         try {
1815             final int i = Integer.parseInt(zIndex.toString());
1816             setStyleAttribute(Definition.Z_INDEX_.getAttributeName(), Integer.toString(i));
1817         }
1818         catch (final NumberFormatException ignored) {
1819             // ignore
1820         }
1821     }
1822 
1823     /**
1824      * Gets the value of the specified property of the style.
1825      * @param name the style property name
1826      * @return empty string if nothing found
1827      */
1828     @JsxFunction
1829     public String getPropertyValue(final String name) {
1830         if (name != null && name.contains("-")) {
1831             final Object value = getProperty(this, StringUtils.cssCamelize(name));
1832             if (value instanceof String) {
1833                 return (String) value;
1834             }
1835         }
1836 
1837         return styleDeclaration_.getStyleAttribute(name);
1838     }
1839 
1840     /**
1841      * Gets the value of the specified property of the style.
1842      * @param name the style property name
1843      * @return empty string if nothing found
1844      */
1845     @JsxFunction
1846     public String getPropertyPriority(final String name) {
1847         return getStylePriority(name);
1848     }
1849 
1850     /**
1851      * Sets the value of the specified property.
1852      *
1853      * @param name the name of the attribute
1854      * @param value the value to assign to the attribute
1855      * @param important may be null
1856      */
1857     @JsxFunction
1858     public void setProperty(final String name, final Object value, final String important) {
1859         String imp = "";
1860         if (!StringUtils.isEmptyOrNull(important) && !"null".equals(important)) {
1861             if (!StyleElement.PRIORITY_IMPORTANT.equalsIgnoreCase(important)) {
1862                 return;
1863             }
1864             imp = StyleElement.PRIORITY_IMPORTANT;
1865         }
1866 
1867         if (LENGTH_PROPERTIES_FFFF.contains(name)) {
1868             setStyleLengthAttribute(name, value, imp, false, false, null);
1869         }
1870         else if (LENGTH_PROPERTIES_TTFF.contains(name)) {
1871             setStyleLengthAttribute(name, value, imp, true, true, null);
1872         }
1873         else if (LENGTH_PROPERTIES_FTFF.contains(name)) {
1874             setStyleLengthAttribute(name, value, imp, false, true, null);
1875         }
1876         else if (Definition.OUTLINE_WIDTH.getAttributeName().equals(name)) {
1877             setStyleLengthAttribute(Definition.OUTLINE_WIDTH.getAttributeName(),
1878                     value, imp, false, false, THIN_MED_THICK);
1879         }
1880         else if (Definition.LETTER_SPACING.getAttributeName().equals(name)) {
1881             setStyleLengthAttribute(Definition.LETTER_SPACING.getAttributeName(), value, imp,
1882                     false, getBrowserVersion().hasFeature(JS_STYLE_LETTER_SPACING_ACCEPTS_PERCENT), null);
1883         }
1884         else if (Definition.WORD_SPACING.getAttributeName().equals(name)) {
1885             setStyleLengthAttribute(Definition.WORD_SPACING.getAttributeName(), value, imp,
1886                     false, getBrowserVersion().hasFeature(JS_STYLE_WORD_SPACING_ACCEPTS_PERCENT), null);
1887         }
1888         else if (Definition.VERTICAL_ALIGN.getAttributeName().equals(name)) {
1889             setStyleLengthAttribute(Definition.VERTICAL_ALIGN.getAttributeName(), value, imp, false, true, null);
1890         }
1891         else {
1892             setStyleAttribute(name, JavaScriptEngine.toString(value), imp);
1893         }
1894     }
1895 
1896     /**
1897      * Removes the named property.
1898      * @param name the name of the property to remove
1899      * @return the value deleted
1900      */
1901     @JsxFunction
1902     public String removeProperty(final Object name) {
1903         return removeStyleAttribute(JavaScriptEngine.toString(name));
1904     }
1905 
1906     /**
1907      * Returns if the specified token is a length.
1908      * @param token the token to check
1909      * @return whether the token is a length or not
1910      */
1911     static boolean isLength(String token) {
1912         if (token.endsWith("em") || token.endsWith("ex") || token.endsWith("px") || token.endsWith("in")
1913             || token.endsWith("cm") || token.endsWith("mm") || token.endsWith("pt") || token.endsWith("pc")
1914             || token.endsWith("%")) {
1915 
1916             if (token.endsWith("%")) {
1917                 token = token.substring(0, token.length() - 1);
1918             }
1919             else {
1920                 token = token.substring(0, token.length() - 2);
1921             }
1922             try {
1923                 Double.parseDouble(token);
1924                 return true;
1925             }
1926             catch (final NumberFormatException ignored) {
1927                 // ignore
1928             }
1929         }
1930         return false;
1931     }
1932 
1933     /**
1934      * {@inheritDoc}
1935      */
1936     @Override
1937     public String toString() {
1938         if (styleDeclaration_ == null) {
1939             return "CSSStyleDeclaration for 'null'"; // for instance on prototype
1940         }
1941 
1942         return "CSSStyleDeclaration for '" + styleDeclaration_ + "'";
1943     }
1944 
1945     /**
1946      * Sets the style attribute which should be treated as an integer in pixels.
1947      * @param name the attribute name
1948      * @param value the attribute value
1949      * @param important important value
1950      * @param auto true if auto is supported
1951      * @param percent true if percent is supported
1952      * @param validValues valid values
1953      */
1954     private void setStyleLengthAttribute(final String name, final Object value, final String important,
1955                 final boolean auto, final boolean percent, final String[] validValues) {
1956         if (JavaScriptEngine.isNaN(value)) {
1957             return;
1958         }
1959 
1960         if (value instanceof Number) {
1961             return;
1962         }
1963 
1964         String valueString = JavaScriptEngine.toString(value);
1965         if (null == value) {
1966             valueString = "";
1967         }
1968 
1969         if (StringUtils.isEmptyOrNull(valueString)) {
1970             setStyleAttribute(name, valueString, important);
1971             return;
1972         }
1973 
1974         if ((auto && AUTO.equals(valueString))
1975                 || INITIAL.equals(valueString)
1976                 || INHERIT.equals(valueString)) {
1977             setStyleAttribute(name, valueString, important);
1978             return;
1979         }
1980 
1981         if (validValues != null && ArrayUtils.contains(validValues, valueString)) {
1982             setStyleAttribute(name, valueString, important);
1983             return;
1984         }
1985 
1986         String unit = "px";
1987         if (percent && valueString.endsWith("%")) {
1988             unit = valueString.substring(valueString.length() - 1);
1989             valueString = valueString.substring(0, valueString.length() - 1);
1990         }
1991         else if (valueString.endsWith("px")
1992             || valueString.endsWith("em")
1993             || valueString.endsWith("ex")
1994             || valueString.endsWith("pt")
1995             || valueString.endsWith("cm")
1996             || valueString.endsWith("mm")
1997             || valueString.endsWith("in")
1998             || valueString.endsWith("pc")
1999             || valueString.endsWith("ch")
2000             || valueString.endsWith("vh")
2001             || valueString.endsWith("vw")) {
2002             unit = valueString.substring(valueString.length() - 2);
2003             valueString = valueString.substring(0, valueString.length() - 2);
2004         }
2005         else if (valueString.endsWith("rem")
2006             || valueString.endsWith("vmin")
2007             || valueString.endsWith("vmax")) {
2008             unit = valueString.substring(valueString.length() - 3);
2009             valueString = valueString.substring(0, valueString.length() - 3);
2010         }
2011         else {
2012             return;
2013         }
2014 
2015         if (!valueString.equals(valueString.trim())) {
2016             // we have a unit but surrounding blanks
2017             return;
2018         }
2019         final double doubleValue = JavaScriptEngine.toNumber(valueString);
2020 
2021         try {
2022             if (Double.isNaN(doubleValue) || Double.isInfinite(doubleValue)) {
2023                 return;
2024             }
2025 
2026             final String valueStr;
2027             if (doubleValue % 1 == 0) {
2028                 valueStr = (int) doubleValue + unit;
2029             }
2030             else {
2031                 valueStr = doubleValue + unit;
2032             }
2033 
2034             setStyleAttribute(name, valueStr, important);
2035         }
2036         catch (final Exception ignored) {
2037             // ignore
2038         }
2039     }
2040 }