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