1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit;
16
17 import java.io.IOException;
18 import java.net.URL;
19 import java.util.ArrayList;
20 import java.util.Collections;
21 import java.util.List;
22
23 import org.apache.commons.logging.Log;
24 import org.apache.commons.logging.LogFactory;
25 import org.htmlunit.html.HtmlPage;
26 import org.htmlunit.javascript.background.JavaScriptJobManager;
27 import org.htmlunit.junit.BrowserRunner;
28 import org.htmlunit.junit.annotation.Tries;
29 import org.junit.Test;
30 import org.junit.runner.RunWith;
31
32
33
34
35
36
37
38 @RunWith(BrowserRunner.class)
39 public class WebClientWaitForBackgroundJobsTest extends SimpleWebTestCase {
40
41 private long startTime_;
42
43 private void startTimedTest() {
44 startTime_ = System.currentTimeMillis();
45 }
46
47 private void assertMaxTestRunTime(final long maxRunTimeMilliseconds) {
48 final long endTime = System.currentTimeMillis();
49 final long runTime = endTime - startTime_;
50 assertTrue("\nTest took too long to run and results may not be accurate. Please try again. "
51 + "\n Actual Run Time: "
52 + runTime
53 + "\n Max Run Time: "
54 + maxRunTimeMilliseconds, runTime < maxRunTimeMilliseconds);
55 }
56
57
58
59
60 @Test
61 public void dontWaitWhenUnnecessary() throws Exception {
62 final String content = DOCTYPE_HTML
63 + "<html>\n"
64 + "<head>\n"
65 + " <title>test</title>\n"
66 + " <script>\n"
67 + " var threadID;\n"
68 + " function test() {\n"
69 + " threadID = setTimeout(doAlert, 10000);\n"
70 + " }\n"
71 + " function doAlert() {\n"
72 + " alert('blah');\n"
73 + " }\n"
74 + " </script>\n"
75 + "</head>\n"
76 + "<body onload='test()'>\n"
77 + "</body>\n"
78 + "</html>";
79
80 final List<String> collectedAlerts = Collections.synchronizedList(new ArrayList<String>());
81 final HtmlPage page = loadPage(content, collectedAlerts);
82 final JavaScriptJobManager jobManager = page.getEnclosingWindow().getJobManager();
83 assertNotNull(jobManager);
84 assertEquals(1, jobManager.getJobCount());
85
86 startTimedTest();
87 assertEquals(1, page.getWebClient().waitForBackgroundJavaScriptStartingBefore(7000));
88 assertMaxTestRunTime(100);
89 assertEquals(1, jobManager.getJobCount());
90 assertEquals(Collections.EMPTY_LIST, collectedAlerts);
91 }
92
93
94
95
96 @Test
97 @Tries(3)
98 public void dontWaitWhenUnnecessary_jobRemovesOtherJob() throws Exception {
99 final String content = DOCTYPE_HTML
100 + "<html>\n"
101 + "<head>\n"
102 + " <title>test</title>\n"
103 + " <script>\n"
104 + " var longTimeoutID;\n"
105 + " function test() {\n"
106 + " longTimeoutID = setTimeout(doAlert('late'), 10000);\n"
107 + " setTimeout(clearLongTimeout, 100);\n"
108 + " setTimeout(doAlert('hello'), 300);\n"
109 + " }\n"
110 + " function clearLongTimeout() {\n"
111 + " alert('clearLongTimeout');\n"
112 + " clearTimeout(longTimeoutID);\n"
113 + " }\n"
114 + " function doAlert(_text) {\n"
115 + " return function doAlert() {\n"
116 + " alert(_text);\n"
117 + " }\n"
118 + " }\n"
119 + " </script>\n"
120 + "</head>\n"
121 + "<body onload='test()'>\n"
122 + "</body>\n"
123 + "</html>";
124
125 final List<String> collectedAlerts = Collections.synchronizedList(new ArrayList<String>());
126 final HtmlPage page = loadPage(content, collectedAlerts);
127 final JavaScriptJobManager jobManager = page.getEnclosingWindow().getJobManager();
128 assertNotNull(jobManager);
129 assertEquals(3, jobManager.getJobCount());
130
131 startTimedTest();
132 assertEquals(0, page.getWebClient().waitForBackgroundJavaScriptStartingBefore(20_000));
133 assertMaxTestRunTime(400);
134 assertEquals(0, jobManager.getJobCount());
135
136 final String[] expectedAlerts = {"clearLongTimeout", "hello"};
137 assertEquals(expectedAlerts, collectedAlerts);
138 }
139
140
141
142
143
144
145 @Test
146 @Tries(3)
147 public void waitCalledDuringJobExecution() throws Exception {
148 final String html = DOCTYPE_HTML
149 + "<html>\n"
150 + "<head>\n"
151 + " <title>test</title>\n"
152 + " <script>\n"
153 + " var intervalId;\n"
154 + " function test() {\n"
155 + " intervalId = setTimeout(doWork, 100);\n"
156 + " }\n"
157 + " function doWork() {\n"
158 + " clearTimeout(intervalId);\n"
159 + " // waitForBackgroundJavaScriptStartingBefore should be called when JS execution is here\n"
160 + " var request = new XMLHttpRequest();\n"
161 + " request.open('GET', 'wait', false);\n"
162 + " request.send('');\n"
163 + " alert('end work');\n"
164 + " }\n"
165 + " </script>\n"
166 + "</head>\n"
167 + "<body onload='test()'>\n"
168 + "</body>\n"
169 + "</html>";
170
171 final ThreadSynchronizer threadSynchronizer = new ThreadSynchronizer();
172 final MockWebConnection webConnection = new MockWebConnection() {
173 @Override
174 public WebResponse getResponse(final WebRequest request) throws IOException {
175 if (request.getUrl().toExternalForm().endsWith("/wait")) {
176 threadSynchronizer.waitForState("just before waitForBackgroundJavaScriptStartingBefore");
177 threadSynchronizer.sleep(400);
178 }
179 return super.getResponse(request);
180 }
181 };
182 webConnection.setResponse(URL_FIRST, html);
183 webConnection.setDefaultResponse("");
184
185 final WebClient client = getWebClient();
186 client.setWebConnection(webConnection);
187
188 final List<String> collectedAlerts = Collections.synchronizedList(new ArrayList<String>());
189 client.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
190
191 final HtmlPage page = client.getPage(URL_FIRST);
192 final JavaScriptJobManager jobManager = page.getEnclosingWindow().getJobManager();
193 assertNotNull(jobManager);
194 assertEquals(1, jobManager.getJobCount());
195
196 startTimedTest();
197 threadSynchronizer.setState("just before waitForBackgroundJavaScriptStartingBefore");
198 assertEquals(0, client.waitForBackgroundJavaScriptStartingBefore(20_000));
199 assertMaxTestRunTime(600);
200 assertEquals(0, jobManager.getJobCount());
201
202 final String[] expectedAlerts = {"end work"};
203 assertEquals(expectedAlerts, collectedAlerts);
204 }
205
206
207
208
209
210
211
212 @Test
213 public void waitWhenLastJobStartsNewOne() throws Exception {
214 final String html = DOCTYPE_HTML
215 + "<html>\n"
216 + "<head>\n"
217 + " <title>test</title>\n"
218 + " <script>\n"
219 + " function test() {\n"
220 + " setTimeout(doWork1, 200);\n"
221 + " }\n"
222 + " function doWork1() {\n"
223 + " alert('work1');\n"
224 + " setTimeout(doWork2, 200);\n"
225 + " }\n"
226 + " function doWork2() {\n"
227 + " alert('work2');\n"
228 + " }\n"
229 + " </script>\n"
230 + "</head>\n"
231 + "<body onload='test()'>\n"
232 + "</body>\n"
233 + "</html>";
234
235 final List<String> collectedAlerts = Collections.synchronizedList(new ArrayList<String>());
236 final HtmlPage page = loadPage(html, collectedAlerts);
237 final JavaScriptJobManager jobManager = page.getEnclosingWindow().getJobManager();
238 assertNotNull(jobManager);
239 assertEquals(1, jobManager.getJobCount());
240
241 startTimedTest();
242 assertEquals(0, page.getWebClient().waitForBackgroundJavaScriptStartingBefore(20_000));
243 assertMaxTestRunTime(1000);
244 assertEquals(0, jobManager.getJobCount());
245
246 final String[] expectedAlerts = {"work1", "work2"};
247 assertEquals(expectedAlerts, collectedAlerts);
248 }
249
250
251
252
253
254
255
256 @Test
257 @Tries(3)
258 public void waitWithsubWindows() throws Exception {
259 final String html = DOCTYPE_HTML
260 + "<html>\n"
261 + "<head>\n"
262 + " <title>test</title>\n"
263 + "</head>\n"
264 + "<body>\n"
265 + "<iframe src='nested.html'></iframe>\n"
266 + "</body>\n"
267 + "</html>";
268 final String nested = DOCTYPE_HTML
269 + "<html>\n"
270 + "<head>\n"
271 + " <title>nested</title>\n"
272 + " <script>\n"
273 + " function test() {\n"
274 + " setTimeout(doWork1, 200);\n"
275 + " }\n"
276 + " function doWork1() {\n"
277 + " alert('work1');\n"
278 + " }\n"
279 + " </script>\n"
280 + "</head>\n"
281 + "<body onload='test()'>\n"
282 + "</body>\n"
283 + "</html>";
284
285 final WebClient client = getWebClient();
286 final List<String> collectedAlerts = Collections.synchronizedList(new ArrayList<String>());
287 client.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
288
289 final MockWebConnection webConnection = new MockWebConnection();
290 webConnection.setResponse(URL_FIRST, html);
291 webConnection.setDefaultResponse(nested);
292
293 client.setWebConnection(webConnection);
294
295 final HtmlPage page = client.getPage(URL_FIRST);
296
297 final JavaScriptJobManager jobManager = page.getEnclosingWindow().getJobManager();
298 assertNotNull(jobManager);
299 assertEquals(0, jobManager.getJobCount());
300
301 startTimedTest();
302 assertEquals(0, client.waitForBackgroundJavaScriptStartingBefore(20_000));
303 assertMaxTestRunTime(300);
304 assertEquals(0, jobManager.getJobCount());
305
306 final String[] expectedAlerts = {"work1"};
307 assertEquals(expectedAlerts, collectedAlerts);
308 }
309
310
311
312
313
314
315
316 @Test
317 @Tries(3)
318 public void newJobStartedAfterWait() throws Exception {
319 final String html = DOCTYPE_HTML
320 + "<html>\n"
321 + "<head>\n"
322 + " <title>test</title>\n"
323 + " <script>\n"
324 + " var request;\n"
325 + " function onReadyStateChange() {\n"
326 + " if (request.readyState == 4) {\n"
327 + " alert('xhr onchange');\n"
328 + " setTimeout(doWork1, 200);\n"
329 + " }\n"
330 + " }\n"
331 + " function test() {\n"
332 + " request = new XMLHttpRequest();\n"
333 + " request.open('GET', 'wait', true);\n"
334 + " request.onreadystatechange = onReadyStateChange;\n"
335 + " // waitForBackgroundJavaScriptStartingBefore should be called when JS execution is in send()\n"
336 + " request.send('');\n"
337 + " }\n"
338 + " function doWork1() {\n"
339 + " alert('work1');\n"
340 + " }\n"
341 + " </script>\n"
342 + "</head>\n"
343 + "<body onload='test()'>\n"
344 + "</body>\n"
345 + "</html>";
346
347 final ThreadSynchronizer threadSynchronizer = new ThreadSynchronizer();
348 final MockWebConnection webConnection = new MockWebConnection() {
349 @Override
350 public WebResponse getResponse(final WebRequest request) throws IOException {
351 if (request.getUrl().toExternalForm().endsWith("/wait")) {
352 threadSynchronizer.waitForState("just before waitForBackgroundJavaScriptStartingBefore");
353 threadSynchronizer.sleep(400);
354 }
355 return super.getResponse(request);
356 }
357 };
358 webConnection.setResponse(URL_FIRST, html);
359 webConnection.setDefaultResponse("");
360
361 final WebClient client = getWebClient();
362 client.setWebConnection(webConnection);
363
364 final List<String> collectedAlerts = Collections.synchronizedList(new ArrayList<String>());
365 client.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
366
367 final HtmlPage page = client.getPage(URL_FIRST);
368 final JavaScriptJobManager jobManager = page.getEnclosingWindow().getJobManager();
369 assertNotNull(jobManager);
370 assertEquals(1, jobManager.getJobCount());
371
372 startTimedTest();
373 threadSynchronizer.setState("just before waitForBackgroundJavaScriptStartingBefore");
374 assertEquals(0, client.waitForBackgroundJavaScriptStartingBefore(20_000));
375 assertMaxTestRunTime(1000);
376 assertEquals(0, jobManager.getJobCount());
377
378 final String[] expectedAlerts = {"xhr onchange", "work1"};
379 assertEquals(expectedAlerts, collectedAlerts);
380 }
381
382
383
384
385
386
387
388
389 @Test
390 @Tries(3)
391 public void waitForJobThatIsAlreadyLate() throws Exception {
392 final String html = DOCTYPE_HTML
393 + "<html>\n"
394 + "<head>\n"
395 + " <script>\n"
396 + " var counter = 0;\n"
397 + " function test() {\n"
398 + " setTimeout(doWork1, 0);\n"
399 + " }\n"
400 + " function doWork1() {\n"
401 + " if (counter++ < 50) {\n"
402 + " setTimeout(doWork1, 0);\n"
403 + " }\n"
404 + " alert('work1');\n"
405 + " }\n"
406 + " </script>\n"
407 + "</head>\n"
408 + "<body onload='test()'>\n"
409 + "</body>\n"
410 + "</html>";
411
412 final MockWebConnection webConnection = new MockWebConnection();
413 webConnection.setResponse(URL_FIRST, html);
414 webConnection.setDefaultResponse("");
415
416 final WebClient client = getWebClient();
417 client.setWebConnection(webConnection);
418
419 final List<String> collectedAlerts = Collections.synchronizedList(new ArrayList<String>());
420 client.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
421
422 client.getPage(URL_FIRST);
423
424 startTimedTest();
425 assertEquals(0, client.waitForBackgroundJavaScriptStartingBefore(1000));
426 assertMaxTestRunTime(1000);
427 assertEquals(51, collectedAlerts.size());
428 }
429
430
431
432
433
434 @Test
435 public void jobSchedulesJobInOtherWindow1() throws Exception {
436 final String html = DOCTYPE_HTML
437 + "<html>\n"
438 + "<head>\n"
439 + " <script>\n"
440 + " var counter = 0;\n"
441 + " function test() {\n"
442 + " var w = window.open('about:blank');\n"
443 + " w.setTimeout(doWork1, 200);\n"
444 + " }\n"
445 + " function doWork1() {\n"
446 + " alert('work1');\n"
447 + " setTimeout(doWork2, 400);\n"
448 + " }\n"
449 + " function doWork2() {\n"
450 + " alert('work2');\n"
451 + " }\n"
452 + " </script>\n"
453 + "</head>\n"
454 + "<body onload='test()'>\n"
455 + "</body>\n"
456 + "</html>";
457
458 final List<String> collectedAlerts = Collections.synchronizedList(new ArrayList<String>());
459 final HtmlPage page = loadPage(html, collectedAlerts);
460
461 startTimedTest();
462 assertEquals(0, page.getWebClient().waitForBackgroundJavaScript(1000));
463 assertMaxTestRunTime(1000);
464
465 final String[] expectedAlerts = {"work1", "work2"};
466 assertEquals(expectedAlerts, collectedAlerts);
467 }
468
469
470
471
472
473 @Test
474 public void jobSchedulesJobInOtherWindow2() throws Exception {
475 final String html = DOCTYPE_HTML
476 + "<html>\n"
477 + "<head>\n"
478 + " <script>\n"
479 + " var counter = 0;\n"
480 + " function test() {\n"
481 + " var w = window.open('about:blank');\n"
482 + " w.setTimeout(doWork1, 200);\n"
483 + " }\n"
484 + " function doWork1() {\n"
485 + " alert('work1');\n"
486 + " setTimeout(doWork2, 400);\n"
487 + " }\n"
488 + " function doWork2() {\n"
489 + " alert('work2');\n"
490 + " }\n"
491 + " </script>\n"
492 + "</head>\n"
493 + "<body onload='test()'>\n"
494 + "</body>\n"
495 + "</html>";
496
497 final List<String> collectedAlerts = Collections.synchronizedList(new ArrayList<String>());
498 final HtmlPage page = loadPage(html, collectedAlerts);
499
500 startTimedTest();
501 assertEquals(0, page.getWebClient().waitForBackgroundJavaScriptStartingBefore(1000));
502 assertMaxTestRunTime(1000);
503
504 final String[] expectedAlerts = {"work1", "work2"};
505 assertEquals(expectedAlerts, collectedAlerts);
506 }
507
508
509
510
511
512
513
514
515
516 @Test
517 @Tries(3)
518 public void waitForBackgroundJavaScriptStartingBefore_hangs() throws Exception {
519 final String html = DOCTYPE_HTML
520 + "<html>\n"
521 + "<head>\n"
522 + " <title>test</title>\n"
523 + " <script>\n"
524 + " var start = new Date().getTime();\n"
525 + " var id = setInterval(doWork1, 35);\n"
526 + " function stopTimer() {\n"
527 + " clearInterval(id);\n"
528 + " }\n"
529 + " function doWork1() {\n"
530 + " if (start + 8000 < new Date().getTime()) {\n"
531 + " document.title = 'failed';\n"
532 + " clearInterval(id);\n"
533 + " }\n"
534 + " }\n"
535 + " </script>\n"
536 + "</head>\n"
537 + "<body>\n"
538 + "<button onclick='stopTimer()'>stop</button>\n"
539 + "</body>\n"
540 + "</html>";
541
542 final WebClient client = getWebClient();
543
544 final MockWebConnection webConnection = new MockWebConnection();
545 webConnection.setDefaultResponse(html);
546 client.setWebConnection(webConnection);
547
548 final HtmlPage page = client.getPage(URL_FIRST);
549
550 int noOfJobs = client.waitForBackgroundJavaScriptStartingBefore(500);
551 assertTrue(noOfJobs == 1 || noOfJobs == 2);
552
553 assertEquals("test", page.getTitleText());
554 noOfJobs = client.waitForBackgroundJavaScriptStartingBefore(500);
555 assertTrue(noOfJobs == 1 || noOfJobs == 2);
556 }
557
558
559
560
561
562
563
564
565 @Test
566 public void jobsFromAClosedWindowShouldntBeIgnore() throws Exception {
567 final String html = DOCTYPE_HTML
568 + "<html><head><title>page 1</title></head>\n"
569 + "<body>\n"
570 + "<iframe src='iframe.html'></iframe>\n"
571 + "</body></html>";
572
573 final String iframe = DOCTYPE_HTML
574 + "<html><body>\n"
575 + "<script>\n"
576 + "setTimeout(function() { parent.location = '/page3.html'; }, 50);\n"
577 + "</script>\n"
578 + "</body></html>";
579 final String page3 = DOCTYPE_HTML
580 + "<html><body><script>\n"
581 + "parent.location = '/delayedPage4.html';\n"
582 + "</script></body></html>";
583
584 final WebClient client = getWebClient();
585
586 final ThreadSynchronizer threadSynchronizer = new ThreadSynchronizer();
587
588 final MockWebConnection webConnection = new MockWebConnection() {
589 @Override
590 public WebResponse getResponse(final WebRequest request) throws IOException {
591 if (request.getUrl().toExternalForm().endsWith("/delayedPage4.html")) {
592 threadSynchronizer.setState("/delayedPage4.html requested");
593 threadSynchronizer.waitForState("ready to call waitForBGJS");
594 threadSynchronizer.sleep(1000);
595 }
596 return super.getResponse(request);
597 }
598 };
599
600 webConnection.setDefaultResponse(html);
601 webConnection.setResponse(new URL(URL_FIRST, "iframe.html"), iframe);
602 webConnection.setResponse(new URL(URL_FIRST, "page3.html"), page3);
603 webConnection.setResponseAsGenericHtml(new URL(URL_FIRST, "delayedPage4.html"), "page 4");
604 client.setWebConnection(webConnection);
605
606 client.getPage(URL_FIRST);
607
608 threadSynchronizer.waitForState("/delayedPage4.html requested");
609 threadSynchronizer.setState("ready to call waitForBGJS");
610 final int noOfJobs = client.waitForBackgroundJavaScriptStartingBefore(500);
611 assertEquals(0, noOfJobs);
612
613 final HtmlPage page = (HtmlPage) client.getCurrentWindow().getEnclosedPage();
614 assertEquals("page 4", page.getTitleText());
615 }
616 }
617
618
619
620
621
622 class ThreadSynchronizer {
623 private String state_ = "initial";
624 private static final Log LOG = LogFactory.getLog(ThreadSynchronizer.class);
625
626 synchronized void setState(final String newState) {
627 state_ = newState;
628 notifyAll();
629 }
630
631
632
633
634
635 public void sleep(final long millis) {
636 try {
637 if (LOG.isDebugEnabled()) {
638 LOG.debug("Sleeping for " + millis + "ms");
639 }
640 Thread.sleep(millis);
641 }
642 catch (final InterruptedException e) {
643 throw new RuntimeException(e);
644 }
645 }
646
647 synchronized void waitForState(final String expectedState) {
648 try {
649 while (!state_.equals(expectedState)) {
650 wait();
651 }
652 }
653 catch (final InterruptedException e) {
654 throw new RuntimeException(e);
655 }
656 }
657 }