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.html;
16  
17  import static org.junit.jupiter.api.Assertions.fail;
18  
19  import java.net.URL;
20  import java.util.ArrayList;
21  import java.util.List;
22  
23  import org.htmlunit.CollectingAlertHandler;
24  import org.htmlunit.FailingHttpStatusCodeException;
25  import org.htmlunit.MockWebConnection;
26  import org.htmlunit.SimpleWebTestCase;
27  import org.htmlunit.WebClient;
28  import org.htmlunit.junit.annotation.Alerts;
29  import org.htmlunit.util.MimeType;
30  import org.junit.jupiter.api.Test;
31  
32  /**
33   * Tests for {@link HtmlScript}.
34   *
35   * @author Marc Guillemot
36   * @author Daniel Gredler
37   * @author Ahmed Ashour
38   * @author Ronald Brill
39   */
40  public class HtmlScriptTest extends SimpleWebTestCase {
41  
42      /**
43       * Verifies that a failing HTTP status code for a JavaScript file request (like a 404 response)
44       * results in a {@link FailingHttpStatusCodeException}, depending on how the client has been
45       * configured.
46       *
47       * @see org.htmlunit.WebClientOptions#isThrowExceptionOnFailingStatusCode()
48       * @throws Exception if an error occurs
49       */
50      @Test
51      public void badExternalScriptReference() throws Exception {
52          final String html = DOCTYPE_HTML
53                  + "<html><head><title>foo</title>\n"
54                  + "<script src='inexistent.js'></script>\n"
55                  + "</head><body></body></html>";
56  
57          final WebClient client = getWebClient();
58  
59          final MockWebConnection webConnection = new MockWebConnection();
60          webConnection.setDefaultResponse("inexistent", 404, "Not Found", MimeType.TEXT_HTML);
61          webConnection.setResponse(URL_FIRST, html);
62          client.setWebConnection(webConnection);
63  
64          try {
65              client.getPage(URL_FIRST);
66              fail("Should throw.");
67          }
68          catch (final FailingHttpStatusCodeException e) {
69              final String url = URL_FIRST.toExternalForm();
70              assertTrue("exception contains URL of failing script", e.getMessage().indexOf(url) > -1);
71              assertEquals(404, e.getStatusCode());
72              assertEquals("Not Found", e.getStatusMessage());
73          }
74  
75          client.getOptions().setThrowExceptionOnFailingStatusCode(false);
76  
77          try {
78              client.getPage(URL_FIRST);
79          }
80          catch (final FailingHttpStatusCodeException e) {
81              fail("Should not throw.");
82          }
83      }
84  
85      /**
86       * @throws Exception if an error occurs
87       */
88      @Test
89      public void asNormalizedText() throws Exception {
90          final String html = DOCTYPE_HTML + "<html><body><script id='s'>var foo = 132;</script></body></html>";
91          final HtmlPage page = loadPage(html);
92          final HtmlScript script = page.getHtmlElementById("s");
93          assertEquals("", script.asNormalizedText());
94      }
95  
96      /**
97       * @throws Exception if an error occurs
98       */
99      @Test
100     @Alerts("hello")
101     public void asXml() throws Exception {
102         final String html = DOCTYPE_HTML
103             + "<html><head><title>foo</title></head><body>\n"
104             + "<script id='script1'>\n"
105             + "  alert('hello');\n"
106             + "</script></body></html>";
107 
108         final HtmlPage page = loadPageWithAlerts(html);
109 
110         // asXml() should be reusable
111         final String xml = page.asXml();
112         assertEquals("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\r\n"
113                 + "<html>\r\n"
114                 + "  <head>\r\n"
115                 + "    <title>foo</title>\r\n"
116                 + "  </head>\r\n"
117                 + "  <body>\r\n"
118                 + "    <script id=\"script1\">\r\n"
119                 + "//<![CDATA[\r\n"
120                 + "\n"
121                 + "  alert('hello');\n"
122                 + "\r\n"
123                 + "//]]>\r\n"
124                 + "    </script>\r\n"
125                 + "  </body>\r\n"
126                 + "</html>",
127                 xml);
128 
129         loadPageWithAlerts(xml);
130     }
131 
132     /**
133      * @throws Exception if an error occurs
134      */
135     @Test
136     public void asXml_scriptNestedInCData() throws Exception {
137         final String script = "//<![CDATA[\n"
138             + "var foo = 132;\n"
139             + "//]]>";
140         final String html = DOCTYPE_HTML + "<html><body><script id='s'>" + script + "</script></body></html>";
141         final HtmlPage page = loadPage(html);
142         final HtmlScript scriptElement = page.getHtmlElementById("s");
143         assertEquals("<script id=\"s\">\r\n" + script + "\r\n</script>",
144                 scriptElement.asXml());
145     }
146 
147     /**
148      * Verifies that cloned script nodes do not reload or re-execute their content (bug 1954869).
149      * @throws Exception if an error occurs
150      */
151     @Test
152     @Alerts("loaded")
153     public void scriptCloneDoesNotReloadScript() throws Exception {
154         final String html = DOCTYPE_HTML + "<html><body><script src='" + URL_SECOND + "'></script></body></html>";
155         final String js = "alert('loaded')";
156 
157         final WebClient client = getWebClient();
158 
159         final MockWebConnection conn = new MockWebConnection();
160         conn.setResponse(URL_FIRST, html);
161         conn.setResponse(URL_SECOND, js, MimeType.TEXT_JAVASCRIPT);
162         client.setWebConnection(conn);
163 
164         final List<String> actual = new ArrayList<>();
165         client.setAlertHandler(new CollectingAlertHandler(actual));
166 
167         final HtmlPage page = client.getPage(URL_FIRST);
168         assertEquals(2, conn.getRequestCount());
169 
170         page.cloneNode(true);
171         assertEquals(2, conn.getRequestCount());
172 
173         assertEquals(getExpectedAlerts(), actual);
174     }
175 
176     /**
177      * @throws Exception on test failure
178      */
179     @Test
180     @Alerts({"c", "f"})
181     public void addEventListener_error_clientThrows() throws Exception {
182         addEventListener_error(true);
183     }
184 
185     /**
186      * @throws Exception on test failure
187      */
188     @Test
189     @Alerts({"c", "f"})
190     public void addEventListener_error_clientDoesNotThrow() throws Exception {
191         addEventListener_error(false);
192     }
193 
194     private void addEventListener_error(final boolean throwOnFailingStatusCode) throws Exception {
195         final URL fourOhFour = new URL(URL_FIRST, "/404");
196         final String html = DOCTYPE_HTML
197             + "<html><head>\n"
198             + "<script>\n"
199             + "  function test() {\n"
200             + "    var s1 = document.createElement('script');\n"
201             + "    s1.text = 'var foo';\n"
202             + "    s1.addEventListener('error', function() {alert('a')}, false);\n"
203             + "    document.body.insertBefore(s1, document.body.firstChild);\n"
204             + "    \n"
205             + "    var s2 = document.createElement('script');\n"
206             + "    s2.text = 'varrrr foo';\n"
207             + "    s2.addEventListener('error', function() {alert('b')}, false);\n"
208             + "    document.body.insertBefore(s2, document.body.firstChild);\n"
209             + "    \n"
210             + "    var s3 = document.createElement('script');\n"
211             + "    s3.src = '//:';\n"
212             + "    s3.addEventListener('error', function() {alert('c')}, false);\n"
213             + "    document.body.insertBefore(s3, document.body.firstChild);\n"
214             + "    \n"
215             + "    var s4 = document.createElement('script');\n"
216             + "    s4.src = '" + URL_SECOND + "';\n"
217             + "    s4.addEventListener('error', function() {alert('d')}, false);\n"
218             + "    document.body.insertBefore(s4, document.body.firstChild);\n"
219             + "    \n"
220             + "    var s5 = document.createElement('script');\n"
221             + "    s5.src = '" + URL_THIRD + "';\n"
222             + "    s5.addEventListener('error', function() {alert('e')}, false);\n"
223             + "    document.body.insertBefore(s5, document.body.firstChild);\n"
224             + "    \n"
225             + "    var s6 = document.createElement('script');\n"
226             + "    s6.src = '" + fourOhFour + "';\n"
227             + "    s6.addEventListener('error', function() {alert('f')}, false);\n"
228             + "    document.body.insertBefore(s6, document.body.firstChild);\n"
229             + "  }\n"
230             + "</script>\n"
231             + "</head>\n"
232             + "<body onload='test()'></body>\n"
233             + "</html>";
234         final WebClient client = getWebClient();
235         client.getOptions().setThrowExceptionOnFailingStatusCode(throwOnFailingStatusCode);
236         client.getOptions().setThrowExceptionOnScriptError(false);
237 
238         final MockWebConnection conn = new MockWebConnection();
239         conn.setResponse(URL_FIRST, html);
240         conn.setResponse(URL_SECOND, "var foo;", MimeType.TEXT_JAVASCRIPT);
241         conn.setResponse(URL_THIRD, "varrrr foo;", MimeType.TEXT_JAVASCRIPT);
242         conn.setResponse(fourOhFour, "", 404, "Missing", MimeType.TEXT_JAVASCRIPT, new ArrayList<>());
243         client.setWebConnection(conn);
244         final List<String> actual = new ArrayList<>();
245         client.setAlertHandler(new CollectingAlertHandler(actual));
246         client.getPage(URL_FIRST);
247         assertEquals(getExpectedAlerts(), actual);
248     }
249 
250     /**
251      * @throws Exception if an error occurs
252      */
253     @Test
254     public void isDisplayed() throws Exception {
255         final String html = DOCTYPE_HTML
256                 + "<html><head><title>Page A</title></head><body><script>var x = 1;</script></body></html>";
257         final HtmlPage page = loadPageWithAlerts(html);
258         final HtmlScript script = page.getFirstByXPath("//script");
259         assertFalse(script.isDisplayed());
260     }
261 
262     /**
263      * Verifies that if a script element executes "window.location.href=someotherpage", then subsequent
264      * script tags, and any onload handler for the original page do not run.
265      * @throws Exception if the test fails
266      */
267     @Test
268     @Alerts({"First script executes", "Second page loading"})
269     public void changingLocationSkipsFurtherScriptsOnPage() throws Exception {
270         final String html = DOCTYPE_HTML
271             + "<html><head></head>\n"
272             + "<body onload='alert(\"body onload executing but should be skipped\")'>\n"
273             + "<script>alert('First script executes')</script>\n"
274             + "<script>window.location.href='" + URL_SECOND + "'</script>\n"
275             + "<script>alert('Third script executing but should be skipped')</script>\n"
276             + "</body></html>";
277 
278         final String secondPage = DOCTYPE_HTML
279             + "<html><head></head><body>\n"
280             + "<script>alert('Second page loading')</script>\n"
281             + "</body></html>";
282 
283         getMockWebConnection().setResponse(URL_SECOND, secondPage);
284         loadPageWithAlerts(html);
285     }
286 }