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;
16  
17  import java.net.URL;
18  import java.util.ArrayList;
19  import java.util.Arrays;
20  import java.util.LinkedList;
21  import java.util.List;
22  
23  import org.apache.commons.lang3.mutable.MutableInt;
24  import org.htmlunit.html.HtmlPage;
25  import org.htmlunit.javascript.background.JavaScriptJob;
26  import org.htmlunit.javascript.background.JavaScriptJobManager;
27  import org.htmlunit.junit.annotation.Alerts;
28  import org.junit.jupiter.api.Test;
29  
30  /**
31   * Tests for {@link TopLevelWindow}.
32   *
33   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
34   * @author Ahmed Ashour
35   * @author Daniel Gredler
36   * @author Ronald Brill
37   */
38  public class TopLevelWindowTest extends SimpleWebTestCase {
39  
40      /**
41       * Tests closing the only open window.
42       * @throws Exception if the test fails
43       */
44      @Test
45      public void closeTheOnlyWindow() throws Exception {
46          final WebClient webClient = getWebClient();
47  
48          final List<WebWindowEvent> events = new LinkedList<>();
49          webClient.addWebWindowListener(new WebWindowListener() {
50              @Override
51              public void webWindowOpened(final WebWindowEvent event) {
52                  events.add(event);
53              }
54  
55              @Override
56              public void webWindowContentChanged(final WebWindowEvent event) {
57                  events.add(event);
58              }
59  
60              @Override
61              public void webWindowClosed(final WebWindowEvent event) {
62                  events.add(event);
63              }
64          });
65  
66          final WebWindow windowToClose = webClient.getCurrentWindow();
67          ((TopLevelWindow) windowToClose).close();
68  
69          assertEquals(2, events.size());
70          assertEquals(new WebWindowEvent(windowToClose, WebWindowEvent.CLOSE, null, null), events.get(0));
71          assertEquals(WebWindowEvent.OPEN, events.get(1).getEventType());
72          assertNull(events.get(1).getOldPage());
73          assertNull(events.get(1).getNewPage());
74  
75          // Since this was the only open window, a new window should have
76          // been created when this one was closed. Verify this.
77          assertNotNull(webClient.getCurrentWindow());
78          assertNotSame(webClient.getCurrentWindow(), windowToClose);
79  
80          assertEquals(1, webClient.getWebWindows().size());
81          assertEquals(1, webClient.getTopLevelWindows().size());
82          assertEquals(webClient.getWebWindows().get(0), webClient.getTopLevelWindows().get(0));
83      }
84  
85      /**
86       * Tests the use of a custom job manager.
87       * @throws Exception if an error occurs
88       */
89      @Test
90      public void useCustomJobManager() throws Exception {
91          final MutableInt jobCount = new MutableInt(0);
92          final JavaScriptJobManager mgr = new JavaScriptJobManager() {
93              /** {@inheritDoc} */
94              @Override
95              public int waitForJobsStartingBefore(final long delayMillis) {
96                  return jobCount.intValue();
97              }
98              /** {@inheritDoc} */
99              @Override
100             public int waitForJobsStartingBefore(final long delayMillis, final long timeoutMillis) {
101                 return jobCount.intValue();
102             }
103             /** {@inheritDoc} */
104             @Override
105             public int waitForJobsStartingBefore(final long delayMillis, final long timeoutMillis,
106                             final JavaScriptJobFilter filter) {
107                 return jobCount.intValue();
108             }
109             /** {@inheritDoc} */
110             @Override
111             public int waitForJobsStartingBefore(final long delayMillis, final JavaScriptJobFilter filter) {
112                 return jobCount.intValue();
113             }
114             /** {@inheritDoc} */
115             @Override
116             public int waitForJobs(final long timeoutMillis) {
117                 return jobCount.intValue();
118             }
119             /** {@inheritDoc} */
120             @Override
121             public void stopJob(final int id) {
122                 // Empty.
123             }
124             /** {@inheritDoc} */
125             @Override
126             public void shutdown() {
127                 // Empty.
128             }
129             /** {@inheritDoc} */
130             @Override
131             public void removeJob(final int id) {
132                 // Empty.
133             }
134             /** {@inheritDoc} */
135             @Override
136             public void removeAllJobs() {
137                 // Empty.
138             }
139             /** {@inheritDoc} */
140             @Override
141             public int getJobCount() {
142                 return jobCount.intValue();
143             }
144             /** {@inheritDoc} */
145             @Override
146             public int getJobCount(final JavaScriptJobFilter filter) {
147                 return jobCount.intValue();
148             }
149             /** {@inheritDoc} */
150             @Override
151             public int addJob(final JavaScriptJob job, final Page page) {
152                 jobCount.increment();
153                 return jobCount.intValue();
154             }
155             /** {@inheritDoc} */
156             @Override
157             public JavaScriptJob getEarliestJob() {
158                 return null;
159             }
160             /** {@inheritDoc} */
161             @Override
162             public JavaScriptJob getEarliestJob(final JavaScriptJobFilter filter) {
163                 return null;
164             }
165             /** {@inheritDoc} */
166             @Override
167             public boolean runSingleJob(final JavaScriptJob job) {
168                 // Empty
169                 return false;
170             }
171             @Override
172             public String jobStatusDump(final JavaScriptJobFilter filter) {
173                 return null;
174             }
175         };
176 
177         final WebWindowListener listener = new WebWindowListener() {
178             /** {@inheritDoc} */
179             @Override
180             public void webWindowOpened(final WebWindowEvent event) {
181                 ((WebWindowImpl) event.getWebWindow()).setJobManager(mgr);
182             }
183             /** {@inheritDoc} */
184             @Override
185             public void webWindowContentChanged(final WebWindowEvent event) {
186                 // Empty.
187             }
188             /** {@inheritDoc} */
189             @Override
190             public void webWindowClosed(final WebWindowEvent event) {
191                 // Empty.
192             }
193         };
194 
195         final WebClient client = getWebClientWithMockWebConnection();
196         client.addWebWindowListener(listener);
197 
198         final TopLevelWindow window = (TopLevelWindow) client.getCurrentWindow();
199         window.setJobManager(mgr);
200 
201         final MockWebConnection conn = getMockWebConnection();
202         conn.setDefaultResponse(DOCTYPE_HTML
203                 + "<html><body><script>window.setTimeout('', 500);</script></body></html>");
204 
205         client.getPage(URL_FIRST);
206         assertEquals(1, jobCount.intValue());
207 
208         client.getPage(URL_FIRST);
209         assertEquals(2, jobCount.intValue());
210     }
211 
212     /**
213      * @throws Exception if an error occurs
214      */
215     @Test
216     public void history() throws Exception {
217         final WebClient client = getWebClientWithMockWebConnection();
218         final TopLevelWindow window = (TopLevelWindow) client.getCurrentWindow();
219         final History history = window.getHistory();
220 
221         final MockWebConnection conn = getMockWebConnection();
222         conn.setResponse(URL_FIRST, DOCTYPE_HTML
223                 + "<html><body><a name='a' href='" + URL_SECOND + "'>foo</a>\n"
224                 + "<a name='b' href='#b'>bar</a></body></html>");
225         conn.setResponse(URL_SECOND, DOCTYPE_HTML
226                 + "<html><body><a name='a' href='" + URL_THIRD + "'>foo</a></body></html>");
227         conn.setResponse(URL_THIRD, DOCTYPE_HTML
228                 + "<html><body><a name='a' href='" + URL_FIRST + "'>foo</a></body></html>");
229 
230         assertEquals(0, history.getLength());
231         assertEquals(-1, history.getIndex());
232 
233         // Load the first page.
234         HtmlPage page = client.getPage(URL_FIRST);
235         assertEquals(1, history.getLength());
236         assertEquals(0, history.getIndex());
237         assertNull(history.getUrl(-1));
238         assertEquals(URL_FIRST, history.getUrl(0));
239         assertEquals(URL_FIRST, window.getEnclosedPage().getUrl());
240         assertNull(history.getUrl(1));
241 
242         // Go to the second page.
243         page = page.getAnchorByName("a").click();
244         assertEquals(2, history.getLength());
245         assertEquals(1, history.getIndex());
246         assertNull(history.getUrl(-1));
247         assertEquals(URL_FIRST, history.getUrl(0));
248         assertEquals(URL_SECOND, history.getUrl(1));
249         assertEquals(URL_SECOND, window.getEnclosedPage().getUrl());
250         assertNull(history.getUrl(2));
251 
252         // Go to the third page.
253         page = page.getAnchorByName("a").click();
254         assertEquals(3, history.getLength());
255         assertEquals(2, history.getIndex());
256         assertNull(history.getUrl(-1));
257         assertEquals(URL_FIRST, history.getUrl(0));
258         assertEquals(URL_SECOND, history.getUrl(1));
259         assertEquals(URL_THIRD, history.getUrl(2));
260         assertEquals(URL_THIRD, window.getEnclosedPage().getUrl());
261         assertNull(history.getUrl(3));
262 
263         // Cycle around back to the first page.
264         page = page.getAnchorByName("a").click();
265         assertEquals(4, history.getLength());
266         assertEquals(3, history.getIndex());
267         assertNull(history.getUrl(-1));
268         assertEquals(URL_FIRST, history.getUrl(0));
269         assertEquals(URL_SECOND, history.getUrl(1));
270         assertEquals(URL_THIRD, history.getUrl(2));
271         assertEquals(URL_FIRST, history.getUrl(3));
272         assertEquals(URL_FIRST, window.getEnclosedPage().getUrl());
273         assertNull(history.getUrl(4));
274 
275         // Go to a hash on the current page.
276         final URL firstUrlWithHash = new URL(URL_FIRST, "#b");
277         page = page.getAnchorByName("b").click();
278         assertEquals(5, history.getLength());
279         assertEquals(4, history.getIndex());
280         assertNull(history.getUrl(-1));
281         assertEquals(URL_FIRST, history.getUrl(0));
282         assertEquals(URL_SECOND, history.getUrl(1));
283         assertEquals(URL_THIRD, history.getUrl(2));
284         assertEquals(URL_FIRST, history.getUrl(3));
285         assertEquals(firstUrlWithHash, history.getUrl(4));
286         assertEquals(firstUrlWithHash, window.getEnclosedPage().getUrl());
287         assertNull(history.getUrl(5));
288 
289         history.back().back();
290         assertEquals(5, history.getLength());
291         assertEquals(2, history.getIndex());
292         assertNull(history.getUrl(-1));
293         assertEquals(URL_FIRST, history.getUrl(0));
294         assertEquals(URL_SECOND, history.getUrl(1));
295         assertEquals(URL_THIRD, history.getUrl(2));
296         assertEquals(URL_FIRST, history.getUrl(3));
297         assertEquals(firstUrlWithHash, history.getUrl(4));
298         assertEquals(URL_THIRD, window.getEnclosedPage().getUrl());
299         assertNull(history.getUrl(5));
300 
301         history.forward();
302         assertEquals(5, history.getLength());
303         assertEquals(3, history.getIndex());
304         assertNull(history.getUrl(-1));
305         assertEquals(URL_FIRST, history.getUrl(0));
306         assertEquals(URL_SECOND, history.getUrl(1));
307         assertEquals(URL_THIRD, history.getUrl(2));
308         assertEquals(URL_FIRST, history.getUrl(3));
309         assertEquals(firstUrlWithHash, history.getUrl(4));
310         assertEquals(URL_FIRST, window.getEnclosedPage().getUrl());
311         assertNull(history.getUrl(5));
312     }
313 
314     /**
315      * Regression test for bug 2808520: onbeforeunload not called when window.close() is called.
316      * @throws Exception if an error occurs
317      */
318     @Test
319     public void onBeforeUnloadCalledOnClose() throws Exception {
320         final String html = DOCTYPE_HTML + "<html><body onbeforeunload='alert(7)'>abc</body></html>";
321         final List<String> alerts = new ArrayList<>();
322         final HtmlPage page = loadPage(html, alerts);
323         assertTrue(alerts.isEmpty());
324         final TopLevelWindow w = (TopLevelWindow) page.getEnclosingWindow();
325         w.close();
326         assertEquals(Arrays.asList("7"), alerts);
327     }
328 
329     /**
330      * Test that no JavaScript error is thrown when using the setTimeout() JS function
331      * while the page is unloading.
332      * Regression test for bug 2956550.
333      * @throws Exception if an error occurs
334      */
335     @Test
336     @Alerts("closing")
337     public void setTimeoutDuringOnUnload() throws Exception {
338         final String html = DOCTYPE_HTML
339             + "<html><head>\n"
340             + "<script>\n"
341             + "function f() {\n"
342             + "  alert('closing');\n"
343             + "  setTimeout(function() { alert('started in onunload'); }, 0);\n"
344             + "}\n"
345             + "window.addEventListener('unload', f, true);\n"
346             + "</script></head>\n"
347             + "<body></body></html>";
348         final List<String> alerts = new ArrayList<>();
349         final HtmlPage page = loadPage(html, alerts);
350         assertTrue(alerts.isEmpty());
351         final TopLevelWindow w = (TopLevelWindow) page.getEnclosingWindow();
352         w.close();
353         getWebClient().waitForBackgroundJavaScript(1000);
354         assertEquals(getExpectedAlerts(), alerts);
355     }
356 
357 }