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