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 java.util.ArrayList;
18  import java.util.List;
19  
20  import org.htmlunit.cssparser.dom.AbstractCSSRuleImpl;
21  import org.htmlunit.cssparser.dom.CSSCharsetRuleImpl;
22  import org.htmlunit.cssparser.dom.CSSMediaRuleImpl;
23  import org.htmlunit.cssparser.dom.CSSRuleListImpl;
24  import org.htmlunit.javascript.JavaScriptEngine;
25  import org.htmlunit.javascript.configuration.JsxClass;
26  import org.htmlunit.javascript.configuration.JsxConstructor;
27  import org.htmlunit.javascript.configuration.JsxFunction;
28  import org.htmlunit.javascript.configuration.JsxGetter;
29  import org.w3c.dom.DOMException;
30  
31  /**
32   * A JavaScript object for {@code CSSGroupingRule}.
33   *
34   * @author Ahmed Ashour
35   * @author Ronald Brill
36   * @author Frank Danek
37   * @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/CSSGroupingRule">MDN doc</a>
38   */
39  @JsxClass
40  public class CSSGroupingRule extends CSSRule {
41  
42      /** The collection of rules defined in this rule. */
43      private CSSRuleList cssRules_;
44      private List<Integer> cssRulesIndexFix_;
45  
46      /**
47       * Creates a new instance.
48       */
49      public CSSGroupingRule() {
50          super();
51      }
52  
53      /**
54       * Creates an instance.
55       */
56      @JsxConstructor
57      @Override
58      public void jsConstructor() {
59          super.jsConstructor();
60      }
61  
62      /**
63       * Creates a new instance.
64       * @param stylesheet the Stylesheet of this rule.
65       * @param rule the wrapped rule
66       */
67      protected CSSGroupingRule(final CSSStyleSheet stylesheet, final CSSMediaRuleImpl rule) {
68          super(stylesheet, rule);
69      }
70  
71      /**
72       * Returns the collection of rules defined in this rule.
73       * @return the collection of rules defined in this rule
74       */
75      @JsxGetter
76      public CSSRuleList getCssRules() {
77          initCssRules();
78          return cssRules_;
79      }
80  
81      /**
82       * Inserts a new rule.
83       * @param rule the CSS rule
84       * @param position the position at which to insert the rule
85       * @return the position of the inserted rule
86       */
87      @JsxFunction
88      public int insertRule(final String rule, final Object position) {
89          final int positionInt;
90          if (position == null) {
91              positionInt = 0;
92          }
93          else if (JavaScriptEngine.isUndefined(position)) {
94              positionInt = 0;
95          }
96          else {
97              positionInt = JavaScriptEngine.toInt32(position);
98          }
99  
100         try {
101             initCssRules();
102             getGroupingRule().insertRule(rule, fixIndex(positionInt));
103             refreshCssRules();
104             return positionInt;
105         }
106         catch (final DOMException e) {
107             // in case of error try with an empty rule
108             final int pos = rule.indexOf('{');
109             if (pos > -1) {
110                 final String newRule = rule.substring(0, pos) + "{}";
111                 try {
112                     getGroupingRule().insertRule(newRule, fixIndex(positionInt));
113                     refreshCssRules();
114                     return positionInt;
115                 }
116                 catch (final DOMException ex) {
117                     throw JavaScriptEngine.asJavaScriptException(
118                             getWindow(), ex.getMessage(), ex.code);
119                 }
120             }
121             throw JavaScriptEngine.asJavaScriptException(
122                     getWindow(),
123                     e.getMessage(),
124                     org.htmlunit.javascript.host.dom.DOMException.SYNTAX_ERR);
125         }
126     }
127 
128     /**
129      * Deletes an existing rule.
130      * @param position the position of the rule to be deleted
131      */
132     @JsxFunction
133     public void deleteRule(final int position) {
134         try {
135             initCssRules();
136             getGroupingRule().deleteRule(fixIndex(position));
137             refreshCssRules();
138         }
139         catch (final DOMException e) {
140             throw JavaScriptEngine.asJavaScriptException(getWindow(), e.getMessage(), e.code);
141         }
142     }
143 
144     private void initCssRules() {
145         if (cssRules_ == null) {
146             cssRules_ = new CSSRuleList(this);
147             cssRulesIndexFix_ = new ArrayList<>();
148             refreshCssRules();
149         }
150     }
151 
152     private int fixIndex(int index) {
153         for (final int fix : cssRulesIndexFix_) {
154             if (fix > index) {
155                 return index;
156             }
157             index++;
158         }
159         return index;
160     }
161 
162     private void refreshCssRules() {
163         if (cssRules_ == null) {
164             return;
165         }
166 
167         cssRules_.clearRules();
168         cssRulesIndexFix_.clear();
169 
170         final CSSRuleListImpl ruleList = getGroupingRule().getCssRules();
171         final List<AbstractCSSRuleImpl> rules = ruleList.getRules();
172         int pos = 0;
173         for (final AbstractCSSRuleImpl rule : rules) {
174             if (rule instanceof CSSCharsetRuleImpl) {
175                 cssRulesIndexFix_.add(pos);
176                 continue;
177             }
178 
179             final CSSRule cssRule = CSSRule.create(getParentStyleSheet(), rule);
180             if (null == cssRule) {
181                 cssRulesIndexFix_.add(pos);
182             }
183             else {
184                 cssRules_.addRule(cssRule);
185             }
186             pos++;
187         }
188     }
189 
190     /**
191      * Returns the wrapped rule, as a media rule.
192      * @return the wrapped rule, as a media rule
193      */
194     private CSSMediaRuleImpl getGroupingRule() {
195         return (CSSMediaRuleImpl) getRule();
196     }
197 }