View Javadoc
1   /*
2    * Copyright (c) 2002-2026 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.css.CssStyleSheet.ABSOLUTE;
19  import static org.htmlunit.css.CssStyleSheet.AUTO;
20  import static org.htmlunit.css.CssStyleSheet.FIXED;
21  import static org.htmlunit.css.CssStyleSheet.INHERIT;
22  import static org.htmlunit.css.CssStyleSheet.INITIAL;
23  import static org.htmlunit.css.CssStyleSheet.RELATIVE;
24  import static org.htmlunit.css.CssStyleSheet.STATIC;
25  import static org.htmlunit.javascript.configuration.SupportedBrowser.CHROME;
26  import static org.htmlunit.javascript.configuration.SupportedBrowser.EDGE;
27  
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.Collections;
31  import java.util.HashSet;
32  import java.util.LinkedHashMap;
33  import java.util.List;
34  import java.util.Locale;
35  import java.util.Map;
36  import java.util.Set;
37  
38  import org.htmlunit.BrowserVersion;
39  import org.htmlunit.corejs.javascript.Scriptable;
40  import org.htmlunit.corejs.javascript.ScriptableObject;
41  import org.htmlunit.css.AbstractCssStyleDeclaration;
42  import org.htmlunit.css.StyleAttributes;
43  import org.htmlunit.css.StyleAttributes.Definition;
44  import org.htmlunit.css.StyleElement;
45  import org.htmlunit.css.WrappedCssStyleDeclaration;
46  import org.htmlunit.cssparser.dom.AbstractCSSRuleImpl;
47  import org.htmlunit.cssparser.dom.DOMExceptionImpl;
48  import org.htmlunit.javascript.HtmlUnitScriptable;
49  import org.htmlunit.javascript.JavaScriptEngine;
50  import org.htmlunit.javascript.configuration.JsxClass;
51  import org.htmlunit.javascript.configuration.JsxConstructor;
52  import org.htmlunit.javascript.configuration.JsxFunction;
53  import org.htmlunit.javascript.configuration.JsxGetter;
54  import org.htmlunit.javascript.configuration.JsxSetter;
55  import org.htmlunit.javascript.configuration.JsxSymbol;
56  import org.htmlunit.javascript.host.Element;
57  import org.htmlunit.util.ArrayUtils;
58  import org.htmlunit.util.StringUtils;
59  
60  /**
61   * A JavaScript object for {@code CSSStyleDeclaration}.
62   *
63   * @author Mike Bowler
64   * @author Christian Sell
65   * @author Daniel Gredler
66   * @author Chris Erskine
67   * @author Ahmed Ashour
68   * @author Rodney Gitzel
69   * @author Sudhan Moghe
70   * @author Ronald Brill
71   * @author Frank Danek
72   * @author Dennis Duysak
73   * @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration">MDN doc</a>
74   */
75  @JsxClass
76  public class CSSStyleDeclaration extends HtmlUnitScriptable {
77  
78      private static final Set<String> LENGTH_PROPERTIES_FFFF = new HashSet<>(Arrays.asList(
79              Definition.BORDER_TOP_WIDTH.getAttributeName(),
80              Definition.BORDER_LEFT_WIDTH.getAttributeName(),
81              Definition.BORDER_BOTTOM_WIDTH.getAttributeName(),
82              Definition.BORDER_RIGHT_WIDTH.getAttributeName()));
83  
84      private static final Set<String> LENGTH_PROPERTIES_TTFF = new HashSet<>(Arrays.asList(
85              Definition.HEIGHT.getAttributeName(),
86              Definition.WIDTH.getAttributeName(),
87              Definition.TOP.getAttributeName(),
88              Definition.LEFT.getAttributeName(),
89              Definition.BOTTOM.getAttributeName(),
90              Definition.RIGHT.getAttributeName(),
91              Definition.MARGIN_TOP.getAttributeName(),
92              Definition.MARGIN_LEFT.getAttributeName(),
93              Definition.MARGIN_BOTTOM.getAttributeName(),
94              Definition.MARGIN_RIGHT.getAttributeName(),
95              Definition.MIN_HEIGHT.getAttributeName(),
96              Definition.MIN_WIDTH.getAttributeName()
97              ));
98  
99      private static final Set<String> LENGTH_PROPERTIES_FTFF = new HashSet<>(Arrays.asList(
100             Definition.FONT_SIZE.getAttributeName(),
101             Definition.TEXT_INDENT.getAttributeName(),
102             Definition.PADDING_TOP.getAttributeName(),
103             Definition.PADDING_LEFT.getAttributeName(),
104             Definition.PADDING_BOTTOM.getAttributeName(),
105             Definition.PADDING_RIGHT.getAttributeName(),
106             Definition.MAX_HEIGHT.getAttributeName(),
107             Definition.MAX_WIDTH.getAttributeName()
108             ));
109 
110     private static final String[] THIN_MED_THICK = {"thin", "medium", "thick"};
111     private static final String[] ALIGN_KEYWORDS =
112         {"baseline", "sub", "super", "text-top", "text-bottom", "middle", "top", "bottom",
113          "inherit", "initial", "revert", "unset"};
114     private static final String[] FONT_SIZES =
115         {"xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large", "xxx-large",
116          "smaller", "larger"};
117 
118     // private static final Log LOG = LogFactory.getLog(CSSStyleDeclaration.class);
119 
120     /** The wrapped CSSStyleDeclaration */
121     private AbstractCssStyleDeclaration styleDeclaration_;
122     private CSSStyleSheet parentStyleSheet_;
123 
124     /**
125      * Creates an instance.
126      */
127     public CSSStyleDeclaration() {
128         super();
129     }
130 
131     /**
132      * JavaScript constructor.
133      *
134      * @param type the event type
135      * @param details the event details (optional)
136      */
137     @JsxConstructor
138     public void jsConstructor(final String type, final ScriptableObject details) {
139         throw JavaScriptEngine.typeError("CSSStyleDeclaration ctor is not available");
140     }
141 
142     /**
143      * Creates an instance and sets its parent scope to the one of the provided element.
144      * @param element the element to which this style is bound
145      * @param styleDeclaration the style declaration to be based on
146      */
147     public CSSStyleDeclaration(final Element element, final AbstractCssStyleDeclaration styleDeclaration) {
148         super();
149         setParentScope(element.getParentScope());
150         setPrototype(getPrototype(getClass()));
151 
152         setDomNode(element.getDomNodeOrNull(), false);
153 
154         if (styleDeclaration == null) {
155             throw new IllegalStateException("styleDeclaration can't be null");
156         }
157         styleDeclaration_ = styleDeclaration;
158     }
159 
160     /**
161      * Creates an instance which wraps the specified style declaration.
162      * @param parentStyleSheet the parent {@link CSSStyleSheet} to use
163      * @param styleDeclaration the style declaration to wrap
164      */
165     CSSStyleDeclaration(final CSSStyleSheet parentStyleSheet, final WrappedCssStyleDeclaration styleDeclaration) {
166         super();
167         setParentScope(parentStyleSheet.getParentScope());
168         setPrototype(getPrototype(getClass()));
169 
170         if (styleDeclaration == null) {
171             throw new IllegalStateException("styleDeclaration can't be null");
172         }
173         styleDeclaration_ = styleDeclaration;
174         parentStyleSheet_ = parentStyleSheet;
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, "", false, true, null);
969     }
970 
971     /**
972      * Gets the {@code margin} style attribute.
973      * @return the style attribute
974      */
975     @JsxGetter
976     public String getMargin() {
977         if (styleDeclaration_ == null) {
978             return null; // prototype
979         }
980         return styleDeclaration_.getMargin();
981     }
982 
983     /**
984      * Sets the {@code margin} style attribute.
985      * @param margin the new attribute
986      */
987     @JsxSetter
988     public void setMargin(final String margin) {
989         setStyleAttribute(Definition.MARGIN.getAttributeName(), margin);
990     }
991 
992     /**
993      * Gets the {@code marginBottom} style attribute.
994      * @return the style attribute
995      */
996     @JsxGetter
997     public String getMarginBottom() {
998         if (styleDeclaration_ == null) {
999             return null; // prototype
1000         }
1001         return styleDeclaration_.getMarginBottom();
1002     }
1003 
1004     /**
1005      * Sets the {@code marginBottom} style attribute.
1006      * @param marginBottom the new attribute
1007      */
1008     @JsxSetter
1009     public void setMarginBottom(final Object marginBottom) {
1010         setStyleLengthAttribute(Definition.MARGIN_BOTTOM.getAttributeName(), marginBottom, "", true, true, null);
1011     }
1012 
1013     /**
1014      * Gets the {@code marginLeft} style attribute.
1015      * @return the style attribute
1016      */
1017     @JsxGetter
1018     public String getMarginLeft() {
1019         if (styleDeclaration_ == null) {
1020             return null; // prototype
1021         }
1022         return styleDeclaration_.getMarginLeft();
1023     }
1024 
1025     /**
1026      * Sets the {@code marginLeft} style attribute.
1027      * @param marginLeft the new attribute
1028      */
1029     @JsxSetter
1030     public void setMarginLeft(final Object marginLeft) {
1031         setStyleLengthAttribute(Definition.MARGIN_LEFT.getAttributeName(), marginLeft, "", true, true, null);
1032     }
1033 
1034     /**
1035      * Gets the {@code marginRight} style attribute.
1036      * @return the style attribute
1037      */
1038     @JsxGetter
1039     public String getMarginRight() {
1040         if (styleDeclaration_ == null) {
1041             return null; // prototype
1042         }
1043         return styleDeclaration_.getMarginRight();
1044     }
1045 
1046     /**
1047      * Sets the {@code marginRight} style attribute.
1048      * @param marginRight the new attribute
1049      */
1050     @JsxSetter
1051     public void setMarginRight(final Object marginRight) {
1052         setStyleLengthAttribute(Definition.MARGIN_RIGHT.getAttributeName(), marginRight, "", true, true, null);
1053     }
1054 
1055     /**
1056      * Gets the {@code marginTop} style attribute.
1057      * @return the style attribute
1058      */
1059     @JsxGetter
1060     public String getMarginTop() {
1061         if (styleDeclaration_ == null) {
1062             return null; // prototype
1063         }
1064         return styleDeclaration_.getMarginTop();
1065     }
1066 
1067     /**
1068      * Sets the {@code marginTop} style attribute.
1069      * @param marginTop the new attribute
1070      */
1071     @JsxSetter
1072     public void setMarginTop(final Object marginTop) {
1073         setStyleLengthAttribute(Definition.MARGIN_TOP.getAttributeName(), marginTop, "", true, true, null);
1074     }
1075 
1076     /**
1077      * Gets the {@code maxHeight} style attribute.
1078      * @return the style attribute
1079      */
1080     @JsxGetter
1081     public String getMaxHeight() {
1082         if (styleDeclaration_ == null) {
1083             return null; // prototype
1084         }
1085         return styleDeclaration_.getMaxHeight();
1086     }
1087 
1088     /**
1089      * Sets the {@code maxHeight} style attribute.
1090      * @param maxHeight the new attribute
1091      */
1092     @JsxSetter
1093     public void setMaxHeight(final Object maxHeight) {
1094         setStyleLengthAttribute(Definition.MAX_HEIGHT.getAttributeName(), maxHeight, "", false, true, null);
1095     }
1096 
1097     /**
1098      * Gets the {@code maxWidth} style attribute.
1099      * @return the style attribute
1100      */
1101     @JsxGetter
1102     public String getMaxWidth() {
1103         if (styleDeclaration_ == null) {
1104             return null; // prototype
1105         }
1106         return styleDeclaration_.getMaxWidth();
1107     }
1108 
1109     /**
1110      * Sets the {@code maxWidth} style attribute.
1111      * @param maxWidth the new attribute
1112      */
1113     @JsxSetter
1114     public void setMaxWidth(final Object maxWidth) {
1115         setStyleLengthAttribute(Definition.MAX_WIDTH.getAttributeName(), maxWidth, "", false, true, null);
1116     }
1117 
1118     /**
1119      * Gets the {@code minHeight} style attribute.
1120      * @return the style attribute
1121      */
1122     @JsxGetter
1123     public String getMinHeight() {
1124         if (styleDeclaration_ == null) {
1125             return null; // prototype
1126         }
1127         return styleDeclaration_.getMinHeight();
1128     }
1129 
1130     /**
1131      * Sets the {@code minHeight} style attribute.
1132      * @param minHeight the new attribute
1133      */
1134     @JsxSetter
1135     public void setMinHeight(final Object minHeight) {
1136         setStyleLengthAttribute(Definition.MIN_HEIGHT.getAttributeName(), minHeight, "", true, true, null);
1137     }
1138 
1139     /**
1140      * Gets the {@code minWidth} style attribute.
1141      * @return the style attribute
1142      */
1143     @JsxGetter
1144     public String getMinWidth() {
1145         if (styleDeclaration_ == null) {
1146             return null; // prototype
1147         }
1148         return styleDeclaration_.getMinWidth();
1149     }
1150 
1151     /**
1152      * Sets the {@code minWidth} style attribute.
1153      * @param minWidth the new attribute
1154      */
1155     @JsxSetter
1156     public void setMinWidth(final Object minWidth) {
1157         setStyleLengthAttribute(Definition.MIN_WIDTH.getAttributeName(), minWidth, "", true, true, null);
1158     }
1159 
1160     /**
1161      * {@inheritDoc}
1162      */
1163     @Override
1164     public Object get(final String name, final Scriptable start) {
1165         if (this != start) {
1166             return super.get(name, start);
1167         }
1168 
1169         Scriptable prototype = getPrototype();
1170         while (prototype != null) {
1171             Object value = prototype.get(name, start);
1172             if (value != Scriptable.NOT_FOUND) {
1173                 return value;
1174             }
1175 
1176             final String camel = StringUtils.cssCamelize(name);
1177             if (!name.equals(camel)) {
1178                 value = prototype.get(camel, start);
1179                 if (value != Scriptable.NOT_FOUND) {
1180                     return value;
1181                 }
1182             }
1183             prototype = prototype.getPrototype();
1184         }
1185 
1186         final Definition style = StyleAttributes.getDefinition(name, getBrowserVersion());
1187         if (style != null) {
1188             return getStyleAttribute(style);
1189         }
1190 
1191         return super.get(name, start);
1192     }
1193 
1194     @Override
1195     public Object get(final int index, final Scriptable start) {
1196         if (index < 0) {
1197             return JavaScriptEngine.UNDEFINED;
1198         }
1199 
1200         final Map<String, StyleElement> style = getStyleMap();
1201         final int size = style.size();
1202         if (index >= size) {
1203             return JavaScriptEngine.UNDEFINED;
1204         }
1205         return style.keySet().toArray(new String[0])[index];
1206     }
1207 
1208     /**
1209      * Get the value for the style attribute.
1210      * @param definition the definition
1211      * @return the value
1212      */
1213     public final String getStyleAttribute(final Definition definition) {
1214         return getStyleAttribute(definition, true);
1215     }
1216 
1217     /**
1218      * Get the value for the style attribute.
1219      * This impl ignores the default getDefaultValueIfEmpty flag, but there is an overload
1220      * in {@link ComputedCSSStyleDeclaration}.
1221      * @param definition the definition
1222      * @param getDefaultValueIfEmpty whether to get the default value if empty or not
1223      * @return the value
1224      */
1225     public String getStyleAttribute(final Definition definition, final boolean getDefaultValueIfEmpty) {
1226         if (styleDeclaration_ == null) {
1227             return ""; // prototype
1228         }
1229         return styleDeclaration_.getStyleAttribute(definition, getDefaultValueIfEmpty);
1230     }
1231 
1232     @Override
1233     public void put(final String name, final Scriptable start, final Object value) {
1234         if (this != start) {
1235             super.put(name, start, value);
1236             return;
1237         }
1238 
1239         final Scriptable prototype = getPrototype();
1240         if (prototype != null && !"constructor".equals(name)) {
1241             if (prototype.get(name, start) != Scriptable.NOT_FOUND) {
1242                 prototype.put(name, start, value);
1243                 return;
1244             }
1245             final String camel = StringUtils.cssCamelize(name);
1246             if (!name.equals(camel) && prototype.get(camel, start) != Scriptable.NOT_FOUND) {
1247                 prototype.put(camel, start, value);
1248                 return;
1249             }
1250         }
1251 
1252         if (getDomNodeOrNull() != null) { // check if prototype or not
1253             final Definition style = StyleAttributes.getDefinition(name, getBrowserVersion());
1254             if (style != null) {
1255                 final String stringValue = JavaScriptEngine.toString(value);
1256                 setStyleAttribute(style.getAttributeName(), stringValue);
1257                 return;
1258             }
1259         }
1260 
1261         super.put(name, start, value);
1262     }
1263 
1264     @Override
1265     public boolean has(final String name, final Scriptable start) {
1266         if (this != start) {
1267             return super.has(name, start);
1268         }
1269 
1270         final BrowserVersion browserVersion = getBrowserVersion();
1271         if (browserVersion != null) {
1272             final Definition style = StyleAttributes.getDefinition(name, getBrowserVersion());
1273             if (style != null) {
1274                 return true;
1275             }
1276         }
1277 
1278         return super.has(name, start);
1279     }
1280 
1281     @Override
1282     public Object[] getIds() {
1283         final List<Object> ids = new ArrayList<>();
1284         for (final Definition styleAttribute : StyleAttributes.getDefinitions(getBrowserVersion())) {
1285             ids.add(styleAttribute.getPropertyName());
1286         }
1287         final Object[] normalIds = super.getIds();
1288         for (final Object o : normalIds) {
1289             if (!ids.contains(o)) {
1290                 ids.add(o);
1291             }
1292         }
1293         return ids.toArray();
1294     }
1295 
1296     /**
1297      * Gets the {@code opacity} style attribute.
1298      * @return the style attribute
1299      */
1300     @JsxGetter
1301     public String getOpacity() {
1302         if (styleDeclaration_ == null) {
1303             return null; // prototype
1304         }
1305         return styleDeclaration_.getOpacity();
1306     }
1307 
1308     /**
1309      * Sets the {@code opacity} style attribute.
1310      * @param opacity the new attribute
1311      */
1312     @JsxSetter
1313     public void setOpacity(final Object opacity) {
1314         if (JavaScriptEngine.isNaN(opacity)) {
1315             return;
1316         }
1317 
1318         final double doubleValue;
1319         if (opacity instanceof Number number) {
1320             doubleValue = number.doubleValue();
1321         }
1322         else {
1323             String valueString = JavaScriptEngine.toString(opacity);
1324 
1325             if (valueString.isEmpty()) {
1326                 setStyleAttribute(Definition.OPACITY.getAttributeName(), valueString);
1327                 return;
1328             }
1329 
1330             valueString = valueString.trim();
1331             try {
1332                 doubleValue = Double.parseDouble(valueString);
1333             }
1334             catch (final NumberFormatException e) {
1335                 // ignore wrong value
1336                 return;
1337             }
1338         }
1339 
1340         if (Double.isNaN(doubleValue) || Double.isInfinite(doubleValue)) {
1341             return;
1342         }
1343         setStyleAttribute(Definition.OPACITY.getAttributeName(), Double.toString(doubleValue));
1344     }
1345 
1346     /**
1347      * Gets the {@code outline} style attribute.
1348      * @return the style attribute
1349      */
1350     @JsxGetter
1351     public String getOutline() {
1352         if (styleDeclaration_ == null) {
1353             return null; // prototype
1354         }
1355         return styleDeclaration_.getOutline();
1356     }
1357 
1358     /**
1359      * Sets the {@code outline} style attribute.
1360      * @param outline the new attribute
1361      */
1362     @JsxSetter
1363     public void setOutline(final String outline) {
1364         setStyleAttribute(Definition.OUTLINE.getAttributeName(), outline);
1365     }
1366 
1367     /**
1368      * Gets the {@code outlineWidth} style attribute.
1369      * @return the style attribute
1370      */
1371     @JsxGetter
1372     public String getOutlineWidth() {
1373         if (styleDeclaration_ == null) {
1374             return null; // prototype
1375         }
1376         return styleDeclaration_.getOutlineWidth();
1377     }
1378 
1379     /**
1380      * Sets the {@code outlineWidth} style attribute.
1381      * @param outlineWidth the new attribute
1382      */
1383     @JsxSetter
1384     public void setOutlineWidth(final Object outlineWidth) {
1385         setStyleLengthAttribute(Definition.OUTLINE_WIDTH.getAttributeName(), outlineWidth, "",
1386                 false, false, THIN_MED_THICK);
1387     }
1388 
1389     /**
1390      * Gets the {@code padding} style attribute.
1391      * @return the style attribute
1392      */
1393     @JsxGetter
1394     public String getPadding() {
1395         if (styleDeclaration_ == null) {
1396             return null; // prototype
1397         }
1398         return styleDeclaration_.getPadding();
1399     }
1400 
1401     /**
1402      * Sets the {@code padding} style attribute.
1403      * @param padding the new attribute
1404      */
1405     @JsxSetter
1406     public void setPadding(final String padding) {
1407         setStyleAttribute(Definition.PADDING.getAttributeName(), padding);
1408     }
1409 
1410     /**
1411      * Gets the {@code paddingBottom} style attribute.
1412      * @return the style attribute
1413      */
1414     @JsxGetter
1415     public String getPaddingBottom() {
1416         if (styleDeclaration_ == null) {
1417             return null; // prototype
1418         }
1419         return styleDeclaration_.getPaddingBottom();
1420     }
1421 
1422     /**
1423      * Sets the {@code paddingBottom} style attribute.
1424      * @param paddingBottom the new attribute
1425      */
1426     @JsxSetter
1427     public void setPaddingBottom(final Object paddingBottom) {
1428         setStyleLengthAttribute(Definition.PADDING_BOTTOM.getAttributeName(),
1429                 paddingBottom, "", false, true, null);
1430     }
1431 
1432     /**
1433      * Gets the {@code paddingLeft} style attribute.
1434      * @return the style attribute
1435      */
1436     @JsxGetter
1437     public String getPaddingLeft() {
1438         if (styleDeclaration_ == null) {
1439             return null; // prototype
1440         }
1441         return styleDeclaration_.getPaddingLeft();
1442     }
1443 
1444     /**
1445      * Sets the {@code paddingLeft} style attribute.
1446      * @param paddingLeft the new attribute
1447      */
1448     @JsxSetter
1449     public void setPaddingLeft(final Object paddingLeft) {
1450         setStyleLengthAttribute(Definition.PADDING_LEFT.getAttributeName(), paddingLeft, "", false, true, null);
1451     }
1452 
1453     /**
1454      * Gets the {@code paddingRight} style attribute.
1455      * @return the style attribute
1456      */
1457     @JsxGetter
1458     public String getPaddingRight() {
1459         if (styleDeclaration_ == null) {
1460             return null; // prototype
1461         }
1462         return styleDeclaration_.getPaddingRight();
1463     }
1464 
1465     /**
1466      * Sets the {@code paddingRight} style attribute.
1467      * @param paddingRight the new attribute
1468      */
1469     @JsxSetter
1470     public void setPaddingRight(final Object paddingRight) {
1471         setStyleLengthAttribute(Definition.PADDING_RIGHT.getAttributeName(),
1472                 paddingRight, "", false, true, null);
1473     }
1474 
1475     /**
1476      * Gets the {@code paddingTop} style attribute.
1477      * @return the style attribute
1478      */
1479     @JsxGetter
1480     public String getPaddingTop() {
1481         if (styleDeclaration_ == null) {
1482             return null; // prototype
1483         }
1484         return styleDeclaration_.getPaddingTop();
1485     }
1486 
1487     /**
1488      * Sets the {@code paddingTop} style attribute.
1489      * @param paddingTop the new attribute
1490      */
1491     @JsxSetter
1492     public void setPaddingTop(final Object paddingTop) {
1493         setStyleLengthAttribute(Definition.PADDING_TOP.getAttributeName(), paddingTop, "", false, true, null);
1494     }
1495 
1496     /**
1497      * Returns the CSSRule that is the parent of this style block or <code>null</code> if this CSSStyleDeclaration is
1498      * not attached to a CSSRule.
1499      * @return the CSSRule that is the parent of this style block or <code>null</code> if this CSSStyleDeclaration is
1500      *      not attached to a CSSRule
1501      */
1502     @JsxGetter
1503     public CSSRule getParentRule() {
1504         final AbstractCSSRuleImpl parentRule = styleDeclaration_.getParentRule();
1505         if (parentRule != null) {
1506             return CSSRule.create(parentStyleSheet_, parentRule);
1507         }
1508         return null;
1509     }
1510 
1511     /**
1512      * Nothing.
1513      * @param parentRule ignored
1514      */
1515     @JsxSetter
1516     public void setParentRule(final CSSRule parentRule) {
1517         // nothing to do
1518     }
1519 
1520     /**
1521      * Gets the {@code right} style attribute.
1522      * @return the style attribute
1523      */
1524     @JsxGetter
1525     public String getRight() {
1526         if (styleDeclaration_ == null) {
1527             return null; // prototype
1528         }
1529         return styleDeclaration_.getRight();
1530     }
1531 
1532     /**
1533      * Sets the {@code right} style attribute.
1534      * @param right the new attribute
1535      */
1536     @JsxSetter
1537     public void setRight(final Object right) {
1538         setStyleLengthAttribute(Definition.RIGHT.getAttributeName(), right, "", true, true, null);
1539     }
1540 
1541     /**
1542      * Gets the {@code rubyAlign} style attribute.
1543      * @return the style attribute
1544      */
1545     @JsxGetter
1546     public String getRubyAlign() {
1547         if (styleDeclaration_ == null) {
1548             return null; // prototype
1549         }
1550         return styleDeclaration_.getRubyAlign();
1551     }
1552 
1553     /**
1554      * Sets the {@code rubyAlign} style attribute.
1555      * @param rubyAlign the new attribute
1556      */
1557     @JsxSetter
1558     public void setRubyAlign(final String rubyAlign) {
1559         setStyleAttribute(Definition.RUBY_ALIGN.getAttributeName(), rubyAlign);
1560     }
1561 
1562     /**
1563      * Gets the {@code size} style attribute.
1564      * @return the style attribute
1565      */
1566     @JsxGetter({CHROME, EDGE})
1567     public String getSize() {
1568         if (styleDeclaration_ == null) {
1569             return null; // prototype
1570         }
1571         return styleDeclaration_.getSize();
1572     }
1573 
1574     /**
1575      * Sets the {@code size} style attribute.
1576      * @param size the new attribute
1577      */
1578     @JsxSetter({CHROME, EDGE})
1579     public void setSize(final String size) {
1580         setStyleAttribute(Definition.SIZE.getAttributeName(), size);
1581     }
1582 
1583     /**
1584      * Gets the {@code textIndent} style attribute.
1585      * @return the style attribute
1586      */
1587     @JsxGetter
1588     public String getTextIndent() {
1589         if (styleDeclaration_ == null) {
1590             return null; // prototype
1591         }
1592         return styleDeclaration_.getTextIndent();
1593     }
1594 
1595     /**
1596      * Sets the {@code textIndent} style attribute.
1597      * @param textIndent the new attribute
1598      */
1599     @JsxSetter
1600     public void setTextIndent(final Object textIndent) {
1601         setStyleLengthAttribute(Definition.TEXT_INDENT.getAttributeName(), textIndent, "", false, true, null);
1602     }
1603 
1604     /**
1605      * Gets the {@code top} style attribute.
1606      * @return the style attribute
1607      */
1608     @JsxGetter
1609     public String getTop() {
1610         if (styleDeclaration_ == null) {
1611             return null; // prototype
1612         }
1613         return styleDeclaration_.getTop();
1614     }
1615 
1616     /**
1617      * Sets the {@code top} style attribute.
1618      * @param top the new attribute
1619      */
1620     @JsxSetter
1621     public void setTop(final Object top) {
1622         setStyleLengthAttribute(Definition.TOP.getAttributeName(), top, "", true, true, null);
1623     }
1624 
1625     /**
1626      * Gets the {@code verticalAlign} style attribute.
1627      * @return the style attribute
1628      */
1629     @JsxGetter
1630     public String getVerticalAlign() {
1631         if (styleDeclaration_ == null) {
1632             return null; // prototype
1633         }
1634         return styleDeclaration_.getVerticalAlign();
1635     }
1636 
1637     /**
1638      * Sets the {@code verticalAlign} style attribute.
1639      * @param verticalAlign the new attribute
1640      */
1641     @JsxSetter
1642     public void setVerticalAlign(final Object verticalAlign) {
1643         setStyleLengthAttribute(Definition.VERTICAL_ALIGN.getAttributeName(),
1644                 verticalAlign, "", false, true, ALIGN_KEYWORDS);
1645     }
1646 
1647     /**
1648      * Gets the {@code width} style attribute.
1649      * @return the style attribute
1650      */
1651     @JsxGetter
1652     public String getWidth() {
1653         if (styleDeclaration_ == null) {
1654             return null; // prototype
1655         }
1656         return styleDeclaration_.getWidth();
1657     }
1658 
1659     /**
1660      * Sets the {@code width} style attribute.
1661      * @param width the new attribute
1662      */
1663     @JsxSetter
1664     public void setWidth(final Object width) {
1665         setStyleLengthAttribute(Definition.WIDTH.getAttributeName(), width, "", true, true, null);
1666     }
1667 
1668     /**
1669      * Gets the {@code widows} style attribute.
1670      * @return the style attribute
1671      */
1672     @JsxGetter({CHROME, EDGE})
1673     public String getWidows() {
1674         if (styleDeclaration_ == null) {
1675             return null; // prototype
1676         }
1677         return styleDeclaration_.getWidows();
1678     }
1679 
1680     /**
1681      * Sets the {@code widows} style attribute.
1682      * @param widows the new attribute
1683      */
1684     @JsxSetter({CHROME, EDGE})
1685     public void setWidows(final String widows) {
1686         if (getBrowserVersion().hasFeature(CSS_BACKGROUND_INITIAL)) {
1687             try {
1688                 if (Integer.parseInt(widows) <= 0) {
1689                     return;
1690                 }
1691             }
1692             catch (final NumberFormatException e) {
1693                 return;
1694             }
1695         }
1696         setStyleAttribute(Definition.WIDOWS.getAttributeName(), widows);
1697     }
1698 
1699     /**
1700      * Gets the {@code orphans} style attribute.
1701      * @return the style attribute
1702      */
1703     @JsxGetter({CHROME, EDGE})
1704     public String getOrphans() {
1705         if (styleDeclaration_ == null) {
1706             return null; // prototype
1707         }
1708         return styleDeclaration_.getOrphans();
1709     }
1710 
1711     /**
1712      * Sets the {@code orphans} style attribute.
1713      * @param orphans the new attribute
1714      */
1715     @JsxSetter({CHROME, EDGE})
1716     public void setOrphans(final String orphans) {
1717         if (getBrowserVersion().hasFeature(CSS_BACKGROUND_INITIAL)) {
1718             try {
1719                 if (Integer.parseInt(orphans) <= 0) {
1720                     return;
1721                 }
1722             }
1723             catch (final NumberFormatException e) {
1724                 return;
1725             }
1726         }
1727         setStyleAttribute(Definition.ORPHANS.getAttributeName(), orphans);
1728     }
1729 
1730     /**
1731      * Gets the {@code position} style attribute.
1732      * @return the style attribute
1733      */
1734     @JsxGetter
1735     public String getPosition() {
1736         if (styleDeclaration_ == null) {
1737             return null; // prototype
1738         }
1739         return styleDeclaration_.getPosition();
1740     }
1741 
1742     /**
1743      * Sets the {@code position} style attribute.
1744      * @param position the new attribute
1745      */
1746     @JsxSetter
1747     public void setPosition(final String position) {
1748         if (position.isEmpty() || STATIC.equalsIgnoreCase(position) || ABSOLUTE.equalsIgnoreCase(position)
1749                 || FIXED.equalsIgnoreCase(position) || RELATIVE.equalsIgnoreCase(position)
1750                 || INITIAL.equalsIgnoreCase(position) || INHERIT.equalsIgnoreCase(position)) {
1751             setStyleAttribute(Definition.POSITION.getAttributeName(), position.toLowerCase(Locale.ROOT));
1752         }
1753     }
1754 
1755     /**
1756      * Gets the {@code wordSpacing} style attribute.
1757      * @return the style attribute
1758      */
1759     @JsxGetter
1760     public String getWordSpacing() {
1761         if (styleDeclaration_ == null) {
1762             return null; // prototype
1763         }
1764         return styleDeclaration_.getWordSpacing();
1765     }
1766 
1767     /**
1768      * Sets the {@code wordSpacing} style attribute.
1769      * @param wordSpacing the new attribute
1770      */
1771     @JsxSetter
1772     public void setWordSpacing(final Object wordSpacing) {
1773         setStyleLengthAttribute(Definition.WORD_SPACING.getAttributeName(), wordSpacing, "",
1774                 false, true, null);
1775     }
1776 
1777     /**
1778      * Gets the {@code zIndex} style attribute.
1779      * @return the style attribute
1780      */
1781     @JsxGetter
1782     public String getZIndex() {
1783         if (styleDeclaration_ == null) {
1784             return null; // prototype
1785         }
1786         return styleDeclaration_.getZIndex();
1787     }
1788 
1789     /**
1790      * Sets the {@code zIndex} style attribute.
1791      * @param zIndex the new attribute
1792      */
1793     @JsxSetter
1794     public void setZIndex(final Object zIndex) {
1795         // empty
1796         if (zIndex == null || StringUtils.isEmptyOrNull(zIndex.toString())) {
1797             setStyleAttribute(Definition.Z_INDEX_.getAttributeName(), "");
1798             return;
1799         }
1800         // undefined
1801         if (JavaScriptEngine.isUndefined(zIndex)) {
1802             return;
1803         }
1804 
1805         // string
1806         if (zIndex instanceof Number number) {
1807             if (number.doubleValue() % 1 == 0) {
1808                 setStyleAttribute(Definition.Z_INDEX_.getAttributeName(), Integer.toString(number.intValue()));
1809             }
1810             return;
1811         }
1812         try {
1813             final int i = Integer.parseInt(zIndex.toString());
1814             setStyleAttribute(Definition.Z_INDEX_.getAttributeName(), Integer.toString(i));
1815         }
1816         catch (final NumberFormatException ignored) {
1817             // ignore
1818         }
1819     }
1820 
1821     /**
1822      * Gets the value of the specified property of the style.
1823      * @param name the style property name
1824      * @return empty string if nothing found
1825      */
1826     @JsxFunction
1827     public String getPropertyValue(final String name) {
1828         if (name != null && name.contains("-")) {
1829             final Object value = getProperty(this, StringUtils.cssCamelize(name));
1830             if (value instanceof String string) {
1831                 return string;
1832             }
1833         }
1834 
1835         return styleDeclaration_.getStyleAttribute(name);
1836     }
1837 
1838     /**
1839      * Gets the value of the specified property of the style.
1840      * @param name the style property name
1841      * @return empty string if nothing found
1842      */
1843     @JsxFunction
1844     public String getPropertyPriority(final String name) {
1845         return getStylePriority(name);
1846     }
1847 
1848     /**
1849      * Sets the value of the specified property.
1850      *
1851      * @param name the name of the attribute
1852      * @param value the value to assign to the attribute
1853      * @param important may be null
1854      */
1855     @JsxFunction
1856     public void setProperty(final String name, final Object value, final String important) {
1857         String imp = "";
1858         if (!StringUtils.isEmptyOrNull(important) && !"null".equals(important)) {
1859             if (!StyleElement.PRIORITY_IMPORTANT.equalsIgnoreCase(important)) {
1860                 return;
1861             }
1862             imp = StyleElement.PRIORITY_IMPORTANT;
1863         }
1864 
1865         if (LENGTH_PROPERTIES_FFFF.contains(name)) {
1866             setStyleLengthAttribute(name, value, imp, false, false, null);
1867         }
1868         else if (LENGTH_PROPERTIES_TTFF.contains(name)) {
1869             setStyleLengthAttribute(name, value, imp, true, true, null);
1870         }
1871         else if (LENGTH_PROPERTIES_FTFF.contains(name)) {
1872             setStyleLengthAttribute(name, value, imp, false, true, null);
1873         }
1874         else if (Definition.OUTLINE_WIDTH.getAttributeName().equals(name)) {
1875             setStyleLengthAttribute(Definition.OUTLINE_WIDTH.getAttributeName(),
1876                     value, imp, false, false, THIN_MED_THICK);
1877         }
1878         else if (Definition.LETTER_SPACING.getAttributeName().equals(name)) {
1879             setStyleLengthAttribute(Definition.LETTER_SPACING.getAttributeName(), value, imp,
1880                     false, true, null);
1881         }
1882         else if (Definition.WORD_SPACING.getAttributeName().equals(name)) {
1883             setStyleLengthAttribute(Definition.WORD_SPACING.getAttributeName(), value, imp,
1884                     false, true, null);
1885         }
1886         else if (Definition.VERTICAL_ALIGN.getAttributeName().equals(name)) {
1887             setStyleLengthAttribute(Definition.VERTICAL_ALIGN.getAttributeName(), value, imp, false, true, null);
1888         }
1889         else {
1890             setStyleAttribute(name, JavaScriptEngine.toString(value), imp);
1891         }
1892     }
1893 
1894     /**
1895      * Removes the named property.
1896      * @param name the name of the property to remove
1897      * @return the value deleted
1898      */
1899     @JsxFunction
1900     public String removeProperty(final Object name) {
1901         return removeStyleAttribute(JavaScriptEngine.toString(name));
1902     }
1903 
1904     /**
1905      * Returns if the specified token is a length.
1906      * @param token the token to check
1907      * @return whether the token is a length or not
1908      */
1909     static boolean isLength(String token) {
1910         if (token.endsWith("em") || token.endsWith("ex") || token.endsWith("px") || token.endsWith("in")
1911             || token.endsWith("cm") || token.endsWith("mm") || token.endsWith("pt") || token.endsWith("pc")
1912             || token.endsWith("%")) {
1913 
1914             if (token.endsWith("%")) {
1915                 token = token.substring(0, token.length() - 1);
1916             }
1917             else {
1918                 token = token.substring(0, token.length() - 2);
1919             }
1920             try {
1921                 Double.parseDouble(token);
1922                 return true;
1923             }
1924             catch (final NumberFormatException ignored) {
1925                 // ignore
1926             }
1927         }
1928         return false;
1929     }
1930 
1931     /**
1932      * {@inheritDoc}
1933      */
1934     @Override
1935     public String toString() {
1936         if (styleDeclaration_ == null) {
1937             return "CSSStyleDeclaration for 'null'"; // for instance on prototype
1938         }
1939 
1940         return "CSSStyleDeclaration for '" + styleDeclaration_ + "'";
1941     }
1942 
1943     /**
1944      * Sets the style attribute which should be treated as an integer in pixels.
1945      * @param name the attribute name
1946      * @param value the attribute value
1947      * @param important important value
1948      * @param auto true if auto is supported
1949      * @param percent true if percent is supported
1950      * @param validValues valid values
1951      */
1952     private void setStyleLengthAttribute(final String name, final Object value, final String important,
1953                 final boolean auto, final boolean percent, final String[] validValues) {
1954         if (JavaScriptEngine.isNaN(value)) {
1955             return;
1956         }
1957 
1958         if (value instanceof Number) {
1959             return;
1960         }
1961 
1962         String valueString = JavaScriptEngine.toString(value);
1963         if (null == value) {
1964             valueString = "";
1965         }
1966 
1967         if (StringUtils.isEmptyOrNull(valueString)) {
1968             setStyleAttribute(name, valueString, important);
1969             return;
1970         }
1971 
1972         if ((auto && AUTO.equals(valueString))
1973                 || INITIAL.equals(valueString)
1974                 || INHERIT.equals(valueString)) {
1975             setStyleAttribute(name, valueString, important);
1976             return;
1977         }
1978 
1979         if (validValues != null && ArrayUtils.contains(validValues, valueString)) {
1980             setStyleAttribute(name, valueString, important);
1981             return;
1982         }
1983 
1984         String unit = "px";
1985         if (percent && valueString.endsWith("%")) {
1986             unit = valueString.substring(valueString.length() - 1);
1987             valueString = valueString.substring(0, valueString.length() - 1);
1988         }
1989         else if (valueString.endsWith("px")
1990             || valueString.endsWith("em")
1991             || valueString.endsWith("ex")
1992             || valueString.endsWith("pt")
1993             || valueString.endsWith("cm")
1994             || valueString.endsWith("mm")
1995             || valueString.endsWith("in")
1996             || valueString.endsWith("pc")
1997             || valueString.endsWith("ch")
1998             || valueString.endsWith("vh")
1999             || valueString.endsWith("vw")) {
2000             unit = valueString.substring(valueString.length() - 2);
2001             valueString = valueString.substring(0, valueString.length() - 2);
2002         }
2003         else if (valueString.endsWith("rem")
2004             || valueString.endsWith("vmin")
2005             || valueString.endsWith("vmax")
2006             || valueString.endsWith("dvw")
2007             || valueString.endsWith("dvh")
2008             || valueString.endsWith("lvw")
2009             || valueString.endsWith("lvh")
2010             || valueString.endsWith("svw")
2011             || valueString.endsWith("svh")) {
2012             unit = valueString.substring(valueString.length() - 3);
2013             valueString = valueString.substring(0, valueString.length() - 3);
2014         }
2015         else if (valueString.endsWith("dvmin")
2016             || valueString.endsWith("dvmax")
2017             || valueString.endsWith("lvmin")
2018             || valueString.endsWith("lvmax")
2019             || valueString.endsWith("svmin")
2020             || valueString.endsWith("svmax")) {
2021             unit = valueString.substring(valueString.length() - 5);
2022             valueString = valueString.substring(0, valueString.length() - 5);
2023         }
2024         else {
2025             return;
2026         }
2027 
2028         if (!valueString.equals(valueString.trim())) {
2029             // we have a unit but surrounding blanks
2030             return;
2031         }
2032         final double doubleValue = JavaScriptEngine.toNumber(valueString);
2033 
2034         try {
2035             if (Double.isNaN(doubleValue) || Double.isInfinite(doubleValue)) {
2036                 return;
2037             }
2038 
2039             final String valueStr;
2040             if (doubleValue % 1 == 0) {
2041                 valueStr = (int) doubleValue + unit;
2042             }
2043             else {
2044                 valueStr = doubleValue + unit;
2045             }
2046 
2047             setStyleAttribute(name, valueStr, important);
2048         }
2049         catch (final Exception ignored) {
2050             // ignore
2051         }
2052     }
2053 }