1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit.javascript.host.xml;
16
17 import java.io.IOException;
18 import java.io.Writer;
19 import java.net.URL;
20 import java.util.ArrayList;
21 import java.util.Collections;
22 import java.util.Enumeration;
23 import java.util.HashMap;
24 import java.util.List;
25 import java.util.Locale;
26 import java.util.Map;
27
28 import javax.servlet.Servlet;
29 import javax.servlet.http.HttpServlet;
30 import javax.servlet.http.HttpServletRequest;
31 import javax.servlet.http.HttpServletResponse;
32
33 import org.htmlunit.CollectingAlertHandler;
34 import org.htmlunit.HttpMethod;
35 import org.htmlunit.MockWebConnection;
36 import org.htmlunit.WebClient;
37 import org.htmlunit.WebRequest;
38 import org.htmlunit.WebResponse;
39 import org.htmlunit.WebServerTestCase;
40 import org.htmlunit.html.DomChangeEvent;
41 import org.htmlunit.html.DomChangeListener;
42 import org.htmlunit.html.DomElement;
43 import org.htmlunit.html.HtmlElement;
44 import org.htmlunit.html.HtmlPage;
45 import org.htmlunit.javascript.host.xml.XMLHttpRequestTest.StreamingServlet;
46 import org.htmlunit.junit.BrowserRunner;
47 import org.htmlunit.junit.annotation.Alerts;
48 import org.htmlunit.junit.annotation.HtmlUnitNYI;
49 import org.htmlunit.util.MimeType;
50 import org.junit.Test;
51 import org.junit.runner.RunWith;
52
53
54
55
56
57
58
59
60
61
62
63
64 @RunWith(BrowserRunner.class)
65 public class XMLHttpRequest3Test extends WebServerTestCase {
66
67 private static final String MSG_NO_CONTENT = "no Content";
68 private static final String MSG_PROCESSING_ERROR = "error processing";
69
70
71
72
73
74 @Test
75 @Alerts({"0", "1", "4", MSG_NO_CONTENT, MSG_PROCESSING_ERROR})
76 public void asyncUseWithNetworkConnectionFailure() throws Exception {
77 final String html = DOCTYPE_HTML
78 + "<html>\n"
79 + "<head>\n"
80 + "<title>XMLHttpRequest Test</title>\n"
81 + "<script>\n"
82 + "var request;\n"
83 + "function testAsync() {\n"
84 + " request = new XMLHttpRequest();\n"
85 + " request.onreadystatechange = onReadyStateChange;\n"
86 + " request.onerror = onError;\n"
87 + " alert(request.readyState);\n"
88 + " request.open('GET', '" + URL_SECOND + "', true);\n"
89 + " request.send('');\n"
90 + "}\n"
91 + "function onError() {\n"
92 + " alert('" + MSG_PROCESSING_ERROR + "');\n"
93 + "}\n"
94 + "function onReadyStateChange() {\n"
95 + " alert(request.readyState);\n"
96 + " if (request.readyState == 4) {\n"
97 + " if (request.responseText == null)\n"
98 + " alert('" + MSG_NO_CONTENT + "');\n"
99 + " else\n"
100 + " throw 'Unexpected content, should be zero length but is: \"' + request.responseText + '\"';\n"
101 + " }\n"
102 + "}\n"
103 + "</script>\n"
104 + "</head>\n"
105 + "<body onload='testAsync()'>\n"
106 + "</body>\n"
107 + "</html>";
108
109 final WebClient client = getWebClient();
110 final MockWebConnection conn = new DisconnectedMockWebConnection();
111 conn.setResponse(URL_FIRST, html);
112 client.setWebConnection(conn);
113 client.getPage(URL_FIRST);
114
115 assertEquals(0, client.waitForBackgroundJavaScriptStartingBefore(1000));
116 assertEquals(getExpectedAlerts(), getCollectedAlerts(null));
117 }
118
119
120
121
122 private static final class DisconnectedMockWebConnection extends MockWebConnection {
123
124 DisconnectedMockWebConnection() {
125 }
126
127
128 @Override
129 public WebResponse getResponse(final WebRequest request) throws IOException {
130 if (URL_SECOND.equals(request.getUrl())) {
131 throw new IOException("Connection refused");
132 }
133 return super.getResponse(request);
134 }
135 }
136
137
138
139
140
141
142 @Test
143 public void noParallelJSExecutionInPage() throws Exception {
144 final String content = DOCTYPE_HTML
145 + "<html><head><script>\n"
146 + "var j = 0;\n"
147 + "function test() {\n"
148 + " req = new XMLHttpRequest();\n"
149 + " req.onreadystatechange = handler;\n"
150 + " req.open('post', 'foo.xml', true);\n"
151 + " req.send('');\n"
152 + " alert('before long loop');\n"
153 + " for (var i = 0; i < 5000; i++) {\n"
154 + " j = j + 1;\n"
155 + " }\n"
156 + " alert('after long loop');\n"
157 + "}\n"
158 + "function handler() {\n"
159 + " if (req.readyState == 4) {\n"
160 + " alert('ready state handler, content loaded: j=' + j);\n"
161 + " }\n"
162 + "}\n"
163 + "</script></head>\n"
164 + "<body onload='test()'></body></html>";
165
166 final WebClient client = getWebClient();
167 final List<String> collectedAlerts = Collections.synchronizedList(new ArrayList<String>());
168 client.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
169 final MockWebConnection conn = new MockWebConnection() {
170 @Override
171 public WebResponse getResponse(final WebRequest webRequest) throws IOException {
172 collectedAlerts.add(webRequest.getUrl().toExternalForm());
173 return super.getResponse(webRequest);
174 }
175 };
176 conn.setResponse(URL_FIRST, content);
177 final URL urlPage2 = new URL(URL_FIRST, "foo.xml");
178 conn.setResponse(urlPage2, "<foo/>\n", MimeType.TEXT_XML);
179 client.setWebConnection(conn);
180 client.getPage(URL_FIRST);
181
182 assertEquals(0, client.waitForBackgroundJavaScriptStartingBefore(1000));
183
184 final String[] alerts = {URL_FIRST.toExternalForm(), "before long loop", "after long loop",
185 urlPage2.toExternalForm(), "ready state handler, content loaded: j=5000" };
186 assertEquals(alerts, collectedAlerts);
187 }
188
189
190
191
192
193 @Test
194 public void methods() throws Exception {
195 testMethod(HttpMethod.GET);
196 testMethod(HttpMethod.HEAD);
197 testMethod(HttpMethod.DELETE);
198 testMethod(HttpMethod.POST);
199 testMethod(HttpMethod.PUT);
200 testMethod(HttpMethod.OPTIONS);
201 testMethod(HttpMethod.TRACE);
202 testMethod(HttpMethod.PATCH);
203 }
204
205
206
207
208 private void testMethod(final HttpMethod method) throws Exception {
209 final String content = DOCTYPE_HTML
210 + "<html><head><script>\n"
211 + "function test() {\n"
212 + " var req = new XMLHttpRequest();\n"
213 + " req.open('" + method.name().toLowerCase(Locale.ROOT) + "', 'foo.xml', false);\n"
214 + " req.send('');\n"
215 + "}\n"
216 + "</script></head>\n"
217 + "<body onload='test()'></body></html>";
218
219 final WebClient client = getWebClient();
220 final MockWebConnection conn = new MockWebConnection();
221 conn.setResponse(URL_FIRST, content);
222 final URL urlPage2 = new URL(URL_FIRST, "foo.xml");
223 conn.setResponse(urlPage2, "<foo/>\n", MimeType.TEXT_XML);
224 client.setWebConnection(conn);
225 client.getPage(URL_FIRST);
226
227 final WebRequest request = conn.getLastWebRequest();
228 assertEquals(urlPage2, request.getUrl());
229 assertSame(method, request.getHttpMethod());
230 }
231
232
233
234
235
236 @Test
237 public void xmlHttpRequestWithDomChangeListenerDeadlock() throws Exception {
238 final String content = DOCTYPE_HTML
239 + "<html><head><title>foo</title>\n"
240 + "<script>\n"
241 + " function test() {\n"
242 + " frames[0].test('foo1.txt', true);\n"
243 + " frames[0].test('foo2.txt', false);\n"
244 + " }\n"
245 + "</script>\n"
246 + "</head>\n"
247 + "<body>\n"
248 + "<p id='p1' title='myTitle' onclick='test()'></p>\n"
249 + "<iframe src='page2.html'></iframe>\n"
250 + "</body></html>";
251
252 final String content2 = DOCTYPE_HTML
253 + "<html><head><title>foo</title>\n"
254 + "<script>\n"
255 + "function test(_src, _async)\n"
256 + "{\n"
257 + " var request = new XMLHttpRequest();\n"
258 + " request.onreadystatechange = onReadyStateChange;\n"
259 + " request.open('GET', _src, _async);\n"
260 + " request.send('');\n"
261 + "}\n"
262 + "function onReadyStateChange() {\n"
263 + " parent.document.getElementById('p1').title = 'new title';\n"
264 + "}\n"
265 + "</script>\n"
266 + "</head>\n"
267 + "<body>\n"
268 + "<p id='p1' title='myTitle'></p>\n"
269 + "</body></html>";
270
271 final MockWebConnection connection = new MockWebConnection() {
272 private boolean gotFoo1_ = false;
273
274 @Override
275 public WebResponse getResponse(final WebRequest webRequest) throws IOException {
276 final String url = webRequest.getUrl().toExternalForm();
277
278 synchronized (this) {
279 while (!gotFoo1_ && url.endsWith("foo2.txt")) {
280 try {
281 wait(100);
282 }
283 catch (final InterruptedException e) {
284 e.printStackTrace();
285 }
286 }
287 }
288 if (url.endsWith("foo1.txt")) {
289 gotFoo1_ = true;
290 }
291 return super.getResponse(webRequest);
292 }
293 };
294 connection.setDefaultResponse("");
295 connection.setResponse(URL_FIRST, content);
296 connection.setResponse(new URL(URL_FIRST, "page2.html"), content2);
297
298 final WebClient webClient = getWebClient();
299 webClient.setWebConnection(connection);
300
301 final HtmlPage page = webClient.getPage(URL_FIRST);
302 final DomChangeListener listener = new DomChangeListener() {
303 @Override
304 public void nodeAdded(final DomChangeEvent event) {
305
306 }
307 @Override
308 public void nodeDeleted(final DomChangeEvent event) {
309
310 }
311 };
312 page.addDomChangeListener(listener);
313 page.getHtmlElementById("p1").click();
314 }
315
316
317
318
319
320 @Test
321 @Alerts({"0", "10"})
322 @HtmlUnitNYI(CHROME = {"0", "1"},
323 EDGE = {"0", "1"},
324 FF = {"0", "1"},
325 FF_ESR = {"0", "1"})
326 public void streaming() throws Exception {
327 final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
328 servlets.put("/test", StreamingServlet.class);
329
330 final String resourceBase = "./src/test/resources/org/htmlunit/javascript/host";
331 startWebServer(resourceBase, null, servlets);
332 final WebClient client = getWebClient();
333 final HtmlPage page = client.getPage(URL_FIRST + "XMLHttpRequestTest_streaming.html");
334 assertEquals(Integer.parseInt(getExpectedAlerts()[0]), client.waitForBackgroundJavaScriptStartingBefore(1000));
335 final HtmlElement body = page.getBody();
336 assertEquals(Integer.parseInt(getExpectedAlerts()[1]), body.asNormalizedText().split("\n").length);
337 }
338
339
340
341
342
343 @Test
344 @Alerts("this == request")
345 public void thisValueInHandler() throws Exception {
346 final String html = DOCTYPE_HTML
347 + "<html>\n"
348 + " <head>\n"
349 + " <title>XMLHttpRequest Test</title>\n"
350 + " <script>\n"
351 + " var request;\n"
352 + " function testAsync() {\n"
353 + " request = new XMLHttpRequest();\n"
354 + " request.onreadystatechange = onReadyStateChange;\n"
355 + " request.open('GET', 'foo.xml', true);\n"
356 + " request.send('');\n"
357 + " }\n"
358 + " function onReadyStateChange() {\n"
359 + " if (request.readyState == 4) {\n"
360 + " if (this == request)\n"
361 + " alert('this == request');\n"
362 + " else if (this == onReadyStateChange)\n"
363 + " alert('this == handler');\n"
364 + " else alert('not expected: ' + this)\n"
365 + " }\n"
366 + " }\n"
367 + " </script>\n"
368 + " </head>\n"
369 + " <body onload='testAsync()'>\n"
370 + " </body>\n"
371 + "</html>";
372
373 final WebClient client = getWebClient();
374 final MockWebConnection conn = new MockWebConnection();
375 conn.setResponse(URL_FIRST, html);
376 conn.setDefaultResponse("");
377 client.setWebConnection(conn);
378 client.getPage(URL_FIRST);
379
380 assertEquals(0, client.waitForBackgroundJavaScriptStartingBefore(1000));
381 assertEquals(getExpectedAlerts(), getCollectedAlerts(null));
382 }
383
384
385
386
387
388
389
390
391 @Test
392 public void ajaxInfluencesSubmitHeaders() throws Exception {
393 final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
394 servlets.put("/content.html", ContentServlet.class);
395 servlets.put("/ajax_headers.html", AjaxHeaderServlet.class);
396 servlets.put("/form_headers.html", FormHeaderServlet.class);
397 startWebServer("./", null, servlets);
398
399 COLLECTED_HEADERS.clear();
400 XMLHttpRequest3Test.STATE_ = 0;
401 final WebClient client = getWebClient();
402
403 final List<String> collectedAlerts = Collections.synchronizedList(new ArrayList<String>());
404 client.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
405
406 final HtmlPage page = client.getPage(URL_FIRST + "content.html");
407 final DomElement elem = page.getElementById("doIt");
408 while (STATE_ < 1) {
409 Thread.sleep(42);
410 }
411 elem.click();
412
413 client.waitForBackgroundJavaScript(DEFAULT_WAIT_TIME.toMillis());
414 assertEquals(COLLECTED_HEADERS.toString(), 2, COLLECTED_HEADERS.size());
415
416 String headers = COLLECTED_HEADERS.get(0);
417 if (!headers.startsWith("Form: ")) {
418 headers = COLLECTED_HEADERS.get(1);
419 }
420 assertTrue(headers, headers.startsWith("Form: "));
421 assertFalse(headers, headers.contains("Html-Unit=is great,;"));
422
423 headers = COLLECTED_HEADERS.get(0);
424 if (!headers.startsWith("Ajax: ")) {
425 headers = COLLECTED_HEADERS.get(1);
426 }
427 assertTrue(headers, headers.startsWith("Ajax: "));
428 assertTrue(headers, headers.contains("Html-Unit=is great,;"));
429 }
430
431 static final List<String> COLLECTED_HEADERS = Collections.synchronizedList(new ArrayList<String>());
432 static int STATE_ = 0;
433
434
435
436
437 public static class ContentServlet extends HttpServlet {
438
439
440
441 @Override
442 protected void doGet(final HttpServletRequest req, final HttpServletResponse res) throws IOException {
443 final String html = DOCTYPE_HTML
444 + "<html><head><script>\n"
445 + " function test() {\n"
446 + " xhr = new XMLHttpRequest();\n"
447 + " xhr.open('POST', 'ajax_headers.html', true);\n"
448 + " xhr.setRequestHeader('Html-Unit', 'is great');\n"
449 + " xhr.send('');\n"
450 + " }\n"
451 + "</script></head>\n"
452 + "<body onload='test()'>\n"
453 + " <form action='form_headers.html' name='myForm'>\n"
454 + " <input name='myField' value='some value'>\n"
455 + " <input type='submit' id='doIt' value='Do It'>\n"
456 + " </form>\n"
457 + "</body></html>";
458
459 res.setContentType(MimeType.TEXT_HTML);
460 final Writer writer = res.getWriter();
461 writer.write(html);
462 }
463 }
464
465
466
467
468 public static class AjaxHeaderServlet extends HttpServlet {
469
470
471
472 @Override
473 protected void doPost(final HttpServletRequest req, final HttpServletResponse res) throws IOException {
474 doGet(req, res);
475 }
476
477
478
479
480 @Override
481 protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
482 final String header = headers(request);
483 STATE_ = 1;
484 try {
485
486 while (STATE_ < 2) {
487 Thread.sleep(42);
488 }
489 }
490 catch (final InterruptedException e) {
491 e.printStackTrace();
492 }
493
494 COLLECTED_HEADERS.add("Ajax: " + header);
495 response.setContentType(MimeType.TEXT_PLAIN);
496 final Writer writer = response.getWriter();
497 writer.write(header);
498 writer.flush();
499 }
500 }
501
502
503
504
505 public static class FormHeaderServlet extends HttpServlet {
506
507
508
509 @Override
510 protected void doPost(final HttpServletRequest req, final HttpServletResponse res) throws IOException {
511 doGet(req, res);
512 }
513
514
515
516
517 @Override
518 protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
519 final String header = headers(request);
520 STATE_ = 2;
521
522 final String html = DOCTYPE_HTML
523 + "<html><head></head>\n"
524 + "<body>\n"
525 + "<p>Form: " + header + "</p<\n"
526 + "</body></html>";
527
528 COLLECTED_HEADERS.add("Form: " + header);
529 response.setContentType(MimeType.TEXT_HTML);
530 final Writer writer = response.getWriter();
531 writer.write(html);
532 writer.flush();
533 }
534 }
535
536 static String headers(final HttpServletRequest request) {
537 final StringBuilder text = new StringBuilder();
538 text.append("Headers: ");
539 final Enumeration<String> headerNames = request.getHeaderNames();
540 while (headerNames.hasMoreElements()) {
541 final String name = headerNames.nextElement();
542 text.append(name);
543 text.append('=');
544 final Enumeration<String> headers = request.getHeaders(name);
545 while (headers.hasMoreElements()) {
546 final String header = headers.nextElement();
547 text.append(header);
548 text.append(',');
549 }
550 text.append(';');
551 }
552 return text.toString();
553 }
554 }