1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit.javascript.host;
16
17 import java.util.ArrayList;
18 import java.util.Collections;
19 import java.util.HashMap;
20 import java.util.List;
21
22 import org.htmlunit.BrowserVersion;
23 import org.htmlunit.CollectingAlertHandler;
24 import org.htmlunit.MockWebConnection;
25 import org.htmlunit.SimpleWebTestCase;
26 import org.htmlunit.WebClient;
27 import org.htmlunit.corejs.javascript.BaseFunction;
28 import org.htmlunit.corejs.javascript.Context;
29 import org.htmlunit.corejs.javascript.Function;
30 import org.htmlunit.corejs.javascript.Scriptable;
31 import org.htmlunit.corejs.javascript.ScriptableObject;
32 import org.htmlunit.html.DomNode;
33 import org.htmlunit.html.HtmlDivision;
34 import org.htmlunit.html.HtmlElement;
35 import org.htmlunit.html.HtmlPage;
36 import org.junit.After;
37 import org.junit.Before;
38 import org.junit.Test;
39
40
41
42
43
44
45
46
47
48 public class WindowConcurrencyTest extends SimpleWebTestCase {
49
50 private WebClient client_;
51 private long startTime_;
52
53 private void startTimedTest() {
54 startTime_ = System.currentTimeMillis();
55 }
56
57 private void assertMaxTestRunTime(final long maxRunTimeMilliseconds) {
58 final long endTime = System.currentTimeMillis();
59 final long runTime = endTime - startTime_;
60 assertTrue("\nTest took too long to run and results may not be accurate. Please try again. "
61 + "\n Actual Run Time: "
62 + runTime
63 + "\n Max Run Time: "
64 + maxRunTimeMilliseconds, runTime < maxRunTimeMilliseconds);
65 }
66
67
68
69
70 @Override
71 @Before
72 public void before() {
73 client_ = new WebClient();
74 }
75
76
77
78
79 @After
80 public void after() {
81 client_.close();
82 }
83
84
85
86
87 @Test
88 public void setTimeout() throws Exception {
89 final String content = DOCTYPE_HTML
90 + "<html><body><script language='JavaScript'>window.setTimeout('alert(\"Yo!\")',1);\n"
91 + "</script></body></html>";
92
93 final List<String> collectedAlerts = Collections.synchronizedList(new ArrayList<String>());
94 loadPage(client_, content, collectedAlerts);
95 assertEquals(0, client_.waitForBackgroundJavaScript(1000));
96 assertEquals(new String[] {"Yo!"}, collectedAlerts);
97 }
98
99
100
101
102 @Test
103 public void setTimeoutByReference() throws Exception {
104 final String content = DOCTYPE_HTML
105 + "<html><body><script language='JavaScript'>\n"
106 + "function doTimeout() {alert('Yo!');}\n"
107 + "window.setTimeout(doTimeout,1);\n"
108 + "</script></body></html>";
109
110 final List<String> collectedAlerts = Collections.synchronizedList(new ArrayList<String>());
111 loadPage(client_, content, collectedAlerts);
112 assertEquals(0, client_.waitForBackgroundJavaScript(1000));
113 assertEquals(new String[] {"Yo!"}, collectedAlerts);
114 }
115
116
117
118
119
120 @Test
121 public void setAndClearInterval() throws Exception {
122 final String content = DOCTYPE_HTML
123 + "<html><body>\n"
124 + "<script>\n"
125 + "window.setInterval('alert(\"Yo!\")', 500);\n"
126 + "function foo() { alert('Yo2'); }\n"
127 + "var i = window.setInterval(foo, 500);\n"
128 + "window.clearInterval(i);\n"
129 + "</script></body></html>";
130
131 final List<String> collectedAlerts = Collections.synchronizedList(new ArrayList<String>());
132 loadPage(client_, content, collectedAlerts);
133 }
134
135
136
137
138 @Test
139 public void setIntervalFunctionReference() throws Exception {
140 final String content = DOCTYPE_HTML
141 + "<html>\n"
142 + "<head>\n"
143 + " <title>test</title>\n"
144 + " <script>\n"
145 + " var threadID;\n"
146 + " function test() {\n"
147 + " threadID = setInterval(doAlert, 100);\n"
148 + " }\n"
149 + " var iterationNumber = 0;\n"
150 + " function doAlert() {\n"
151 + " alert('blah');\n"
152 + " if (++iterationNumber >= 3) {\n"
153 + " clearInterval(threadID);\n"
154 + " }\n"
155 + " }\n"
156 + " </script>\n"
157 + "</head>\n"
158 + "<body onload='test()'>\n"
159 + "</body>\n"
160 + "</html>";
161
162 final List<String> collectedAlerts = Collections.synchronizedList(new ArrayList<String>());
163 loadPage(client_, content, collectedAlerts);
164 assertEquals(0, client_.waitForBackgroundJavaScript(1000));
165 assertEquals(Collections.nCopies(3, "blah"), collectedAlerts);
166 }
167
168
169
170
171 @Test
172 public void clearInterval() throws Exception {
173 final String html = DOCTYPE_HTML
174 + "<html><body onload='test()'><script>\n"
175 + " var count;\n"
176 + " var id;\n"
177 + " function test() {\n"
178 + " count = 0;\n"
179 + " id = setInterval(callback, 100);\n"
180 + " }\n"
181 + " function callback() {\n"
182 + " count++;\n"
183 + " clearInterval(id);\n"
184 + " // Give the callback time to show its ugly face.\n"
185 + " // If it fires between now and then, we'll know.\n"
186 + " setTimeout('alert(count)', 500);\n"
187 + " }\n"
188 + "</script></body></html>";
189 final String[] expected = {"1"};
190 final List<String> actual = Collections.synchronizedList(new ArrayList<String>());
191 startTimedTest();
192 loadPage(client_, html, actual);
193 assertEquals(0, client_.waitForBackgroundJavaScript(10_000));
194 assertEquals(expected, actual);
195 assertMaxTestRunTime(5000);
196 }
197
198
199
200
201
202
203 @Test
204 public void setTimeoutStopped() throws Exception {
205 final String firstContent = DOCTYPE_HTML
206 + "<html><head>\n"
207 + "<script language='JavaScript'>window.setTimeout('alert(\"Yo!\")', 10000);</script>\n"
208 + "</head><body onload='document.location.replace(\"" + URL_SECOND + "\")'></body></html>";
209 final String secondContent = DOCTYPE_HTML
210 + "<html><head><title>Second</title></head><body></body></html>";
211
212 final List<String> collectedAlerts = Collections.synchronizedList(new ArrayList<String>());
213 client_.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
214
215 final MockWebConnection webConnection = new MockWebConnection();
216 webConnection.setResponse(URL_FIRST, firstContent);
217 webConnection.setResponse(URL_SECOND, secondContent);
218 client_.setWebConnection(webConnection);
219
220 final HtmlPage page = client_.getPage(URL_FIRST);
221 assertEquals(0, page.getWebClient().waitForBackgroundJavaScript(2000));
222 assertEquals("Second", page.getTitleText());
223 assertEquals(Collections.EMPTY_LIST, collectedAlerts);
224 }
225
226
227
228
229 @Test
230 public void clearTimeout() throws Exception {
231 final String content = DOCTYPE_HTML
232 + "<html>\n"
233 + "<head>\n"
234 + " <title>test</title>\n"
235 + " <script>\n"
236 + " function test() {\n"
237 + " var id = setTimeout('doAlert()', 2000);\n"
238 + " clearTimeout(id);\n"
239 + " }\n"
240 + " function doAlert() {\n"
241 + " alert('blah');\n"
242 + " }\n"
243 + " </script>\n"
244 + "</head>\n"
245 + "<body onload='test()'>\n"
246 + "</body>\n"
247 + "</html>";
248 final List<String> collectedAlerts = Collections.synchronizedList(new ArrayList<String>());
249 loadPage(client_, content, collectedAlerts);
250 client_.waitForBackgroundJavaScript(2000);
251 assertEquals(Collections.EMPTY_LIST, collectedAlerts);
252 }
253
254
255
256
257
258
259 @Test
260 public void clearTimeout_DoesNotStopExecutingCallback() throws Exception {
261 final String html = DOCTYPE_HTML
262 + "<html><body onload='test()'><script>\n"
263 + " var id;\n"
264 + " function test() {\n"
265 + " id = setTimeout(callback, 1);\n"
266 + " }\n"
267 + " function callback() {\n"
268 + " alert(id != 0);\n"
269 + " clearTimeout(id);\n"
270 + " // Make sure we weren't stopped.\n"
271 + " alert('completed');\n"
272 + " }\n"
273 + "</script><div id='a'></div></body></html>";
274 final String[] expected = {"true", "completed"};
275 final List<String> actual = Collections.synchronizedList(new ArrayList<String>());
276 loadPage(client_, html, actual);
277 client_.waitForBackgroundJavaScript(5000);
278 assertEquals(expected, actual);
279 }
280
281
282
283
284
285
286 @Test
287 public void nestedSetTimeoutAboveMaxPriority() throws Exception {
288 final int max = Thread.MAX_PRIORITY + 1;
289 final String content = DOCTYPE_HTML
290 + "<html><body><script language='JavaScript'>\n"
291 + "var depth = 0;\n"
292 + "var maxdepth = " + max + ";\n"
293 + "function addAnother() {\n"
294 + " if (depth < maxdepth) {\n"
295 + " window.alert('ping');\n"
296 + " depth++;\n"
297 + " window.setTimeout('addAnother();', 1);\n"
298 + " }\n"
299 + "}\n"
300 + "addAnother();\n"
301 + "</script></body></html>";
302
303 final List<String> collectedAlerts = Collections.synchronizedList(new ArrayList<String>());
304 loadPage(client_, content, collectedAlerts);
305 assertEquals(0, client_.waitForBackgroundJavaScript((max + 1) * 1000));
306 assertEquals(Collections.nCopies(max, "ping"), collectedAlerts);
307 }
308
309
310
311
312
313
314 @Test
315 public void clearInterval_threadInterrupt() throws Exception {
316 doTestClearX_threadInterrupt("Interval");
317 }
318
319
320
321
322
323
324 @Test
325 public void clearTimeout_threadInterrupt() throws Exception {
326 doTestClearX_threadInterrupt("Timeout");
327 }
328
329 private void doTestClearX_threadInterrupt(final String x) throws Exception {
330 final String html = DOCTYPE_HTML
331 + "<html><head><title>foo</title><script>\n"
332 + " function f() {\n"
333 + " alert('started');\n"
334 + " clear" + x + "(window.timeoutId);\n"
335 + " mySpecialFunction();\n"
336 + " alert('finished');\n"
337 + " }\n"
338 + " function test() {\n"
339 + " window.timeoutId = set" + x + "(f, 10);\n"
340 + " }\n"
341 + "</script></head><body>\n"
342 + "<span id='clickMe' onclick='test()'>click me</span>\n"
343 + "</body></html>";
344
345 final String[] expectedAlerts = {"started", "finished"};
346
347 final List<String> collectedAlerts = new ArrayList<>();
348 final HtmlPage page = loadPage(client_, html, collectedAlerts);
349 final Function mySpecialFunction = new BaseFunction() {
350 @Override
351 public Object call(final Context cx, final Scriptable scope,
352 final Scriptable thisObj, final Object[] args) {
353 if (Thread.currentThread().isInterrupted()) {
354 throw new RuntimeException("My thread is already interrupted");
355 }
356 return null;
357 }
358 };
359 final ScriptableObject window = page.getEnclosingWindow().getScriptableObject();
360 ScriptableObject.putProperty(window, "mySpecialFunction", mySpecialFunction);
361 page.getHtmlElementById("clickMe").click();
362 client_.waitForBackgroundJavaScript(5000);
363 assertEquals(expectedAlerts, collectedAlerts);
364 }
365
366
367
368
369
370 @Test
371 public void verifyCloseStopsJavaScript() throws Exception {
372 final String html = DOCTYPE_HTML
373 + "<html><head><title>foo</title><script>\n"
374 + " function f() {\n"
375 + " alert('Oh no!');\n"
376 + " }\n"
377 + " function test() {\n"
378 + " window.timeoutId = setInterval(f, 1000);\n"
379 + " }\n"
380 + "</script></head><body onload='test()'>\n"
381 + "</body></html>";
382
383 final List<String> collectedAlerts = new ArrayList<>();
384 client_.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
385
386 final MockWebConnection webConnection = new MockWebConnection();
387 webConnection.setDefaultResponse(html);
388 client_.setWebConnection(webConnection);
389
390 client_.getPage(URL_FIRST);
391 client_.close();
392 client_.waitForBackgroundJavaScript(5000);
393 assertTrue(collectedAlerts.isEmpty());
394 }
395
396
397
398
399
400 @Test
401 public void verifyGoingToNewPageStopsJavaScript() throws Exception {
402 final String html1 = DOCTYPE_HTML
403 + "<html><head><title>foo</title><script>\n"
404 + " function f() {\n"
405 + " alert('Oh no!');\n"
406 + " }\n"
407 + " function test() {\n"
408 + " window.timeoutId = setInterval(f, 1000);\n"
409 + " }\n"
410 + "</script></head><body onload='test()'>\n"
411 + "</body></html>";
412 final String html2 = "<html></html>";
413
414 final List<String> collectedAlerts = new ArrayList<>();
415 client_.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
416
417 final MockWebConnection conn = new MockWebConnection();
418 conn.setResponse(URL_FIRST, html1);
419 conn.setResponse(URL_SECOND, html2);
420 client_.setWebConnection(conn);
421
422 client_.getPage(URL_FIRST);
423 client_.getPage(URL_SECOND);
424
425 client_.waitForBackgroundJavaScript(5000);
426 client_.waitForBackgroundJavaScript(5000);
427
428 assertTrue(collectedAlerts.isEmpty());
429 }
430
431
432
433
434
435 @Test
436 public void setTimeoutOnFrameWindow() throws Exception {
437 final String html = DOCTYPE_HTML
438 + "<html><head><title>foo</title><script>\n"
439 + " function test() {\n"
440 + " frames[0].setTimeout(f, 0);\n"
441 + " }\n"
442 + " function f() {\n"
443 + " alert('in f');\n"
444 + " }\n"
445 + "</script></head><body onload='test()'>\n"
446 + "<iframe src='about:blank'></iframe>\n"
447 + "</body></html>";
448
449 final List<String> collectedAlerts = new ArrayList<>();
450 client_.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
451
452 final MockWebConnection conn = new MockWebConnection();
453 conn.setDefaultResponse(html);
454 client_.setWebConnection(conn);
455
456 client_.getPage(URL_FIRST);
457 assertEquals(0, client_.waitForBackgroundJavaScriptStartingBefore(1000));
458
459 final String[] expectedAlerts = {"in f"};
460 assertEquals(expectedAlerts, collectedAlerts);
461 }
462
463
464
465
466
467
468 @Test
469 public void concurrentModificationException_computedStyles() throws Exception {
470 final String html = DOCTYPE_HTML
471 + "<html><head><script>\n"
472 + "function test() {\n"
473 + " getComputedStyle(document.body, null);\n"
474 + "}\n"
475 + "</script></head><body onload='test()'>\n"
476 + "<iframe src='foo.html' name='myFrame' id='myFrame'></iframe>\n"
477 + "</body></html>";
478
479 final String html2 = DOCTYPE_HTML
480 + "<html><head><script>\n"
481 + "function forceStyleComputationInParent() {\n"
482 + " var newNode = parent.document.createElement('span');\n"
483 + " parent.document.body.appendChild(newNode);\n"
484 + " parent.getComputedStyle(newNode, null);\n"
485 + "}\n"
486 + "setInterval(forceStyleComputationInParent, 10);\n"
487 + "</script></head></body></html>";
488
489 try (WebClient client = new WebClient(BrowserVersion.FIREFOX)) {
490 final MockWebConnection webConnection = new MockWebConnection();
491 webConnection.setResponse(URL_FIRST, html);
492 webConnection.setDefaultResponse(html2);
493 client.setWebConnection(webConnection);
494
495 final HtmlPage page1 = client.getPage(URL_FIRST);
496
497
498
499 final HtmlElement elt = new HtmlDivision("div", page1, new HashMap<>()) {
500 @Override
501 public DomNode getParentNode() {
502
503 try {
504 Thread.sleep(1000);
505 }
506 catch (final InterruptedException e) {
507 throw new RuntimeException(e);
508 }
509 return super.getParentNode();
510 }
511 };
512 page1.getBody().appendChild(elt);
513 }
514 }
515 }