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