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