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 java.io.IOException;
18  import java.io.StringReader;
19  import java.util.ArrayList;
20  import java.util.List;
21  
22  import org.apache.commons.logging.Log;
23  import org.apache.commons.logging.LogFactory;
24  import org.htmlunit.corejs.javascript.VarScope;
25  import org.htmlunit.css.CssStyleSheet;
26  import org.htmlunit.cssparser.dom.AbstractCSSRuleImpl;
27  import org.htmlunit.cssparser.dom.CSSCharsetRuleImpl;
28  import org.htmlunit.cssparser.dom.CSSRuleListImpl;
29  import org.htmlunit.cssparser.parser.InputSource;
30  import org.htmlunit.javascript.JavaScriptEngine;
31  import org.htmlunit.javascript.configuration.JsxClass;
32  import org.htmlunit.javascript.configuration.JsxConstructor;
33  import org.htmlunit.javascript.configuration.JsxFunction;
34  import org.htmlunit.javascript.configuration.JsxGetter;
35  import org.htmlunit.javascript.host.html.HTMLElement;
36  import org.w3c.dom.DOMException;
37  
38  /**
39   * A JavaScript object for {@code CSSStyleSheet}.
40   *
41   * @see <a href="http://msdn2.microsoft.com/en-us/library/ms535871.aspx">MSDN doc</a>
42   * @author Marc Guillemot
43   * @author Daniel Gredler
44   * @author Ahmed Ashour
45   * @author Ronald Brill
46   * @author Guy Burton
47   * @author Frank Danek
48   * @author Carsten Steul
49   */
50  @JsxClass
51  public class CSSStyleSheet extends StyleSheet {
52  
53      private static final Log LOG = LogFactory.getLog(CSSStyleSheet.class);
54  
55      /** The parsed stylesheet which this host object wraps. */
56      private CssStyleSheet styleSheet_;
57  
58      /** The collection of rules defined in this style sheet. */
59      private CSSRuleList cssRules_;
60      private List<Integer> cssRulesIndexFix_;
61  
62      /**
63       * Creates a new empty stylesheet.
64       */
65      public CSSStyleSheet() {
66          super(null);
67          styleSheet_ = new CssStyleSheet(null, (InputSource) null, null);
68      }
69  
70      /**
71       * Creates a new empty stylesheet.
72       */
73      @Override
74      @JsxConstructor
75      public void jsConstructor() {
76          super.jsConstructor();
77          styleSheet_ = new CssStyleSheet(null, (InputSource) null, null);
78      }
79  
80      /**
81       * Creates a new stylesheet representing the CSS stylesheet for the specified input source.
82       * @param element the owning node
83       * @param source the input source which contains the CSS stylesheet which this stylesheet host object represents
84       * @param uri this stylesheet's URI (used to resolved contained @import rules)
85       */
86      public CSSStyleSheet(final HTMLElement element, final InputSource source, final String uri) {
87          super(element);
88  
89          setParentScope(getTopLevelScope(element.getParentScope()));
90          setPrototype(getPrototype(CSSStyleSheet.class));
91  
92          styleSheet_ = new CssStyleSheet(element.getDomNodeOrDie(), source, uri);
93      }
94  
95      /**
96       * Creates a new stylesheet representing the CSS stylesheet for the specified input source.
97       * @param element the owning node
98       * @param styleSheet the source which contains the CSS stylesheet which this stylesheet host object represents
99       * @param uri this stylesheet's URI (used to resolved contained @import rules)
100      */
101     public CSSStyleSheet(final HTMLElement element, final String styleSheet, final String uri) {
102         super(element);
103 
104         CssStyleSheet css = null;
105         try (InputSource source = new InputSource(new StringReader(styleSheet))) {
106             css = new CssStyleSheet(element.getDomNodeOrDie(), source, uri);
107         }
108         catch (final IOException e) {
109             LOG.error(e.getMessage(), e);
110         }
111 
112         setParentScope(element.getParentScope());
113         setPrototype(getPrototype(CSSStyleSheet.class));
114 
115         styleSheet_ = css;
116     }
117 
118     /**
119      * Creates a new stylesheet representing the specified CSS stylesheet.
120      * @param element the owning node
121      * @param parentScope the parent scope
122      * @param cssStyleSheet the CSS stylesheet which this stylesheet host object represents
123      */
124     public CSSStyleSheet(final HTMLElement element, final VarScope parentScope,
125             final CssStyleSheet cssStyleSheet) {
126         super(element);
127 
128         setParentScope(parentScope);
129         setPrototype(getPrototype(CSSStyleSheet.class));
130         styleSheet_ = cssStyleSheet;
131     }
132 
133     /**
134      * Returns the wrapped stylesheet.
135      * @return the wrapped stylesheet
136      */
137     public CssStyleSheet getCssStyleSheet() {
138         return styleSheet_;
139     }
140 
141     /**
142      * Retrieves the collection of rules defined in this style sheet.
143      * @return the collection of rules defined in this style sheet
144      */
145     @JsxGetter
146     public CSSRuleList getRules() {
147         return getCssRules();
148     }
149 
150     /**
151      * Returns the collection of rules defined in this style sheet.
152      * @return the collection of rules defined in this style sheet
153      */
154     @JsxGetter
155     public CSSRuleList getCssRules() {
156         initCssRules();
157         return cssRules_;
158     }
159 
160     /**
161      * Inserts a new rule.
162      * @param rule the CSS rule
163      * @param position the position at which to insert the rule
164      * @see <a href="http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleSheet">DOM level 2</a>
165      * @return the position of the inserted rule
166      */
167     @JsxFunction
168     public int insertRule(final String rule, final int position) {
169         try {
170             initCssRules();
171             getCssStyleSheet().getWrappedSheet().insertRule(rule, fixIndex(position));
172             refreshCssRules();
173             return position;
174         }
175         catch (final DOMException e) {
176             // in case of error try with an empty rule
177             final int pos = rule.indexOf('{');
178             if (pos > -1) {
179                 final String newRule = rule.substring(0, pos) + "{}";
180                 try {
181                     getCssStyleSheet().getWrappedSheet().insertRule(newRule, fixIndex(position));
182                     refreshCssRules();
183                     return position;
184                 }
185                 catch (final DOMException ex) {
186                     throw JavaScriptEngine.asJavaScriptException(getWindow(), ex.getMessage(), ex.code);
187                 }
188             }
189             throw JavaScriptEngine.asJavaScriptException(getWindow(), e.getMessage(), e.code);
190         }
191     }
192 
193     private void refreshCssRules() {
194         if (cssRules_ == null) {
195             return;
196         }
197 
198         cssRules_.clearRules();
199         cssRulesIndexFix_.clear();
200 
201         final CSSRuleListImpl ruleList = getCssStyleSheet().getWrappedSheet().getCssRules();
202         final List<AbstractCSSRuleImpl> rules = ruleList.getRules();
203         int pos = 0;
204         for (final AbstractCSSRuleImpl rule : rules) {
205             if (rule instanceof CSSCharsetRuleImpl) {
206                 cssRulesIndexFix_.add(pos);
207                 continue;
208             }
209 
210             final CSSRule cssRule = CSSRule.create(this, rule);
211             if (null == cssRule) {
212                 cssRulesIndexFix_.add(pos);
213             }
214             else {
215                 cssRules_.addRule(cssRule);
216             }
217             pos++;
218         }
219 
220         // reset our index also
221         getCssStyleSheet().getWrappedSheet().resetRuleIndex();
222     }
223 
224     private int fixIndex(int index) {
225         for (final int fix : cssRulesIndexFix_) {
226             if (fix > index) {
227                 return index;
228             }
229             index++;
230         }
231         return index;
232     }
233 
234     /**
235      * Deletes an existing rule.
236      * @param position the position of the rule to be deleted
237      * @see <a href="http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleSheet">DOM level 2</a>
238      */
239     @JsxFunction
240     public void deleteRule(final int position) {
241         try {
242             initCssRules();
243             getCssStyleSheet().getWrappedSheet().deleteRule(fixIndex(position));
244             refreshCssRules();
245         }
246         catch (final DOMException e) {
247             throw JavaScriptEngine.asJavaScriptException(getWindow(), e.getMessage(), e.code);
248         }
249     }
250 
251     /**
252      * Adds a new rule.
253      * @see <a href="http://msdn.microsoft.com/en-us/library/aa358796.aspx">MSDN</a>
254      * @param selector the selector name
255      * @param rule the rule
256      * @return always return -1 as of MSDN documentation
257      */
258     @JsxFunction
259     public int addRule(final String selector, final String rule) {
260         String completeRule = selector + " {" + rule + "}";
261         try {
262             initCssRules();
263             getCssStyleSheet().getWrappedSheet().insertRule(completeRule,
264                     getCssStyleSheet().getWrappedSheet().getCssRules().getLength());
265             refreshCssRules();
266         }
267         catch (final DOMException e) {
268             // in case of error try with an empty rule
269             completeRule = selector + " {}";
270             try {
271                 getCssStyleSheet().getWrappedSheet().insertRule(completeRule,
272                         getCssStyleSheet().getWrappedSheet().getCssRules().getLength());
273                 refreshCssRules();
274             }
275             catch (final DOMException ex) {
276                 throw JavaScriptEngine.asJavaScriptException(getWindow(), ex.getMessage(), ex.code);
277             }
278         }
279         return -1;
280     }
281 
282     /**
283      * Deletes an existing rule.
284      * @param position the position of the rule to be deleted
285      * @see <a href="http://msdn.microsoft.com/en-us/library/ms531195(v=VS.85).aspx">MSDN</a>
286      */
287     @JsxFunction
288     public void removeRule(final int position) {
289         try {
290             initCssRules();
291             getCssStyleSheet().getWrappedSheet().deleteRule(fixIndex(position));
292             refreshCssRules();
293         }
294         catch (final DOMException e) {
295             throw JavaScriptEngine.throwAsScriptRuntimeEx(e);
296         }
297     }
298 
299     /**
300      * Returns this stylesheet's URI (used to resolved contained @import rules).
301      * For inline styles this is the page uri.
302      * @return this stylesheet's URI (used to resolved contained @import rules)
303      */
304     @Override
305     public String getUri() {
306         return getCssStyleSheet().getUri();
307     }
308 
309     private void initCssRules() {
310         if (cssRules_ == null) {
311             cssRules_ = new CSSRuleList(this);
312             cssRulesIndexFix_ = new ArrayList<>();
313             refreshCssRules();
314         }
315     }
316 }