1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit.javascript;
16
17 import static org.junit.Assert.fail;
18
19 import java.io.ByteArrayOutputStream;
20 import java.io.IOException;
21 import java.lang.reflect.Field;
22 import java.net.URL;
23 import java.util.ArrayList;
24 import java.util.Collections;
25 import java.util.List;
26 import java.util.zip.GZIPOutputStream;
27
28 import org.htmlunit.BrowserVersion;
29 import org.htmlunit.CollectingAlertHandler;
30 import org.htmlunit.FailingHttpStatusCodeException;
31 import org.htmlunit.HttpHeader;
32 import org.htmlunit.HttpMethod;
33 import org.htmlunit.MockWebConnection;
34 import org.htmlunit.ScriptException;
35 import org.htmlunit.SimpleWebTestCase;
36 import org.htmlunit.WebClient;
37 import org.htmlunit.WebRequest;
38 import org.htmlunit.WebWindow;
39 import org.htmlunit.corejs.javascript.Context;
40 import org.htmlunit.corejs.javascript.ContextFactory;
41 import org.htmlunit.corejs.javascript.Function;
42 import org.htmlunit.corejs.javascript.Script;
43 import org.htmlunit.corejs.javascript.Scriptable;
44 import org.htmlunit.html.DomNode;
45 import org.htmlunit.html.HtmlButtonInput;
46 import org.htmlunit.html.HtmlElement;
47 import org.htmlunit.html.HtmlForm;
48 import org.htmlunit.html.HtmlFrame;
49 import org.htmlunit.html.HtmlPage;
50 import org.htmlunit.html.HtmlScript;
51 import org.htmlunit.html.HtmlTextInput;
52 import org.htmlunit.junit.BrowserRunner;
53 import org.htmlunit.junit.annotation.Alerts;
54 import org.htmlunit.junit.annotation.Retry;
55 import org.htmlunit.util.NameValuePair;
56 import org.htmlunit.util.UrlUtils;
57 import org.junit.Test;
58 import org.junit.runner.RunWith;
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74 @RunWith(BrowserRunner.class)
75 public class JavaScriptEngineTest extends SimpleWebTestCase {
76
77
78
79
80 @Test
81 public void setJavascriptEnabled_false() throws Exception {
82 final String html = DOCTYPE_HTML
83 + "<html><head><title>foo</title><script>\n"
84 + " document.form1.textfield1 = 'blue'"
85 + "</script></head><body>\n"
86 + "<p>hello world</p>\n"
87 + "<form name='form1'>\n"
88 + " <input type='text' name='textfield1' id='textfield1' value='foo' />\n"
89 + " <input type='text' name='textfield2' id='textfield2'/>\n"
90 + "</form>\n"
91 + "</body></html>";
92
93 getWebClientWithMockWebConnection().getOptions().setJavaScriptEnabled(false);
94
95 final HtmlPage page = loadPageWithAlerts(html);
96
97 final HtmlTextInput textInput = page.getHtmlElementById("textfield1");
98 assertEquals("foo", textInput.getValueAttribute());
99 assertEquals("foo", textInput.getValue());
100 }
101
102
103
104
105
106 @Test
107 public void setInputValue() throws Exception {
108 final String content = DOCTYPE_HTML
109 + "<html><head><title>foo</title><script>\n"
110 + "function doTest() {\n"
111 + " document.form1.textfield1.value = 'blue'"
112 + "}\n"
113 + "</script></head><body onload='doTest()'>\n"
114 + "<p>hello world</p>\n"
115 + "<form name='form1'>\n"
116 + " <input type='text' name='textfield1' id='textfield1' value='foo' />\n"
117 + " <input type='text' name='textfield2' id='textfield2'/>\n"
118 + "</form>\n"
119 + "</body></html>";
120 final List<String> collectedAlerts = null;
121 final HtmlPage page = loadPage(content, collectedAlerts);
122
123 final HtmlTextInput textInput = page.getHtmlElementById("textfield1");
124 assertEquals("foo", textInput.getValueAttribute());
125 assertEquals("blue", textInput.getValue());
126 }
127
128
129
130
131
132 @Test
133 public void scopeOfNewFunctionCalledFormOtherWindow() throws Exception {
134 final String firstContent = DOCTYPE_HTML
135 + "<html><head>\n"
136 + "<script>\n"
137 + "var foo = 'foo';\n"
138 + "var test = new Function('alert(foo);');\n"
139 + "</script>\n"
140 + "</head>\n"
141 + "<body onload='test()'>\n"
142 + " <iframe src='page2.html'/>\n"
143 + "</body>\n"
144 + "</html>";
145
146 final String secondContent = DOCTYPE_HTML
147 + "<html><head><script>\n"
148 + "var foo = 'foo2';\n"
149 + "parent.test();\n"
150 + "var f = parent.test;\n"
151 + "f();\n"
152 + "</script></head></html>";
153
154 final WebClient client = getWebClient();
155 final MockWebConnection webConnection = new MockWebConnection();
156 webConnection.setDefaultResponse(secondContent);
157 webConnection.setResponse(URL_FIRST, firstContent);
158 client.setWebConnection(webConnection);
159
160 final String[] expectedAlerts = {"foo", "foo", "foo"};
161
162 final List<String> collectedAlerts = new ArrayList<>();
163 client.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
164 client.getPage(URL_FIRST);
165
166 assertEquals(expectedAlerts, collectedAlerts);
167 }
168
169
170
171
172
173
174 @Test
175 public void scopeInInactivePage() throws Exception {
176 final String firstContent = DOCTYPE_HTML
177 + "<html><head>\n"
178 + "<script>\n"
179 + "var foo = 'foo';\n"
180 + "</script>\n"
181 + "</head>\n"
182 + "<body>\n"
183 + " <a href='page2.html'>to page 2</a>\n"
184 + " <div id='testdiv' onclick='alert(foo)'>foo</div>\n"
185 + "</body>\n"
186 + "</html>";
187
188 final WebClient client = getWebClient();
189 final MockWebConnection webConnection = new MockWebConnection();
190 webConnection.setDefaultResponse("<html></html>");
191 webConnection.setResponse(URL_FIRST, firstContent);
192 client.setWebConnection(webConnection);
193
194 final String[] expectedAlerts = {"foo"};
195
196 final List<String> collectedAlerts = new ArrayList<>();
197 client.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
198 final HtmlPage page = client.getPage(URL_FIRST);
199 final HtmlElement div = page.getHtmlElementById("testdiv");
200
201 div.click();
202 assertEquals(expectedAlerts, collectedAlerts);
203 collectedAlerts.clear();
204
205 page.getAnchors().get(0).click();
206
207
208 div.click();
209 assertEquals(Collections.emptyList(), collectedAlerts);
210 }
211
212
213
214
215 @Test
216 @Alerts("got here")
217 public void externalScript() throws Exception {
218 final String html = DOCTYPE_HTML
219 + "<html><head><title>foo</title><script src='/foo.js' id='script1'/>\n"
220 + "</head><body>\n"
221 + "<p>hello world</p>\n"
222 + "<form name='form1'>\n"
223 + " <input type='text' name='textfield1' id='textfield1' value='foo' />\n"
224 + " <input type='text' name='textfield2' id='textfield2'/>\n"
225 + "</form>\n"
226 + "</body></html>";
227
228 final String jsContent = "alert('got here');\n";
229
230 getMockWebConnection().setResponse(new URL(URL_FIRST, "foo.js"), jsContent,
231 "text/javascript");
232
233 final HtmlPage page = loadPageWithAlerts(html);
234 final HtmlScript htmlScript = page.getHtmlElementById("script1");
235 assertNotNull(htmlScript);
236 }
237
238
239
240
241
242
243
244 @Test
245 public void externalScriptWithApostrophesInComment() throws Exception {
246 final WebClient client = getWebClient();
247 final MockWebConnection webConnection = new MockWebConnection();
248
249 final String htmlContent = DOCTYPE_HTML
250 + "<html><head><title>foo</title>\n"
251 + "<script src='/foo.js' id='script1'><!-- this shouldn't be a problem --></script>\n"
252 + "<script src='/foo2.js' id='script2'><!-- this shouldn't be a problem --></script>\n"
253 + "</head><body>\n"
254 + "<p>hello world</p>\n"
255 + "</body></html>";
256
257 webConnection.setResponse(URL_FIRST, htmlContent);
258 webConnection.setResponse(new URL(URL_FIRST, "foo.js"), "alert('got here');", "text/javascript");
259 webConnection.setResponse(new URL(URL_FIRST, "foo2.js"), "alert('got here 2');", "text/javascript");
260 client.setWebConnection(webConnection);
261
262 final String[] expectedAlerts = {"got here", "got here 2"};
263 final List<String> collectedAlerts = new ArrayList<>();
264 client.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
265
266 final HtmlPage page = client.getPage(URL_FIRST);
267 assertEquals(expectedAlerts, collectedAlerts);
268
269 assertNotNull(page.getHtmlElementById("script1"));
270 assertNotNull(page.getHtmlElementById("script2"));
271 }
272
273
274
275
276
277 @Test
278 public void scriptErrorContainsPageUrl() throws Exception {
279
280 final String content1 = DOCTYPE_HTML
281 + "<html><head><script>a.foo</script>\n"
282 + "</head><body>\n"
283 + "</body></html>";
284
285 try {
286 loadPageWithAlerts(content1);
287 }
288 catch (final Exception e) {
289 assertTrue(e.getMessage().indexOf(URL_FIRST.toString()) > -1);
290 }
291
292
293 final WebClient client = getWebClient();
294 final MockWebConnection webConnection = new MockWebConnection();
295
296 final String content2 = DOCTYPE_HTML
297 + "<html><head><title>foo</title><script src='/foo.js'/>\n"
298 + "</head><body>\n"
299 + "</body></html>";
300
301 final String jsContent = "a.foo = 213;\n";
302
303 webConnection.setResponse(URL_FIRST, content2);
304 final URL urlScript = new URL(URL_FIRST, "foo.js");
305 webConnection.setResponse(urlScript, jsContent, "text/javascript");
306 client.setWebConnection(webConnection);
307
308 try {
309 client.getPage(URL_FIRST);
310 }
311 catch (final Exception e) {
312 assertTrue(e.getMessage(), e.getMessage().indexOf(urlScript.toString()) > -1);
313 }
314 }
315
316
317
318
319 @Test
320 @Alerts("gZip")
321 public void externalScriptGZipEncoded() throws Exception {
322 final MockWebConnection webConnection = getMockWebConnection();
323
324 final String jsContent = "function doTest() { alert('gZip'); }";
325 final ByteArrayOutputStream bytes = new ByteArrayOutputStream();
326 try (GZIPOutputStream gzipper = new GZIPOutputStream(bytes)) {
327 gzipper.write(jsContent.getBytes("ASCII"));
328 }
329
330 final List<NameValuePair> headers = new ArrayList<>();
331 headers.add(new NameValuePair("Content-Encoding", "gzip"));
332 webConnection.setResponse(new URL(URL_FIRST, "foo.js"),
333 bytes.toByteArray(), 200, "OK", "text/javascript", headers);
334
335 final String htmlContent = DOCTYPE_HTML
336 + "<html><head>\n"
337 + "<title>foo</title>\n"
338 + "<script src='/foo.js'></script>\n"
339 + "</head><body onload='doTest()'>\n"
340 + "</body></html>";
341
342 loadPageWithAlerts(htmlContent);
343 }
344
345
346
347
348
349 @Test
350 @Alerts("done")
351 public void externalScriptEmptyGZipEncoded() throws Exception {
352 final MockWebConnection webConnection = getMockWebConnection();
353
354 try (ByteArrayOutputStream bytes = new ByteArrayOutputStream()) {
355 bytes.write("".getBytes("ASCII"));
356
357 final List<NameValuePair> headers = new ArrayList<>();
358 headers.add(new NameValuePair(HttpHeader.CONTENT_LENGTH, "0"));
359 headers.add(new NameValuePair("Content-Encoding", "gzip"));
360 webConnection.setResponse(new URL(URL_FIRST, "foo.js"),
361 bytes.toByteArray(), 200, "OK", "text/javascript", headers);
362 }
363
364 final String htmlContent = DOCTYPE_HTML
365 + "<html><head>\n"
366 + "<title>foo</title>\n"
367 + "<script src='/foo.js'></script>\n"
368 + "</head><body onload='alert(\"done\");'>\n"
369 + "</body></html>";
370
371 loadPageWithAlerts(htmlContent);
372 }
373
374
375
376
377
378 @Test
379 public void externalScriptBrokenGZipEncoded() throws Exception {
380 final MockWebConnection webConnection = getMockWebConnection();
381
382 try (ByteArrayOutputStream bytes = new ByteArrayOutputStream()) {
383 final String jsContent = "function doTest() { alert('gZip'); }";
384 bytes.write(jsContent.getBytes("ASCII"));
385
386 final List<NameValuePair> headers = new ArrayList<>();
387 headers.add(new NameValuePair("Content-Encoding", "gzip"));
388 webConnection.setResponse(new URL(URL_FIRST, "foo.js"),
389 bytes.toByteArray(), 200, "OK", "text/javascript", headers);
390 }
391
392 final String htmlContent = DOCTYPE_HTML
393 + "<html><head>\n"
394 + "<title>foo</title>\n"
395 + "<script src='/foo.js'></script>\n"
396 + "</head><body onload='doTest();alert(\"done\");'>\n"
397 + "</body></html>";
398
399 try {
400 loadPageWithAlerts(htmlContent);
401 fail("ScriptException expected");
402 }
403 catch (final ScriptException e) {
404
405 }
406 }
407
408
409
410
411 @Test
412 public void referencingVariablesFromOneScriptToAnother_Regression() throws Exception {
413 final WebClient client = getWebClient();
414 final MockWebConnection webConnection = new MockWebConnection();
415
416 final String htmlContent = DOCTYPE_HTML
417 + "<html><head><title>foo</title><script src='./test.js'></script>\n"
418 + "<script>var testLocalVariable = new Array();</script>\n"
419 + "</head><body onload='testNestedMethod();' >\n"
420 + "<form name='form1' method='POST' action='../foo' >\n"
421 + " <input type='submit' value='Login' name='loginButton'>\n"
422 + "</form></body></html>";
423
424 final String jsContent
425 = "function testNestedMethod() {\n"
426 + " if (testLocalVariable == null)\n"
427 + " testLocalVariable = 'foo';\n"
428 + "} ";
429
430 webConnection.setResponse(URL_FIRST, htmlContent);
431 webConnection.setResponse(new URL(URL_FIRST, "test.js"), jsContent, "text/javascript");
432 client.setWebConnection(webConnection);
433
434 final HtmlPage page = client.getPage(URL_FIRST);
435 assertEquals("foo", page.getTitleText());
436 }
437
438
439
440
441 @Test
442 public void javaScriptUrl() throws Exception {
443 final String htmlContent = DOCTYPE_HTML
444 + "<html><head><script language='javascript'>\n"
445 + "var f1 = '<html><head><title>frame1</title></head><body><h1>frame1</h1></body></html>';\n"
446 + "var f2 = '<html><head><title>frame2</title></head><body><h1>frame2</h1></body></html>';\n"
447 + "</script></head>\n"
448 + "<frameset border='0' frameborder='0' framespacing='0' rows='100,*'>\n"
449 + " <frame id='frame1' src='javascript:parent.f1'/>\n"
450 + " <frame id='frame2' src='javascript:parent.f2'/>\n"
451 + "</frameset></html>";
452
453 final HtmlPage page = loadPage(htmlContent, null);
454
455 final HtmlPage page1 = (HtmlPage) ((HtmlFrame) page.getHtmlElementById("frame1")).getEnclosedPage();
456 final HtmlPage page2 = (HtmlPage) ((HtmlFrame) page.getHtmlElementById("frame2")).getEnclosedPage();
457
458 assertNotNull("page1", page1);
459 assertNotNull("page2", page2);
460
461 assertEquals("frame1", page1.getTitleText());
462 assertEquals("frame2", page2.getTitleText());
463 }
464
465
466
467
468
469
470 @Test
471 public void thisDotInOnClick() throws Exception {
472 final String htmlContent = DOCTYPE_HTML
473 + "<html><head><title>First</title><script>function foo(message){alert(message);}</script><body>\n"
474 + "<form name='form1'><input type='submit' name='button1' onClick='foo(this.name)'></form>\n"
475 + "</body></html>";
476
477 final List<String> collectedAlerts = new ArrayList<>();
478 final HtmlPage page = loadPage(htmlContent, collectedAlerts);
479 assertEquals("First", page.getTitleText());
480
481 page.getFormByName("form1").getInputByName("button1").click();
482
483 final String[] expectedAlerts = {"button1"};
484 assertEquals(expectedAlerts, collectedAlerts);
485 }
486
487
488
489
490 @Test
491 public void functionDefinedInExternalFile_CalledFromInlineScript() throws Exception {
492 final WebClient client = getWebClient();
493 final MockWebConnection webConnection = new MockWebConnection();
494
495 final String htmlContent = DOCTYPE_HTML
496 + "<html><head><title>foo</title><script src='./test.js'></script>\n"
497 + "</head><body>\n"
498 + " <script>externalMethod()</script>\n"
499 + "</body></html>";
500
501 final String jsContent
502 = "function externalMethod() {\n"
503 + " alert('Got to external method');\n"
504 + "} ";
505
506 webConnection.setResponse(
507 new URL("http://first/index.html"),
508 htmlContent);
509 webConnection.setResponse(
510 new URL("http://first/test.js"),
511 jsContent, "text/javascript");
512 client.setWebConnection(webConnection);
513
514 final List<String> collectedAlerts = new ArrayList<>();
515 client.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
516
517 final HtmlPage page = client.getPage("http://first/index.html");
518 assertEquals("foo", page.getTitleText());
519 assertEquals(new String[] {"Got to external method"}, collectedAlerts);
520 }
521
522
523
524
525
526
527
528 @Test
529 public void externalScriptWithNewLineBeforeClosingScriptTag() throws Exception {
530 final WebClient client = getWebClient();
531 final MockWebConnection webConnection = new MockWebConnection();
532
533 final String htmlContent = DOCTYPE_HTML
534 + "<html><head><title>foo</title>\n"
535 + "</head><body>\n"
536 + "<script src='test.js'>\n</script>\n"
537 + "</body></html>";
538
539 final String jsContent
540 = "function externalMethod() {\n"
541 + " alert('Got to external method');\n"
542 + "}\n"
543 + "externalMethod();\n";
544
545 webConnection.setResponse(URL_FIRST, htmlContent);
546 webConnection.setDefaultResponse(jsContent, 200, "OK", "text/javascript");
547 client.setWebConnection(webConnection);
548
549 final List<String> collectedAlerts = new ArrayList<>();
550 client.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
551
552 client.getPage(URL_FIRST);
553 assertEquals(new String[] {"Got to external method"}, collectedAlerts);
554 }
555
556
557
558
559
560 @Test
561 public void functionDefinedInSameFile() throws Exception {
562 final String htmlContent = DOCTYPE_HTML
563 + "<html><head><title>First</title><script>\n"
564 + "function showFoo(foo) {\n"
565 + " alert('Foo is: |' + foo + '|');\n"
566 + "}\n"
567 + "</script>\n"
568 + "</head><body><form name='form1'>\n"
569 + "<input name='text1' type='text'>\n"
570 + "<input name='button1' type='button' onclick='showFoo(document.form1.text1.value);'>\n"
571 + "</form></body></html>";
572
573 final List<String> collectedAlerts = new ArrayList<>();
574
575 final HtmlPage page = loadPage(htmlContent, collectedAlerts);
576 assertEquals("First", page.getTitleText());
577
578 final HtmlForm form = page.getFormByName("form1");
579 final HtmlTextInput textInput = form.getInputByName("text1");
580 textInput.setValue("flintstone");
581
582 final HtmlButtonInput button = form.getInputByName("button1");
583 assertEquals(Collections.EMPTY_LIST, collectedAlerts);
584
585 button.click();
586
587 assertEquals(new String[] {"Foo is: |flintstone|"}, collectedAlerts);
588 }
589
590
591
592
593
594 @Test
595 public void javaScriptEngineCallsForVariableAccess() throws Exception {
596 final WebClient client = getWebClient();
597 final MockWebConnection webConnection = new MockWebConnection();
598
599 final List<String> collectedAlerts = new ArrayList<>();
600 client.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
601
602 final String content = DOCTYPE_HTML
603 + "<html><head><title>foo</title><script>\n"
604 + "myDate = 'foo';\n"
605 + "function doUnqualifiedVariableAccess() {\n"
606 + " alert('unqualified: ' + myDate);\n"
607 + "}\n"
608 + "function doQualifiedVariableAccess() {\n"
609 + " alert('qualified: ' + window.myDate);\n"
610 + "}\n"
611 + "</script></head><body>\n"
612 + "<p>hello world</p>\n"
613 + "<a id='unqualified' onclick='doUnqualifiedVariableAccess();'>unqualified</a>\n"
614 + "<a id='qualified' onclick='doQualifiedVariableAccess();'>qualified</a>\n"
615 + "</body></html>";
616
617 webConnection.setDefaultResponse(content);
618 client.setWebConnection(webConnection);
619 final CountingJavaScriptEngine countingJavaScriptEngine = new CountingJavaScriptEngine(client);
620 client.setJavaScriptEngine(countingJavaScriptEngine);
621
622 final HtmlPage page = client.getPage(URL_FIRST);
623
624 assertEquals(1, countingJavaScriptEngine.getExecutionCount());
625 assertEquals(0, countingJavaScriptEngine.getCallCount());
626
627 page.getHtmlElementById("unqualified").click();
628 assertEquals(1, countingJavaScriptEngine.getExecutionCount());
629 assertEquals(1, countingJavaScriptEngine.getCallCount());
630
631 page.getHtmlElementById("qualified").click();
632 assertEquals(1, countingJavaScriptEngine.getExecutionCount());
633 assertEquals(2, countingJavaScriptEngine.getCallCount());
634
635 final String[] expectedAlerts = {"unqualified: foo", "qualified: foo"};
636 assertEquals(expectedAlerts, collectedAlerts);
637 }
638
639
640
641
642
643 @Test
644 public void activeXObjectNoMap() throws Exception {
645 try {
646 loadPage(getJavaScriptContent("new ActiveXObject()"));
647 fail("An exception should be thrown for zero argument constructor.");
648 }
649 catch (final ScriptException e) {
650
651 }
652
653 try {
654 loadPage(getJavaScriptContent("new ActiveXObject(1, '2', '3')"));
655 fail("An exception should be thrown for a three argument constructor.");
656 }
657 catch (final ScriptException e) {
658
659 }
660
661 try {
662 loadPage(getJavaScriptContent("new ActiveXObject(a)"));
663 fail("An exception should be thrown for an undefined parameter in the constructor.");
664 }
665 catch (final ScriptException e) {
666
667 }
668
669 try {
670 loadPage(getJavaScriptContent("new ActiveXObject(10)"));
671 fail("An exception should be thrown for an integer parameter in the constructor.");
672 }
673 catch (final ScriptException e) {
674
675 }
676
677 try {
678 loadPage(getJavaScriptContent("new ActiveXObject('UnknownObject')"));
679 fail("An exception should be thrown for a null map.");
680 }
681 catch (final ScriptException e) {
682
683 }
684 }
685
686 private static String getJavaScriptContent(final String javascript) {
687 return DOCTYPE_HTML
688 + "<html><head><title>foo</title><script>\n"
689 + javascript
690 + "</script></head><body>\n"
691 + "<p>hello world</p>\n"
692 + "<form name='form1'>\n"
693 + " <input type='text' name='textfield1' id='textfield1' value='foo' />\n"
694 + " <input type='text' name='textfield2' id='textfield2'/>\n"
695 + "</form>\n"
696 + "</body></html>";
697 }
698
699
700
701
702
703 @Test
704 public void scriptErrorIsolated() throws Exception {
705 final String content = DOCTYPE_HTML
706 + "<html>\n"
707 + "<head>\n"
708 + "<script>alert(1);</script>\n"
709 + "<script>alert(2</script>\n"
710 + "<script>alert(3);</script>\n"
711 + "</head>\n"
712 + "<body onload='alert(4);notExisting()'>\n"
713 + "<button onclick='alert(5)'>click me</button>\n"
714 + "</body>\n"
715 + "</html>";
716
717 final String[] expectedAlerts = {"1", "3", "4"};
718
719 final WebClient client = getWebClient();
720 final MockWebConnection webConnection = new MockWebConnection();
721 webConnection.setDefaultResponse(content);
722 client.setWebConnection(webConnection);
723 final List<String> collectedAlerts = new ArrayList<>();
724 client.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
725
726
727 try {
728 client.getPage(URL_FIRST);
729 fail("Should have thrown a script error");
730 }
731 catch (final Exception e) {
732
733 }
734 assertEquals(new String[] {"1"}, collectedAlerts);
735 collectedAlerts.clear();
736
737
738 client.getOptions().setThrowExceptionOnScriptError(false);
739 client.getPage(URL_FIRST);
740
741 assertEquals(expectedAlerts, collectedAlerts);
742 }
743
744
745
746
747
748
749
750
751 @Test
752 public void prototypeScope() throws Exception {
753 prototypeScope("String", "'some string'");
754 prototypeScope("Number", "9");
755 prototypeScope("Date", "new Date()");
756 prototypeScope("Function", "function() {}");
757 prototypeScope("Array", "[]");
758 }
759
760 private void prototypeScope(final String name, final String value) throws Exception {
761 final String content1 = DOCTYPE_HTML
762 + "<html><head>\n"
763 + "<script>\n"
764 + "window.open('second.html', 'secondWindow');\n"
765 + "</script>\n"
766 + "</head><body></body></html>";
767
768 final String content2 = DOCTYPE_HTML
769 + "<html><head>\n"
770 + "<script>\n"
771 + "alert('in page 2');\n"
772 + name + ".prototype.foo = function() {\n"
773 + " alert('in foo');\n"
774 + "};\n"
775 + "var x = " + value + ";\n"
776 + "x.foo();\n"
777 + "</script>\n"
778 + "</head><body></body></html>";
779
780 final String[] expectedAlerts = {"in page 2", "in foo"};
781
782 final WebClient client = getWebClient();
783 final MockWebConnection webConnection = new MockWebConnection();
784 webConnection.setDefaultResponse(content2);
785 webConnection.setResponse(URL_FIRST, content1);
786 client.setWebConnection(webConnection);
787
788 final List<String> collectedAlerts = new ArrayList<>();
789 client.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
790
791 client.getPage(URL_FIRST);
792 assertEquals(expectedAlerts, collectedAlerts);
793 }
794
795
796
797
798 @Test
799 public void timeout() throws Exception {
800 final WebClient client = getWebClient();
801 final long timeout = 2000;
802 final long oldTimeout = client.getJavaScriptTimeout();
803 client.setJavaScriptTimeout(timeout);
804
805 try {
806 client.getOptions().setThrowExceptionOnScriptError(false);
807
808 final String content = DOCTYPE_HTML + "<html><body><script>while(1) {}</script></body></html>";
809 final MockWebConnection webConnection = new MockWebConnection();
810 webConnection.setDefaultResponse(content);
811 client.setWebConnection(webConnection);
812
813 final Exception[] exceptions = {null};
814 final Thread runner = new Thread() {
815 @Override
816 public void run() {
817 try {
818 client.getPage(URL_FIRST);
819 }
820 catch (final Exception e) {
821 exceptions[0] = e;
822 }
823 }
824 };
825
826 runner.start();
827
828 runner.join(timeout * 2);
829 if (runner.isAlive()) {
830 runner.interrupt();
831 fail("Script was still running after timeout");
832 }
833 assertNull(exceptions[0]);
834 }
835 finally {
836 client.setJavaScriptTimeout(oldTimeout);
837 }
838 }
839
840 private static final class CountingJavaScriptEngine extends JavaScriptEngine {
841 private int scriptExecutionCount_ = 0;
842 private int scriptCallCount_ = 0;
843 private int scriptCompileCount_ = 0;
844 private int scriptExecuteScriptCount_ = 0;
845
846
847
848
849
850 protected CountingJavaScriptEngine(final WebClient client) {
851 super(client);
852 }
853
854
855 @Override
856 public Object execute(
857 final HtmlPage page, final Scriptable scope,
858 final String sourceCode, final String sourceName, final int startLine) {
859 scriptExecutionCount_++;
860 return super.execute(page, scope, sourceCode, sourceName, startLine);
861 }
862
863
864 @Override
865 public Object execute(final HtmlPage page, final Scriptable scope, final Script script) {
866 scriptExecuteScriptCount_++;
867 return super.execute(page, scope, script);
868 }
869
870
871 @Override
872 public Script compile(final HtmlPage page, final Scriptable scope,
873 final String sourceCode, final String sourceName, final int startLine) {
874 scriptCompileCount_++;
875 return super.compile(page, scope, sourceCode, sourceName, startLine);
876 }
877
878
879 @Override
880 public Object callFunction(
881 final HtmlPage page, final Function javaScriptFunction,
882 final Scriptable thisObject, final Object[] args,
883 final DomNode htmlElementScope) {
884 scriptCallCount_++;
885 return super.callFunction(page, javaScriptFunction, thisObject, args, htmlElementScope);
886 }
887
888
889
890
891 public int getCallCount() {
892 return scriptCallCount_;
893 }
894
895
896
897
898 public int getExecutionCount() {
899 return scriptExecutionCount_;
900 }
901
902
903
904
905 public int getCompileCount() {
906 return scriptCompileCount_;
907 }
908
909
910
911
912 public int getExecuteScriptCount() {
913 return scriptExecuteScriptCount_;
914 }
915 }
916
917
918
919
920 @Test
921 @Alerts({"", "ex thrown"})
922 public void commentNoDoubleSlash() throws Exception {
923 final String html = DOCTYPE_HTML
924 + "<html><head>\n"
925 + "<script><!-- alert(1);\n"
926 + " alert(2);\n"
927 + "alert(3) --></script>\n"
928 + "</head>\n"
929 + "<body>\n"
930 + "</body></html>";
931
932 final String expectedExThrown = getExpectedAlerts()[1];
933 String exceptionThrown = "no ex";
934 try {
935 setExpectedAlerts(getExpectedAlerts()[0]);
936 loadPageWithAlerts(html);
937 }
938 catch (final ScriptException e) {
939 exceptionThrown = "ex thrown";
940 assertEquals(5, e.getFailingLineNumber());
941 }
942
943 assertEquals(expectedExThrown, exceptionThrown);
944 }
945
946
947
948
949
950 @Test
951 public void compiledScriptCached() throws Exception {
952 final String content1 = DOCTYPE_HTML
953 + "<html><head><title>foo</title>\n"
954 + "<script src='script.js'></script>\n"
955 + "</head><body>\n"
956 + "<a href='page2.html'>to page 2</a>\n"
957 + "</body></html>";
958 final String content2 = DOCTYPE_HTML
959 + "<html><head><title>page 2</title>\n"
960 + "<script src='script.js'></script>\n"
961 + "</head><body>\n"
962 + "</body></html>";
963 final String script = "alert(document.title)";
964
965 final WebClient client = getWebClient();
966 final MockWebConnection connection = new MockWebConnection();
967 client.setWebConnection(connection);
968 connection.setResponse(URL_FIRST, content1);
969 connection.setResponse(new URL(URL_FIRST, "page2.html"), content2);
970
971 final List<NameValuePair> headersAllowingCache = new ArrayList<>();
972 headersAllowingCache.add(new NameValuePair("Last-Modified", "Sun, 15 Jul 2007 20:46:27 GMT"));
973 connection.setResponse(new URL(URL_FIRST, "script.js"), script,
974 200, "ok", "text/javascript", headersAllowingCache);
975
976 final CountingJavaScriptEngine countingJavaScriptEngine = new CountingJavaScriptEngine(client);
977 client.setJavaScriptEngine(countingJavaScriptEngine);
978
979 final List<String> collectedAlerts = new ArrayList<>();
980 client.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
981 final HtmlPage page1 = client.getPage(URL_FIRST);
982 assertEquals(new String[] {"foo"}, collectedAlerts);
983 assertEquals(1, countingJavaScriptEngine.getExecuteScriptCount());
984 assertEquals(1, countingJavaScriptEngine.getCompileCount());
985
986 collectedAlerts.clear();
987 page1.getAnchors().get(0).click();
988 assertEquals(new String[] {"page 2"}, collectedAlerts);
989 assertEquals(2, countingJavaScriptEngine.getExecuteScriptCount());
990 assertEquals(1, countingJavaScriptEngine.getCompileCount());
991 }
992
993
994
995
996
997
998
999 @Test
1000 public void scriptTags_AllLocalContent() throws Exception {
1001 final String content = DOCTYPE_HTML
1002 + "<html>\n"
1003 + "<head><title>foo</title>\n"
1004 + "<script>One</script>\n"
1005 + "<script language='javascript'>Two</script>\n"
1006 + "<script type='text/javascript'>Three</script>\n"
1007 + "<script type='text/perl'>Four</script>\n"
1008 + "</head>\n"
1009 + "<body>\n"
1010 + "<p>hello world</p>\n"
1011 + "</body></html>";
1012 final List<String> collectedScripts = new ArrayList<>();
1013 loadPageAndCollectScripts(content, collectedScripts);
1014
1015
1016 final String[] expectedScripts = {"One", "Two", "Three"};
1017
1018 assertEquals(expectedScripts, collectedScripts);
1019 }
1020
1021 private HtmlPage loadPageAndCollectScripts(final String html, final List<String> collectedScripts)
1022 throws Exception {
1023
1024 final WebClient client = getWebClient();
1025 client.setJavaScriptEngine(new JavaScriptEngine(client) {
1026 @Override
1027 public Object execute(final HtmlPage htmlPage, final Scriptable scope,
1028 final String sourceCode, final String sourceName, final int startLine) {
1029 collectedScripts.add(sourceCode);
1030 return null;
1031 }
1032 @Override
1033 public Object callFunction(
1034 final HtmlPage htmlPage, final Function javaScriptFunction,
1035 final Scriptable thisObject, final Object[] args,
1036 final DomNode htmlElement) {
1037 return null;
1038 }
1039 @Override
1040 public boolean isScriptRunning() {
1041 return false;
1042 }
1043 });
1044
1045 final MockWebConnection webConnection = new MockWebConnection();
1046
1047 webConnection.setDefaultResponse(html);
1048 client.setWebConnection(webConnection);
1049
1050 final HtmlPage page = client.getPage(new WebRequest(URL_FIRST, HttpMethod.POST));
1051 return page;
1052 }
1053
1054
1055
1056
1057
1058 @Test
1059 public void noGlobalContextFactoryUsed() {
1060 final WebClient client1 = getWebClient();
1061 final WebClient client2 = createNewWebClient();
1062
1063 final HtmlUnitContextFactory cf1 = client1.getJavaScriptEngine().getContextFactory();
1064 final HtmlUnitContextFactory cf2 = client2.getJavaScriptEngine().getContextFactory();
1065
1066 assertFalse(cf1 == cf2);
1067 assertFalse(cf1 == ContextFactory.getGlobal());
1068 assertFalse(cf2 == ContextFactory.getGlobal());
1069 }
1070
1071
1072
1073
1074
1075 @Test
1076 public void catchBackgroundJSErrors() throws Exception {
1077 final WebClient webClient = getWebClient();
1078 final List<ScriptException> jsExceptions = new ArrayList<>();
1079 final JavaScriptEngine myEngine = new JavaScriptEngine(webClient) {
1080 @Override
1081 protected void handleJavaScriptException(final ScriptException scriptException,
1082 final boolean triggerOnError) {
1083 jsExceptions.add(scriptException);
1084 super.handleJavaScriptException(scriptException, triggerOnError);
1085 }
1086 };
1087 webClient.setJavaScriptEngine(myEngine);
1088
1089 final String html = DOCTYPE_HTML
1090 + "<html>\n"
1091 + "<head><title>Test page</title><\n"
1092 + "<script>\n"
1093 + "function myFunction() {\n"
1094 + " document.title = 'New title';\n"
1095 + " notExisting(); // will throw\n"
1096 + "}\n"
1097 + "window.setTimeout(myFunction, 5);\n"
1098 + "</script>\n"
1099 + "</head>\n"
1100 + "<body>\n"
1101 + "</body></html>";
1102
1103 final MockWebConnection webConnection = new MockWebConnection();
1104 webConnection.setDefaultResponse(html);
1105 webClient.setWebConnection(webConnection);
1106
1107 final HtmlPage page = webClient.getPage(URL_FIRST);
1108 webClient.waitForBackgroundJavaScript(10_000);
1109 assertEquals("New title", page.getTitleText());
1110
1111 assertEquals(1, jsExceptions.size());
1112 final ScriptException exception = jsExceptions.get(0);
1113 assertTrue("Message: " + exception.getMessage(),
1114 exception.getMessage().contains("\"notExisting\" is not defined"));
1115 }
1116
1117
1118
1119
1120
1121 @Test
1122 public void daemonExecutorThread() throws Exception {
1123 final String html = DOCTYPE_HTML
1124 + "<html><body><script>\n"
1125 + "function f() { alert('foo'); }\n"
1126 + "setTimeout(f, 5);\n"
1127 + "</script>\n"
1128 + "</body></html>";
1129
1130 final List<String> collectedAlerts = new ArrayList<>();
1131 final HtmlPage page = loadPage(html, collectedAlerts);
1132
1133 Thread.sleep(20);
1134 final List<Thread> jsThreads = getJavaScriptThreads();
1135 assertEquals(1, jsThreads.size());
1136 final Thread jsThread = jsThreads.get(0);
1137 assertEquals("JS executor for " + page.getWebClient(), jsThread.getName());
1138 assertTrue(jsThread.isDaemon());
1139 }
1140
1141
1142
1143
1144 @Test
1145 public void shutdown() throws Exception {
1146 final String html = "<html></html>";
1147 final HtmlPage page = loadPage(html);
1148
1149 @SuppressWarnings("resource")
1150 final WebClient webClient = getWebClient();
1151 final AbstractJavaScriptEngine<?> engine = webClient.getJavaScriptEngine();
1152
1153 engine.addPostponedAction(new PostponedAction(page, "shutdown test") {
1154 @Override
1155 public void execute() throws Exception {
1156
1157 }
1158 });
1159 assertEquals(1, getPostponedActions(engine).get().size());
1160 webClient.close();
1161
1162 assertNull(getPostponedActions(engine).get());
1163 }
1164
1165 @SuppressWarnings("unchecked")
1166 private static ThreadLocal<List<PostponedAction>> getPostponedActions(final AbstractJavaScriptEngine<?> engine) {
1167 try {
1168 final Field field = engine.getClass().getDeclaredField("postponedActions_");
1169 field.setAccessible(true);
1170 return (ThreadLocal<List<PostponedAction>>) field.get(engine);
1171 }
1172 catch (final Exception e) {
1173 throw Context.throwAsScriptRuntimeEx(e);
1174 }
1175 }
1176
1177
1178
1179
1180 @Test
1181 @Retry
1182 @Alerts("starting")
1183 public void shutdownShouldKill() throws Exception {
1184 final String html = DOCTYPE_HTML
1185 + "<html>\n"
1186 + "<head><title>Test page</title>\n"
1187 + "<script>\n"
1188 + " function test() {\n"
1189 + " alert('starting');\n"
1190 + " while(true) { Math.sin(3.14) * Math.sin(3.14);}\n"
1191 + " }\n"
1192 + "</script>\n"
1193 + "</head>\n"
1194 + "<body onload='setTimeout(test, 50);'>\n"
1195 + "</body></html>";
1196
1197 try (WebClient webClient = getWebClient()) {
1198
1199
1200 webClient.setJavaScriptTimeout(DEFAULT_WAIT_TIME.toMillis() * 10);
1201
1202 final List<String> collectedAlerts = new ArrayList<>();
1203 webClient.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
1204
1205 loadPage(html);
1206 Thread.sleep(100);
1207 assertEquals(getExpectedAlerts(), collectedAlerts);
1208
1209 }
1210 Thread.sleep(400);
1211 try {
1212 assertTrue(getJavaScriptThreads().isEmpty());
1213 }
1214 catch (final AssertionError e) {
1215 Thread.sleep(DEFAULT_WAIT_TIME.toMillis() * 10);
1216 assertTrue(getJavaScriptThreads().isEmpty());
1217 }
1218 }
1219
1220
1221
1222
1223 @Test
1224 @Retry
1225 @Alerts("starting")
1226 public void shutdownShouldKillJavaScriptTimeout() throws Exception {
1227 final String html = DOCTYPE_HTML
1228 + "<html>\n"
1229 + "<head><title>Test page</title>\n"
1230 + "<script>\n"
1231 + " function test() {\n"
1232 + " alert('starting');\n"
1233 + " while(true) { Math.sin(3.14) * Math.sin(3.14);}\n"
1234 + " }\n"
1235 + "</script>\n"
1236 + "</head>\n"
1237 + "<body onload='setTimeout(test, 50);'>\n"
1238 + "</body></html>";
1239
1240 try (WebClient webClient = getWebClient()) {
1241 webClient.setJavaScriptTimeout(DEFAULT_WAIT_TIME.toMillis());
1242
1243 final List<String> collectedAlerts = new ArrayList<>();
1244 webClient.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
1245
1246 loadPage(html);
1247 Thread.sleep(100);
1248 assertEquals(getExpectedAlerts(), collectedAlerts);
1249
1250 }
1251 Thread.sleep(400);
1252 assertTrue(getJavaScriptThreads().isEmpty());
1253 }
1254
1255
1256
1257
1258 @Test
1259 @Alerts("unload")
1260 public void shutdownOnUnload() throws Exception {
1261 final String html = DOCTYPE_HTML
1262 + "<html>\n"
1263 + "<head><title>Test page</title>\n"
1264 + "<script>\n"
1265 + " window.onbeforeunload = function(e) {\n"
1266 + " window.open('about:blank');\n"
1267 + " }\n"
1268 + "</script>\n"
1269 + "</head>\n"
1270 + "<body'>\n"
1271 + "</body></html>";
1272
1273 loadPage(html);
1274
1275 getWebClient().close();
1276 assertTrue(getJavaScriptThreads().isEmpty());
1277 }
1278
1279
1280
1281
1282
1283 @Test
1284 public void nonStandardBrowserVersion() throws Exception {
1285 final BrowserVersion browser = new BrowserVersion.BrowserVersionBuilder(BrowserVersion.CHROME)
1286 .setApplicationName("Explorer")
1287 .setApplicationVersion("5.0")
1288 .build();
1289
1290 try (WebClient client = new WebClient(browser)) {
1291 client.openWindow(UrlUtils.URL_ABOUT_BLANK, "TestWindow");
1292 }
1293 }
1294
1295
1296
1297
1298
1299
1300 @Test
1301 public void initRaceCondition() throws Exception {
1302 final String html = DOCTYPE_HTML
1303 + "<html>\n"
1304 + "<head><title>Test page</title><\n"
1305 + "<script>\n"
1306 + " var d = document.visibilityState;\n"
1307 + "</script>\n"
1308 + "</head>\n"
1309 + "<body>\n"
1310 + "</body></html>";
1311
1312 final MockWebConnection webConnection = new MockWebConnection();
1313 webConnection.setDefaultResponse(html);
1314 try (WebClient webClient = getWebClient()) {
1315 webClient.setWebConnection(webConnection);
1316
1317 final WebWindow window1 = webClient.getCurrentWindow();
1318 final WebWindow window2 = webClient.openWindow(null, "window2", window1);
1319
1320 final int runs = 100;
1321
1322 final Thread t1 = new Thread(new Runnable() {
1323
1324 @Override
1325 public void run() {
1326 try {
1327 for (int i = 0; i < runs; i++) {
1328 webClient.getPage(window1, new WebRequest(URL_FIRST));
1329 }
1330 }
1331 catch (final FailingHttpStatusCodeException | IOException e) {
1332 throw new RuntimeException(e);
1333 }
1334 }
1335 });
1336
1337 final Thread t2 = new Thread(new Runnable() {
1338
1339 @Override
1340 public void run() {
1341 try {
1342 for (int i = 0; i < runs; i++) {
1343 webClient.getPage(window2, new WebRequest(URL_FIRST));
1344 }
1345 }
1346 catch (final FailingHttpStatusCodeException | IOException e) {
1347 throw new RuntimeException(e);
1348 }
1349 }
1350 });
1351
1352 t1.start();
1353 t2.start();
1354
1355 t1.join();
1356 t2.join();
1357 }
1358 }
1359
1360
1361
1362
1363
1364 @Test
1365 public void useAfterShutdownShouldNotCreateThreads() {
1366 @SuppressWarnings("resource")
1367 final WebClient webClient = getWebClient();
1368 final WebWindow window = webClient.getCurrentWindow();
1369 final AbstractJavaScriptEngine<?> engine = webClient.getJavaScriptEngine();
1370 webClient.close();
1371 engine.registerWindowAndMaybeStartEventLoop(window);
1372 assertTrue(getJavaScriptThreads().isEmpty());
1373 }
1374 }