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