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.Serializable;
18  import java.util.function.Predicate;
19  
20  import org.htmlunit.WebClient;
21  import org.htmlunit.WebWindow;
22  import org.htmlunit.corejs.javascript.Scriptable;
23  import org.htmlunit.corejs.javascript.WithScope;
24  import org.htmlunit.html.DomNode;
25  import org.htmlunit.html.HtmlAttributeChangeEvent;
26  import org.htmlunit.html.HtmlElement;
27  import org.htmlunit.html.HtmlLink;
28  import org.htmlunit.html.HtmlStyle;
29  import org.htmlunit.javascript.HtmlUnitScriptable;
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.dom.AbstractList.EffectOnCache;
36  import org.htmlunit.javascript.host.dom.Document;
37  import org.htmlunit.javascript.host.html.HTMLCollection;
38  import org.htmlunit.javascript.host.html.HTMLElement;
39  import org.htmlunit.javascript.host.html.HTMLLinkElement;
40  import org.htmlunit.javascript.host.html.HTMLStyleElement;
41  
42  /**
43   * <p>An ordered list of stylesheets, accessible via <code>document.styleSheets</code>, as specified by the
44   * <a href="http://www.w3.org/TR/DOM-Level-2-Style/stylesheets.html#StyleSheets-StyleSheetList">DOM
45   * Level 2 Style spec</a> and the <a href="https://developer.mozilla.org/en-US/docs/DOM/document.styleSheets">Gecko
46   * DOM Guide</a>.</p>
47   *
48   * <p>If CSS is disabled via {@link org.htmlunit.WebClientOptions#setCssEnabled(boolean)}, instances
49   * of this class will always be empty. This allows us to check for CSS enablement/disablement in a single
50   * location, without having to sprinkle checks throughout the code.</p>
51   *
52   * @author Daniel Gredler
53   * @author Ahmed Ashour
54   * @author Ronald Brill
55   * @author Frank Danek
56   * @author Carsten Steul
57   */
58  @JsxClass
59  public class StyleSheetList extends HtmlUnitScriptable {
60  
61      /**
62       * We back the stylesheet list with an {@link HTMLCollection} of styles/links because this list must be "live".
63       */
64      private HTMLCollection nodes_;
65  
66      /**
67       * Creates an instance.
68       */
69      public StyleSheetList() {
70          super();
71      }
72  
73      /**
74       * JavaScript constructor.
75       */
76      @JsxConstructor
77      public void jsConstructor() {
78          // nothing to do
79      }
80  
81      /**
82       * Creates a new style sheet list owned by the specified document.
83       *
84       * @param document the owning document
85       */
86      public StyleSheetList(final Document document) {
87          super();
88  
89          final WebWindow webWindow = document.getWindow().getWebWindow();
90          setParentScope(new WithScope(getTopLevelScope(document.getParentScope()), document));
91          setPrototype(getPrototype(getClass()));
92  
93          final WebClient webClient = webWindow.getWebClient();
94  
95          if (webClient.getOptions().isCssEnabled()) {
96              nodes_ = new HTMLCollection(document.getDomNodeOrDie(), true);
97  
98              nodes_.setEffectOnCacheFunction(
99                      (java.util.function.Function<HtmlAttributeChangeEvent, EffectOnCache> & Serializable)
100                     event -> {
101                         final HtmlElement node = event.getHtmlElement();
102                         if (node instanceof HtmlLink && "rel".equalsIgnoreCase(event.getName())) {
103                             return EffectOnCache.RESET;
104                         }
105                         return EffectOnCache.NONE;
106                     });
107 
108             nodes_.setIsMatchingPredicate(
109                     (Predicate<DomNode> & Serializable)
110                     node -> {
111                         if (node instanceof HtmlStyle) {
112                             return true;
113                         }
114                         if (node instanceof HtmlLink link) {
115                             return link.isActiveStyleSheetLink();
116                         }
117                         return false;
118                     });
119         }
120         else {
121             nodes_ = HTMLCollection.emptyCollection(getWindow().getDomNodeOrDie());
122         }
123     }
124 
125     /**
126      * Returns the list's length.
127      *
128      * @return the list's length
129      */
130     @JsxGetter
131     public int getLength() {
132         return nodes_.getLength();
133     }
134 
135     /**
136      * Returns the style sheet at the specified index.
137      *
138      * @param index the index of the style sheet to return
139      * @return the style sheet at the specified index
140      */
141     @JsxFunction
142     public Object item(final int index) {
143         final Object item = get(index, this);
144         if (JavaScriptEngine.UNDEFINED == item) {
145             return null;
146         }
147         return item;
148     }
149 
150     /**
151      * {@inheritDoc}
152      */
153     @Override
154     public Object get(final int index, final Scriptable start) {
155         if (this == start) {
156             if (nodes_ == null || index < 0 || index >= nodes_.getLength()) {
157                 return JavaScriptEngine.UNDEFINED;
158             }
159 
160             final HTMLElement element = (HTMLElement) nodes_.item(Integer.valueOf(index));
161 
162             // <style type="text/css"> ... </style>
163             if (element instanceof HTMLStyleElement styleElement) {
164                 return styleElement.getSheet();
165             }
166             // <link rel="stylesheet" type="text/css" href="..." />
167             return ((HTMLLinkElement) element).getSheet();
168         }
169         return super.get(index, start);
170     }
171 
172     /**
173      * {@inheritDoc}
174      */
175     @Override
176     protected Object equivalentValues(final Object value) {
177         return value != null
178                 && getClass() == value.getClass()
179                 && getDomNodeOrNull() == ((StyleSheetList) value).getDomNodeOrNull();
180     }
181 }