View Javadoc
1   /*
2    * Copyright (c) 2002-2025 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.javascript.host.xml;
16  
17  import static java.nio.charset.StandardCharsets.ISO_8859_1;
18  import static java.nio.charset.StandardCharsets.UTF_8;
19  
20  import java.io.ByteArrayOutputStream;
21  import java.io.File;
22  import java.io.IOException;
23  import java.io.OutputStream;
24  import java.io.Writer;
25  import java.net.URL;
26  import java.util.ArrayList;
27  import java.util.Arrays;
28  import java.util.HashMap;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.zip.GZIPOutputStream;
32  
33  import javax.servlet.Servlet;
34  import javax.servlet.ServletException;
35  import javax.servlet.http.HttpServlet;
36  import javax.servlet.http.HttpServletRequest;
37  import javax.servlet.http.HttpServletResponse;
38  
39  import org.apache.commons.io.FileUtils;
40  import org.apache.commons.io.IOUtils;
41  import org.htmlunit.HttpHeader;
42  import org.htmlunit.HttpMethod;
43  import org.htmlunit.WebDriverTestCase;
44  import org.htmlunit.WebRequest;
45  import org.htmlunit.javascript.host.xml.XMLHttpRequestTest.BasicAuthenticationServlet;
46  import org.htmlunit.junit.annotation.Alerts;
47  import org.htmlunit.junit.annotation.HtmlUnitNYI;
48  import org.htmlunit.util.MimeType;
49  import org.htmlunit.util.NameValuePair;
50  import org.junit.jupiter.api.Disabled;
51  import org.junit.jupiter.api.Test;
52  import org.openqa.selenium.By;
53  import org.openqa.selenium.WebDriver;
54  
55  /**
56   * Additional tests for {@link XMLHttpRequest} using already WebDriverTestCase.
57   *
58   * @author Marc Guillemot
59   * @author Ahmed Ashour
60   * @author Ronald Brill
61   * @author Sebastian Cato
62   * @author Frank Danek
63   * @author Thorsten Wendelmuth
64   * @author Anton Demydenko
65   */
66  public class XMLHttpRequest2Test extends WebDriverTestCase {
67  
68      /**
69       * This produced a deadlock situation with HtmlUnit-2.6 and HttmlUnit-2.7-SNAPSHOT on 17.09.09.
70       * The reason is that HtmlUnit has currently one "JS execution thread" per window, synchronizing on the
71       * owning page BUT the XHR callback execution are synchronized on their "owning" page.
72       * This test isn't really executed now to avoid the deadlock.
73       * Strangely, this test seem to fail even without the implementation of the "/setStateXX" handling
74       * on the "server side".
75       * Strange thing.
76       *
77       * Update 28.01.2013:
78       * no deadlock occur anymore (we use a single JS execution thread for a while). Activating the test as it may help.
79       * Update 28.02.2013:
80       * deadlock does occur (at least on the build server). Disabling the test again.
81       *
82       * @throws Exception if the test fails
83       */
84      @Test
85      @Disabled
86      public void deadlock() throws Exception {
87          final String jsCallSynchXHR = "function callSynchXHR(url) {\n"
88              + "  var xhr = new XMLHttpRequest();\n"
89              + "  xhr.open('GET', url, false);\n"
90              + "  xhr.send('');\n"
91              + "}\n";
92          final String jsCallASynchXHR = "function callASynchXHR(url) {\n"
93              + "  var xhr = new XMLHttpRequest();\n"
94              + "  var handler = function() {\n"
95              + "    if (xhr.readyState == 4)\n"
96              + "      log(xhr.responseText);\n"
97              + "  }\n"
98              + "  xhr.onreadystatechange = handler;\n"
99              + "  xhr.open('GET', url, true);\n"
100             + "  xhr.send('');\n"
101             + "}\n";
102 
103         final String html = DOCTYPE_HTML
104             + "<html><head><script>\n"
105             + jsCallSynchXHR
106             + jsCallASynchXHR
107             + "function testMain() {\n"
108             + "  // set state 1 and wait for state 2\n"
109             + "  callSynchXHR('/setState1/setState3');\n"
110             + "  // call function with XHR and handler in frame\n"
111             + "  myFrame.contentWindow.callASynchXHR('/fooCalledFromFrameCalledFromMain');\n"
112             + "}\n"
113             + "</script></head>\n"
114             + "<body onload='testMain()'>\n"
115             + "<iframe id='myFrame' src='frame.html'></iframe>\n"
116             + "</body></html>";
117 
118         final String frame = DOCTYPE_HTML
119             + "<html><head><script>\n"
120             + jsCallSynchXHR
121             + jsCallASynchXHR
122             + "function testFrame() {\n"
123             + "  // set state 2\n"
124             + "  callSynchXHR('/setState2');\n"
125             + "  // call function with XHR and handler in parent\n"
126             + "  parent.callASynchXHR('/fooCalledFromMainCalledFromFrame');\n"
127             + "}\n"
128             + "setTimeout(testFrame, 10);\n"
129             + "</script></head>\n"
130             + "<body></body></html>";
131 
132         getMockWebConnection().setResponse(new URL(URL_FIRST, "frame.html"), frame);
133         getMockWebConnection().setDefaultResponse(""); // for all XHR
134 
135         loadPage2(html);
136     }
137 
138     /**
139      * @throws Exception if an error occurs
140      */
141     @Test
142     public void setRequestHeader() throws Exception {
143         final String html = DOCTYPE_HTML
144             + "<html><head><script>\n"
145             + "  function test() {\n"
146             + "    var xhr = new XMLHttpRequest();\n"
147             + "    xhr.open('GET', 'second.html', false);\n"
148             + "    xhr.setRequestHeader('Accept', 'text/javascript, application/javascript, */*');\n"
149             + "    xhr.setRequestHeader('Accept-Language', 'ar-eg');\n"
150             + "    xhr.send('');\n"
151             + "  }\n"
152             + "</script></head><body onload='test()'></body></html>";
153 
154         getMockWebConnection().setDefaultResponse("");
155         loadPage2(html);
156 
157         final WebRequest lastRequest = getMockWebConnection().getLastWebRequest();
158         final Map<String, String> headers = lastRequest.getAdditionalHeaders();
159         assertEquals("text/javascript, application/javascript, */*", headers.get(HttpHeader.ACCEPT));
160         assertEquals("ar-eg", headers.get(HttpHeader.ACCEPT_LANGUAGE));
161     }
162 
163     /**
164      * Content-Length header is simply ignored by browsers as it
165      * is the browser's responsibility to set it.
166      * @throws Exception if an error occurs
167      */
168     @Test
169     public void requestHeader_contentLength() throws Exception {
170         requestHeader_contentLength("1234");
171         requestHeader_contentLength("11");
172         requestHeader_contentLength(null);
173     }
174 
175     private void requestHeader_contentLength(final String headerValue) throws Exception {
176         final String body = "hello world";
177         final String setHeader = headerValue == null ? ""
178                 : "xhr.setRequestHeader('Content-length', 1234);\n";
179         final String html = DOCTYPE_HTML
180             + "<html><body><script>\n"
181             + "var xhr = new XMLHttpRequest();\n"
182             + "xhr.open('POST', 'second.html', false);\n"
183             + "var body = '" + body + "';\n"
184             + "xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');\n"
185             + setHeader
186             + "xhr.send(body);\n"
187             + "</script></body></html>";
188 
189         getMockWebConnection().setDefaultResponse("");
190         loadPage2(html);
191 
192         final WebRequest lastRequest = getMockWebConnection().getLastWebRequest();
193         final Map<String, String> headers = lastRequest.getAdditionalHeaders();
194         assertEquals("" + body.length(), headers.get(HttpHeader.CONTENT_LENGTH));
195     }
196 
197     /**
198      * @throws Exception if an error occurs
199      */
200     @Test
201     public void requestHeaderSendBlob() throws Exception {
202         final String html = DOCTYPE_HTML
203             + "<html><body><script>\n"
204             + "var xhr = new XMLHttpRequest();\n"
205             + "xhr.open('POST', 'second.html', false);\n"
206             + "xhr.setRequestHeader('Content-Type', 'text/plain');\n"
207             + "xhr.setRequestHeader('Content-length', 1234);\n"
208 
209             + "var body = ['hello world'];\n"
210             + "var blob = new Blob(body);\n"
211             + "xhr.send(blob);\n"
212             + "</script></body></html>";
213 
214         getMockWebConnection().setDefaultResponse("");
215         loadPage2(html);
216 
217         final WebRequest lastRequest = getMockWebConnection().getLastWebRequest();
218         final Map<String, String> headers = lastRequest.getAdditionalHeaders();
219         assertEquals("11", headers.get(HttpHeader.CONTENT_LENGTH));
220         assertEquals("text/plain", headers.get(HttpHeader.CONTENT_TYPE));
221     }
222 
223     /**
224      * @throws Exception if an error occurs
225      */
226     @Test
227     public void requestHeaderSendFile() throws Exception {
228         final String html = DOCTYPE_HTML
229             + "<html><body>\n"
230 
231             + "<input id='fileupload' type='file'/>"
232             + "<button id='testBtn' onclick='test()'>Tester</button>\n"
233 
234             + "<script>\n"
235             + "function test() {\n"
236             + "  var xhr = new XMLHttpRequest();\n"
237             + "  xhr.open('POST', 'second.html', false);\n"
238             + "  xhr.setRequestHeader('Content-Type', 'text/csv');\n"
239 
240             + "  var fileInput = document.getElementById('fileupload');"
241 
242             + "  xhr.send(fileInput.files[0]);\n"
243             + "}\n"
244             + "</script></body></html>";
245 
246         final WebDriver driver = loadPage2(html);
247 
248         final File tstFile = File.createTempFile("HtmlUnitUploadTest", ".txt");
249         try {
250             FileUtils.writeStringToFile(tstFile, "Hello HtmlUnit", ISO_8859_1);
251 
252             final String path = tstFile.getCanonicalPath();
253             driver.findElement(By.id("fileupload")).sendKeys(path);
254 
255             driver.findElement(By.id("testBtn")).click();
256 
257             final WebRequest lastRequest = getMockWebConnection().getLastWebRequest();
258             final Map<String, String> headers = lastRequest.getAdditionalHeaders();
259             assertEquals("text/csv", headers.get(HttpHeader.CONTENT_TYPE));
260         }
261         finally {
262             FileUtils.deleteQuietly(tstFile);
263         }
264     }
265 
266     /**
267      * XHR.open throws an exception if URL parameter is null or empty string.
268      * @throws Exception if an error occurs
269      */
270     @Test
271     @Alerts({"5", "pass", "pass", "pass", "pass"})
272     public void openThrowOnEmptyUrl() throws Exception {
273         final String html = DOCTYPE_HTML
274             + "<html><head>\n"
275             + "<script>\n"
276             + LOG_TITLE_FUNCTION
277             + "var xhr = new XMLHttpRequest();\n"
278             + "var values = [null, '', ' ', '  \\t  '];\n"
279             + "for (var i = 0; i < values.length; i++) {\n"
280             + "  try {\n"
281             + "    xhr.open('GET', values[i], false);\n"
282             + "    xhr.send('');\n"
283             + "    log('pass');\n"
284             + "  } catch(e) { logEx(e) }\n"
285             + "}\n"
286             + "</script>\n"
287             + "</head>\n"
288             + "<body></body>\n</html>";
289         getMockWebConnection().setDefaultResponse("Error: not found", 404, "Not Found", MimeType.TEXT_HTML);
290 
291         final int expectedRequests = Integer.parseInt(getExpectedAlerts()[0]);
292         setExpectedAlerts(Arrays.copyOfRange(getExpectedAlerts(), 1, getExpectedAlerts().length));
293 
294         loadPageVerifyTitle2(html);
295 
296         assertEquals(expectedRequests, getMockWebConnection().getRequestCount());
297     }
298 
299     /**
300      * Test access to the XML DOM.
301      * @throws Exception if the test fails
302      */
303     @Test
304     @Alerts({"1", "bla", "someAttr", "someValue", "true", "foo", "2", "fi1"})
305     public void responseXML() throws Exception {
306         testResponseXML(MimeType.TEXT_XML);
307         testResponseXML(null);
308     }
309 
310     /**
311      * Test access to responseXML when the content type indicates that it is not XML.
312      * @throws Exception if the test fails
313      */
314     @Test
315     @Alerts("null")
316     public void responseXML_badContentType() throws Exception {
317         final String html = DOCTYPE_HTML
318             + "<html><head>\n"
319             + "<script>\n"
320             + LOG_TITLE_FUNCTION
321             + "function test() {\n"
322             + "  var xhr = new XMLHttpRequest();\n"
323             + "  xhr.open('GET', 'foo.xml', false);\n"
324             + "  xhr.send('');\n"
325             + "  log(xhr.responseXML);\n"
326             + "}\n"
327             + "</script>\n"
328             + "</head>\n"
329             + "<body onload='test()'></body></html>";
330 
331         final URL urlFoo = new URL(URL_FIRST, "foo.xml");
332         getMockWebConnection().setResponse(urlFoo, "<bla someAttr='someValue'><foo><fi id='fi1'/><fi/></foo></bla>\n",
333                 MimeType.TEXT_PLAIN);
334         loadPageVerifyTitle2(html);
335     }
336 
337     private void testResponseXML(final String contentType) throws Exception {
338         final String html = DOCTYPE_HTML
339             + "<html><head>\n"
340             + "<script>\n"
341             + LOG_TITLE_FUNCTION
342             + "function test() {\n"
343             + "  var xhr = new XMLHttpRequest();\n"
344             + "  xhr.open('GET', 'foo.xml', false);\n"
345             + "  xhr.send('');\n"
346             + "  var childNodes = xhr.responseXML.childNodes;\n"
347             + "  log(childNodes.length);\n"
348             + "  var rootNode = childNodes[0];\n"
349             + "  log(rootNode.nodeName);\n"
350             + "  log(rootNode.attributes[0].nodeName);\n"
351             + "  log(rootNode.attributes[0].nodeValue);\n"
352             + "  log(rootNode.attributes['someAttr'] == rootNode.attributes[0]);\n"
353             + "  log(rootNode.firstChild.nodeName);\n"
354             + "  log(rootNode.firstChild.childNodes.length);\n"
355             + "  log(xhr.responseXML.getElementsByTagName('fi').item(0).attributes[0].nodeValue);\n"
356             + "}\n"
357             + "</script>\n"
358             + "</head>\n"
359             + "<body onload='test()'></body></html>";
360 
361         final URL urlFoo = new URL(URL_FIRST, "foo.xml");
362         getMockWebConnection().setResponse(urlFoo, "<bla someAttr='someValue'><foo><fi id='fi1'/><fi/></foo></bla>\n",
363             contentType);
364         loadPageVerifyTitle2(html);
365     }
366 
367     /**
368      * Test access to responseXML when the content type indicates that it is not XML.
369      * @throws Exception if the test fails
370      */
371     @Test
372     @Alerts("null")
373     public void responseXML_sendNotCalled() throws Exception {
374         final String html = DOCTYPE_HTML
375             + "<html><head>\n"
376             + "<script>\n"
377             + LOG_TITLE_FUNCTION
378             + "function test() {\n"
379             + "  var xhr = new XMLHttpRequest();\n"
380             + "  xhr.open('GET', 'foo.xml', false);\n"
381             + "  log(xhr.responseXML);\n"
382             + "}\n"
383             + "</script>\n"
384             + "</head>\n"
385             + "<body onload='test()'></body></html>";
386 
387         final URL urlFoo = new URL(URL_FIRST, "foo.xml");
388         getMockWebConnection().setResponse(urlFoo, "<bla someAttr='someValue'><foo><fi id='fi1'/><fi/></foo></bla>\n",
389                 MimeType.TEXT_PLAIN);
390         loadPageVerifyTitle2(html);
391     }
392 
393     /**
394      * Test for Bug 2891430: HtmlUnit should not violate the same-origin policy with FF3.
395      * Note: FF3.5 doesn't enforce this same-origin policy.
396      * @throws Exception if the test fails
397      */
398     @Test
399     @Alerts("ok")
400     public void sameOriginPolicy() throws Exception {
401         sameOriginPolicy(URL_THIRD.toString());
402     }
403 
404     /**
405      * @throws Exception if the test fails
406      */
407     @Test
408     @Alerts("ok")
409     public void sameOriginPolicy_aboutBlank() throws Exception {
410         sameOriginPolicy("about:blank");
411     }
412 
413     private void sameOriginPolicy(final String url) throws Exception {
414         final String html = DOCTYPE_HTML
415             + "<html><head>\n"
416             + "<script>\n"
417             + LOG_TITLE_FUNCTION
418             + "function test() {\n"
419             + "  var xhr = new XMLHttpRequest();\n"
420             + "  try {\n"
421             + "    xhr.open('GET', '" + url + "', false);\n"
422             + "    log('ok');\n"
423             + "  } catch(e) { logEx(e); }\n"
424             + "}\n"
425             + "</script>\n"
426             + "</head>\n"
427             + "<body onload='test()'></body></html>";
428 
429         getMockWebConnection().setResponse(URL_THIRD, "<bla/>", MimeType.TEXT_XML);
430         loadPageVerifyTitle2(html);
431     }
432 
433     /**
434      * @throws Exception if an error occurs
435      */
436     @Test
437     public void put() throws Exception {
438         final String html = DOCTYPE_HTML
439             + "<html><head><script>\n"
440             + "  function test() {\n"
441             + "    var xhr = new XMLHttpRequest();\n"
442             + "    xhr.open('PUT', 'second.html', false);\n"
443             + "    xhr.send('Something');\n"
444             + "  }\n"
445             + "</script></head><body onload='test()'></body></html>";
446 
447         getMockWebConnection().setDefaultResponse("");
448         loadPage2(html);
449 
450         final String requestBody = getMockWebConnection().getLastWebRequest().getRequestBody();
451         assertEquals("Something", requestBody);
452     }
453 
454     /**
455      * Regression test for bug 2952333.
456      * This test was causing a java.lang.ClassCastException:
457      * org.htmlunit.xml.XmlPage cannot be cast to org.htmlunit.html.HtmlPage
458      * @throws Exception if an error occurs
459      */
460     @Test
461     @Alerts("[object XMLDocument]")
462     public void iframeInResponse() throws Exception {
463         final String html = DOCTYPE_HTML
464             + "<html><head>\n"
465             + "<script>\n"
466             + LOG_TITLE_FUNCTION
467             + "var xhr = new XMLHttpRequest();\n"
468             + "xhr.open('GET', 'foo.xml', false);\n"
469             + "xhr.send('');\n"
470             + "log(xhr.responseXML);\n"
471             + "</script></head><body></body></html>";
472 
473         final String xml = DOCTYPE_HTML
474             + "<html xmlns='http://www.w3.org/1999/xhtml'>\n"
475             + "<body><iframe></iframe></body></html>";
476         getMockWebConnection().setDefaultResponse(xml, MimeType.TEXT_XML);
477         loadPageVerifyTitle2(html);
478     }
479 
480     /**
481      * Ensures that XHR download is performed without altering other JS jobs.
482      * Currently HtmlUnit doesn't behave correctly here because download and callback execution
483      * are executed within the same synchronize block on the HtmlPage.
484      * @throws Exception if an error occurs
485      */
486     @Test
487     @Alerts({"in timeout", "hello"})
488     @HtmlUnitNYI(CHROME = {"hello", "in timeout"},
489             EDGE = {"hello", "in timeout"},
490             FF = {"hello", "in timeout"},
491             FF_ESR = {"hello", "in timeout"})
492     public void xhrDownloadInBackground() throws Exception {
493         final String html = DOCTYPE_HTML
494             + "<html><head>\n"
495             + "<script>\n"
496             + LOG_TITLE_FUNCTION
497             + "var xhr = new XMLHttpRequest();\n"
498             + "var handler = function() {\n"
499             + "  if (xhr.readyState == 4)\n"
500             + "    log(xhr.responseText);\n"
501             + "}\n"
502             + "xhr.onreadystatechange = handler;\n"
503             + "xhr.open('GET', '/delay200/foo.txt', true);\n"
504             + "xhr.send('');\n"
505             + "setTimeout(function() { log('in timeout');}, 5);\n"
506             + "</script></head><body></body></html>";
507 
508         getMockWebConnection().setDefaultResponse("hello", MimeType.TEXT_PLAIN);
509         loadPage2(html);
510         verifyTitle2(DEFAULT_WAIT_TIME, getWebDriver(), getExpectedAlerts());
511     }
512 
513     /**
514      * Ensures that XHR callback is executed before a timeout, even if it is time
515      * to execute this one.
516      * @throws Exception if an error occurs
517      */
518     @Test
519     @Alerts("hello in timeout")
520     public void xhrCallbackBeforeTimeout() throws Exception {
521         final String html = DOCTYPE_HTML
522             + "<html><head><script>\n"
523             + "function wait() {\n"
524             + "  var xhr = new XMLHttpRequest();\n"
525             + "  xhr.open('GET', '/delay200/foo.txt', false);\n"
526             + "  xhr.send('');\n"
527             + "}\n"
528             + "function doTest() {\n"
529             + "  setTimeout(function() { document.title += ' in timeout'; }, 5);\n"
530             + "  wait();\n"
531             + "  var xhr2 = new XMLHttpRequest();\n"
532             + "  var handler = function() {\n"
533             + "    if (xhr2.readyState == 4)\n"
534             + "      document.title += xhr2.responseText;\n"
535             + "  }\n"
536             + "  xhr2.onreadystatechange = handler;\n"
537             + "  xhr2.open('GET', '/foo.txt', true);\n"
538             + "  xhr2.send('');\n"
539             + "  wait();\n"
540             + "}\n"
541             + "setTimeout(doTest, 10);\n"
542             + "</script></head><body></body></html>";
543 
544         getMockWebConnection().setDefaultResponse("hello", MimeType.TEXT_PLAIN);
545         final WebDriver driver = loadPage2(html);
546         assertTitle(driver, getExpectedAlerts()[0]);
547     }
548 
549     /**
550      * @throws Exception if an error occurs
551      */
552     @Test
553     @Alerts("a=b,0; cookie: null")
554     public void post() throws Exception {
555         final String html = DOCTYPE_HTML
556             + "<html><head>\n"
557             + "<script>\n"
558             + LOG_TITLE_FUNCTION
559             + "function test() {\n"
560             + "  var xhr = new XMLHttpRequest();\n"
561             + "  xhr.open('POST', '/test2?a=b', false);\n"
562             + "  xhr.send('');\n"
563             + "  log(xhr.responseText);\n"
564             + "}\n"
565             + "</script></head><body onload='test()'></body></html>";
566 
567         final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
568         servlets.put("/test2", PostServlet2.class);
569 
570         loadPage2(html, servlets);
571         verifyTitle2(getWebDriver(), getExpectedAlerts());
572     }
573 
574     /**
575      * @throws Exception if an error occurs
576      */
577     @Test
578     @Alerts("a=b,0; cookie: cookie=sweet")
579     public void post_cookies() throws Exception {
580         final String html = DOCTYPE_HTML
581             + "<html><head>\n"
582             + "<script>\n"
583             + LOG_TITLE_FUNCTION
584             + "function test() {\n"
585             + "  var xhr = new XMLHttpRequest();\n"
586             + "  xhr.open('POST', '/test2?a=b', false);\n"
587             + "  xhr.send('');\n"
588             + "  log(xhr.responseText);\n"
589             + "}\n"
590             + "</script></head><body onload='test()'></body></html>";
591 
592         final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
593         servlets.put("/test2", PostServlet2.class);
594 
595         List<NameValuePair> responseHeader = null;
596         responseHeader = new ArrayList<>();
597         responseHeader.add(new NameValuePair("Set-Cookie", "cookie=sweet"));
598 
599         getMockWebConnection().setResponse(URL_FIRST, html,
600                 200, "OK", "text/html;charset=ISO-8859-1", ISO_8859_1, responseHeader);
601         loadPage2(URL_FIRST, null);
602 
603         loadPage2(html, servlets);
604         verifyTitle2(getWebDriver(), getExpectedAlerts());
605     }
606 
607     /**
608      * Servlet for {@link #post()}.
609      */
610     public static class PostServlet2 extends HttpServlet {
611 
612         @Override
613         protected void doPost(final HttpServletRequest req, final HttpServletResponse resp)
614             throws ServletException, IOException {
615             final Writer writer = resp.getWriter();
616 
617             writer.write(req.getQueryString() + ',' + req.getContentLength());
618 
619             final String cookie = req.getHeader(HttpHeader.COOKIE);
620             writer.write("; cookie: " + cookie);
621         }
622     }
623 
624     /**
625      * Firefox up to 3.6 does not call "onreadystatechange" handler if sync.
626      * @throws Exception if the test fails
627      */
628     @Test
629     @Alerts("4")
630     public void onreadystatechange_sync() throws Exception {
631         final String html = DOCTYPE_HTML
632             + "<html>\n"
633             + "  <head>\n"
634             + "    <script>\n"
635             + LOG_TITLE_FUNCTION
636             + "      var xhr;\n"
637             + "      function test() {\n"
638             + "        xhr = new XMLHttpRequest();\n"
639             + "        xhr.open('GET', '" + URL_SECOND + "', false);\n"
640             + "        xhr.onreadystatechange = onStateChange;\n"
641             + "        xhr.send('');\n"
642             + "      }\n"
643             + "      function onStateChange() {\n"
644             + "        log(xhr.readyState);\n"
645             + "      }\n"
646             + "    </script>\n"
647             + "  </head>\n"
648             + "  <body onload='test()'>\n"
649             + "  </body>\n"
650             + "</html>";
651 
652         final String xml =
653               "<xml>\n"
654             + "<content>blah</content>\n"
655             + "<content>blah2</content>\n"
656             + "</xml>";
657 
658         getMockWebConnection().setResponse(URL_SECOND, xml, MimeType.TEXT_XML);
659         loadPageVerifyTitle2(html);
660     }
661 
662     /**
663      * Firefox up to 3.6 does not call "onreadystatechange" handler if sync.
664      * Firefox provides an event parameter.
665      * @throws Exception if the test fails
666      */
667     @Test
668     @Alerts("[object Event]#[object XMLHttpRequest]")
669     public void onreadystatechangeSyncWithParam() throws Exception {
670         final String html = DOCTYPE_HTML
671             + "<html>\n"
672             + "  <head>\n"
673             + "    <script>\n"
674             + LOG_TITLE_FUNCTION
675             + "      var xhr;\n"
676             + "      function test() {\n"
677             + "        xhr = new XMLHttpRequest();\n"
678             + "        xhr.open('GET', '" + URL_SECOND + "', false);\n"
679             + "        xhr.onreadystatechange = onStateChange;\n"
680             + "        xhr.send('');\n"
681             + "      }\n"
682             + "      function onStateChange(e) {\n"
683             + "        if (xhr.readyState == 4) {\n"
684             + "          if(e) log(e + '#' + e.target);\n"
685             + "          else log('no param');\n"
686             + "        }\n"
687             + "      }\n"
688             + "    </script>\n"
689             + "  </head>\n"
690             + "  <body onload='test()'>\n"
691             + "  </body>\n"
692             + "</html>";
693 
694         final String xml =
695               "<xml>\n"
696             + "<content>blah</content>\n"
697             + "</xml>";
698 
699         getMockWebConnection().setResponse(URL_SECOND, xml, MimeType.TEXT_XML);
700         loadPageVerifyTitle2(html);
701     }
702 
703     /**
704      * Firefox up to 3.6 does not call "onreadystatechange" handler if sync.
705      * Firefox provides an event parameter.
706      * @throws Exception if the test fails
707      */
708     @Test
709     @Alerts("[object Event]#[object XMLHttpRequest]")
710     public void onreadystatechangeAsyncWithParam() throws Exception {
711         final String html = DOCTYPE_HTML
712             + "<html>\n"
713             + "  <head>\n"
714             + "    <script>\n"
715             + LOG_TITLE_FUNCTION
716             + "      var xhr;\n"
717             + "      function test() {\n"
718             + "        xhr = new XMLHttpRequest();\n"
719             + "        xhr.open('GET', '" + URL_SECOND + "', true);\n"
720             + "        xhr.onreadystatechange = onStateChange;\n"
721             + "        xhr.send('');\n"
722             + "      }\n"
723             + "      function onStateChange(e) {\n"
724             + "        if (xhr.readyState == 4) {\n"
725             + "          if(e) log(e + '#' + e.target);\n"
726             + "          else log('no param');\n"
727             + "        }\n"
728             + "      }\n"
729             + "    </script>\n"
730             + "  </head>\n"
731             + "  <body onload='test()'>\n"
732             + "  </body>\n"
733             + "</html>";
734 
735         final String xml =
736               "<xml>\n"
737             + "<content>blah</content>\n"
738             + "</xml>";
739 
740         getMockWebConnection().setResponse(URL_SECOND, xml, MimeType.TEXT_XML);
741         loadPage2(html);
742         verifyTitle2(DEFAULT_WAIT_TIME, getWebDriver(), getExpectedAlerts());
743     }
744 
745     /**
746      * Test the simplest CORS case. A cross-origin simple request,
747      * server replies "allow *".
748      * @throws Exception if the test fails.
749      */
750     @Test
751     @Alerts({"ok", "4"})
752     public void sameOriginCorsSimple() throws Exception {
753         final String html = DOCTYPE_HTML
754             + "<html><head>\n"
755             + "<script>\n"
756             + LOG_TITLE_FUNCTION
757             + "function test() {\n"
758             + "  var xhr = new XMLHttpRequest();\n"
759             + "  try {\n"
760             + "    xhr.open('GET', '" + URL_CROSS_ORIGIN + "', false);\n"
761             + "    log('ok');\n"
762             + "    xhr.send();\n"
763             + "    log(xhr.readyState);\n"
764             + "  } catch(e) { logEx(e); }\n"
765             + "}\n"
766             + "</script>\n"
767             + "</head>\n"
768             + "<body onload='test()'></body></html>";
769 
770         final List<NameValuePair> responseHeaders = new ArrayList<>();
771         responseHeaders.add(new NameValuePair("access-control-allow-origin", "*"));
772         getMockWebConnection().setResponse(URL_CROSS_ORIGIN,
773                                            "<empty/>",
774                                            200,
775                                            "OK",
776                                            MimeType.TEXT_XML,
777                                            UTF_8, responseHeaders);
778         loadPageVerifyTitle2(html);
779     }
780 
781     /**
782      * Test the correct origin header.
783      * @throws Exception if the test fails.
784      */
785     @Test
786     @Alerts({"ok", "4", "<null>"})
787     public void baseUrlAbsoluteRequest() throws Exception {
788         final String html = DOCTYPE_HTML
789             + "<html><head>\n"
790             + "<base href='" + URL_CROSS_ORIGIN_BASE + "'>\n"
791             + "<script>\n"
792             + LOG_TITLE_FUNCTION
793             + "function test() {\n"
794             + "  var xhr = new XMLHttpRequest();\n"
795             + "  try {\n"
796             + "    xhr.open('GET', '" + URL_SECOND + "', false);\n"
797             + "    log('ok');\n"
798             + "    xhr.send();\n"
799             + "    log(xhr.readyState);\n"
800             + "  } catch(e) { logEx(e); }\n"
801             + "}\n"
802             + "</script>\n"
803             + "</head>\n"
804             + "<body onload='test()'></body></html>";
805 
806         final List<NameValuePair> responseHeaders = new ArrayList<>();
807         responseHeaders.add(new NameValuePair("access-control-allow-origin", "*"));
808         getMockWebConnection().setResponse(URL_SECOND,
809                                            "<empty/>",
810                                            200,
811                                            "OK",
812                                            MimeType.TEXT_XML,
813                                            UTF_8, responseHeaders);
814 
815         final WebDriver driver = loadPage2(html);
816         verifyTitle2(driver, Arrays.copyOfRange(getExpectedAlerts(), 0, 2));
817 
818         final Map<String, String> lastAdditionalHeaders = getMockWebConnection().getLastAdditionalHeaders();
819         String origin = lastAdditionalHeaders.get(HttpHeader.ORIGIN);
820         if (origin == null) {
821             origin = "<null>";
822         }
823         assertEquals(getExpectedAlerts()[2], origin);
824     }
825 
826     /**
827      * Test the correct origin header.
828      * @throws Exception if the test fails.
829      */
830     @Test
831     @Alerts({"ok", "4", "§§URL§§"})
832     public void baseUrlAbsoluteRequestOtherUrl() throws Exception {
833         final String html = DOCTYPE_HTML
834             + "<html><head>\n"
835             + "<base href='" + URL_CROSS_ORIGIN_BASE + "'>\n"
836             + "<script>\n"
837             + LOG_TITLE_FUNCTION
838             + "function test() {\n"
839             + "  var xhr = new XMLHttpRequest();\n"
840             + "  try {\n"
841             + "    xhr.open('GET', '" + URL_CROSS_ORIGIN2 + "', false);\n"
842             + "    log('ok');\n"
843             + "    xhr.send();\n"
844             + "    log(xhr.readyState);\n"
845             + "  } catch(e) { logEx(e); }\n"
846             + "}\n"
847             + "</script>\n"
848             + "</head>\n"
849             + "<body onload='test()'></body></html>";
850 
851         final List<NameValuePair> responseHeaders = new ArrayList<>();
852         responseHeaders.add(new NameValuePair("access-control-allow-origin", "*"));
853         getMockWebConnection().setResponse(URL_CROSS_ORIGIN2,
854                                            "<empty/>",
855                                            200,
856                                            "OK",
857                                            MimeType.TEXT_XML,
858                                            UTF_8, responseHeaders);
859 
860         expandExpectedAlertsVariables("http://localhost:" + PORT);
861         final WebDriver driver = loadPage2(html);
862         verifyTitle2(driver, Arrays.copyOfRange(getExpectedAlerts(), 0, 2));
863 
864         final Map<String, String> lastAdditionalHeaders = getMockWebConnection().getLastAdditionalHeaders();
865         String origin = lastAdditionalHeaders.get(HttpHeader.ORIGIN);
866         if (origin == null) {
867             origin = "<null>";
868         }
869         assertEquals(getExpectedAlerts()[2], origin);
870     }
871 
872     /**
873      * Test the correct origin header.
874      * @throws Exception if the test fails.
875      */
876     @Test
877     @Alerts({"ok", "4", "§§URL§§"})
878     public void baseUrlRelativeRequest() throws Exception {
879         final String html = DOCTYPE_HTML
880             + "<html><head>\n"
881             + "<base href='" + URL_CROSS_ORIGIN_BASE + "'>\n"
882             + "<script>\n"
883             + LOG_TITLE_FUNCTION
884             + "function test() {\n"
885             + "  var xhr = new XMLHttpRequest();\n"
886             + "  try {\n"
887             + "    xhr.open('GET', 'corsAllowAll', false);\n"
888             + "    log('ok');\n"
889             + "    xhr.send();\n"
890             + "    log(xhr.readyState);\n"
891             + "  } catch(e) { log('exception ' + e); }\n"
892             + "}\n"
893             + "</script>\n"
894             + "</head>\n"
895             + "<body onload='test()'></body></html>";
896 
897         final List<NameValuePair> responseHeaders = new ArrayList<>();
898         responseHeaders.add(new NameValuePair("access-control-allow-origin", "*"));
899         getMockWebConnection().setResponse(new URL(URL_CROSS_ORIGIN_BASE, "/corsAllowAll"),
900                                            "<empty/>",
901                                            200,
902                                            "OK",
903                                            MimeType.TEXT_XML,
904                                            UTF_8, responseHeaders);
905 
906         expandExpectedAlertsVariables("http://localhost:" + PORT);
907         final WebDriver driver = loadPage2(html);
908         verifyTitle2(driver, Arrays.copyOfRange(getExpectedAlerts(), 0, 2));
909 
910         final Map<String, String> lastAdditionalHeaders = getMockWebConnection().getLastAdditionalHeaders();
911         String origin = lastAdditionalHeaders.get(HttpHeader.ORIGIN);
912         if (origin == null) {
913             origin = "<null>";
914         }
915         assertEquals(getExpectedAlerts()[2], origin);
916     }
917 
918     @Override
919     protected boolean needThreeConnections() {
920         return true;
921     }
922 
923     /**
924      * Test XMLHttpRequest with basic authentication.
925      * @throws Exception on failure
926      */
927     @Test
928     @Alerts("Basic:Zm9vOmJhcg==")
929     public void basicAuthenticationRequest() throws Exception {
930         final String html = DOCTYPE_HTML
931                         + "<html>\n"
932                         + "  <head>\n"
933                         + "    <script>\n"
934                         + LOG_TITLE_FUNCTION
935                         + "      var request;\n"
936                         + "      function testBasicAuth() {\n"
937                         + "        var request = new XMLHttpRequest();\n"
938                         + "        request.open('GET', '/protected/token', false, 'foo', 'bar');\n"
939                         + "        request.send();\n"
940                         + "        log(request.responseText);\n"
941                         + "      }\n"
942                         + "    </script>\n"
943                         + "  </head>\n"
944                         + "  <body onload='testBasicAuth()'>\n"
945                         + "  </body>\n"
946                         + "</html>";
947 
948         final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
949         servlets.put("/protected/token", BasicAuthenticationServlet.class);
950 
951         loadPage2(html, servlets);
952         verifyTitle2(getWebDriver(), getExpectedAlerts());
953     }
954 
955     /**
956      * Test XMLHttpRequest with basic authentication.
957      * @throws Exception on failure
958      */
959     @Test
960     @Alerts("<xml></xml>")
961     public void openNullUserIdNullPassword() throws Exception {
962         final String html =
963                 "<html>\n"
964                         + "  <head>\n"
965                         + "    <script>\n"
966                         + LOG_TITLE_FUNCTION
967                         + "      var request;\n"
968                         + "      function testBasicAuth() {\n"
969                         + "        var request = new XMLHttpRequest();\n"
970                         + "        request.open('GET', '" + URL_SECOND + "', false, null, null);\n"
971                         + "        request.send();\n"
972                         + "        log(request.responseText);\n"
973                         + "      }\n"
974                         + "    </script>\n"
975                         + "  </head>\n"
976                         + "  <body onload='testBasicAuth()'>\n"
977                         + "  </body>\n"
978                         + "</html>";
979 
980         getMockWebConnection().setResponse(URL_SECOND, "<xml></xml>", MimeType.TEXT_XML);
981 
982         loadPageVerifyTitle2(html);
983     }
984 
985     /**
986      * @throws Exception if an error occurs
987      */
988     @Test
989     @Alerts("PATCH|some body data")
990     public void patch() throws Exception {
991         final String html = DOCTYPE_HTML
992             + "<html><head>\n"
993             + "<script>\n"
994             + LOG_TITLE_FUNCTION
995             + "function test() {\n"
996             + "  var xhr = new XMLHttpRequest();\n"
997             + "  xhr.open('PATCH', '/test2', false);\n"
998             + "  xhr.send('some body data');\n"
999             + "  log(xhr.responseText);\n"
1000             + "}\n"
1001             + "</script></head><body onload='test()'></body></html>";
1002 
1003         final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
1004         servlets.put("/test2", PatchServlet2.class);
1005 
1006         loadPage2(html, servlets);
1007         verifyTitle2(getWebDriver(), getExpectedAlerts());
1008     }
1009 
1010     /**
1011      * Servlet for {@link #patch()}.
1012      */
1013     public static class PatchServlet2 extends HttpServlet {
1014 
1015         @Override
1016         protected void service(final HttpServletRequest req, final HttpServletResponse resp)
1017             throws ServletException, IOException {
1018             final Writer writer = resp.getWriter();
1019             writer.write(req.getMethod());
1020             writer.write('|');
1021             writer.write(IOUtils.toString(req.getReader()));
1022         }
1023     }
1024 
1025     /**
1026      * Servlet for {@link #encodedXml()}.
1027      */
1028     public static class EncodedXmlServlet extends HttpServlet {
1029         private static final String RESPONSE = "<xml><content>blah</content></xml>";
1030 
1031         /**
1032          * {@inheritDoc}
1033          */
1034         @Override
1035         protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
1036             final byte[] bytes = RESPONSE.getBytes(UTF_8);
1037             final ByteArrayOutputStream bos = new ByteArrayOutputStream();
1038             final GZIPOutputStream gout = new GZIPOutputStream(bos);
1039             gout.write(bytes);
1040             gout.finish();
1041 
1042             final byte[] encoded = bos.toByteArray();
1043 
1044             response.setContentType(MimeType.TEXT_XML);
1045             response.setCharacterEncoding(UTF_8.name());
1046             response.setStatus(200);
1047             response.setContentLength(encoded.length);
1048             response.setHeader("Content-Encoding", "gzip");
1049 
1050             final OutputStream rout = response.getOutputStream();
1051             rout.write(encoded);
1052         }
1053     }
1054 
1055     /**
1056      * @throws Exception if the test fails
1057      */
1058     @Test
1059     @Alerts({"<xml><content>blah</content></xml>", "text/xml;charset=utf-8", "gzip", "45"})
1060     public void encodedXml() throws Exception {
1061         final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
1062         servlets.put("/test", EncodedXmlServlet.class);
1063 
1064         final String html = DOCTYPE_HTML
1065                         + "<html>\n"
1066                         + "  <head>\n"
1067                         + "    <script>\n"
1068                         + LOG_TITLE_FUNCTION
1069                         + "      var request;\n"
1070                         + "      function testBasicAuth() {\n"
1071                         + "        var request = new XMLHttpRequest();\n"
1072                         + "        request.open('GET', '/test', false, null, null);\n"
1073                         + "        request.send();\n"
1074                         + "        log(request.responseText);\n"
1075                         + "        log(request.getResponseHeader('content-type'));\n"
1076                         + "        log(request.getResponseHeader('content-encoding'));\n"
1077                         + "        log(request.getResponseHeader('content-length'));\n"
1078                         + "      }\n"
1079                         + "    </script>\n"
1080                         + "  </head>\n"
1081                         + "  <body onload='testBasicAuth()'>\n"
1082                         + "  </body>\n"
1083                         + "</html>";
1084 
1085         loadPage2(html, servlets);
1086         verifyTitle2(getWebDriver(), getExpectedAlerts());
1087     }
1088 
1089     /**
1090      * @throws Exception if the test fails
1091      */
1092     @Test
1093     @Alerts({"", "", "content-type: text/xml;charset=iso-8859-1\\n"
1094                         + "date XYZ GMT\\n"
1095                         + "server: Jetty(XXX)\\n"
1096                         + "transfer-encoding: chunked\\n"})
1097     @HtmlUnitNYI(CHROME = {"", "", "Date XYZ GMT\\nContent-Type: text/xml;charset=iso-8859-1\\n"
1098                                         + "Transfer-Encoding: chunked\\n"
1099                                         + "Server: Jetty(XXX)\\n"},
1100             EDGE = {"", "", "Date XYZ GMT\\nContent-Type: text/xml;charset=iso-8859-1\\n"
1101                                 + "Transfer-Encoding: chunked\\n"
1102                                 + "Server: Jetty(XXX)\\n"},
1103             FF = {"", "", "Date XYZ GMT\\nContent-Type: text/xml;charset=iso-8859-1\\n"
1104                                 + "Transfer-Encoding: chunked\\n"
1105                                 + "Server: Jetty(XXX)\\n"},
1106             FF_ESR = {"", "", "Date XYZ GMT\\nContent-Type: text/xml;charset=iso-8859-1\\n"
1107                                 + "Transfer-Encoding: chunked\\n"
1108                                 + "Server: Jetty(XXX)\\n"})
1109     public void getAllResponseHeaders() throws Exception {
1110         final String html = DOCTYPE_HTML
1111                         + "<html>\n"
1112                         + "  <head>\n"
1113                         + "    <script>\n"
1114                         + "      function log(msg) {\n"
1115                         + "        msg = ('' + msg).replace(/\\r\\n/g, '\\\\n');"
1116                         + "        msg = msg.replace(/\\n/g, '\\\\n');"
1117                         + "        window.document.title += msg + '§';"
1118                         + "      }\n"
1119                         + "      var request;\n"
1120                         + "      function testBasicAuth() {\n"
1121                         + "        var request = new XMLHttpRequest();\n"
1122                         + "        try {\n"
1123                         + "          log(request.getAllResponseHeaders());\n"
1124                         + "        } catch(e) { log('exception-created'); }\n"
1125 
1126                         + "        request.open('GET', '" + URL_SECOND + "', false, null, null);\n"
1127                         + "        try {\n"
1128                         + "          log(request.getAllResponseHeaders());\n"
1129                         + "        } catch(e) { log('exception-opened'); }\n"
1130 
1131                         + "        request.send();\n"
1132                         + "        try {\n"
1133                         + "          log(request.getAllResponseHeaders().replace(/Jetty\\(.*\\)/, 'Jetty(XXX)')"
1134                         + "             .replace(/Date.*GMT/, 'Date XYZ GMT').replace(/date.*GMT/, 'date XYZ GMT'));\n"
1135                         + "        } catch(e) { log('exception-sent'); }\n"
1136                         + "      }\n"
1137                         + "    </script>\n"
1138                         + "  </head>\n"
1139                         + "  <body onload='testBasicAuth()'>\n"
1140                         + "  </body>\n"
1141                         + "</html>";
1142 
1143         getMockWebConnection().setResponse(URL_SECOND, "<xml></xml>", MimeType.TEXT_XML);
1144 
1145         loadPageVerifyTitle2(html);
1146     }
1147 
1148     /**
1149      * @throws Exception if the test fails
1150      */
1151     @Test
1152     @Alerts({"null", "null", "null", "null",
1153              "text/xml;charset=iso-8859-1", "text/xml;charset=iso-8859-1",
1154              "text/xml;charset=iso-8859-1", "null"})
1155     public void getResponseHeader() throws Exception {
1156         final String html = DOCTYPE_HTML
1157                         + "<html>\n"
1158                         + "  <head>\n"
1159                         + "    <script>\n"
1160                         + LOG_TITLE_FUNCTION
1161                         + "      var request;\n"
1162                         + "      function testBasicAuth() {\n"
1163                         + "        var request = new XMLHttpRequest();\n"
1164                         + "        try {\n"
1165                         + "          log(request.getResponseHeader('Content-Type'));\n"
1166                         + "          log(request.getResponseHeader('unknown'));\n"
1167                         + "        } catch(e) { log('exception-created'); }\n"
1168 
1169                         + "        request.open('GET', '" + URL_SECOND + "', false, null, null);\n"
1170                         + "        try {\n"
1171                         + "          log(request.getResponseHeader('Content-Type'));\n"
1172                         + "          log(request.getResponseHeader('unknown'));\n"
1173                         + "        } catch(e) { log('exception-opened'); }\n"
1174 
1175                         + "        request.send();\n"
1176                         + "        try {\n"
1177                         + "          log(request.getResponseHeader('Content-Type'));\n"
1178                         + "          log(request.getResponseHeader('content-type'));\n"
1179                         + "          log(request.getResponseHeader('coNTENt-type'));\n"
1180                         + "          log(request.getResponseHeader('unknown'));\n"
1181                         + "        } catch(e) { log('exception-sent'); }\n"
1182                         + "      }\n"
1183                         + "    </script>\n"
1184                         + "  </head>\n"
1185                         + "  <body onload='testBasicAuth()'>\n"
1186                         + "  </body>\n"
1187                         + "</html>";
1188 
1189         getMockWebConnection().setResponse(URL_SECOND, "<xml></xml>", MimeType.TEXT_XML);
1190 
1191         loadPageVerifyTitle2(html);
1192     }
1193 
1194     /**
1195      * @throws Exception if the test fails
1196      */
1197     @Test
1198     @Alerts("exception catched")
1199     public void createFromPrototypeAndDefineProperty() throws Exception {
1200         final String html = DOCTYPE_HTML
1201             + "<html><body>\n"
1202             + "<script>\n"
1203             + LOG_TITLE_FUNCTION
1204             + "var f = function() {};\n"
1205             + "f.prototype = Object.create(window.XMLHttpRequest.prototype);\n"
1206             + "try {\n"
1207             + "  f.prototype['onerror'] = function() {};\n"
1208             + "  log('no exception');\n"
1209             + "} catch(e) { log('exception catched'); }\n"
1210             + "</script></body></html>";
1211 
1212         loadPageVerifyTitle2(html);
1213     }
1214 
1215     /**
1216      * @throws Exception if the test fails
1217      */
1218     @Test
1219     @Alerts("exception for onerror")
1220     @HtmlUnitNYI(CHROME = "read onerror",
1221             EDGE = "read onerror",
1222             FF = "read onerror",
1223             FF_ESR = "read onerror")
1224     public void readPropertyFromPrototypeShouldThrow() throws Exception {
1225         final String html = DOCTYPE_HTML
1226             + "<html><body>\n"
1227             + "<script>\n"
1228             + LOG_TITLE_FUNCTION
1229             + "var p = 'onerror';\n"
1230             + "try {\n"
1231             + "  var x = window.XMLHttpRequest.prototype[p];\n"
1232             + "  log('read ' + p);\n"
1233             + "} catch(e) { log('exception for ' + p); }\n"
1234             + "</script></body></html>";
1235 
1236         loadPageVerifyTitle2(html);
1237     }
1238 
1239     /**
1240      * @throws Exception if the test fails
1241      */
1242     @Test
1243     @Alerts("4")
1244     public void onreadystatechange_eventListener() throws Exception {
1245         final String html = DOCTYPE_HTML
1246             + "<html>\n"
1247             + "  <head>\n"
1248             + "    <script>\n"
1249             + LOG_TITLE_FUNCTION
1250             + "      var xhr;\n"
1251             + "      function test() {\n"
1252             + "        xhr = new XMLHttpRequest();\n"
1253             + "        xhr.open('GET', '" + URL_SECOND + "', false);\n"
1254             + "        xhr.addEventListener('readystatechange', onStateChange);\n"
1255             + "        xhr.send('');\n"
1256             + "      }\n"
1257             + "      function onStateChange() {\n"
1258             + "        log(xhr.readyState);\n"
1259             + "      }\n"
1260             + "    </script>\n"
1261             + "  </head>\n"
1262             + "  <body onload='test()'>\n"
1263             + "  </body>\n"
1264             + "</html>";
1265 
1266         final String xml =
1267               "<xml>\n"
1268             + "<content>blah</content>\n"
1269             + "<content>blah2</content>\n"
1270             + "</xml>";
1271 
1272         getMockWebConnection().setResponse(URL_SECOND, xml, MimeType.TEXT_XML);
1273         loadPageVerifyTitle2(html);
1274     }
1275 
1276     /**
1277      * @throws Exception if the test fails
1278      */
1279     @Test
1280     @Alerts("3")
1281     public void sendPostWithRedirect307() throws Exception {
1282         postRedirect(307, HttpMethod.POST, new URL(URL_FIRST, "/page2.html").toExternalForm(), "param=content");
1283     }
1284 
1285     /**
1286      * @throws Exception if the test fails
1287      */
1288     @Test
1289     @Alerts("3")
1290     public void sendPostWithRedirect308() throws Exception {
1291         postRedirect(308, HttpMethod.POST, new URL(URL_FIRST, "/page2.html").toExternalForm(), "param=content");
1292     }
1293 
1294     private void postRedirect(final int code, final HttpMethod httpMethod,
1295             final String redirectUrl, final String content) throws Exception {
1296         final String html = DOCTYPE_HTML
1297             + "<html><head><script>\n"
1298             + "  function test() {\n"
1299             + "    var xhr = new XMLHttpRequest();\n"
1300             + "    try {\n"
1301             + "      xhr.open('POST', 'redirect.html', false);\n"
1302             + "      xhr.send('" + content + "');\n"
1303             + "    } catch(e) { logEx(e); }\n"
1304             + "  }\n"
1305             + "</script></head>\n"
1306             + "<body onload='test()'></body></html>";
1307 
1308         final int reqCount = getMockWebConnection().getRequestCount();
1309 
1310         final URL url = new URL(URL_FIRST, "page2.html");
1311         getMockWebConnection().setResponse(url, html);
1312 
1313         final List<NameValuePair> headers = new ArrayList<>();
1314         headers.add(new NameValuePair("Location", redirectUrl));
1315         getMockWebConnection().setDefaultResponse("", code, "* Redirect", null, headers);
1316 
1317         expandExpectedAlertsVariables(URL_FIRST);
1318         loadPage2(html);
1319 
1320         assertEquals(reqCount + Integer.parseInt(getExpectedAlerts()[0]), getMockWebConnection().getRequestCount());
1321         assertEquals(httpMethod, getMockWebConnection().getLastWebRequest().getHttpMethod());
1322         assertNotNull(getMockWebConnection().getLastWebRequest().getRequestBody());
1323         assertFalse(getMockWebConnection().getLastWebRequest().getRequestBody().isEmpty());
1324         assertEquals(content, getMockWebConnection().getLastWebRequest().getRequestBody());
1325     }
1326 }