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.background;
16  
17  import java.util.ArrayList;
18  import java.util.Collections;
19  import java.util.List;
20  
21  import org.htmlunit.MockWebConnection;
22  import org.htmlunit.SimpleWebTestCase;
23  import org.htmlunit.TopLevelWindow;
24  import org.htmlunit.html.HtmlAnchor;
25  import org.htmlunit.html.HtmlInlineFrame;
26  import org.htmlunit.html.HtmlPage;
27  import org.junit.jupiter.api.Test;
28  
29  /**
30   * Tests for {@link JavaScriptJobManagerImpl} using the full HtmlUnit stack. Minimal unit tests
31   * which do not use the full HtmlUnit stack go in {@link JavaScriptJobManagerMinimalTest}.
32   *
33   * @author Brad Clarke
34   * @author Ahmed Ashour
35   */
36  public class JavaScriptJobManagerTest extends SimpleWebTestCase {
37      private long startTime_;
38  
39      private void startTimedTest() {
40          startTime_ = System.currentTimeMillis();
41      }
42  
43      private void assertMaxTestRunTime(final long maxRunTimeMilliseconds) {
44          final long endTime = System.currentTimeMillis();
45          final long runTime = endTime - startTime_;
46          assertTrue("\nTest took too long to run and results may not be accurate. Please try again. "
47              + "\n  Actual Run Time: "
48              + runTime
49              + "\n  Max Run Time: "
50              + maxRunTimeMilliseconds, runTime < maxRunTimeMilliseconds);
51      }
52  
53      /**
54       * @throws Exception if the test fails
55       */
56      @Test
57      public void setClearTimeoutUsesManager() throws Exception {
58          final String content = DOCTYPE_HTML
59              + "<html>\n"
60              + "<head>\n"
61              + "  <title>test</title>\n"
62              + "  <script>\n"
63              + "    var threadID;\n"
64              + "    function test() {\n"
65              + "      threadID = setTimeout(doAlert, 10000);\n"
66              + "    }\n"
67              + "    function doAlert() {\n"
68              + "      alert('blah');\n"
69              + "    }\n"
70              + "  </script>\n"
71              + "</head>\n"
72              + "<body onload='test()'>\n"
73              + "<a onclick='clearTimeout(threadID);' id='clickme'/>\n"
74              + "</body>\n"
75              + "</html>";
76  
77          final List<String> collectedAlerts = Collections.synchronizedList(new ArrayList<String>());
78          startTimedTest();
79          final HtmlPage page = loadPage(content, collectedAlerts);
80          final JavaScriptJobManager jobManager = page.getEnclosingWindow().getJobManager();
81          assertNotNull(jobManager);
82          assertEquals(1, jobManager.getJobCount());
83          final HtmlAnchor a = page.getHtmlElementById("clickme");
84          a.click();
85          jobManager.waitForJobs(7000);
86          assertEquals(0, jobManager.getJobCount());
87          assertEquals(Collections.EMPTY_LIST, collectedAlerts);
88          assertMaxTestRunTime(10_000);
89      }
90  
91      /**
92       * @throws Exception if the test fails
93       */
94      @Test
95      public void setClearIntervalUsesManager() throws Exception {
96          final String content = DOCTYPE_HTML
97              + "<html>\n"
98              + "<head>\n"
99              + "  <title>test</title>\n"
100             + "  <script>\n"
101             + "    var threadID;\n"
102             + "    function test() {\n"
103             + "      threadID = setInterval(doAlert, 100);\n"
104             + "    }\n"
105             + "    var iterationNumber = 0;\n"
106             + "    function doAlert() {\n"
107             + "      alert('blah');\n"
108             + "      if (++iterationNumber >= 3) {\n"
109             + "        clearInterval(threadID);\n"
110             + "      }\n"
111             + "    }\n"
112             + "  </script>\n"
113             + "</head>\n"
114             + "<body onload='test()'>\n"
115             + "</body>\n"
116             + "</html>";
117 
118         final List<String> collectedAlerts = Collections.synchronizedList(new ArrayList<String>());
119         final HtmlPage page = loadPage(content, collectedAlerts);
120 
121         // loading the page takes some time (on our slow build machine);
122         // start the timer after loading the page
123         startTimedTest();
124 
125         final JavaScriptJobManager jobManager = page.getEnclosingWindow().getJobManager();
126         assertNotNull(jobManager);
127         assertEquals(1, jobManager.getJobCount());
128         jobManager.waitForJobs(DEFAULT_WAIT_TIME.toMillis());
129         assertEquals(0, jobManager.getJobCount());
130         assertEquals(Collections.nCopies(3, "blah"), collectedAlerts);
131 
132         assertMaxTestRunTime(DEFAULT_WAIT_TIME.toMillis() + 100);
133     }
134 
135     /**
136      * @throws Exception if the test fails
137      */
138     @Test
139     public void navigationStopThreadsInChildWindows() throws Exception {
140         final String firstContent = DOCTYPE_HTML
141             + "<html><head><title>First</title></head><body>\n"
142             + "<iframe id='iframe1' src='"
143             + URL_SECOND
144             + "'></iframe>\n"
145             + "<a href='"
146             + URL_THIRD.toExternalForm()
147             + "' id='clickme'>click me</a>\n"
148             + "</body></html>";
149         final String secondContent = DOCTYPE_HTML
150             + "<html><head><title>Second</title></head><body>\n"
151             + "<script>\n"
152             + "setInterval('', 30000);\n"
153             + "</script>\n"
154             + "</body></html>";
155         final String thirdContent = DOCTYPE_HTML
156             + "<html><head><title>Third</title></head><body></body></html>";
157 
158         final MockWebConnection webConnection = getMockWebConnection();
159         webConnection.setResponse(URL_SECOND, secondContent);
160         webConnection.setResponse(URL_THIRD, thirdContent);
161 
162         final HtmlPage page = loadPage(firstContent);
163         final HtmlInlineFrame iframe = page.getHtmlElementById("iframe1");
164         final JavaScriptJobManager mgr = iframe.getEnclosedWindow().getJobManager();
165         assertEquals("inner frame should show child thread", 1, mgr.getJobCount());
166 
167         final HtmlAnchor anchor = page.getHtmlElementById("clickme");
168         final HtmlPage newPage = anchor.click();
169         assertEquals("new page should load", "Third", newPage.getTitleText());
170         assertTrue("frame should be gone", newPage.getFrames().isEmpty());
171 
172         mgr.waitForJobs(10_000);
173         assertEquals("job manager should have no jobs left", 0, mgr.getJobCount());
174     }
175 
176     /**
177      * Test for Bug #487 that makes sure closing a window prevents a
178      * recursive setTimeout from continuing forever.
179      *
180      * @throws Exception if the test fails
181      */
182     @Test
183     public void interruptAllWithRecursiveSetTimeout() throws Exception {
184         final String content = DOCTYPE_HTML
185             + "<html>\n"
186             + "<head>\n"
187             + "  <title>test</title>\n"
188             + "  <script>\n"
189             + "    var threadID;\n"
190             + "    function test() {\n"
191             + "      alert('ping');\n"
192             + "      threadID = setTimeout(test, 5);\n"
193             + "    }\n"
194             + "  </script>\n"
195             + "</head>\n"
196             + "<body onload='test()'>\n"
197             + "</body>\n"
198             + "</html>";
199 
200         final List<String> collectedAlerts = Collections.synchronizedList(new ArrayList<String>());
201         final HtmlPage page = loadPage(content, collectedAlerts);
202         final JavaScriptJobManager jobManager = page.getEnclosingWindow().getJobManager();
203         assertNotNull(jobManager);
204 
205         // Not perfect, but 100 chances to start should be enough for a loaded system
206         Thread.sleep(500);
207 
208         assertFalse("At least one alert should have fired by now", collectedAlerts.isEmpty());
209         ((TopLevelWindow) page.getEnclosingWindow()).close();
210 
211         // 100 chances to stop
212         jobManager.waitForJobs(500);
213 
214         final int finalValue = collectedAlerts.size();
215 
216         // 100 chances to fail
217         jobManager.waitForJobs(500);
218 
219         assertEquals("No new alerts should have happened", finalValue, collectedAlerts.size());
220     }
221 }