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;
16  
17  import static java.util.Arrays.asList;
18  import static org.junit.jupiter.api.Assertions.assertNotEquals;
19  import static org.junit.jupiter.api.Assertions.fail;
20  
21  import java.io.ByteArrayOutputStream;
22  import java.io.File;
23  import java.io.FileInputStream;
24  import java.io.FileNotFoundException;
25  import java.io.IOException;
26  import java.io.OutputStreamWriter;
27  import java.net.URI;
28  import java.net.URL;
29  import java.util.ArrayList;
30  import java.util.Arrays;
31  import java.util.Collections;
32  import java.util.LinkedList;
33  import java.util.List;
34  
35  import org.apache.commons.io.FileUtils;
36  import org.apache.commons.io.IOUtils;
37  import org.apache.commons.lang3.StringUtils;
38  import org.apache.commons.lang3.mutable.MutableInt;
39  import org.htmlunit.cssparser.parser.CSSErrorHandler;
40  import org.htmlunit.cssparser.parser.CSSException;
41  import org.htmlunit.cssparser.parser.CSSParseException;
42  import org.htmlunit.html.HtmlAnchor;
43  import org.htmlunit.html.HtmlButton;
44  import org.htmlunit.html.HtmlButtonInput;
45  import org.htmlunit.html.HtmlElement;
46  import org.htmlunit.html.HtmlInlineFrame;
47  import org.htmlunit.html.HtmlPage;
48  import org.htmlunit.html.parser.HTMLParser;
49  import org.htmlunit.html.parser.neko.HtmlUnitNekoHtmlParser;
50  import org.htmlunit.http.HttpStatus;
51  import org.htmlunit.javascript.host.html.HTMLStyleElement;
52  import org.htmlunit.junit.annotation.Alerts;
53  import org.htmlunit.util.MimeType;
54  import org.htmlunit.util.NameValuePair;
55  import org.htmlunit.util.UrlUtils;
56  import org.htmlunit.xml.XmlPage;
57  import org.junit.jupiter.api.Test;
58  
59  /**
60   * Tests for {@link WebClient}.
61   *
62   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
63   * @author <a href="mailto:cse@dynabean.de">Christian Sell</a>
64   * @author <a href="mailto:bcurren@esomnie.com">Ben Curren</a>
65   * @author Marc Guillemot
66   * @author David D. Kilzer
67   * @author Chris Erskine
68   * @author Hans Donner
69   * @author Paul King
70   * @author Ahmed Ashour
71   * @author Daniel Gredler
72   * @author Sudhan Moghe
73   * @author Ronald Brill
74   * @author Carsten Steul
75   * @author Joerg Werner
76   */
77  public class WebClientTest extends SimpleWebTestCase {
78  
79      /**
80       * Test the situation where credentials are required but they haven't been specified.
81       *
82       * @throws Exception if something goes wrong
83       */
84      @Test
85      public void credentialProvider_NoCredentials() throws Exception {
86          final String htmlContent = DOCTYPE_HTML
87                  + "<html><head><title>foo</title></head><body>\n"
88                  + "No access</body></html>";
89          final WebClient client = getWebClient();
90          client.getOptions().setPrintContentOnFailingStatusCode(false);
91  
92          final MockWebConnection webConnection = new MockWebConnection();
93          webConnection.setDefaultResponse(htmlContent, 401, "Credentials missing or just plain wrong",
94                  MimeType.TEXT_PLAIN);
95          client.setWebConnection(webConnection);
96  
97          try {
98              client.getPage(new WebRequest(URL_FIRST, HttpMethod.POST));
99              fail("Expected FailingHttpStatusCodeException");
100         }
101         catch (final FailingHttpStatusCodeException e) {
102             assertEquals(401, e.getStatusCode());
103         }
104     }
105 
106     /**
107      * Test that the {@link WebWindowEvent#CHANGE} window event gets fired at the
108      * appropriate time.
109      * @throws Exception if something goes wrong
110      */
111     @Test
112     public void htmlWindowEvents_changed() throws Exception {
113         final String htmlContent = DOCTYPE_HTML
114                 + "<html><head><title>foo</title></head><body>\n"
115                 + "<a href='http://www.foo2.com' id='a2'>link to foo2</a>\n"
116                 + "</body></html>";
117         final WebClient client = getWebClient();
118 
119         final List<WebWindowEvent> events = new LinkedList<>();
120         client.addWebWindowListener(new WebWindowListener() {
121             @Override
122             public void webWindowOpened(final WebWindowEvent event) {
123                 events.add(event);
124             }
125 
126             @Override
127             public void webWindowContentChanged(final WebWindowEvent event) {
128                 events.add(event);
129             }
130 
131             @Override
132             public void webWindowClosed(final WebWindowEvent event) {
133                 events.add(event);
134             }
135         });
136 
137         final MockWebConnection webConnection = new MockWebConnection();
138         webConnection.setDefaultResponse(htmlContent);
139         client.setWebConnection(webConnection);
140 
141         final HtmlPage firstPage = client.getPage(URL_FIRST);
142         final HtmlAnchor anchor = firstPage.getHtmlElementById("a2");
143 
144         final List<WebWindowEvent> firstExpectedEvents = Arrays.asList(new WebWindowEvent[] {
145             new WebWindowEvent(client.getCurrentWindow(), WebWindowEvent.CHANGE, null, firstPage)
146         });
147         assertEquals(firstExpectedEvents, events);
148 
149         events.clear();
150         final HtmlPage secondPage = anchor.click();
151 
152         final List<WebWindowEvent> secondExpectedEvents = Arrays.asList(new WebWindowEvent[] {
153             new WebWindowEvent(client.getCurrentWindow(), WebWindowEvent.CHANGE, firstPage, secondPage)
154         });
155         assertEquals(secondExpectedEvents, events);
156     }
157 
158     /**
159      * Test that the {@link WebWindowEvent#OPEN} window event gets fired at
160      * the appropriate time.
161      * @throws Exception if something goes wrong
162      */
163     @Test
164     public void htmlWindowEvents_opened() throws Exception {
165         final String page1Content = DOCTYPE_HTML
166                 + "<html><head><title>foo</title>\n"
167                 + "<script>window.open('" + URL_SECOND + "', 'myNewWindow')</script>\n"
168                 + "</head><body>\n"
169                 + "<a href='http://www.foo2.com' id='a2'>link to foo2</a>\n"
170                 + "</body></html>";
171         final String page2Content = DOCTYPE_HTML + "<html><head><title>foo</title></head><body></body></html>";
172 
173         final WebClient client = getWebClient();
174         final List<WebWindowEvent> events = new LinkedList<>();
175         client.addWebWindowListener(new WebWindowListener() {
176             @Override
177             public void webWindowOpened(final WebWindowEvent event) {
178                 events.add(event);
179             }
180 
181             @Override
182             public void webWindowContentChanged(final WebWindowEvent event) {
183                 events.add(event);
184             }
185 
186             @Override
187             public void webWindowClosed(final WebWindowEvent event) {
188                 events.add(event);
189             }
190         });
191 
192         final MockWebConnection webConnection = new MockWebConnection();
193         webConnection.setResponse(URL_FIRST, page1Content);
194         webConnection.setResponse(URL_SECOND, page2Content);
195 
196         client.setWebConnection(webConnection);
197 
198         final HtmlPage firstPage = client.getPage(URL_FIRST);
199         assertEquals("foo", firstPage.getTitleText());
200 
201         final WebWindow firstWindow = client.getCurrentWindow();
202         final WebWindow secondWindow = client.getWebWindowByName("myNewWindow");
203         final List<WebWindowEvent> expectedEvents = Arrays.asList(new WebWindowEvent[] {
204             new WebWindowEvent(
205                 secondWindow, WebWindowEvent.OPEN, null, null),
206             new WebWindowEvent(
207                 secondWindow, WebWindowEvent.CHANGE, null, secondWindow.getEnclosedPage()),
208             new WebWindowEvent(
209                 firstWindow, WebWindowEvent.CHANGE, null, firstPage),
210         });
211         assertEquals(expectedEvents, events);
212     }
213 
214     /**
215      * Test that the {@link WebWindowEvent#CLOSE} window event gets fired at
216      * the appropriate time.
217      * @throws Exception if something goes wrong
218      */
219     @Test
220     public void htmlWindowEvents_closedFromFrame() throws Exception {
221         final String firstContent = DOCTYPE_HTML
222                 + "<html><head><title>first</title></head><body>\n"
223                 + "<iframe src='" + URL_THIRD + "' id='frame1'></iframe>\n"
224                 + "<a href='" + URL_SECOND + "' id='a2'>link to foo2</a>\n"
225                 + "</body></html>";
226         final String secondContent = DOCTYPE_HTML + "<html><head><title>second</title></head><body></body></html>";
227         final String thirdContent = DOCTYPE_HTML + "<html><head><title>third</title></head><body></body></html>";
228         final WebClient client = getWebClient();
229 
230         final MockWebConnection webConnection = new MockWebConnection();
231         webConnection.setResponse(URL_FIRST, firstContent);
232         webConnection.setResponse(URL_SECOND, secondContent);
233         webConnection.setResponse(URL_THIRD, thirdContent);
234 
235         client.setWebConnection(webConnection);
236 
237         final HtmlPage firstPage = client.getPage(URL_FIRST);
238         assertEquals("first", firstPage.getTitleText());
239 
240         final List<WebWindowEvent> events = new LinkedList<>();
241         client.addWebWindowListener(new WebWindowListener() {
242             @Override
243             public void webWindowOpened(final WebWindowEvent event) {
244                 events.add(event);
245             }
246 
247             @Override
248             public void webWindowContentChanged(final WebWindowEvent event) {
249                 events.add(event);
250             }
251 
252             @Override
253             public void webWindowClosed(final WebWindowEvent event) {
254                 events.add(event);
255             }
256         });
257 
258         final HtmlInlineFrame frame = firstPage.getHtmlElementById("frame1");
259         final HtmlPage thirdPage = (HtmlPage) frame.getEnclosedPage();
260 
261         // Load the second page
262         final HtmlAnchor anchor = firstPage.getHtmlElementById("a2");
263         final HtmlPage secondPage = anchor.click();
264         assertEquals("second", secondPage.getTitleText());
265 
266         final WebWindow firstWindow = client.getCurrentWindow();
267         final List<WebWindowEvent> expectedEvents = Arrays.asList(new WebWindowEvent[] {
268             new WebWindowEvent(
269                 frame.getEnclosedWindow(), WebWindowEvent.CLOSE, thirdPage, null),
270             new WebWindowEvent(
271                 firstWindow, WebWindowEvent.CHANGE, firstPage, secondPage),
272         });
273         assertEquals(expectedEvents.get(0), events.get(0));
274         assertEquals(expectedEvents, events);
275     }
276 
277     /**
278      * Test a 301 redirection code where the original request was a GET.
279      * @throws Exception if something goes wrong
280      */
281     @Test
282     public void redirection301_MovedPermanently_GetMethod() throws Exception {
283         doTestRedirection(301, HttpMethod.GET, HttpMethod.GET, URL_SECOND.toExternalForm());
284     }
285 
286     /**
287      * Common utility for GET after POST redirection on same URLs
288      * @param statusCode the code to return from the initial request
289      * @throws Exception if the test fails
290      */
291     private void doTestRedirectionSameUrlAfterPost(final int statusCode) throws Exception {
292         final String firstContent = DOCTYPE_HTML + "<html><head><title>First</title></head><body></body></html>";
293         final String secondContent = DOCTYPE_HTML + "<html><head><title>Second</title></head><body></body></html>";
294 
295         final WebClient webClient = getWebClient();
296 
297         final List<NameValuePair> headers =
298                 Collections.singletonList(new NameValuePair("Location", URL_FIRST.toExternalForm()));
299 
300         // builds a webconnection that first sends a redirect and then a "normal" response for
301         // the same requested URL
302         final MockWebConnection webConnection = new MockWebConnection() {
303             private int count_ = 0;
304             @Override
305             public WebResponse getResponse(final WebRequest webRequest) throws IOException {
306                 ++count_;
307                 if (count_ == 1) {
308                     final WebResponse response = super.getResponse(webRequest);
309                     setResponse(webRequest.getUrl(), secondContent);
310                     return response;
311                 }
312                 return super.getResponse(webRequest);
313             }
314         };
315         webConnection.setResponse(URL_FIRST, firstContent, statusCode, "Some error", MimeType.TEXT_HTML, headers);
316         webClient.setWebConnection(webConnection);
317 
318         final HtmlPage page = webClient.getPage(new WebRequest(URL_FIRST, HttpMethod.POST));
319         final WebResponse webResponse = page.getWebResponse();
320         // A redirect should have happened
321         assertEquals(200, webResponse.getStatusCode());
322         assertEquals(URL_FIRST, webResponse.getWebRequest().getUrl());
323         assertEquals("Second", page.getTitleText());
324         assertSame(HttpMethod.GET, webResponse.getWebRequest().getHttpMethod());
325     }
326 
327     /**
328      * From the HTTP spec:  If the 301 status code is received in response
329      * to a request other than GET or HEAD, the user agent MUST NOT automatically
330      * redirect the request unless it can be confirmed by the user, since this
331      * might change the conditions under which the request was issued.
332      * BUT Firefox follows the redirection
333      * @throws Exception if something goes wrong
334      */
335     @Test
336     public void redirection301_MovedPermanently_PostMethod() throws Exception {
337         doTestRedirection(301, HttpMethod.POST, HttpMethod.GET, URL_SECOND.toExternalForm());
338     }
339 
340     /**
341      * @throws Exception if the test fails
342      */
343     @Test
344     public void redirection301_MovedPermanently_PostMethod2() throws Exception {
345         doTestRedirectionSameUrlAfterPost(301);
346     }
347 
348     /**
349      * From the HTTP spec:  Note: RFC 1945 and RFC 2068 specify that the client
350      * is not allowed to change the method on the redirected request. However,
351      * most existing user agent implementations treat 302 as if it were a 303
352      * response, performing a GET on the Location field-value regardless
353      * of the original request method. The status codes 303 and 307 have
354      * been added for servers that wish to make unambiguously clear which
355      * kind of reaction is expected of the client.
356      * @throws Exception if something goes wrong
357      */
358     @Test
359     public void redirection302_MovedTemporarily_PostMethod() throws Exception {
360         doTestRedirection(302, HttpMethod.POST, HttpMethod.GET, URL_SECOND.toExternalForm());
361     }
362 
363     /**
364      * @throws Exception if the test fails
365      */
366     @Test
367     public void redirection302_MovedTemporarily_PostMethod2() throws Exception {
368         doTestRedirectionSameUrlAfterPost(302);
369     }
370 
371     /**
372      * Test a 302 redirection code.
373      * @throws Exception if something goes wrong
374      */
375     @Test
376     public void redirection302_MovedTemporarily_GetMethod() throws Exception {
377         doTestRedirection(302, HttpMethod.GET, HttpMethod.GET, URL_SECOND.toExternalForm());
378     }
379 
380     /**
381      * Test a 302 redirection code with "," in URL parameters.
382      * @throws Exception if something goes wrong
383      */
384     @Test
385     public void redirection302_MovedTemporarily_CommaInParameters() throws Exception {
386         doTestRedirection(302, HttpMethod.GET, HttpMethod.GET, URL_SECOND + "/foo.html?foo1=abc&foo2=1,2,3,4");
387     }
388 
389     /**
390      * Tests a 303 redirection code. This should be the same as a 302.
391      * @throws Exception if something goes wrong
392      */
393     @Test
394     public void redirection303_SeeOther_GetMethod() throws Exception {
395         doTestRedirection(303, HttpMethod.GET, HttpMethod.GET, URL_SECOND.toExternalForm());
396     }
397 
398     /**
399      * Tests a 303 redirection code - this should be the same as a 302.
400      * @throws Exception if something goes wrong
401      */
402     @Test
403     public void redirection303_SeeOther_PostMethod() throws Exception {
404         doTestRedirection(303, HttpMethod.POST, HttpMethod.GET, URL_SECOND.toExternalForm());
405     }
406 
407     /**
408      * @throws Exception if something goes wrong
409      */
410     @Test
411     public void redirection303_SeeOther_PostMethod2() throws Exception {
412         doTestRedirectionSameUrlAfterPost(303);
413     }
414 
415     /**
416      * Tests a 307 redirection code.
417      * @throws Exception if something goes wrong
418      */
419     @Test
420     public void redirection307_TemporaryRedirect_GetMethod() throws Exception {
421         doTestRedirection(307, HttpMethod.GET, HttpMethod.GET, URL_SECOND.toExternalForm());
422     }
423 
424     /**
425      * Tests a 307 redirection code.
426      * @throws Exception if something goes wrong
427      */
428     @Test
429     public void redirection307_TemporaryRedirect_PostMethod() throws Exception {
430         doTestRedirection(307, HttpMethod.POST, HttpMethod.POST, URL_SECOND.toExternalForm());
431     }
432 
433     /**
434      * Basic logic for all the redirection tests.
435      *
436      * @param statusCode the code to return from the initial request
437      * @param initialRequestMethod the initial request
438      * @param expectedRedirectedRequestMethod the submit method of the second (redirected) request
439      *        If a redirect is not expected to happen then this must be null
440      * @param newLocation the Location set in the redirection header
441      * @throws Exception if the test fails
442      */
443     private void doTestRedirection(
444             final int statusCode,
445             final HttpMethod initialRequestMethod,
446             final HttpMethod expectedRedirectedRequestMethod,
447             final String newLocation)
448         throws Exception {
449 
450         doTestRedirection(statusCode, initialRequestMethod, expectedRedirectedRequestMethod, newLocation, false);
451         doTestRedirection(statusCode, initialRequestMethod, expectedRedirectedRequestMethod, newLocation, true);
452     }
453 
454     /**
455      * Browsers allow many redirections to the same URL before to stop redirections.
456      * See Bug 1619765 and feature request 1472343.
457      * @throws Exception if the test fails
458      */
459     @Test
460     public void redirectionSameURL() throws Exception {
461         final HtmlPage page1 = getPageWithRedirectionsSameURL(1);
462         assertEquals("Second", page1.getTitleText());
463 
464         try {
465             getPageWithRedirectionsSameURL(30);
466         }
467         catch (final Exception e) {
468             assertTrue(e.getMessage(), e.getMessage().contains("Too much redirect"));
469         }
470     }
471 
472     private HtmlPage getPageWithRedirectionsSameURL(final int nbRedirections) throws Exception {
473         final String firstContent = DOCTYPE_HTML + "<html><head><title>First</title></head><body></body></html>";
474         final String secondContent = DOCTYPE_HTML + "<html><head><title>Second</title></head><body></body></html>";
475 
476         final WebClient webClient = getWebClient();
477 
478         final URL url = URL_FIRST;
479         final List<NameValuePair> headers =
480                 Collections.singletonList(new NameValuePair("Location", URL_FIRST.toExternalForm()));
481         final MockWebConnection webConnection = new MockWebConnection() {
482             private int count_ = 0;
483             @Override
484             public WebResponse getResponse(final WebRequest webRequest) throws IOException {
485                 ++count_;
486                 if (count_ < nbRedirections) {
487                     setResponse(url, firstContent, 302, "Redirect needed " + count_, MimeType.TEXT_HTML, headers);
488                     return super.getResponse(webRequest);
489                 }
490                 else if (count_ == nbRedirections) {
491                     final WebResponse response = super.getResponse(webRequest);
492                     setResponse(webRequest.getUrl(), secondContent);
493                     return response;
494                 }
495                 else {
496                     return super.getResponse(webRequest);
497                 }
498             }
499         };
500         webConnection.setResponse(url, firstContent, 302, "Redirect needed", MimeType.TEXT_HTML, headers);
501         webClient.setWebConnection(webConnection);
502         webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);
503 
504         return webClient.getPage(url);
505     }
506 
507     /**
508      * Verifies that any additional headers in the original {@link WebRequest} instance are kept
509      * and sent to the redirect location. Specifically, the "Referer" header set in various locations was
510      * being lost during redirects (see bug 1987911).
511      * @throws Exception if an error occurs
512      */
513     @Test
514     public void redirection_AdditionalHeadersMaintained() throws Exception {
515         redirection_AdditionalHeadersMaintained(301);
516         redirection_AdditionalHeadersMaintained(302);
517     }
518 
519     private void redirection_AdditionalHeadersMaintained(final int statusCode) throws Exception {
520         final WebClient client = getWebClient();
521         final MockWebConnection conn = new MockWebConnection();
522         client.setWebConnection(conn);
523 
524         final List<NameValuePair> headers = asList(new NameValuePair("Location", URL_SECOND.toString()));
525         conn.setResponse(URL_FIRST, "", statusCode, "", MimeType.TEXT_HTML, headers);
526         conn.setResponse(URL_SECOND, DOCTYPE_HTML + "<html><body>abc</body></html>");
527 
528         final WebRequest request = new WebRequest(URL_FIRST);
529         request.setAdditionalHeader("foo", "bar");
530         client.getPage(request);
531 
532         assertEquals(URL_SECOND, conn.getLastWebRequest().getUrl());
533         assertEquals("bar", conn.getLastAdditionalHeaders().get("foo"));
534     }
535 
536     /**
537      * Basic logic for all the redirection tests.
538      *
539      * @param statusCode the code to return from the initial request
540      * @param initialRequestMethod the initial request
541      * @param expectedRedirectedRequestMethod the submit method of the second (redirected) request
542      *        If a redirect is not expected to happen then this must be null
543      * @param newLocation the Location set in the redirection header
544      * @param useProxy indicates if the test should be performed with a proxy
545      * @throws Exception if the test fails
546      */
547     @SuppressWarnings("resource")
548     private void doTestRedirection(
549             final int statusCode,
550             final HttpMethod initialRequestMethod,
551             final HttpMethod expectedRedirectedRequestMethod,
552             final String newLocation,
553             final boolean useProxy)
554         throws Exception {
555 
556         final String firstContent = DOCTYPE_HTML + "<html><head><title>First</title></head><body></body></html>";
557         final String secondContent = DOCTYPE_HTML + "<html><head><title>Second</title></head><body></body></html>";
558 
559         final WebClient webClient;
560         final String proxyHost;
561         final int proxyPort;
562         if (useProxy) {
563             proxyHost = "someHost";
564             proxyPort = 12233345;
565             webClient = new WebClient(getBrowserVersion(), proxyHost, proxyPort);
566         }
567         else {
568             proxyHost = null;
569             proxyPort = 0;
570             webClient = getWebClient();
571         }
572 
573         webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);
574         webClient.getOptions().setPrintContentOnFailingStatusCode(false);
575 
576         final List<NameValuePair> headers = Collections.singletonList(new NameValuePair("Location", newLocation));
577         final MockWebConnection webConnection = new MockWebConnection();
578         webConnection.setResponse(URL_FIRST, firstContent, statusCode, "Some error", MimeType.TEXT_HTML, headers);
579         webConnection.setResponse(new URL(newLocation), secondContent);
580 
581         webClient.setWebConnection(webConnection);
582 
583         final URL url = URL_FIRST;
584 
585         HtmlPage page;
586         WebResponse webResponse;
587 
588         //
589         // Second time redirection is turned on (default setting)
590         //
591         page = webClient.getPage(new WebRequest(url, initialRequestMethod));
592         webResponse = page.getWebResponse();
593         if (expectedRedirectedRequestMethod == null) {
594             // No redirect should have happened
595             assertEquals(statusCode, webResponse.getStatusCode());
596             assertEquals(initialRequestMethod, webConnection.getLastMethod());
597         }
598         else {
599             // A redirect should have happened
600             assertEquals(HttpStatus.OK_200, webResponse.getStatusCode());
601             assertEquals(newLocation, webResponse.getWebRequest().getUrl());
602             assertEquals("Second", page.getTitleText());
603             assertEquals(expectedRedirectedRequestMethod, webConnection.getLastMethod());
604         }
605         assertEquals(proxyHost, webConnection.getLastWebRequest().getProxyHost());
606         assertEquals(proxyPort, webConnection.getLastWebRequest().getProxyPort());
607         assertNull(webConnection.getLastWebRequest().getProxyScheme());
608         assertFalse(webConnection.getLastWebRequest().isSocksProxy());
609 
610         //
611         // Second time redirection is turned off
612         //
613         webClient.getOptions().setRedirectEnabled(false);
614         page = webClient.getPage(new WebRequest(url, initialRequestMethod));
615         webResponse = page.getWebResponse();
616         assertEquals(statusCode, webResponse.getStatusCode());
617         assertEquals(initialRequestMethod, webConnection.getLastMethod());
618         assertEquals(proxyHost, webConnection.getLastWebRequest().getProxyHost());
619         assertEquals(proxyPort, webConnection.getLastWebRequest().getProxyPort());
620         assertNull(webConnection.getLastWebRequest().getProxyScheme());
621         assertFalse(webConnection.getLastWebRequest().isSocksProxy());
622 
623         webClient.close();
624     }
625 
626     /**
627      * Test passing in a null page creator.
628      */
629     @Test
630     public void setPageCreator_null() {
631         final WebClient webClient = getWebClient();
632         try {
633             webClient.setPageCreator(null);
634             fail("Expected NullPointerException");
635         }
636         catch (final NullPointerException e) {
637             // expected path
638         }
639     }
640 
641     /**
642      * Test {@link WebClient#setPageCreator(PageCreator)}.
643      * @throws Exception if something goes wrong
644      */
645     @Test
646     public void setPageCreator() throws Exception {
647         final String page1Content = DOCTYPE_HTML
648                 + "<html><head><title>foo</title>\n"
649                 + "</head><body>\n"
650                 + "<a href='http://www.foo2.com' id='a2'>link to foo2</a>\n"
651                 + "</body></html>";
652         final WebClient client = getWebClient();
653 
654         final MockWebConnection webConnection = new MockWebConnection();
655         webConnection.setResponse(URL_FIRST, page1Content);
656 
657         client.setWebConnection(webConnection);
658         final List<Page> collectedPageCreationItems = new ArrayList<>();
659         client.setPageCreator(new CollectingPageCreator(collectedPageCreationItems));
660 
661         final Page page = client.getPage(URL_FIRST);
662         assertTrue("instanceof TextPage", page instanceof TextPage);
663 
664         final List<Page> expectedPageCreationItems = Arrays.asList(new Page[] {page});
665 
666         assertEquals(expectedPageCreationItems, collectedPageCreationItems);
667     }
668 
669     /** A PageCreator that collects data. */
670     private static class CollectingPageCreator implements PageCreator {
671         private final List<Page> collectedPages_;
672 
673         private static final HTMLParser HTML_PARSER = new HtmlUnitNekoHtmlParser();
674 
675         /**
676          * Creates an instance.
677          * @param list the list that will contain the data
678          */
679         CollectingPageCreator(final List<Page> list) {
680             collectedPages_ = list;
681         }
682 
683         /**
684          * Creates a page.
685          * @param webResponse the web response
686          * @param webWindow the web window
687          * @return the new page
688          * @throws IOException if an IO problem occurs
689          */
690         @Override
691         public Page createPage(final WebResponse webResponse, final WebWindow webWindow) throws IOException {
692             final Page page = new TextPage(webResponse, webWindow);
693             webWindow.setEnclosedPage(page);
694             collectedPages_.add(page);
695             return page;
696         }
697 
698         @Override
699         public HTMLParser getHtmlParser() {
700             return HTML_PARSER;
701         }
702     }
703 
704     /**
705      * Tests loading a page with POST parameters.
706      * @throws Exception if something goes wrong
707      */
708     @Test
709     public void loadPage_PostWithParameters() throws Exception {
710         final String htmlContent = DOCTYPE_HTML
711                 + "<html><head><title>foo</title></head><body>\n"
712                 + "</body></html>";
713         final WebClient client = getWebClient();
714 
715         final MockWebConnection webConnection = new MockWebConnection();
716         webConnection.setDefaultResponse(htmlContent);
717         client.setWebConnection(webConnection);
718 
719         final String urlString = "http://first?a=b";
720         final URL url = new URL(urlString);
721         final HtmlPage page = client.getPage(new WebRequest(url, HttpMethod.POST));
722 
723         assertEquals("http://first/?a=b", page.getUrl());
724     }
725 
726     /**
727      * Test that double / in query string are not changed.
728      * @throws Exception if something goes wrong
729      */
730     @Test
731     public void loadPage_SlashesInQueryString() throws Exception {
732         final String htmlContent = DOCTYPE_HTML
733                 + "<html><head><title>foo</title></head>\n"
734                 + "<body><a href='foo.html?id=UYIUYTY//YTYUY..F'>to page 2</a>\n"
735                 + "</body></html>";
736 
737         final WebClient client = getWebClient();
738 
739         final MockWebConnection webConnection = new MockWebConnection();
740         webConnection.setDefaultResponse(htmlContent);
741         client.setWebConnection(webConnection);
742 
743         final HtmlPage page = client.getPage(URL_FIRST);
744         final Page page2 = page.getAnchors().get(0).click();
745         final URL url2 = new URL(URL_FIRST, "foo.html?id=UYIUYTY//YTYUY..F");
746         assertEquals(url2.toExternalForm(), page2.getUrl());
747     }
748 
749     /**
750      * Test loading a file page.
751      *
752      * @throws Exception if something goes wrong
753      */
754     @Test
755     public void loadFilePage() throws Exception {
756         // Create a real file to read.
757         // It could be useful to have existing files to test in a special location in filesystem.
758         // It will be really needed when we have to test binary files using the file protocol.
759 
760         final String htmlContent = DOCTYPE_HTML + "<html><head><title>foo</title></head><body></body></html>";
761         final File currentDirectory = new File((new File("")).getAbsolutePath());
762         final File tmpFile = File.createTempFile("test", ".html", currentDirectory);
763         tmpFile.deleteOnExit();
764         final String encoding = (new OutputStreamWriter(new ByteArrayOutputStream())).getEncoding();
765         FileUtils.writeStringToFile(tmpFile, htmlContent, encoding);
766 
767         // Test a normal file URL.
768 
769         final WebClient client = getWebClient();
770         final URL url = new URL("file://" + tmpFile.getCanonicalPath());
771         final HtmlPage page = client.getPage(url);
772 
773         assertEquals(htmlContent, page.getWebResponse().getContentAsString());
774         assertEquals(MimeType.TEXT_HTML, page.getWebResponse().getContentType());
775         assertEquals(200, page.getWebResponse().getStatusCode());
776         assertEquals("foo", page.getTitleText());
777 
778         // Test a file URL with a query portion (needs to work for Dojo, for example).
779 
780         final URL url2 = new URL(url + "?with=query");
781         final HtmlPage page2 = client.getPage(url2);
782 
783         assertEquals(htmlContent, page2.getWebResponse().getContentAsString());
784         assertEquals(MimeType.TEXT_HTML, page2.getWebResponse().getContentType());
785         assertEquals(200, page2.getWebResponse().getStatusCode());
786         assertEquals("foo", page2.getTitleText());
787 
788         // Test a file URL with a ref portion (needs to work for Dojo, for example).
789 
790         final URL url3 = new URL(url + "#reference");
791         final HtmlPage page3 = client.getPage(url3);
792 
793         assertEquals(htmlContent, page3.getWebResponse().getContentAsString());
794         assertEquals(MimeType.TEXT_HTML, page3.getWebResponse().getContentType());
795         assertEquals(200, page3.getWebResponse().getStatusCode());
796         assertEquals("foo", page3.getTitleText());
797     }
798 
799     /**
800      * Test loading a file page with non ascii names.
801      *
802      * @throws Exception if something goes wrong
803      */
804     @Test
805     public void loadFilePageEncoded() throws Exception {
806         final WebClient client = getWebClient();
807 
808         final String whitespaceFilename = "white space.txt";
809         final URL whitespaceFileURL = getClass().getClassLoader().getResource(whitespaceFilename);
810         assertNotNull("Resource '" + whitespaceFilename + "' not found", whitespaceFileURL);
811         final File whitespaceFile = new File(whitespaceFileURL.toURI());
812         assertTrue("File '" + whitespaceFile.getAbsolutePath() + "' does not exist", whitespaceFile.exists());
813 
814         String url = "file://" + whitespaceFile.getCanonicalPath();
815         Page page = client.getPage(url);
816         assertEquals("the name of this file contains a blank", page.getWebResponse().getContentAsString());
817 
818         // encode the whitespace
819         url = "file://" + whitespaceFile.getCanonicalPath().replace(" ", "%20");
820         page = client.getPage(url);
821         assertEquals("the name of this file contains a blank", page.getWebResponse().getContentAsString());
822 
823         final String unicodeFilename = "\u6A94\u6848\uD30C\uC77C\u30D5\u30A1\u30A4\u30EB\u0645\u0644\u0641.txt";
824         final URL unicodeFileURL = getClass().getClassLoader().getResource(unicodeFilename);
825         assertNotNull("Resource '" + unicodeFilename + "' not found", unicodeFileURL);
826         final File unicodeFile = new File(unicodeFileURL.toURI());
827         assertTrue("File '" + unicodeFile.getAbsolutePath() + "' does not exist", unicodeFile.exists());
828 
829         url = "file://" + unicodeFile.getCanonicalPath();
830         page = client.getPage(url);
831         assertEquals("", page.getWebResponse().getContentAsString());
832 
833         url = url.replace(
834                 unicodeFilename,
835                 "%e6%aa%94%e6%a1%88%ed%8c%8c%ec%9d%bc%e3%83%95%e3%82%a1%e3%82%a4%e3%83%ab%d9%85%d9%84%d9%81.txt");
836         page = client.getPage(url);
837         assertEquals("", page.getWebResponse().getContentAsString());
838     }
839 
840     /**
841      * Test loading a file page with XML content. Regression test for bug 1113487.
842      *
843      * @throws Exception if something goes wrong
844      */
845     @Test
846     public void loadFilePageXml() throws Exception {
847         final String xmlContent = "<?xml version='1.0' encoding='UTF-8'?>\n"
848                 + "<dataset>\n"
849                 + "<table name=\"USER\">\n"
850                 + "<column>ID</column>\n"
851                 + "<row>\n"
852                 + "<value>116517</value>\n"
853                 + "</row>\n"
854                 + "</table>\n"
855                 + "</dataset>";
856         final File currentDirectory = new File((new File("")).getAbsolutePath());
857         final File tmpFile = File.createTempFile("test", ".xml", currentDirectory);
858         tmpFile.deleteOnExit();
859         final String encoding = (new OutputStreamWriter(new ByteArrayOutputStream())).getEncoding();
860         FileUtils.writeStringToFile(tmpFile, xmlContent, encoding);
861 
862         final URL fileURL = new URL("file://" + tmpFile.getCanonicalPath());
863 
864         final WebClient client = getWebClient();
865         final XmlPage page = (XmlPage) client.getPage(fileURL);
866 
867         assertEquals(xmlContent, page.getWebResponse().getContentAsString());
868         // "text/xml" or "application/xml", it doesn't matter
869         assertEquals("/xml", StringUtils.substring(page.getWebResponse().getContentType(), -4));
870         assertEquals(200, page.getWebResponse().getStatusCode());
871     }
872 
873     /**
874      * Test redirecting with JavaScript during page load.
875      * @throws Exception if something goes wrong
876      */
877     @Test
878     public void redirectViaJavaScriptDuringInitialPageLoad() throws Exception {
879         final String firstContent = DOCTYPE_HTML
880                 + "<html><head><title>First</title><script>\n"
881                 + "location.href='" + URL_SECOND + "';\n"
882                 + "</script></head><body></body></html>";
883         final String secondContent = DOCTYPE_HTML
884                 + "<html><head><title>Second</title></head><body></body></html>";
885 
886         final WebClient webClient = getWebClient();
887 
888         final MockWebConnection webConnection = new MockWebConnection();
889         webConnection.setResponse(URL_FIRST, firstContent);
890         webConnection.setResponse(URL_SECOND, secondContent);
891 
892         webClient.setWebConnection(webConnection);
893 
894         final URL url = URL_FIRST;
895 
896         final HtmlPage page = webClient.getPage(url);
897         assertEquals("Second", page.getTitleText());
898     }
899 
900     /**
901      * Test {@link WebClient#loadWebResponseInto(WebResponse,WebWindow)}.
902      * @throws Exception if the test fails
903      */
904     @Test
905     public void loadWebResponseInto() throws Exception {
906         final WebClient webClient = getWebClient();
907         final WebResponse webResponse = new StringWebResponse(
908                 DOCTYPE_HTML + "<html><head><title>first</title></head><body></body></html>", URL_FIRST);
909 
910         final Page page = webClient.loadWebResponseInto(webResponse, webClient.getCurrentWindow());
911         assertTrue(HtmlPage.class.isInstance(page));
912 
913         final HtmlPage htmlPage = (HtmlPage) page;
914         assertEquals("first", htmlPage.getTitleText());
915     }
916 
917     /**
918      * Verifies that exceptions are thrown on failing status code and the returned page
919      * is still set as the current page in the WebWindow.
920      *
921      * @throws Exception if test fails
922      */
923     @Test
924     public void getPageFailingStatusCode() throws Exception {
925         final String firstContent = DOCTYPE_HTML + "<html><head><title>Hello World</title></head><body></body></html>";
926 
927         final WebClient webClient = getWebClient();
928 
929         final MockWebConnection webConnection = new MockWebConnection();
930         webConnection.setResponse(URL_FIRST, firstContent, 500, "BOOM", MimeType.TEXT_HTML, Collections.emptyList());
931         webClient.setWebConnection(webConnection);
932         webClient.getOptions().setThrowExceptionOnFailingStatusCode(true);
933         webClient.getOptions().setPrintContentOnFailingStatusCode(false);
934         try {
935             webClient.getPage(URL_FIRST);
936             fail("Should have thrown");
937         }
938         catch (final FailingHttpStatusCodeException e) {
939             assertEquals(e.getStatusCode(), 500);
940             assertEquals(e.getStatusMessage(), "BOOM");
941             assertEquals(firstContent, e.getResponse().getContentAsString());
942         }
943         final HtmlPage page = (HtmlPage) webClient.getCurrentWindow().getEnclosedPage();
944         assertEquals("Hello World", page.getTitleText());
945     }
946 
947     /**
948      * @throws Exception if the test fails
949      */
950     @Test
951     public void proxyConfig() throws Exception {
952         // Create the client.
953         final String defaultProxyHost = "defaultProxyHost";
954         final int defaultProxyPort = 777;
955         try (WebClient webClient = new WebClient(getBrowserVersion(),
956                 defaultProxyHost, defaultProxyPort)) {
957 
958             // Configure the mock web connection.
959             final String html = DOCTYPE_HTML + "<html><head><title>Hello World</title></head><body></body></html>";
960             final MockWebConnection webConnection = new MockWebConnection();
961             webConnection.setResponse(URL_FIRST, html);
962             webClient.setWebConnection(webConnection);
963 
964             // Make sure the default proxy settings are used.
965             webClient.getPage(URL_FIRST);
966             assertEquals(defaultProxyHost, webConnection.getLastWebRequest().getProxyHost());
967             assertEquals(defaultProxyPort, webConnection.getLastWebRequest().getProxyPort());
968             assertNull(webConnection.getLastWebRequest().getProxyScheme());
969             assertFalse(webConnection.getLastWebRequest().isSocksProxy());
970 
971             // Change the webclient default proxy settings.
972             final String defaultProxyHost2 = "defaultProxyHost2";
973             final int defaultProxyPort2 = 532;
974             webClient.getOptions().getProxyConfig().setProxyHost(defaultProxyHost2);
975             webClient.getOptions().getProxyConfig().setProxyPort(defaultProxyPort2);
976 
977             // Make sure the new default proxy settings are used.
978             webClient.getPage(URL_FIRST);
979             assertEquals(defaultProxyHost2, webConnection.getLastWebRequest().getProxyHost());
980             assertEquals(defaultProxyPort2, webConnection.getLastWebRequest().getProxyPort());
981             assertNull(webConnection.getLastWebRequest().getProxyScheme());
982             assertFalse(webConnection.getLastWebRequest().isSocksProxy());
983 
984             // Make sure the custom proxy settings are used.
985             final String customProxyHost = "customProxyHost";
986             final int customProxyPort = 1000;
987             final WebRequest request = new WebRequest(URL_FIRST);
988             request.setProxyHost(customProxyHost);
989             request.setProxyPort(customProxyPort);
990             webClient.getPage(request);
991             assertEquals(customProxyHost, webConnection.getLastWebRequest().getProxyHost());
992             assertEquals(customProxyPort, webConnection.getLastWebRequest().getProxyPort());
993             assertNull(webConnection.getLastWebRequest().getProxyScheme());
994             assertFalse(webConnection.getLastWebRequest().isSocksProxy());
995 
996             // Make sure the proxy bypass works with default proxy settings.
997             webClient.getOptions().getProxyConfig().addHostsToProxyBypass(URL_FIRST.getHost());
998             webClient.getPage(URL_FIRST);
999             assertNull(webConnection.getLastWebRequest().getProxyHost());
1000             assertEquals(0, webConnection.getLastWebRequest().getProxyPort());
1001             assertNull(webConnection.getLastWebRequest().getProxyScheme());
1002             assertFalse(webConnection.getLastWebRequest().isSocksProxy());
1003 
1004             // Make sure the proxy bypass doesn't work with custom proxy settings.
1005             webClient.getPage(request);
1006             assertEquals(customProxyHost, webConnection.getLastWebRequest().getProxyHost());
1007             assertEquals(customProxyPort, webConnection.getLastWebRequest().getProxyPort());
1008             assertNull(webConnection.getLastWebRequest().getProxyScheme());
1009             assertFalse(webConnection.getLastWebRequest().isSocksProxy());
1010 
1011             // Make sure we can remove proxy bypass filters.
1012             webClient.getOptions().getProxyConfig().removeHostsFromProxyBypass(URL_FIRST.getHost());
1013             webClient.getPage(URL_FIRST);
1014             assertEquals(defaultProxyHost2, webConnection.getLastWebRequest().getProxyHost());
1015             assertEquals(defaultProxyPort2, webConnection.getLastWebRequest().getProxyPort());
1016             assertNull(webConnection.getLastWebRequest().getProxyScheme());
1017             assertFalse(webConnection.getLastWebRequest().isSocksProxy());
1018         }
1019     }
1020 
1021     /**
1022      * Regression test for http://sourceforge.net/p/htmlunit/bugs/431/.
1023      * @throws Exception if an error occurs
1024      */
1025     @Test
1026     public void proxyConfigWithRedirect() throws Exception {
1027         final String defaultProxyHost = "defaultProxyHost";
1028         final int defaultProxyPort = 777;
1029         final String html = DOCTYPE_HTML + "<html><head><title>Hello World</title></head><body></body></html>";
1030         try (WebClient webClient = new WebClient(getBrowserVersion(), defaultProxyHost, defaultProxyPort)) {
1031 
1032             webClient.getOptions().getProxyConfig().addHostsToProxyBypass("hostToByPass");
1033 
1034             final String location2 = "http://hostToByPass/foo.html";
1035             final List<NameValuePair> headers = Collections.singletonList(new NameValuePair("Location", location2));
1036             final MockWebConnection webConnection = new MockWebConnection();
1037             webConnection.setResponse(URL_FIRST, html, 302, "Some error", MimeType.TEXT_HTML, headers);
1038             webConnection.setResponse(new URL(location2),
1039                     DOCTYPE_HTML + "<html><head><title>2nd page</title></head></html>");
1040             webClient.setWebConnection(webConnection);
1041 
1042             final Page page2 = webClient.getPage(URL_FIRST);
1043             webClient.getPage(URL_FIRST);
1044             assertEquals(null, webConnection.getLastWebRequest().getProxyHost());
1045             assertEquals(0, webConnection.getLastWebRequest().getProxyPort());
1046             assertNull(webConnection.getLastWebRequest().getProxyScheme());
1047             assertFalse(webConnection.getLastWebRequest().isSocksProxy());
1048             assertEquals(location2, page2.getUrl());
1049 
1050             // Make sure default proxy settings are used.
1051             webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);
1052             webClient.getOptions().setRedirectEnabled(false);
1053             final Page page1 = webClient.getPage(URL_FIRST);
1054             assertEquals(defaultProxyHost, webConnection.getLastWebRequest().getProxyHost());
1055             assertEquals(defaultProxyPort, webConnection.getLastWebRequest().getProxyPort());
1056             assertNull(webConnection.getLastWebRequest().getProxyScheme());
1057             assertFalse(webConnection.getLastWebRequest().isSocksProxy());
1058             assertEquals(URL_FIRST, page1.getUrl());
1059         }
1060     }
1061 
1062     /**
1063      * @throws Exception if the test fails
1064      */
1065     @Test
1066     public void proxyConfigForJS() throws Exception {
1067         final String defaultProxyHost = "defaultProxyHost";
1068         final int defaultProxyPort = 777;
1069         final String html = DOCTYPE_HTML
1070                 + "<html><head><title>Hello World</title>\n"
1071                 + "<script language='javascript' type='text/javascript' src='foo.js'></script>\n"
1072                 + "</head><body></body></html>";
1073         try (WebClient webClient = new WebClient(getBrowserVersion(), defaultProxyHost, defaultProxyPort)) {
1074             final MockWebConnection webConnection = new MockWebConnection();
1075             webConnection.setResponse(URL_FIRST, html);
1076             webConnection.setResponse(new URL(URL_FIRST, "foo.js"), "", "text/javascript");
1077             webClient.setWebConnection(webConnection);
1078 
1079             // Make sure default proxy settings are used.
1080             webClient.getPage(URL_FIRST);
1081             assertEquals(defaultProxyHost, webConnection.getLastWebRequest().getProxyHost());
1082             assertEquals(defaultProxyPort, webConnection.getLastWebRequest().getProxyPort());
1083             assertNull(webConnection.getLastWebRequest().getProxyScheme());
1084             assertFalse(webConnection.getLastWebRequest().isSocksProxy());
1085 
1086             // Make sure proxy bypass works with default proxy settings.
1087             webClient.getOptions().getProxyConfig().addHostsToProxyBypass(URL_FIRST.getHost());
1088             webClient.getPage(URL_FIRST);
1089             assertNull(webConnection.getLastWebRequest().getProxyHost());
1090             assertEquals(0, webConnection.getLastWebRequest().getProxyPort());
1091             assertNull(webConnection.getLastWebRequest().getProxyScheme());
1092             assertFalse(webConnection.getLastWebRequest().isSocksProxy());
1093 
1094             // Make sure we can remove proxy bypass filters.
1095             webClient.getOptions().getProxyConfig().removeHostsFromProxyBypass(URL_FIRST.getHost());
1096             webClient.getPage(URL_FIRST);
1097             assertEquals(defaultProxyHost, webConnection.getLastWebRequest().getProxyHost());
1098             assertEquals(defaultProxyPort, webConnection.getLastWebRequest().getProxyPort());
1099             assertNull(webConnection.getLastWebRequest().getProxyScheme());
1100             assertFalse(webConnection.getLastWebRequest().isSocksProxy());
1101         }
1102     }
1103 
1104     /**
1105      * Test {@link WebClient#expandUrl(URL,String)} for the case where an anchor name
1106      * was specified.
1107      * @throws Exception if the test fails
1108      */
1109     @Test
1110     public void expandUrl() throws Exception {
1111         final String prefix = URL_FIRST.toExternalForm();
1112         assertEquals(prefix + "#second", WebClient.expandUrl(URL_FIRST, "#second"));
1113         assertEquals(prefix + "?a=1&b=2", WebClient.expandUrl(new URL(prefix + "?a=1&b=2"), ""));
1114         assertEquals(prefix + "?b=2&c=3", WebClient.expandUrl(new URL(prefix + "?a=1&b=2"), "?b=2&c=3"));
1115         assertEquals("file:/home/myself/test.js",
1116                 WebClient.expandUrl(new URL("file:/home/myself/myTest.html"), "test.js"));
1117     }
1118 
1119     /**
1120      * @throws Exception if the test fails
1121      */
1122     @Test
1123     public void expandUrlWithFile() throws Exception {
1124         final String urlString = "http://host/page.html";
1125         final URL url = new URL(urlString);
1126         assertEquals(urlString + "#second", WebClient.expandUrl(url, "#second"));
1127     }
1128 
1129     /** Test the accessors for refreshHandler. */
1130     @Test
1131     public void refreshHandlerAccessors() {
1132         final WebClient webClient = getWebClient();
1133         assertTrue(ImmediateRefreshHandler.class.isInstance(webClient.getRefreshHandler()));
1134 
1135         final RefreshHandler handler = new ImmediateRefreshHandler();
1136         webClient.setRefreshHandler(handler);
1137         assertSame(handler, webClient.getRefreshHandler());
1138     }
1139 
1140     /**
1141      * Apparently if the browsers receive a charset that they don't understand, they ignore
1142      * it and assume ISO-8895-1. Ensure we do the same.
1143      * @throws Exception if the test fails
1144      */
1145     @Test
1146     public void badCharset() throws Exception {
1147         final String page1Content = DOCTYPE_HTML
1148                 + "<html><head><title>foo</title>\n"
1149                 + "</head><body></body></html>";
1150         final WebClient client = getWebClient();
1151 
1152         final MockWebConnection webConnection = new MockWebConnection();
1153         webConnection.setResponse(URL_FIRST, page1Content, "text/html; charset=garbage");
1154 
1155         client.setWebConnection(webConnection);
1156 
1157         final Page page = client.getPage(URL_FIRST);
1158         assertTrue(HtmlPage.class.isInstance(page));
1159     }
1160 
1161     /**
1162      * Colons are legal in the path of a URL but {@link WebClient#expandUrl(URL,String)} was
1163      * blowing up on this case. Ensure it's fixed.
1164      * @throws Exception if the test fails
1165      */
1166     @Test
1167     public void expandUrlHandlesColonsInRelativeUrl() throws Exception {
1168         final URL newUrl = WebClient.expandUrl(new URL("http://host/foo"), "/bar/blah:de:blah");
1169         assertEquals("http://host/bar/blah:de:blah", newUrl);
1170     }
1171 
1172     /**
1173      * Test reuse of a single {@link HtmlPage} object to submit the same form multiple times.
1174      * @throws Exception if test fails
1175      */
1176     @Test
1177     public void reusingHtmlPageToSubmitFormMultipleTimes() throws Exception {
1178         final String firstContent = DOCTYPE_HTML
1179                 + "<html><head><title>First</title></head>\n"
1180                 + "<body onload='document.myform.mysubmit.focus()'>\n"
1181                 + "<form action='" + URL_SECOND + "' name='myform'>\n"
1182                 + "<input type='submit' name='mysubmit'>\n"
1183                 + "</form></body></html>";
1184         final String secondContent = DOCTYPE_HTML
1185                 + "<html><head><title>Second</title></head><body>Second</body></html>";
1186 
1187         final WebClient webClient = getWebClient();
1188 
1189         final MockWebConnection webConnection = new MockWebConnection();
1190         webConnection.setResponse(URL_FIRST, firstContent);
1191         webConnection.setDefaultResponse(secondContent);
1192 
1193         webClient.setWebConnection(webConnection);
1194 
1195         final HtmlPage page = webClient.getPage(URL_FIRST);
1196         for (int i = 0; i < 100; i++) {
1197             final HtmlElement button = page.getFormByName("myform").getInputByName("mysubmit");
1198             button.click();
1199         }
1200     }
1201 
1202     /**
1203      * Test the value of window.opener when a link has target="_top".
1204      * @throws Exception if test fails
1205      */
1206     @Test
1207     public void openerInFrameset() throws Exception {
1208         final String firstContent = DOCTYPE_HTML
1209                 + "<html><head><script>alert(window.opener)</script><frameset cols='*'>\n"
1210                 + "<frame src='" + URL_SECOND + "'>\n"
1211                 + "</frameset>\n"
1212                 + "</html>";
1213         final String secondContent = DOCTYPE_HTML
1214                 + "<html><body><a href='" + URL_FIRST + "' target='_top'>to top</a></body></html>";
1215 
1216         final WebClient webClient = getWebClient();
1217 
1218         final MockWebConnection webConnection = new MockWebConnection();
1219         webConnection.setResponse(URL_FIRST, firstContent);
1220         webConnection.setResponse(URL_SECOND, secondContent);
1221         webClient.setWebConnection(webConnection);
1222 
1223         final List<String> collectedAlerts = new ArrayList<>();
1224         webClient.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
1225 
1226         final HtmlPage page = webClient.getPage(URL_FIRST);
1227         final HtmlPage pageInFrame = (HtmlPage) page.getFrames().get(0).getEnclosedPage();
1228         pageInFrame.getAnchors().get(0).click();
1229 
1230         final String[] expectedAlerts = {"null", "null"};
1231         assertEquals(expectedAlerts, collectedAlerts);
1232     }
1233 
1234     /**
1235      * @throws Exception if an error occurs
1236      */
1237     @Test
1238     public void guessContentType() throws Exception {
1239         final WebClient c = getWebClient();
1240 
1241         // tests empty files, type should be determined from file suffix
1242         assertEquals("empty.png", MimeType.IMAGE_PNG, c.guessContentType(getTestFile("empty.png")));
1243         assertEquals("empty.jpg", MimeType.IMAGE_JPEG, c.guessContentType(getTestFile("empty.jpg")));
1244         assertEquals("empty.gif", MimeType.IMAGE_GIF, c.guessContentType(getTestFile("empty.gif")));
1245         assertEquals("empty.js", MimeType.TEXT_JAVASCRIPT, c.guessContentType(getTestFile("empty.js")));
1246         assertEquals("empty.css", "text/css", c.guessContentType(getTestFile("empty.css")));
1247 
1248         // test real files with bad file suffix
1249         assertEquals("tiny-png.img", MimeType.IMAGE_PNG, c.guessContentType(getTestFile("tiny-png.img")));
1250         assertEquals("tiny-jpg.img", MimeType.IMAGE_JPEG, c.guessContentType(getTestFile("tiny-jpg.img")));
1251         assertEquals("tiny-gif.img", MimeType.IMAGE_GIF, c.guessContentType(getTestFile("tiny-gif.img")));
1252 
1253         // tests XHTML files, types will be determined based on a mixture of file suffixes and contents
1254         // note that "xhtml.php" returns content type "text/xml" in Firefox, but "application/xml" is good enough...
1255         assertEquals("xhtml.php", MimeType.APPLICATION_XML, c.guessContentType(getTestFile("xhtml.php")));
1256         assertEquals("xhtml.htm", MimeType.TEXT_HTML, c.guessContentType(getTestFile("xhtml.htm")));
1257         assertEquals("xhtml.html", MimeType.TEXT_HTML, c.guessContentType(getTestFile("xhtml.html")));
1258         assertEquals("xhtml.xhtml", MimeType.APPLICATION_XHTML, c.guessContentType(getTestFile("xhtml.xhtml")));
1259     }
1260 
1261     /**
1262      * Test that no encoding disturb file reads from filesystem.
1263      * For instance this test failed under Linux with LANG=de_DE.UTF-8 or LANG=C
1264      * but worked for LANG=de_DE.ISO-8859-1
1265      * @throws Exception if the test fails
1266      */
1267     @Test
1268     public void binaryFileFromFileSystem() throws Exception {
1269         final String testfileName = "tiny-jpg.img";
1270         final File testfile = getTestFile(testfileName);
1271         final byte[] directBytes = IOUtils.toByteArray(new FileInputStream(testfile));
1272         final String directStr = hexRepresentation(directBytes);
1273         final WebClient client = getWebClient();
1274         final Page testpage = client.getPage(testfile.toURI().toURL());
1275         final byte[] webclientBytes = IOUtils.toByteArray(testpage.getWebResponse().getContentAsStream());
1276         final String webclientStr = hexRepresentation(webclientBytes);
1277         assertEquals(directStr, webclientStr);
1278     }
1279 
1280     /**
1281      * Helper to make hex diff human easier to read for human eyes
1282      * @param digest the bytes
1283      * @return the hex representation
1284      */
1285     private static String hexRepresentation(final byte[] digest) {
1286         final StringBuilder hexString = new StringBuilder();
1287         for (final byte b : digest) {
1288             hexString.append(Integer.toHexString(0xFF & b));
1289             hexString.append(" ");
1290         }
1291         return hexString.toString().trim();
1292     }
1293 
1294     /**
1295      * Gets the file located in testfiles from the file name
1296      * @param fileName the file name
1297      * @return the file
1298      * @throws Exception if a pb occurs
1299      */
1300     private File getTestFile(final String fileName) throws Exception {
1301         final URL url = getClass().getClassLoader().getResource("testfiles/" + fileName);
1302         if (url == null) {
1303             throw new FileNotFoundException(fileName);
1304         }
1305         final File file = new File(new URI(url.toString()));
1306 
1307         return file;
1308     }
1309 
1310     /**
1311      * Test that additional header are correctly transmitted to the web connection.
1312      * @throws Exception if something goes wrong
1313      */
1314     @Test
1315     public void requestHeader() throws Exception {
1316         final String content = DOCTYPE_HTML + "<html></html>";
1317         final WebClient client = getWebClient();
1318 
1319         final MockWebConnection webConnection = new MockWebConnection();
1320         webConnection.setDefaultResponse(content);
1321         client.setWebConnection(webConnection);
1322 
1323         client.getPage(URL_FIRST);
1324         assertNull(webConnection.getLastAdditionalHeaders().get("foo-header"));
1325 
1326         client.addRequestHeader("foo-header", "foo value");
1327         client.getPage(URL_FIRST);
1328         assertEquals("foo value", webConnection.getLastAdditionalHeaders().get("foo-header"));
1329 
1330         client.removeRequestHeader("foo-header");
1331         client.getPage(URL_FIRST);
1332         assertNull(webConnection.getLastAdditionalHeaders().get("foo-header"));
1333     }
1334 
1335     /**
1336      * Request - Client - Default Headers.
1337      *
1338      * @throws Exception if something goes wrong
1339      */
1340     @Test
1341     public void requestHeaderOverwritesClient() throws Exception {
1342         final String fromRequest = "from request";
1343         final String fromClient = "from client";
1344 
1345         final String content = DOCTYPE_HTML + "<html></html>";
1346         final WebClient client = getWebClient();
1347 
1348         final MockWebConnection webConnection = new MockWebConnection();
1349         webConnection.setDefaultResponse(content);
1350         client.setWebConnection(webConnection);
1351 
1352         final WebRequest wr = new WebRequest(URL_FIRST);
1353         client .getPage(wr);
1354         assertNotEquals(fromRequest, webConnection.getLastAdditionalHeaders().get(HttpHeader.ACCEPT_LANGUAGE));
1355         assertNotEquals(fromClient, webConnection.getLastAdditionalHeaders().get(HttpHeader.ACCEPT_LANGUAGE));
1356 
1357         // request overwrites default
1358         wr.setAdditionalHeader(HttpHeader.ACCEPT_LANGUAGE, fromRequest);
1359         client .getPage(wr);
1360         assertEquals(fromRequest, webConnection.getLastAdditionalHeaders().get(HttpHeader.ACCEPT_LANGUAGE));
1361         assertNotEquals(fromClient, webConnection.getLastAdditionalHeaders().get(HttpHeader.ACCEPT_LANGUAGE));
1362 
1363         // request overwrites client
1364         client.addRequestHeader(HttpHeader.ACCEPT_LANGUAGE, fromClient);
1365         client .getPage(wr);
1366         assertEquals(fromRequest, webConnection.getLastAdditionalHeaders().get(HttpHeader.ACCEPT_LANGUAGE));
1367         assertNotEquals(fromClient, webConnection.getLastAdditionalHeaders().get(HttpHeader.ACCEPT_LANGUAGE));
1368     }
1369 
1370     /**
1371      * Request - Client - Default Headers.
1372      *
1373      * @throws Exception if something goes wrong
1374      */
1375     @Test
1376     public void clientHeaderOverwritesDefault() throws Exception {
1377         final String fromClient = "from client";
1378 
1379         final String content = DOCTYPE_HTML + "<html></html>";
1380         final WebClient client = getWebClient();
1381 
1382         final MockWebConnection webConnection = new MockWebConnection();
1383         webConnection.setDefaultResponse(content);
1384         client.setWebConnection(webConnection);
1385 
1386         client .getPage(URL_FIRST);
1387         assertNotEquals(fromClient, webConnection.getLastAdditionalHeaders().get(HttpHeader.ACCEPT_LANGUAGE));
1388 
1389         // client overwrites default
1390         client.addRequestHeader(HttpHeader.ACCEPT_LANGUAGE, fromClient);
1391         client .getPage(URL_FIRST);
1392         assertEquals(fromClient, webConnection.getLastAdditionalHeaders().get(HttpHeader.ACCEPT_LANGUAGE));
1393     }
1394 
1395     /**
1396      * @throws Exception if something goes wrong
1397      */
1398     @Test
1399     public void requestHeaderDoNotOverwriteWebRequestAcceptHeader() throws Exception {
1400         final String content = DOCTYPE_HTML + "<html></html>";
1401         final WebClient webClient = getWebClient();
1402 
1403         final MockWebConnection webConnection = new MockWebConnection();
1404         webConnection.setDefaultResponse(content);
1405         webClient.setWebConnection(webConnection);
1406 
1407         // default accept header
1408         webClient.getPage(URL_FIRST);
1409         assertNotNull(webConnection.getLastAdditionalHeaders().get(HttpHeader.ACCEPT));
1410         assertNotEquals("application/pdf", webConnection.getLastAdditionalHeaders().get(HttpHeader.ACCEPT));
1411 
1412         // request with accept header
1413         final WebRequest wr = new WebRequest(URL_FIRST, "application/pdf",
1414                                     webClient.getBrowserVersion().getAcceptEncodingHeader());
1415         webClient.getPage(wr);
1416         assertEquals("application/pdf", webConnection.getLastAdditionalHeaders().get(HttpHeader.ACCEPT));
1417 
1418         // request has an accept header use the one from the request
1419         webClient.addRequestHeader(HttpHeader.ACCEPT, MimeType.IMAGE_PNG);
1420         webClient.getPage(wr);
1421         assertEquals("application/pdf", webConnection.getLastAdditionalHeaders().get(HttpHeader.ACCEPT));
1422 
1423         // request has no longer an accept header use the one from the client
1424         wr.removeAdditionalHeader(HttpHeader.ACCEPT);
1425         webClient.getPage(wr);
1426         assertEquals("image/png", webConnection.getLastAdditionalHeaders().get(HttpHeader.ACCEPT));
1427     }
1428 
1429     /**
1430      * @throws Exception if something goes wrong
1431      */
1432     @Test
1433     public void requestHeaderDoNotOverwriteWebRequestAcceptHeader2() throws Exception {
1434         final String content = DOCTYPE_HTML + "<html></html>";
1435         final WebClient client = getWebClient();
1436 
1437         final MockWebConnection webConnection = new MockWebConnection();
1438         webConnection.setDefaultResponse(content);
1439         client.setWebConnection(webConnection);
1440 
1441         // default accept header
1442         client.getPage(URL_FIRST);
1443         assertNotNull(webConnection.getLastAdditionalHeaders().get(HttpHeader.ACCEPT));
1444         assertNotEquals("application/pdf", webConnection.getLastAdditionalHeaders().get(HttpHeader.ACCEPT));
1445 
1446         // request with accept header
1447         final WebRequest wr = new WebRequest(URL_FIRST, HttpMethod.GET);
1448         wr.setAdditionalHeader(HttpHeader.ACCEPT, "application/pdf");
1449         client.getPage(wr);
1450         assertEquals("application/pdf", webConnection.getLastAdditionalHeaders().get(HttpHeader.ACCEPT));
1451 
1452         // request has an accept header use the one from the request
1453         client.addRequestHeader(HttpHeader.ACCEPT, "image/png");
1454         client.getPage(wr);
1455         assertEquals("application/pdf", webConnection.getLastAdditionalHeaders().get(HttpHeader.ACCEPT));
1456 
1457         // request has no longer an accept header use the one from the client
1458         wr.removeAdditionalHeader(HttpHeader.ACCEPT);
1459         client.getPage(wr);
1460         assertEquals("image/png", webConnection.getLastAdditionalHeaders().get(HttpHeader.ACCEPT));
1461     }
1462 
1463     /**
1464      * Test that content type is looked in a case insensitive way.
1465      * Cf <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>:
1466      * "All media type values, subtype values, and parameter names as defined
1467      * are case-insensitive".
1468      * @throws Exception if something goes wrong
1469      */
1470     @Test
1471     public void contentTypeCaseInsensitive() throws Exception {
1472         final String content = DOCTYPE_HTML
1473                 + "<html><head>\n"
1474                 + "<script type='Text/Javascript' src='foo.js'></script>\n"
1475                 + "</head></html>";
1476         final WebClient client = getWebClient();
1477 
1478         final MockWebConnection webConnection = new MockWebConnection();
1479         webConnection.setDefaultResponse("alert('foo')", 200, "OK", "Text/Javascript");
1480         client.setWebConnection(webConnection);
1481 
1482         final List<String> collectedAlerts = new ArrayList<>();
1483         client.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
1484         final String[] expectedAlerts = {"foo"};
1485 
1486         webConnection.setResponse(URL_FIRST, content, "Text/Html");
1487         assertTrue(HtmlPage.class.isInstance(client.getPage(URL_FIRST)));
1488         assertEquals(expectedAlerts, collectedAlerts);
1489 
1490         webConnection.setResponse(URL_FIRST, content, "Text/Xml");
1491         assertTrue(XmlPage.class.isInstance(client.getPage(URL_FIRST)));
1492         webConnection.setResponse(URL_FIRST, content, MimeType.APPLICATION_XML);
1493         assertTrue(XmlPage.class.isInstance(client.getPage(URL_FIRST)));
1494 
1495         webConnection.setResponse(URL_FIRST, content, MimeType.TEXT_PLAIN);
1496         assertTrue(TextPage.class.isInstance(client.getPage(URL_FIRST)));
1497 
1498         webConnection.setResponse(URL_FIRST, "", "Text/JavaScript");
1499         assertTrue(HtmlPage.class.isInstance(client.getPage(URL_FIRST)));
1500     }
1501 
1502     /**
1503      * Load a JavaScript function from an external file using src references
1504      * inside a script element.
1505      *
1506      * @throws Exception if the test fails
1507      */
1508     @Test
1509     public void loadFilePageWithExternalJS() throws Exception {
1510         final File currentDirectory = new File((new File("")).getAbsolutePath());
1511 
1512         final String encoding = (new OutputStreamWriter(new ByteArrayOutputStream())).getEncoding();
1513 
1514         // JavaScript file
1515         final File tmpFileJS = File.createTempFile("test", ".js", currentDirectory);
1516         tmpFileJS.deleteOnExit();
1517         FileUtils.writeStringToFile(tmpFileJS, "alert('foo')", encoding);
1518 
1519         // HTML file
1520         final String html = DOCTYPE_HTML
1521                 + "<html><head></head><body>\n"
1522                 + "<script language='javascript' type='text/javascript' src='" + tmpFileJS.getName() + "'></script>\n"
1523                 + "</body></html>";
1524         final File tmpFile = File.createTempFile("test", ".html", currentDirectory);
1525         tmpFile.deleteOnExit();
1526         FileUtils.writeStringToFile(tmpFile, html, encoding);
1527 
1528         final URL fileURL = new URL("file://" + tmpFile.getCanonicalPath());
1529         final WebClient webClient = getWebClient();
1530         final List<String> collectedAlerts = new ArrayList<>();
1531         webClient.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
1532         webClient.getPage(fileURL);
1533 
1534         final String[] expectedAlerts = {"foo"};
1535         assertEquals(expectedAlerts, collectedAlerts);
1536     }
1537 
1538     /**
1539      * Verifies that {@link WebClient#getPage(WebWindow, WebRequest)} calls OnBeforeUnload
1540      * on the specified window's page, not on the client's "current" page.
1541      * @throws Exception if an error occurs
1542      */
1543     @Test
1544     public void onBeforeUnloadCalledOnCorrectPage() throws Exception {
1545         final String html = DOCTYPE_HTML + "<html><body onbeforeunload='alert(7)'><iframe></iframe></body></html>";
1546         final List<String> alerts = new ArrayList<>();
1547         loadPage(html, alerts);
1548         assertTrue(alerts.isEmpty());
1549     }
1550 
1551     /**
1552      * Verifies that URLs are automatically encoded before being sent to the server, like
1553      * regular browsers do (verified by sniffing HTTP headers).
1554      * @throws Exception if an error occurs
1555      */
1556     @Test
1557     public void urlEncoding() throws Exception {
1558         final URL url = new URL("http://host/x+y\u00E9/a\u00E9 b?c \u00E9 d");
1559         final HtmlPage page = loadPage(BrowserVersion.FIREFOX, DOCTYPE_HTML + "<html></html>", new ArrayList<>(), url);
1560         final WebRequest wrs = page.getWebResponse().getWebRequest();
1561         assertEquals("http://host/x+y%C3%A9/a%C3%A9%20b?c%20%C3%A9%20d", wrs.getUrl());
1562     }
1563 
1564     /**
1565      * Verifies that URLs are automatically encoded before being sent to the server, like
1566      * regular browsers do (verified by sniffing HTTP headers).
1567      * @throws Exception if an error occurs
1568      */
1569     @Test
1570     public void urlEncoding2() throws Exception {
1571         final URL url = new URL("http://host/x+y\u00E9/a\u00E9 b?c \u00E9 d");
1572         final HtmlPage page = loadPage(BrowserVersion.BEST_SUPPORTED,
1573                 DOCTYPE_HTML + "<html></html>", new ArrayList<>(), url);
1574         final WebRequest wrs = page.getWebResponse().getWebRequest();
1575         assertEquals("http://host/x+y%C3%A9/a%C3%A9%20b?c%20%C3%A9%20d", wrs.getUrl());
1576     }
1577 
1578     /**
1579      * Test that '+' is not encoded in URLs.
1580      * @throws Exception if the test fails
1581      */
1582     @Test
1583     public void plusNotEncodedInUrl() throws Exception {
1584         final URL url = new URL("http://host/search/my+category/");
1585         final HtmlPage page = loadPage(DOCTYPE_HTML + "<html></html>", new ArrayList<>(), url);
1586         final WebRequest wrs = page.getWebResponse().getWebRequest();
1587         assertEquals("http://host/search/my+category/", wrs.getUrl());
1588     }
1589 
1590     /**
1591      * @throws Exception if an error occurs
1592      */
1593     @Test
1594     public void cssEnablementControlsCssLoading() throws Exception {
1595         final WebClient client = getWebClient();
1596         final MockWebConnection conn = new MockWebConnection();
1597         client.setWebConnection(conn);
1598 
1599         final String html = DOCTYPE_HTML
1600                         + "<html>\n"
1601                         + "  <head>\n"
1602                         + "    <link href='" + URL_SECOND + "' rel='stylesheet'></link>\n"
1603                         + "  </head>\n"
1604                         + "  <body onload='alert(document.styleSheets.length)'>\n"
1605                         + "    <div>abc</div>\n"
1606                         + "  </body>\n"
1607                         + "</html>";
1608         conn.setResponse(URL_FIRST, html);
1609 
1610         final String css = ".foo { color: green; }";
1611         conn.setResponse(URL_SECOND, css, 200, "OK", MimeType.TEXT_CSS, new ArrayList<>());
1612 
1613         final List<String> actual = new ArrayList<>();
1614         client.setAlertHandler(new CollectingAlertHandler(actual));
1615 
1616         client.getPage(URL_FIRST);
1617         assertEquals(new String[]{"1"}, actual);
1618 
1619         actual.clear();
1620         client.getOptions().setCssEnabled(false);
1621         client.getPage(URL_FIRST);
1622         assertEquals(new String[]{"0"}, actual);
1623 
1624         actual.clear();
1625         client.getOptions().setCssEnabled(true);
1626         client.getPage(URL_FIRST);
1627         assertEquals(new String[]{"1"}, actual);
1628     }
1629 
1630     /**
1631      * @throws Exception if test fails
1632      */
1633     @Test
1634     public void getPageDataProtocol() throws Exception {
1635         final WebClient webClient = getWebClient();
1636 
1637         final String html = DOCTYPE_HTML + "<html><body>DataUrl Test</body></html>";
1638 
1639         final Page page = webClient.getPage("data:text/html;charset=utf-8," + html);
1640         assertEquals("DataUrl Test", ((HtmlPage) page).asNormalizedText());
1641     }
1642 
1643     /**
1644      * @throws Exception if test fails
1645      */
1646     @Test
1647     public void getPageJavascriptProtocol() throws Exception {
1648         final WebClient webClient = getWebClient();
1649         final MockWebConnection webConnection = new MockWebConnection();
1650         webConnection.setDefaultResponse(DOCTYPE_HTML
1651                 + "<html><head><title>Hello World</title></head><body></body></html>");
1652         webClient.setWebConnection(webConnection);
1653 
1654         final List<String> collectedAlerts = new ArrayList<>();
1655         webClient.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
1656 
1657         Page page = webClient.getPage("javascript:void(alert(document.location))");
1658         assertEquals("about:blank", page.getUrl());
1659         assertEquals(new String[] {"about:blank"}, collectedAlerts);
1660         collectedAlerts.clear();
1661 
1662         page = webClient.getPage(URL_FIRST);
1663         final Page page2 = webClient.getPage("javascript:void(alert(document.title))");
1664         assertSame(page, page2);
1665         assertEquals(new String[] {"Hello World"}, collectedAlerts);
1666 
1667         webClient.getPage("javascript:void(document.body.setAttribute('foo', window.screen.availWidth))");
1668         assertEquals("1920", ((HtmlPage) page).getBody().getAttribute("foo"));
1669     }
1670 
1671     /**
1672      * @throws Exception if test fails
1673      */
1674     @Test
1675     public void getPageJavascriptProtocolTextPage() throws Exception {
1676         final WebClient webClient = getWebClient();
1677         final MockWebConnection webConnection = new MockWebConnection();
1678         webConnection.setDefaultResponse("some text", "plain/text");
1679         webClient.setWebConnection(webConnection);
1680 
1681         final List<String> collectedAlerts = new ArrayList<>();
1682         webClient.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
1683 
1684         Page page = webClient.getPage(URL_FIRST);
1685 
1686         page = webClient.getPage("javascript:void(alert(document.location))");
1687         assertEquals("about:blank", page.getUrl());
1688         assertEquals(new String[] {"about:blank"}, collectedAlerts);
1689         collectedAlerts.clear();
1690     }
1691 
1692     /**
1693      * @throws Exception if the test fails
1694      */
1695     @Test
1696     public void javaScriptTimeout() throws Exception {
1697         final WebClient client = getWebClient();
1698         final long timeout = 2000;
1699         final long oldTimeout = client.getJavaScriptTimeout();
1700         client.setJavaScriptTimeout(timeout);
1701 
1702         try {
1703             client.getOptions().setThrowExceptionOnScriptError(false);
1704 
1705             final String content = DOCTYPE_HTML + "<html><body><script>while(1) {}</script></body></html>";
1706             final MockWebConnection webConnection = new MockWebConnection();
1707             webConnection.setDefaultResponse(content);
1708             client.setWebConnection(webConnection);
1709 
1710             final Exception[] exceptions = {null};
1711             final Thread runner = new Thread() {
1712                 @Override
1713                 public void run() {
1714                     try {
1715                         client.getPage(URL_FIRST);
1716                     }
1717                     catch (final Exception e) {
1718                         exceptions[0] = e;
1719                     }
1720                 }
1721             };
1722 
1723             runner.start();
1724 
1725             runner.join(timeout * 2);
1726             if (runner.isAlive()) {
1727                 runner.interrupt();
1728                 fail("Script was still running after timeout");
1729             }
1730             assertNull(exceptions[0]);
1731         }
1732         finally {
1733             client.setJavaScriptTimeout(oldTimeout);
1734         }
1735     }
1736 
1737     /**
1738      * Protects against the regression detailed in bug 1975445.
1739      * @throws Exception if an error occurs
1740      */
1741     @Test
1742     public void openWindowWithNullUrl() throws Exception {
1743         final WebClient client = getWebClient();
1744         final WebWindow window = client.openWindow(null, "TestingWindow");
1745         assertNotNull(window);
1746     }
1747 
1748     /**
1749      * Basic window tracking testing.
1750      * @throws Exception if an error occurs
1751      */
1752     @Test
1753     public void basicWindowTracking() throws Exception {
1754         // Create mock web connection.
1755         final MockWebConnection conn = new MockWebConnection();
1756         conn.setDefaultResponse(DOCTYPE_HTML + "<html></html");
1757 
1758         // Make sure a new client start with a single window.
1759         final WebClient client = getWebClient();
1760         client.setWebConnection(conn);
1761         assertEquals(1, client.getWebWindows().size());
1762 
1763         // Make sure the initial window is the current window.
1764         final WebWindow window1 = client.getCurrentWindow();
1765         assertSame(window1, client.getCurrentWindow());
1766         assertNotNull(window1);
1767 
1768         // Make sure that we keep track of a new window when we open it.
1769         final WebWindow window2 = client.openWindow(URL_FIRST, "blah");
1770         assertSame(window2, client.getCurrentWindow());
1771         assertEquals(2, client.getWebWindows().size());
1772         assertNotNull(window2);
1773 
1774         // Make sure that we keep track of another new window when we open it.
1775         final WebWindow window3 = client.openWindow(URL_SECOND, "foo");
1776         assertSame(window3, client.getCurrentWindow());
1777         assertEquals(3, client.getWebWindows().size());
1778         assertNotNull(window3);
1779 
1780         // Close the last window, make sure that the second window becomes the current window.
1781         ((TopLevelWindow) window3).close();
1782         assertSame(window2, client.getCurrentWindow());
1783         assertEquals(2, client.getWebWindows().size());
1784 
1785         // Close the first window, make sure that the second window is still the current window.
1786         ((TopLevelWindow) window1).close();
1787         assertSame(window2, client.getCurrentWindow());
1788         assertEquals(1, client.getWebWindows().size());
1789 
1790         // Close the only remaining window, make sure the client still has a current window.
1791         ((TopLevelWindow) window2).close();
1792         assertNotNull(client.getCurrentWindow());
1793         assertNotSame(window1, client.getCurrentWindow());
1794         assertNotSame(window2, client.getCurrentWindow());
1795         assertNotSame(window3, client.getCurrentWindow());
1796         assertEquals(1, client.getWebWindows().size());
1797     }
1798 
1799     /**
1800      * Previous window should become current window after current window is closed in onLoad event.
1801      * @throws Exception if an error occurs
1802      */
1803     @Test
1804     public void windowTracking_SpecialCase1() throws Exception {
1805         final WebClient webClient = getWebClient();
1806         final MockWebConnection conn = new MockWebConnection();
1807 
1808         final String html1 = DOCTYPE_HTML
1809                 + "<html><head><title>First</title></head>\n"
1810                 + "<body><form name='form1'>\n"
1811                 + "<button id='clickme' onClick='window.open(\"" + URL_SECOND + "\");'>Click me</button>\n"
1812                 + "</form></body></html>";
1813         conn.setResponse(URL_FIRST, html1);
1814 
1815         final String html2 = DOCTYPE_HTML
1816                 + "<html><head><title>Second</title></head>\n"
1817                 + "<body onload='doTest()'>\n"
1818                 + "<script>\n"
1819                 + "  function doTest() {\n"
1820                 + "    window.close();\n"
1821                 + "  }\n"
1822                 + "</script></body></html>";
1823         conn.setDefaultResponse(html2);
1824 
1825         webClient.setWebConnection(conn);
1826         final HtmlPage firstPage = webClient.getPage(URL_FIRST);
1827         final HtmlButton buttonA = firstPage.getHtmlElementById("clickme");
1828         buttonA.click();
1829         assertNotNull(webClient.getCurrentWindow().getEnclosedPage());
1830         assertEquals("First", ((HtmlPage) webClient.getCurrentWindow().getEnclosedPage()).getTitleText());
1831     }
1832 
1833     /**
1834      * Previous window should become current window after current window is closed while loading the page.
1835      * @throws Exception if an error occurs
1836      */
1837     @Test
1838     public void windowTracking_SpecialCase2() throws Exception {
1839         final WebClient webClient = getWebClient();
1840         final MockWebConnection conn = new MockWebConnection();
1841 
1842         final String html1 = DOCTYPE_HTML
1843                 + "<html><head><title>First</title></head>\n"
1844                 + "<body><form name='form1'>\n"
1845                 + "<button id='clickme' onClick='window.open(\"" + URL_SECOND + "\");'>Click me</button>\n"
1846                 + "</form></body></html>";
1847         conn.setResponse(URL_FIRST, html1);
1848 
1849         final String html2 = DOCTYPE_HTML
1850                 + "<html><head><title>Third</title>\n"
1851                 + "<script type=\"text/javascript\">\n"
1852                 + "     window.close();\n"
1853                 + "</script></head></html>";
1854         conn.setDefaultResponse(html2);
1855 
1856         webClient.setWebConnection(conn);
1857         final HtmlPage firstPage = webClient.getPage(URL_FIRST);
1858         final HtmlButton buttonA = firstPage.getHtmlElementById("clickme");
1859         buttonA.click();
1860         assertNotNull(webClient.getCurrentWindow().getEnclosedPage());
1861         assertEquals("First", ((HtmlPage) webClient.getCurrentWindow().getEnclosedPage()).getTitleText());
1862     }
1863 
1864     /**
1865      * Previous window should become current window after current window is closed.
1866      * @throws Exception if an error occurs
1867      */
1868     @Test
1869     @Alerts({})
1870     public void windowTracking_SpecialCase3() throws Exception {
1871         final WebClient webClient = getWebClient();
1872         final MockWebConnection conn = new MockWebConnection();
1873         final List<String> collectedAlerts = new ArrayList<>();
1874         webClient.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
1875 
1876         final String html1 = DOCTYPE_HTML
1877                 + "<html><head><title>First</title></head>\n"
1878                 + "<body>\n"
1879                 + "<button id='clickme' onClick='window.open(\"" + URL_SECOND + "\");'>Click me</button>\n"
1880                 + "</body></html>";
1881         conn.setResponse(URL_FIRST, html1);
1882 
1883         final String html2 = DOCTYPE_HTML
1884                 + "<html><head><title>Second</title></head>\n"
1885                 + "<body onUnload='doTest()'>\n"
1886                 + "<form name='form1' action='" + URL_THIRD + "'>\n"
1887                 + "<button id='clickme' type='button' onclick='postBack();'>Submit</button></form>\n"
1888                 + "<script>\n"
1889                 + "    function doTest() {\n"
1890                 + "      window.close();\n"
1891                 + "    }\n"
1892                 + "    function postBack() {\n"
1893                 + "      var frm = document.forms[0];\n"
1894                 + "      frm.submit();\n"
1895                 + "    }\n"
1896                 + "</script></body></html>";
1897         conn.setResponse(URL_SECOND, html2);
1898 
1899         final String html3 = DOCTYPE_HTML
1900                 + "<html><head><title>Third</title>\n"
1901                 + "<script type=\"text/javascript\">\n"
1902                 + "     alert('Third page loaded');\n"
1903                 + "     window.close();\n"
1904                 + "</script></head></html>";
1905         conn.setResponse(URL_THIRD, html3);
1906         conn.setDefaultResponse(html3);
1907 
1908         webClient.setWebConnection(conn);
1909         final HtmlPage firstPage = webClient.getPage(URL_FIRST);
1910 
1911         final HtmlButton buttonA = firstPage.getHtmlElementById("clickme");
1912         buttonA.click();
1913         final HtmlPage secondPage = (HtmlPage) webClient.getCurrentWindow().getEnclosedPage();
1914         assertEquals("Second", secondPage.getTitleText());
1915 
1916         final HtmlButton buttonB = secondPage.getHtmlElementById("clickme");
1917         buttonB.click();
1918         assertEquals("First", ((HtmlPage) webClient.getCurrentWindow().getEnclosedPage()).getTitleText());
1919         assertEquals(getExpectedAlerts(), collectedAlerts);
1920     }
1921 
1922     /**
1923      * Bug 2890847: Triggering the creation of an empty frame based on some user action should not
1924      * make the empty frame the current window.
1925      * @throws Exception if an error occurs
1926      */
1927     @Test
1928     public void windowTracking_SpecialCase4() throws Exception {
1929         final WebClient client = getWebClient();
1930         final MockWebConnection conn = new MockWebConnection();
1931         client.setWebConnection(conn);
1932 
1933         final String html = DOCTYPE_HTML
1934                 + "<html><head><title>Test</title></head><body>\n"
1935                 + "<div id='d' onclick='this.innerHTML+=\"<iframe></iframe>\";'>go</div></body></html>";
1936         conn.setResponse(URL_FIRST, html);
1937 
1938         final HtmlPage page = client.getPage(URL_FIRST);
1939         page.getHtmlElementById("d").click();
1940         assertEquals("Test", ((HtmlPage) client.getCurrentWindow().getEnclosedPage()).getTitleText());
1941     }
1942 
1943     /**
1944      * @throws Exception if an error occurs
1945      */
1946     @Test
1947     public void openWindowWithAboutBlank() throws Exception {
1948         final WebClient client = getWebClient();
1949         final WebWindow window = client.openWindow(UrlUtils.URL_ABOUT_BLANK, "TestingWindow");
1950         assertNotNull(window);
1951     }
1952 
1953     /**
1954      * @throws Exception if an error occurs
1955      */
1956     @Test
1957     public void openWindowFromTextContent() throws Exception {
1958         final WebClient client = getWebClient();
1959         final MockWebConnection webConnection = new MockWebConnection();
1960         client.setWebConnection(webConnection);
1961         webConnection.setResponse(URL_FIRST, "some text", MimeType.TEXT_PLAIN);
1962 
1963         client.getPage(URL_FIRST);
1964         final WebWindow window = client.openWindow(URL_FIRST, "TestingWindow");
1965         assertNotNull(window);
1966         assertTrue(window.getEnclosedPage().toString(), window.getEnclosedPage() instanceof TextPage);
1967     }
1968 
1969     /**
1970      * @throws Exception if an error occurs
1971      */
1972     @Test
1973     public void cssErrorHandler() throws Exception {
1974         final WebClient client = getWebClient();
1975         assertTrue(client.getCssErrorHandler() instanceof DefaultCssErrorHandler);
1976 
1977         final MutableInt fatals = new MutableInt();
1978         final MutableInt errors = new MutableInt();
1979         final MutableInt warnings = new MutableInt();
1980         final StringBuilder errorUri = new StringBuilder();
1981         final CSSErrorHandler handler = new CSSErrorHandler() {
1982             @Override
1983             public void warning(final CSSParseException exception) throws CSSException {
1984                 warnings.increment();
1985             }
1986             @Override
1987             public void fatalError(final CSSParseException exception) throws CSSException {
1988                 fatals.increment();
1989             }
1990             @Override
1991             public void error(final CSSParseException exception) throws CSSException {
1992                 errors.increment();
1993                 errorUri.append(exception.getURI());
1994             }
1995         };
1996         client.setCssErrorHandler(handler);
1997         assertEquals(handler, client.getCssErrorHandler());
1998 
1999         final MockWebConnection conn = new MockWebConnection();
2000         conn.setResponse(URL_FIRST, DOCTYPE_HTML + "<html><body><style></style></body></html>");
2001         conn.setResponse(URL_SECOND, DOCTYPE_HTML + "<html><body><style>.x{color:red;}</style></body></html>");
2002         conn.setResponse(URL_THIRD, DOCTYPE_HTML + "<html><body><style>.x{color{}}}</style></body></html>");
2003         client.setWebConnection(conn);
2004 
2005         final HtmlPage page1 = client.getPage(URL_FIRST);
2006         ((HTMLStyleElement) page1.getBody().getFirstChild().getScriptableObject()).getSheet();
2007         assertEquals(0, warnings.intValue());
2008         assertEquals(0, errors.intValue());
2009         assertEquals(0, fatals.intValue());
2010 
2011         final HtmlPage page2 = client.getPage(URL_SECOND);
2012         ((HTMLStyleElement) page2.getBody().getFirstChild().getScriptableObject()).getSheet();
2013         assertEquals(0, warnings.intValue());
2014         assertEquals(0, errors.intValue());
2015         assertEquals(0, fatals.intValue());
2016 
2017         final HtmlPage page3 = client.getPage(URL_THIRD);
2018         ((HTMLStyleElement) page3.getBody().getFirstChild().getScriptableObject()).getSheet();
2019         assertEquals(1, warnings.intValue());
2020         assertEquals(2, errors.intValue());
2021         assertEquals(0, fatals.intValue());
2022         assertEquals("http://127.0.0.1:" + PORT + "/third/http://127.0.0.1:" + PORT + "/third/", errorUri.toString());
2023     }
2024 
2025     /**
2026      * Tests that the JavaScript parent scope is set correctly when shuffling windows around.
2027      * @throws Exception if test fails
2028      */
2029     @Test
2030     public void maintainJavaScriptParentScope() throws Exception {
2031         final String basicContent = DOCTYPE_HTML
2032                 + "<html><head>\n"
2033                 + "<title>basicContentTitle</title>\n"
2034                 + "</head><body>\n"
2035                 + "<p>Hello World</p>\n"
2036                 + "</body></html>";
2037 
2038         final String jsContent = DOCTYPE_HTML
2039                 + "<html><head>\n"
2040                 + "<title>jsContentTitle</title>\n"
2041                 + "<script>function foo() {alert('Ran Here')}</script>\n"
2042                 + "<script>function bar() {}</script>\n"
2043                 + "</head><body onload='bar()'>\n"
2044                 + "<input type='button' id='button' onclick='foo()'/>"
2045                 + "</body></html>";
2046 
2047         final HtmlPage jsPage = loadPage(jsContent);
2048         final WebClient webClient = jsPage.getWebClient();
2049         final WebWindow firstWindow = webClient.getCurrentWindow();
2050         getMockConnection(jsPage).setResponse(URL_SECOND, basicContent);
2051 
2052         final CollectingAlertHandler alertHandler = new CollectingAlertHandler();
2053         webClient.setAlertHandler(alertHandler);
2054 
2055         final HtmlButtonInput buttonBefore = jsPage.getHtmlElementById("button");
2056 
2057         final WebWindow secondWindow = webClient.openWindow(null, "second window");
2058         webClient.setCurrentWindow(secondWindow);
2059         webClient.getPage(URL_SECOND);
2060 
2061         webClient.setCurrentWindow(firstWindow);
2062 
2063         final HtmlPage currentPage = (HtmlPage) webClient.getCurrentWindow().getEnclosedPage();
2064         final HtmlButtonInput buttonAfter = currentPage.getHtmlElementById("button");
2065         assertSame(buttonBefore, buttonAfter);
2066 
2067         buttonAfter.click();
2068 
2069         assertEquals(1, alertHandler.getCollectedAlerts().size());
2070         assertEquals("Ran Here", alertHandler.getCollectedAlerts().get(0));
2071     }
2072 
2073     /**
2074      * @throws Exception if test fails
2075      */
2076     @Test
2077     public void currentWindow() throws Exception {
2078         final WebClient client = getWebClient();
2079 
2080         final MockWebConnection conn = new MockWebConnection();
2081         final String html = DOCTYPE_HTML
2082                 + "<html><body onload='document.getElementById(\"f\").src=\"frame.html\";'>\n"
2083                 + "<iframe id='f'></iframe></body></html>";
2084         conn.setResponse(URL_FIRST, html);
2085         final URL frameUrl = new URL(URL_FIRST, "frame.html");
2086         conn.setResponse(frameUrl, DOCTYPE_HTML + "<html><body></body></html>");
2087         conn.setResponse(URL_SECOND, DOCTYPE_HTML + "<html><body></body></html>");
2088         client.setWebConnection(conn);
2089 
2090         client.getPage(URL_FIRST);
2091         assertEquals(2, client.getWebWindows().size());
2092         assertEquals(frameUrl,
2093                 client.getCurrentWindow().getEnclosedPage().getUrl());
2094 
2095         // loading a new page should be done in the top window
2096         client.getPage(URL_SECOND);
2097         assertTrue(client.getCurrentWindow() instanceof TopLevelWindow);
2098         assertEquals(1, client.getWebWindows().size());
2099     }
2100 
2101     /**
2102      * @throws Exception if test fails
2103      */
2104     @Test
2105     public void currentWindow2() throws Exception {
2106         final String html = DOCTYPE_HTML
2107                 + "<html><head><script>\n"
2108                 + "function createFrame() {\n"
2109                 + "  var f = document.createElement('iframe');\n"
2110                 + "  f.setAttribute('style', 'width: 0pt; height: 0pt');\n"
2111                 + "  document.body.appendChild(f);\n"
2112                 + "  f.src = \"javascript:''\";\n"
2113                 + "}\n"
2114                 + "</script></head>\n"
2115                 + "<body onload='setTimeout(createFrame, 10)'></body></html>";
2116 
2117         final HtmlPage page = loadPage(html);
2118         assertTrue(page.getEnclosingWindow() instanceof TopLevelWindow);
2119         page.getWebClient().waitForBackgroundJavaScriptStartingBefore(1000);
2120 
2121         assertSame(page.getEnclosingWindow(), page.getWebClient().getCurrentWindow());
2122     }
2123 
2124     /**
2125      * @throws Exception if an error occurs
2126      */
2127     @Test
2128     public void getTopLevelWindows() throws Exception {
2129         @SuppressWarnings("resource")
2130         final WebClient client = getWebClient();
2131         final MockWebConnection conn = new MockWebConnection();
2132         conn.setResponse(URL_FIRST, DOCTYPE_HTML + "<html><body><iframe></iframe></body></html>");
2133         conn.setResponse(URL_SECOND, DOCTYPE_HTML + "<html><body></body></html>");
2134         client.setWebConnection(conn);
2135 
2136         final WebWindow firstWindow = client.getWebWindows().get(0);
2137 
2138         assertEquals(1, client.getWebWindows().size());
2139         assertEquals(1, client.getTopLevelWindows().size());
2140         assertEquals(client.getCurrentWindow(), client.getWebWindows().get(0));
2141         assertEquals(client.getCurrentWindow(), client.getTopLevelWindows().get(0));
2142         assertEquals(firstWindow, client.getWebWindows().get(0));
2143         assertEquals(firstWindow, client.getTopLevelWindows().get(0));
2144 
2145         client.getPage(URL_FIRST);
2146 
2147         assertEquals(2, client.getWebWindows().size());
2148         assertEquals(1, client.getTopLevelWindows().size());
2149         assertEquals(client.getCurrentWindow(), client.getWebWindows().get(0));
2150         assertEquals(client.getCurrentWindow(), client.getTopLevelWindows().get(0));
2151         assertEquals(firstWindow, client.getWebWindows().get(0));
2152         assertEquals(firstWindow, client.getTopLevelWindows().get(0));
2153 
2154         client.getPage(URL_SECOND);
2155 
2156         assertEquals(1, client.getWebWindows().size());
2157         assertEquals(1, client.getTopLevelWindows().size());
2158         assertEquals(client.getCurrentWindow(), client.getWebWindows().get(0));
2159         assertEquals(client.getCurrentWindow(), client.getTopLevelWindows().get(0));
2160         assertEquals(firstWindow, client.getWebWindows().get(0));
2161         assertEquals(firstWindow, client.getTopLevelWindows().get(0));
2162 
2163         client.openWindow(URL_SECOND, "a");
2164 
2165         assertEquals(2, client.getWebWindows().size());
2166         assertEquals(2, client.getTopLevelWindows().size());
2167         assertEquals(client.getCurrentWindow(), client.getWebWindows().get(1));
2168         assertEquals(client.getCurrentWindow(), client.getTopLevelWindows().get(1));
2169         assertEquals(client.getWebWindows().get(1), client.getTopLevelWindows().get(1));
2170         assertEquals(firstWindow, client.getWebWindows().get(0));
2171         assertEquals(firstWindow, client.getTopLevelWindows().get(0));
2172         assertNotEquals(firstWindow, client.getWebWindows().get(1));
2173         assertNotEquals(firstWindow, client.getTopLevelWindows().get(1));
2174 
2175         client.openWindow(URL_SECOND, "b");
2176 
2177         assertEquals(3, client.getWebWindows().size());
2178         assertEquals(3, client.getTopLevelWindows().size());
2179         assertEquals(client.getCurrentWindow(), client.getWebWindows().get(2));
2180         assertEquals(client.getCurrentWindow(), client.getTopLevelWindows().get(2));
2181         assertEquals(firstWindow, client.getWebWindows().get(0));
2182         assertEquals(firstWindow, client.getTopLevelWindows().get(0));
2183         assertEquals(client.getWebWindows().get(1), client.getTopLevelWindows().get(1));
2184         assertNotEquals(firstWindow, client.getWebWindows().get(1));
2185         assertNotEquals(firstWindow, client.getTopLevelWindows().get(1));
2186         assertEquals(client.getWebWindows().get(2), client.getTopLevelWindows().get(2));
2187         assertNotEquals(firstWindow, client.getWebWindows().get(2));
2188         assertNotEquals(firstWindow, client.getTopLevelWindows().get(2));
2189 
2190         client.close();
2191 
2192         assertEquals(0, client.getWebWindows().size());
2193         assertEquals(0, client.getTopLevelWindows().size());
2194         assertNull(client.getCurrentWindow());
2195     }
2196 
2197     /**
2198      * Test that the result of getTopLevelWindows() is usable without
2199      * getting a ConcurrentModificationException.
2200      *
2201      * @throws Exception if an error occurs
2202      */
2203     @Test
2204     public void getTopLevelWindowsJSConcurrency() throws Exception {
2205         final String html = DOCTYPE_HTML
2206                 + "<html><head><title>Toplevel</title></head>\n<body>\n"
2207                 + "<script>\n"
2208                 + "  setInterval(function() {\n"
2209                 + "    window.open('');\n"
2210                 + "  }, 10);\n"
2211                 + "</script>\n"
2212                 + "</body></html>\n";
2213 
2214         final WebClient client = getWebClientWithMockWebConnection();
2215         getMockWebConnection().setResponse(URL_FIRST, html);
2216 
2217         client.getPage(URL_FIRST);
2218         final List<TopLevelWindow> windows = client.getTopLevelWindows();
2219         for (int i = 0; i < 100; i++) {
2220             for (final TopLevelWindow window : windows) {
2221                 Thread.sleep(13);
2222                 window.getName();
2223             }
2224         }
2225     }
2226 
2227     /**
2228      * Regression test for Bug #861.
2229      *
2230      * @throws Exception if something goes wrong
2231      */
2232     @Test
2233     public void urlWithDirectoryUp() throws Exception {
2234         final URL url = new URL("http://htmlunit.sf.net/foo.html");
2235         final URL urlWithDirectoryUp = new URL("http://htmlunit.sf.net/bla/../foo.html");
2236 
2237         final WebClient client = getWebClient();
2238         final MockWebConnection webConnection = new MockWebConnection();
2239         webConnection.setResponse(url, "");
2240         client.setWebConnection(webConnection);
2241 
2242         final Page page = client.getPage(urlWithDirectoryUp);
2243         assertEquals(url, page.getUrl());
2244     }
2245 
2246     /**
2247      * Test that close() stops all threads. This wasn't the case as
2248      * of HtmlUnit-2.7-SNAPSHOT 11.12.2009.
2249      * @throws Exception if test fails
2250      */
2251     @Test
2252     public void close() throws Exception {
2253         final String html = DOCTYPE_HTML
2254                 + "<html><head></head>\n"
2255                 + "<body onload='setInterval(addFrame, 1)'>\n"
2256                 + "<iframe src='second.html'></iframe>\n"
2257                 + "<script>\n"
2258                 + "function addFrame() {\n"
2259                 + "  var f = document.createElement('iframe');\n"
2260                 + "  f.src = 'second.html';\n"
2261                 + "  document.body.appendChild(f);\n"
2262                 + "}\n"
2263                 + "</script>\n"
2264                 + "</body></html>";
2265 
2266         final String html2 = DOCTYPE_HTML
2267                 + "<html><head><script>\n"
2268                 + "function doSomething() {}\n"
2269                 + "setInterval(doSomething, 100);\n"
2270                 + "</script>\n"
2271                 + "</head><body></body></html>";
2272 
2273         getMockWebConnection().setResponse(URL_FIRST, html);
2274         getMockWebConnection().setDefaultResponse(html2);
2275 
2276         @SuppressWarnings("resource")
2277         final WebClient webClient = getWebClient();
2278         final int initialJSThreads = getJavaScriptThreads().size();
2279         webClient.setWebConnection(getMockWebConnection());
2280         webClient.getPage(URL_FIRST);
2281 
2282         int nbJSThreads = getJavaScriptThreads().size();
2283         final int nbNewJSThreads = nbJSThreads - initialJSThreads;
2284         assertTrue(nbNewJSThreads + " threads", nbNewJSThreads > 0);
2285 
2286         // close and verify that the WebClient is clean
2287         webClient.close();
2288 
2289         assertEquals(0, webClient.getWebWindows().size());
2290         assertEquals(0, webClient.getTopLevelWindows().size());
2291         assertNull(webClient.getCurrentWindow());
2292 
2293         nbJSThreads = getJavaScriptThreads().size();
2294         assertEquals(initialJSThreads, nbJSThreads);
2295     }
2296 
2297     /**
2298      * Tests that setThrowExceptionOnScriptError also works,
2299      * if an exception is thrown from onerror handler.
2300      * Regression test for bug 3534371.
2301      *
2302      * @throws Exception if the test fails
2303      */
2304     @Test
2305     public void test() throws Exception {
2306         final String html = DOCTYPE_HTML
2307                 + "<html><body>\n"
2308                 + "<script type='application/javascript'>\n"
2309                 + "  window.onerror = function() { foo.bar() };\n"
2310                 + "  doit();\n"
2311                 + "</script>\n"
2312                 + "</body></html>";
2313 
2314         final WebClient webClient = getWebClient();
2315         webClient.getOptions().setJavaScriptEnabled(true);
2316         webClient.getOptions().setThrowExceptionOnScriptError(false);
2317 
2318         loadPage(html);
2319     }
2320 
2321     /**
2322      * Testcase for issue #1652.
2323      * @throws Exception if the test fails
2324      */
2325     @Test
2326     public void aboutBlankSharedRequest() throws Exception {
2327         final WebClient webClient = getWebClient();
2328 
2329         final WebWindow firstWindow = webClient.openWindow(UrlUtils.URL_ABOUT_BLANK, "Window 1");
2330         assertNotNull(firstWindow);
2331 
2332         final WebRequest firstRequest1 = firstWindow.getEnclosedPage().getWebResponse().getWebRequest();
2333         assertEquals("about:blank", firstRequest1.getUrl().toExternalForm());
2334         firstRequest1.setUrl(UrlUtils.toUrlSafe(UrlUtils.ABOUT_BLANK + "#anchor"));
2335 
2336         final WebWindow secondWindow = webClient.openWindow(UrlUtils.URL_ABOUT_BLANK, "Window 2");
2337         assertNotNull(secondWindow);
2338         final WebRequest secondRequest = secondWindow.getEnclosedPage().getWebResponse().getWebRequest();
2339         assertEquals("about:blank", secondRequest.getUrl().toExternalForm());
2340     }
2341 
2342     /**
2343      * @throws Exception if the test fails
2344      */
2345     @Test
2346     public void closeToClearCache() throws Exception {
2347         final CacheMock cache = new CacheMock();
2348         try (WebClient webClient = getWebClient()) {
2349             webClient.setCache(cache);
2350         }
2351 
2352         assertEquals(1, cache.getClearCallCount());
2353     }
2354 
2355     private static final class CacheMock extends Cache {
2356         private int clearCallCount_;
2357 
2358         @Override
2359         public void clear() {
2360             clearCallCount_++;
2361             super.clear();
2362         }
2363 
2364         public int getClearCallCount() {
2365             return clearCallCount_;
2366         }
2367     }
2368 
2369 
2370     /**
2371      * @throws Exception if an error occurs
2372      */
2373     @Test
2374     public void webSocketDisabled() throws Exception {
2375         final WebClient client = getWebClient();
2376         final MockWebConnection conn = new MockWebConnection();
2377         client.setWebConnection(conn);
2378 
2379         final String html = DOCTYPE_HTML
2380                     + "<html>\n"
2381                     + "  <head>\n"
2382                     + "    <script>alert('WebSocket' in window);</script>\n"
2383                     + "  </head>\n"
2384                     + "  <body>\n"
2385                     + "  </body>\n"
2386                     + "</html>";
2387         conn.setResponse(URL_FIRST, html);
2388 
2389         final List<String> actual = new ArrayList<>();
2390         client.setAlertHandler(new CollectingAlertHandler(actual));
2391 
2392         client.getPage(URL_FIRST);
2393         assertEquals(new String[]{"true"}, actual);
2394 
2395         actual.clear();
2396         client.getOptions().setWebSocketEnabled(false);
2397         client.getPage(URL_FIRST);
2398         assertEquals(new String[]{"false"}, actual);
2399 
2400         actual.clear();
2401         client.getOptions().setWebSocketEnabled(true);
2402         client.getPage(URL_FIRST);
2403         assertEquals(new String[]{"true"}, actual);
2404     }
2405 
2406     /**
2407      * @throws Exception if an error occurs
2408      */
2409     @Test
2410     public void loadHtmlCodeIntoCurrentWindow() throws Exception {
2411         final String htmlCode = DOCTYPE_HTML
2412                 + "<html>"
2413                 + "  <head>"
2414                 + "    <title>Title</title>"
2415                 + "  </head>"
2416                 + "  <body>"
2417                 + "    content..."
2418                 + "  </body>"
2419                 + "</html> ";
2420 
2421         final WebClient client = getWebClient();
2422         final HtmlPage page = client.loadHtmlCodeIntoCurrentWindow(htmlCode);
2423         assertEquals("content...", page.getBody().asNormalizedText());
2424     }
2425 
2426     /**
2427      * @throws Exception if an error occurs
2428      */
2429     @Test
2430     public void loadXHtmlCodeIntoCurrentWindow() throws Exception {
2431         final String htmlCode = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\""
2432                 + "\"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">"
2433                 + "<html xmlns=\"http://www.w3.org/1999/xhtml\">"
2434                 + "  <head>"
2435                 + "    <title>Title</title>"
2436                 + "  </head>"
2437                 + "  <body>"
2438                 + "    content..."
2439                 + "  </body>"
2440                 + "</html> ";
2441 
2442         final WebClient client = getWebClient();
2443         final HtmlPage page = client.loadXHtmlCodeIntoCurrentWindow(htmlCode);
2444         assertEquals("content...", page.getBody().asNormalizedText());
2445     }
2446 
2447     /**
2448      * @throws Exception if test fails
2449      */
2450     @Test
2451     public void reset() throws Exception {
2452         final String html = DOCTYPE_HTML
2453                 + "<html><head><title>testpage</title></head>\n"
2454                 + "<body>\n"
2455                 + "</body></html>";
2456 
2457         final String html2 = DOCTYPE_HTML
2458                 + "<html><head><title>testpage</title></head>\n"
2459                 + "<body>\n"
2460                 + "<script>document.title = 'js'</script>\n"
2461                 + "</body></html>";
2462 
2463         getMockWebConnection().setResponse(URL_FIRST, html);
2464 
2465         @SuppressWarnings("resource")
2466         final WebClient webClient = getWebClientWithMockWebConnection();
2467 
2468         assertEquals(1, webClient.getWebWindows().size());
2469         assertEquals(1, webClient.getTopLevelWindows().size());
2470         assertNotNull(webClient.getCurrentWindow());
2471         assertNull(webClient.getCurrentWindow().getEnclosedPage());
2472 
2473         webClient.getPage(URL_FIRST);
2474 
2475         assertEquals(1, webClient.getWebWindows().size());
2476         assertEquals(1, webClient.getTopLevelWindows().size());
2477         assertNotNull(webClient.getCurrentWindow());
2478         assertEquals("testpage", ((HtmlPage) webClient.getCurrentWindow().getEnclosedPage()).getTitleText());
2479 
2480         webClient.reset();
2481 
2482         getMockWebConnection().setResponse(URL_FIRST, html);
2483         webClient.setWebConnection(getMockWebConnection());
2484 
2485         assertEquals(1, webClient.getWebWindows().size());
2486         assertEquals(1, webClient.getTopLevelWindows().size());
2487         assertNotNull(webClient.getCurrentWindow());
2488         assertNull(webClient.getCurrentWindow().getEnclosedPage());
2489 
2490         webClient.getPage(URL_FIRST);
2491 
2492         assertEquals(1, webClient.getWebWindows().size());
2493         assertEquals(1, webClient.getTopLevelWindows().size());
2494         assertNotNull(webClient.getCurrentWindow());
2495         assertEquals("testpage", ((HtmlPage) webClient.getCurrentWindow().getEnclosedPage()).getTitleText());
2496 
2497         webClient.reset();
2498         getMockWebConnection().setResponse(URL_FIRST, html2);
2499         webClient.setWebConnection(getMockWebConnection());
2500 
2501         assertEquals(1, webClient.getWebWindows().size());
2502         assertEquals(1, webClient.getTopLevelWindows().size());
2503         assertNotNull(webClient.getCurrentWindow());
2504         assertNull(webClient.getCurrentWindow().getEnclosedPage());
2505 
2506         webClient.getPage(URL_FIRST);
2507 
2508         assertEquals(1, webClient.getWebWindows().size());
2509         assertEquals(1, webClient.getTopLevelWindows().size());
2510         assertNotNull(webClient.getCurrentWindow());
2511         assertEquals("js", ((HtmlPage) webClient.getCurrentWindow().getEnclosedPage()).getTitleText());
2512     }
2513 
2514     /**
2515      * @exception Exception If the test fails
2516      */
2517     @Test
2518     public void loginFlowClickSubmitRedirect() throws Exception {
2519         final String startPage = DOCTYPE_HTML
2520                 + "<html><title>Start page</title>"
2521                 + "<form action='submit.html' method='post'>"
2522                 + "  <input type='submit' name='mysubmit' id='mySubmit'>"
2523                 + "</form>"
2524                 + "<a href='submit.html' id='myAnchor'>Tester</a>\n"
2525                 + "</body></html>";
2526 
2527         int reqCount = getMockWebConnection().getRequestCount();
2528 
2529         final String submitPage = DOCTYPE_HTML
2530                 + "<html><title>Submit page</title>"
2531                 + "<body onload='document.forms[0].submit()'>"
2532                 + "</body>"
2533                 + "<form action='redirect.html' method='post'>"
2534                 + "</form>"
2535                 + "</html>";
2536         final URL urlSubmitPage = new URL(URL_FIRST, "submit.html");
2537         getMockWebConnection().setResponse(urlSubmitPage, submitPage);
2538 
2539         final List<NameValuePair> headers = new ArrayList<>();
2540         headers.add(new NameValuePair("Location", "/landing.html"));
2541         final URL urlRedirectPage = new URL(URL_FIRST, "redirect.html");
2542         getMockWebConnection().setResponse(urlRedirectPage, "", 302, "Found", null, headers);
2543 
2544         final String landingPage = DOCTYPE_HTML
2545                 + "<html><title>Landing page</title>"
2546                 + "<body></html>";
2547         final URL urlLandingPage = new URL(URL_FIRST, "landing.html");
2548         getMockWebConnection().setResponse(urlLandingPage, landingPage);
2549 
2550         // test by clicking the submit button
2551         HtmlPage page = loadPage(startPage);
2552         assertEquals("Start page", page.getTitleText());
2553 
2554         HtmlPage resultPage = page.getElementById("mySubmit").click();
2555         assertEquals("Landing page", resultPage.getTitleText());
2556 
2557         assertEquals(reqCount + 4, getMockWebConnection().getRequestCount());
2558         assertEquals(urlLandingPage.toExternalForm(), getMockWebConnection().getLastWebRequest().getUrl().toString());
2559 
2560         // test by clicking the anchor
2561         reqCount = getMockWebConnection().getRequestCount();
2562         page = loadPage(startPage);
2563         assertEquals("Start page", page.getTitleText());
2564 
2565         resultPage = page.getElementById("myAnchor").click();
2566         assertEquals("Landing page", resultPage.getTitleText());
2567 
2568         assertEquals(reqCount + 4, getMockWebConnection().getRequestCount());
2569         assertEquals(urlLandingPage.toExternalForm(), getMockWebConnection().getLastWebRequest().getUrl().toString());
2570     }
2571 
2572     /**
2573      * Test for https://github.com/HtmlUnit/htmlunit/issues/701.
2574      * @throws Exception if test fails
2575      */
2576     @Test
2577     public void getPageInSeparateThread() throws Exception {
2578         final String html = DOCTYPE_HTML
2579                 + "<html><head><title>testpage</title></head>\n"
2580                 + "<body>\n"
2581                 + "</body></html>";
2582 
2583         getMockWebConnection().setResponse(URL_FIRST, html);
2584 
2585         try (WebClient webClient = getWebClientWithMockWebConnection()) {
2586             final TestThread testThread = new TestThread(webClient);
2587             testThread.start();
2588 
2589             testThread.join();
2590         }
2591     }
2592 
2593     private static final class TestThread extends Thread {
2594         private final WebClient webClient_;
2595 
2596         private TestThread(final WebClient webClient) {
2597             webClient_ = webClient;
2598         }
2599 
2600         @Override
2601         public void run() {
2602             try {
2603                 final HtmlPage page = webClient_.getPage(URL_FIRST);
2604                 assertEquals("testpage", page.getTitleText());
2605             }
2606             catch (FailingHttpStatusCodeException | IOException e) {
2607                 throw new RuntimeException(e);
2608             }
2609         }
2610     }
2611 }