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