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 org.htmlunit.BrowserVersion;
18  import org.htmlunit.SimpleWebTestCase;
19  import org.htmlunit.css.ComputedCssStyleDeclaration;
20  import org.htmlunit.css.CssStyleSheet;
21  import org.htmlunit.cssparser.parser.CSSErrorHandler;
22  import org.htmlunit.cssparser.parser.CSSException;
23  import org.htmlunit.cssparser.parser.CSSOMParser;
24  import org.htmlunit.cssparser.parser.CSSParseException;
25  import org.htmlunit.cssparser.parser.javacc.CSS3Parser;
26  import org.htmlunit.cssparser.parser.selector.Selector;
27  import org.htmlunit.cssparser.parser.selector.SelectorList;
28  import org.htmlunit.cssparser.parser.selector.SelectorListImpl;
29  import org.htmlunit.html.DomElement;
30  import org.htmlunit.html.HtmlElement;
31  import org.htmlunit.html.HtmlForm;
32  import org.htmlunit.html.HtmlInput;
33  import org.htmlunit.html.HtmlPage;
34  import org.htmlunit.html.HtmlStyle;
35  import org.htmlunit.javascript.host.html.HTMLStyleElement;
36  import org.htmlunit.junit.annotation.Alerts;
37  import org.junit.jupiter.api.Test;
38  import org.w3c.dom.NodeList;
39  
40  /**
41   * Unit tests for {@link CSSStyleSheet}.
42   *
43   * @author Marc Guillemot
44   * @author Ahmed Ashour
45   * @author Frank Danek
46   * @author Ronald Brill
47   */
48  public class CSSStyleSheet2Test extends SimpleWebTestCase {
49  
50      /**
51       * @throws Exception if the test fails
52       */
53      @Test
54      public void selects_miscSelectors() throws Exception {
55          final String html = DOCTYPE_HTML
56              + "<html><head><title>test</title>\n"
57              + "</head><body><style></style>\n"
58              + "<form name='f1' action='foo' class='yui-log'>\n"
59              + "<div><div><input name='i1' id='m1'></div></div>\n"
60              + "<input name='i2' class='yui-log'>\n"
61              + "<button name='b1' class='yui-log'>\n"
62              + "<button name='b2'>\n"
63              + "</form>\n"
64              + "</body></html>";
65  
66          final HtmlPage page = loadPage(html);
67          final HtmlElement body = page.getBody();
68          final HtmlForm form = page.getFormByName("f1");
69          final HtmlInput input1 = (HtmlInput) page.getElementsByName("i1").get(0);
70          final HtmlInput input2 = (HtmlInput) page.getElementsByName("i2").get(0);
71          final DomElement button1 = page.getElementsByName("b1").get(0);
72          final DomElement button2 = page.getElementsByName("b2").get(0);
73          final BrowserVersion browserVersion = getBrowserVersion();
74  
75          final HtmlStyle node = (HtmlStyle) page.getElementsByTagName("style").item(0);
76          final HTMLStyleElement host = (HTMLStyleElement) node.getScriptableObject();
77          final CSSStyleSheet sheet = host.getSheet();
78  
79          Selector selector = parseSelector(sheet, "*.yui-log input");
80  
81          assertFalse(CssStyleSheet.selects(browserVersion, selector, body, null, false, true));
82          assertFalse(CssStyleSheet.selects(browserVersion, selector, form, null, false, true));
83          assertTrue(CssStyleSheet.selects(browserVersion, selector, input1, null, false, true));
84          assertTrue(CssStyleSheet.selects(browserVersion, selector, input2, null, false, true));
85          assertFalse(CssStyleSheet.selects(browserVersion, selector, button1, null, false, true));
86          assertFalse(CssStyleSheet.selects(browserVersion, selector, button2, null, false, true));
87  
88          selector = parseSelector(sheet, "#m1");
89          assertTrue(CssStyleSheet.selects(browserVersion, selector, input1, null, false, true));
90          assertFalse(CssStyleSheet.selects(browserVersion, selector, input2, null, false, true));
91      }
92  
93      private static Selector parseSelector(final CSSStyleSheet sheet, final String rule) throws Exception {
94          return parseSelectors(rule).get(0);
95      }
96  
97      private static SelectorList parseSelectors(final String source) throws Exception {
98          SelectorList selectors;
99          final CSSErrorHandler errorHandler = new CSSErrorHandler() {
100 
101             @Override
102             public void warning(final CSSParseException exception) throws CSSException {
103                 throw exception;
104             }
105 
106             @Override
107             public void fatalError(final CSSParseException exception) throws CSSException {
108                 throw exception;
109             }
110 
111             @Override
112             public void error(final CSSParseException exception) throws CSSException {
113                 throw exception;
114             }
115         };
116         final CSSOMParser parser = new CSSOMParser(new CSS3Parser());
117         parser.setErrorHandler(errorHandler);
118         selectors = parser.parseSelectors(source);
119         // in case of error parseSelectors returns null
120         if (null == selectors) {
121             selectors = new SelectorListImpl();
122         }
123         return selectors;
124     }
125 
126     /**
127      * @throws Exception if an error occurs
128      */
129     @Test
130     public void selects_anyNodeSelector() throws Exception {
131         testSelects("*", true, true, true);
132     }
133 
134     /**
135      * @throws Exception if an error occurs
136      */
137     @Test
138     public void selects_childSelector() throws Exception {
139         testSelects("body > div", false, true, false);
140     }
141 
142     /**
143      * @throws Exception if an error occurs
144      */
145     @Test
146     public void selects_descendantSelector() throws Exception {
147         testSelects("body span", false, false, true);
148     }
149 
150     /**
151      * @throws Exception if an error occurs
152      */
153     @Test
154     public void selects_elementSelector() throws Exception {
155         testSelects("div", false, true, false);
156     }
157 
158     /**
159      * @throws Exception if an error occurs
160      */
161     @Test
162     public void selects_directAdjacentSelector() throws Exception {
163         testSelects("span + span", false, false, true);
164     }
165 
166     /**
167      * @throws Exception if an error occurs
168      */
169     @Test
170     public void selects_conditionalSelector_idCondition() throws Exception {
171         testSelects("span#s", false, false, true);
172         testSelects("#s", false, false, true);
173         testSelects("span[id=s]", false, false, true);
174     }
175 
176     /**
177      * @throws Exception if an error occurs
178      */
179     @Test
180     public void selectsIdConditionWithSpecialChars() throws Exception {
181         final String html = DOCTYPE_HTML
182               + "<html><body><style></style>\n"
183               + "<div id='d:e'></div>\n"
184               + "<div id='d-e'></div>\n"
185               + "</body></html>";
186         final HtmlPage page = loadPage(html);
187         final BrowserVersion browserVersion = getBrowserVersion();
188 
189         Selector selector = parseSelectors("#d\\:e").get(0);
190         assertTrue(CssStyleSheet.selects(browserVersion, selector, page.getHtmlElementById("d:e"), null, false, true));
191 
192         selector = parseSelectors("#d-e").get(0);
193         assertTrue(CssStyleSheet.selects(browserVersion, selector, page.getHtmlElementById("d-e"), null, false, true));
194     }
195 
196     /**
197      * @throws Exception if an error occurs
198      */
199     @Test
200     public void selects_conditionalSelector_classCondition() throws Exception {
201         testSelects("div.bar", false, true, false);
202         testSelects(".bar", false, true, false);
203         testSelects("div[class~=bar]", false, true, false);
204     }
205 
206     /**
207      * @throws Exception if an error occurs
208      */
209     @Test
210     public void selects_pseudoClass_root() throws Exception {
211         testSelects(":root", false, false, false);
212     }
213 
214     /**
215      * @throws Exception if an error occurs
216      */
217     @Test
218     public void selects_pseudoClass_lang() throws Exception {
219         testSelects(":lang(en)", false, true, true);
220         testSelects(":lang(de)", false, false, false);
221     }
222 
223     /**
224      * @throws Exception if an error occurs
225      */
226     @Test
227     public void selects_pseudoClass_negation() throws Exception {
228         testSelects(":not(div)", true, false, true);
229     }
230 
231     private void testSelects(final String css, final boolean selectBody, final boolean selectDivD,
232         final boolean selectSpanS) throws Exception {
233         final String html = DOCTYPE_HTML
234             + "<html>\n"
235             + "  <body id='b'>\n"
236             + "    <style></style>\n"
237             + "    <div id='d' class='foo bar' lang='en-GB'>\n"
238             + "      <span>x</span>\n"
239             + "      <span id='s'>a</span>b\n"
240             + "    </div>\n"
241             + "  </body>\n"
242             + "</html>";
243         final HtmlPage page = loadPage(html);
244         final BrowserVersion browserVersion = getBrowserVersion();
245 
246         final Selector selector = parseSelectors(css).get(0);
247         assertEquals(selectBody,
248                 CssStyleSheet.selects(browserVersion, selector, page.getHtmlElementById("b"), null, false, true));
249         assertEquals(selectDivD,
250                 CssStyleSheet.selects(browserVersion, selector, page.getHtmlElementById("d"), null, false, true));
251         assertEquals(selectSpanS,
252                 CssStyleSheet.selects(browserVersion, selector, page.getHtmlElementById("s"), null, false, true));
253     }
254 
255     /**
256      * Test for #1300.
257      * @throws Exception if the test fails
258      */
259     @Test
260     @Alerts("ComputedCssStyleDeclaration for 'HtmlBody[<body>]'")
261     public void brokenExternalCSS() throws Exception {
262         final String html = "<html><head>\n"
263             + "<link rel='stylesheet' type='text/css' href='" + URL_SECOND + "'/></head></html>";
264 
265         getMockWebConnection().setResponse(URL_SECOND, "body { font-weight: 900\\9; }");
266         final HtmlPage htmlPage = loadPage(html);
267 
268         final NodeList list = htmlPage.getElementsByTagName("body");
269         final HtmlElement element = (HtmlElement) list.item(0);
270         final ComputedCssStyleDeclaration style = htmlPage.getEnclosingWindow().getComputedStyle(element, null);
271         assertEquals(getExpectedAlerts()[0], style.toString());
272     }
273 }