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.html;
16  
17  import static java.nio.charset.StandardCharsets.ISO_8859_1;
18  import static java.nio.charset.StandardCharsets.UTF_8;
19  
20  import java.io.IOException;
21  import java.net.MalformedURLException;
22  import java.net.URL;
23  import java.nio.charset.StandardCharsets;
24  import java.util.ArrayList;
25  import java.util.Arrays;
26  import java.util.Collections;
27  import java.util.Iterator;
28  import java.util.LinkedList;
29  import java.util.List;
30  import java.util.Set;
31  
32  import javax.xml.parsers.DocumentBuilder;
33  import javax.xml.parsers.DocumentBuilderFactory;
34  
35  import org.apache.commons.io.IOUtils;
36  import org.apache.commons.lang3.SerializationUtils;
37  import org.htmlunit.CollectingAlertHandler;
38  import org.htmlunit.ElementNotFoundException;
39  import org.htmlunit.HttpMethod;
40  import org.htmlunit.ImmediateRefreshHandler;
41  import org.htmlunit.IncorrectnessListener;
42  import org.htmlunit.MockWebConnection;
43  import org.htmlunit.OnbeforeunloadHandler;
44  import org.htmlunit.Page;
45  import org.htmlunit.SimpleWebTestCase;
46  import org.htmlunit.StringWebResponse;
47  import org.htmlunit.WebClient;
48  import org.htmlunit.WebRequest;
49  import org.htmlunit.WebResponse;
50  import org.htmlunit.html.HtmlElementTest.HtmlAttributeChangeListenerTestImpl;
51  import org.htmlunit.junit.annotation.Alerts;
52  import org.htmlunit.util.Cookie;
53  import org.htmlunit.util.MimeType;
54  import org.htmlunit.util.NameValuePair;
55  import org.htmlunit.util.StringUtils;
56  import org.junit.jupiter.api.Assertions;
57  import org.junit.jupiter.api.Test;
58  import org.w3c.dom.NodeList;
59  
60  /**
61   * Tests for {@link HtmlPage}.
62   *
63   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
64   * @author Noboru Sinohara
65   * @author David K. Taylor
66   * @author Andreas Hangler
67   * @author <a href="mailto:cse@dynabean.de">Christian Sell</a>
68   * @author Marc Guillemot
69   * @author Ahmed Ashour
70   * @author Frank Danek
71   * @author Ronald Brill
72   */
73  public class HtmlPageTest extends SimpleWebTestCase {
74  
75      /** The doctype prefix for standards mode. */
76      public static final String STANDARDS_MODE_PREFIX_
77          = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n";
78  
79      /**
80       * @throws Exception if the test fails
81       */
82      @Test
83      public void formSubmit() throws Exception {
84          final String htmlContent = DOCTYPE_HTML
85              + "<html>\n"
86              + "<head><title>foo</title></head>\n"
87              + "<body>\n"
88              + "<p>hello world</p>\n"
89              + "<form id='form1' action='/formSubmit' method='PoSt'>\n"
90              + "<input type='text' NAME='textInput1' value='textInput1'/>\n"
91              + "<input type='text' name='textInput2' value='textInput2'/>\n"
92              + "<input type='hidden' name='hidden1' value='hidden1'/>\n"
93              + "<input type='submit' name='submitInput1' value='push me'/>\n"
94              + "</form>\n"
95              + "</body></html>";
96          final HtmlPage page = loadPage(htmlContent);
97          final MockWebConnection webConnection = getMockConnection(page);
98  
99          final HtmlForm form = page.getHtmlElementById("form1");
100         final HtmlInput textInput = form.getInputByName("textInput1");
101         textInput.setValue("foo");
102 
103         final HtmlSubmitInput button = form.getInputByName("submitInput1");
104         final HtmlPage secondPage = button.click();
105 
106         final List<NameValuePair> expectedParameters = new ArrayList<>();
107         expectedParameters.add(new NameValuePair("textInput1", "foo"));
108         expectedParameters.add(new NameValuePair("textInput2", "textInput2"));
109         expectedParameters.add(new NameValuePair("hidden1", "hidden1"));
110         expectedParameters.add(new NameValuePair("submitInput1", "push me"));
111 
112         final URL expectedUrl = new URL(URL_FIRST, "formSubmit");
113         final URL actualUrl = secondPage.getUrl();
114         assertEquals("url", expectedUrl, actualUrl);
115         assertSame("method", HttpMethod.POST, webConnection.getLastMethod());
116         assertEquals("parameters", expectedParameters, webConnection.getLastParameters());
117         assertNotNull(secondPage);
118     }
119 
120     /**
121      * @throws Exception if the test fails
122      */
123     @Test
124     public void getElementByIdNull() throws Exception {
125         final String htmlContent = DOCTYPE_HTML
126             + "<html>\n"
127             + "<head><title>foo</title></head>\n"
128             + "<body>\n"
129             + "<p>hello world</p>\n"
130             + "</body></html>";
131 
132         final HtmlPage page = loadPage(htmlContent);
133 
134         assertNull(page.getElementById(null));
135     }
136 
137     /**
138      * @throws Exception if the test fails
139      */
140     @Test
141     public void getElementsByIdNull() throws Exception {
142         final String htmlContent = DOCTYPE_HTML
143             + "<html>\n"
144             + "<head><title>foo</title></head>\n"
145             + "<body>\n"
146             + "<p>hello world</p>\n"
147             + "</body></html>";
148 
149         final HtmlPage page = loadPage(htmlContent);
150 
151         final List<DomElement> elements = page.getElementsById(null);
152         assertNotNull(elements);
153         assertEquals(0, elements.size());
154     }
155 
156     /**
157      * @throws Exception if the test fails
158      */
159     @Test
160     public void getElementsByIdOrNameNull() throws Exception {
161         final String htmlContent = DOCTYPE_HTML
162             + "<html>\n"
163             + "<head><title>foo</title></head>\n"
164             + "<body>\n"
165             + "<p>hello world</p>\n"
166             + "</body></html>";
167 
168         final HtmlPage page = loadPage(htmlContent);
169 
170         final List<DomElement> elements = page.getElementsByIdAndOrName(null);
171         assertNotNull(elements);
172         assertEquals(0, elements.size());
173     }
174 
175     /**
176      * @throws Exception if the test fails
177      */
178     @Test
179     public void getElementByNameNull() throws Exception {
180         final String htmlContent = DOCTYPE_HTML
181             + "<html>\n"
182             + "<head><title>foo</title></head>\n"
183             + "<body>\n"
184             + "<p>hello world</p>\n"
185             + "</body></html>";
186 
187         final HtmlPage page = loadPage(htmlContent);
188 
189         Assertions.assertThrows(ElementNotFoundException.class, () -> page.getElementByName(null));
190     }
191 
192     /**
193      * @throws Exception if the test fails
194      */
195     @Test
196     public void getElementsByNameNull() throws Exception {
197         final String htmlContent = DOCTYPE_HTML
198             + "<html>\n"
199             + "<head><title>foo</title></head>\n"
200             + "<body>\n"
201             + "<p>hello world</p>\n"
202             + "</body></html>";
203 
204         final HtmlPage page = loadPage(htmlContent);
205 
206         final List<DomElement> elements = page.getElementsByName(null);
207         assertNotNull(elements);
208         assertEquals(0, elements.size());
209     }
210 
211     /**
212      * Tests getHtmlElement() for all elements that can be loaded.
213      * @throws Exception if the test fails
214      */
215     @Test
216     public void getHtmlElement() throws Exception {
217         final String htmlContent = DOCTYPE_HTML
218             + "<html>\n"
219             + "<head><title>foo</title></head>\n"
220             + "<body>\n"
221             + "  <p>hello world</p>\n"
222             + "  <form id='form1' id='form1' action='/formSubmit' method='post'>\n"
223             + "  <input type='text' NAME='textInput1' value='textInput1'/>\n"
224             + "  <button type='submit' name='button1'>foobar</button>\n"
225             + "  <select name='select1'>\n"
226             + "    <option value='option1'>Option1</option>\n"
227             + "  </select>\n"
228             + "  <textarea name='textArea1'>foobar</textarea>\n"
229             + "  </form>\n"
230             + "  <a href='http://www.foo.com' name='anchor1'>foo.com</a>\n"
231             + "  <table id='table1'>\n"
232             + "    <tr>\n"
233             + "      <th id='header1'>Header</th>\n"
234             + "      <td id='data1'>Data</td>\n"
235             + "    </tr>\n"
236             + "  </table>\n"
237             + "</body></html>";
238         final HtmlPage page = loadPage(htmlContent);
239 
240         final HtmlForm form = page.getHtmlElementById("form1");
241         assertSame("form1", form, page.getHtmlElementById("form1")); //huh??
242 
243         final HtmlInput input = form.getInputByName("textInput1");
244         assertSame("input1", input, form.getInputByName("textInput1")); //??
245 
246         final HtmlButton button = form.getButtonByName("button1");
247         assertSame("button1", button, form.getButtonByName("button1"));
248 
249         final HtmlSelect select = form.getSelectsByName("select1").get(0);
250         assertSame("select1", select, form.getSelectsByName("select1").get(0));
251 
252         final HtmlOption option = select.getOptionByValue("option1");
253         assertSame("option1", option, select.getOptionByValue("option1"));
254 
255         final HtmlTable table = page.getHtmlElementById("table1");
256         assertSame("table1", table, page.getHtmlElementById("table1"));
257 
258         final HtmlAnchor anchor = page.getAnchorByName("anchor1");
259         assertSame("anchor1", anchor, page.getAnchorByName("anchor1"));
260         assertSame("anchor3", anchor, page.getAnchorByHref("http://www.foo.com"));
261         assertSame("anchor4", anchor, page.getAnchorByText("foo.com"));
262 
263         final HtmlTableRow tableRow = table.getRow(0);
264         assertSame("tableRow1", tableRow, table.getRow(0));
265 
266         final HtmlTableHeaderCell tableHeaderCell = (HtmlTableHeaderCell) tableRow.getCell(0);
267         assertSame("tableHeaderCell1", tableHeaderCell, tableRow.getCell(0));
268         assertSame("tableHeaderCell2", tableHeaderCell, page.getHtmlElementById("header1"));
269 
270         final HtmlTableDataCell tableDataCell = (HtmlTableDataCell) tableRow.getCell(1);
271         assertSame("tableDataCell1", tableDataCell, tableRow.getCell(1));
272         assertSame("tableDataCell2", tableDataCell, page.getHtmlElementById("data1"));
273 
274         final HtmlTextArea textArea = form.getTextAreaByName("textArea1");
275         assertSame("textArea1", textArea, form.getTextAreaByName("textArea1"));
276     }
277 
278     /**
279      * Tests getHtmlElement() for all elements that can be loaded.
280      * @throws Exception if the test fails
281      */
282     @Test
283     public void getAnchorByText() throws Exception {
284         final String htmlContent = DOCTYPE_HTML
285             + "<html>\n"
286             + "<head></head>\n"
287             + "<body>\n"
288             + "  <a href='http://www.foo.com' id='anchor1'>anchor text</a>\n"
289             + "  <a href='http://www.foo.com' id='anchor2'><span>anchor text inside span</span></a>\n"
290             + "  <a href='http://www.foo.com' id='anchor3'>"
291                 + "<svg><rect x='1' y='11' width='8' height='8'/></svg>"
292                 + "<span>complex</span>"
293                 + "</a>\n"
294             + "</body></html>";
295         final HtmlPage page = loadPage(htmlContent);
296 
297         assertSame("anchor1", page.getElementById("anchor1"), page.getAnchorByText("anchor text"));
298         assertSame("anchor2", page.getElementById("anchor2"), page.getAnchorByText("anchor text inside span"));
299         assertSame("anchor3", page.getElementById("anchor3"), page.getAnchorByText("complex"));
300     }
301 
302     /**
303      * @throws Exception if the test fails
304      */
305     @Test
306     public void getTabbableElements_None() throws Exception {
307         final String htmlContent = DOCTYPE_HTML
308             + "<html>\n"
309             + "<head><title>foo</title></head>\n"
310             + "<body>\n"
311             + "<p>hello world</p>\n"
312             + "<table><tr><td>foo</td></tr></table>\n"
313             + "</body></html>";
314 
315         final HtmlPage page = loadPage(htmlContent);
316 
317         assertEquals(Collections.EMPTY_LIST, page.getTabbableElements());
318     }
319 
320     /**
321      * @throws Exception if the test fails
322      */
323     @Test
324     public void getTabbableElements_OneEnabled_OneDisabled() throws Exception {
325         final String htmlContent = DOCTYPE_HTML
326             + "<html>\n"
327             + "<head><title>foo</title></head>\n"
328             + "<body>\n"
329             + "<form><p>hello world</p>\n"
330             + "<input name='foo' type='submit' disabled='disabled' id='foo'/>\n"
331             + "<input name='bar' type='submit' id='bar'/>\n"
332             + "</form></body></html>";
333         final HtmlPage page = loadPage(htmlContent);
334 
335         final List<HtmlElement> expectedElements = new ArrayList<>();
336         expectedElements.add(page.getHtmlElementById("bar"));
337 
338         assertEquals(expectedElements, page.getTabbableElements());
339     }
340 
341     /**
342      * @throws Exception if the test fails
343      */
344     @Test
345     public void getTabbableElements() throws Exception {
346         final String htmlContent = DOCTYPE_HTML
347             + "<html>\n"
348             + "<head><title>foo</title></head>\n"
349             + "<body>\n"
350             + "<a id='a' tabindex='1'>foo</a>\n"
351             + "<a id='b'>foo</a>\n"
352             + "<form>\n"
353             + "<a id='c' tabindex='3'>foo</a>\n"
354             + "<a id='d' tabindex='2'>foo</a>\n"
355             + "<a id='e' tabindex='0'>foo</a>\n"
356             + "</form>\n"
357             + "<a id='f' tabindex='3'>foo</a>\n"
358             + "<a id='g' tabindex='1'>foo</a>\n"
359             + "<a id='q' tabindex='-1'>foo</a>\n"
360             + "<form><p>hello world</p>\n"
361             + "<input name='foo' type='submit' disabled='disabled' id='foo'/>\n"
362             + "<input name='bar' type='submit' id='bar'/>\n"
363             + "</form></body></html>";
364         final HtmlPage page = loadPage(htmlContent);
365 
366         final List<HtmlElement> expectedElements = Arrays.asList(new HtmlElement[] {page.getHtmlElementById("a"),
367                 page.getHtmlElementById("g"), page.getHtmlElementById("d"),
368                 page.getHtmlElementById("c"), page.getHtmlElementById("f"),
369                 page.getHtmlElementById("e"), page.getHtmlElementById("b"),
370                 page.getHtmlElementById("bar")});
371 
372         assertEquals(expectedElements, page.getTabbableElements());
373 
374         final String[] expectedIds = {"a", "g", "d", "c", "f", "e", "b", "bar"};
375         assertEquals(expectedIds, page.getTabbableElementIds());
376     }
377 
378     /**
379      * @throws Exception if the test fails
380      */
381     @Test
382     public void getHtmlElementByAccessKey() throws Exception {
383         final String htmlContent = DOCTYPE_HTML
384             + "<html>\n"
385             + "<head><title>foo</title></head>\n"
386             + "<body>\n"
387             + "<a id='a' accesskey='a'>foo</a>\n"
388             + "<a id='b'>foo</a>\n"
389             + "<form>\n"
390             + "<a id='c' accesskey='c'>foo</a>\n"
391             + "</form>\n"
392             + "<form><p>hello world</p>\n"
393             + "<input name='foo' type='submit' disabled='disabled' id='foo' accesskey='f'/>\n"
394             + "<input name='bar' type='submit' id='bar'/>\n"
395             + "</form></body></html>";
396         final HtmlPage page = loadPage(htmlContent);
397 
398         assertEquals(page.getHtmlElementById("a"), page.getHtmlElementByAccessKey('A'));
399         assertEquals(page.getHtmlElementById("c"), page.getHtmlElementByAccessKey('c'));
400         assertNull(page.getHtmlElementByAccessKey('z'));
401     }
402 
403     /**
404      * @throws Exception if the test fails
405      */
406     @Test
407     public void getHtmlElementsByAccessKey() throws Exception {
408         final String htmlContent = DOCTYPE_HTML
409             + "<html>\n"
410             + "<head><title>foo</title></head><body>\n"
411             + "<a id='a' accesskey='a'>foo</a>\n"
412             + "<a id='b' accesskey='a'>foo</a>\n"
413             + "<form>\n"
414             + "<a id='c' accesskey='c'>foo</a>\n"
415             + "</form></body></html>";
416         final HtmlPage page = loadPage(htmlContent);
417 
418         final List<HtmlElement> expectedElements = Arrays.asList(new HtmlElement[] {page.getHtmlElementById("a"),
419                 page.getHtmlElementById("b")});
420         final List<HtmlElement> collectedElements = page.getHtmlElementsByAccessKey('a');
421         assertEquals(expectedElements, collectedElements);
422     }
423 
424     /**
425      * @throws Exception if the test fails
426      */
427     @Test
428     public void getFullQualifiedUrl_NoBaseSpecified() throws Exception {
429         final String htmlContent = DOCTYPE_HTML
430             + "<html><head><title>foo</title></head><body>\n"
431             + "<form id='form1'>\n"
432             + "<table><tr><td><input type='text' id='foo'/></td></tr></table>\n"
433             + "</form></body></html>";
434         final WebClient client = getWebClient();
435 
436         final MockWebConnection webConnection = new MockWebConnection();
437         webConnection.setDefaultResponse(htmlContent);
438         client.setWebConnection(webConnection);
439 
440         final String urlString = URL_FIRST.toExternalForm();
441         final HtmlPage page = client.getPage(URL_FIRST);
442 
443         assertEquals(urlString, page.getFullyQualifiedUrl(""));
444         assertEquals(urlString + "foo", page.getFullyQualifiedUrl("foo"));
445         assertEquals("http://foo.com/bar", page.getFullyQualifiedUrl("http://foo.com/bar"));
446         assertEquals("mailto:me@foo.com", page.getFullyQualifiedUrl("mailto:me@foo.com"));
447 
448         assertEquals(urlString + "foo", page.getFullyQualifiedUrl("foo"));
449         assertEquals(urlString + "bbb", page.getFullyQualifiedUrl("aaa/../bbb"));
450         assertEquals(urlString + "c/d", page.getFullyQualifiedUrl("c/./d"));
451 
452         final HtmlPage secondPage = client.getPage(urlString + "foo/bar?a=b&c=d");
453         assertEquals(urlString + "foo/bar?a=b&c=d", secondPage.getFullyQualifiedUrl(""));
454         assertEquals(urlString + "foo/one", secondPage.getFullyQualifiedUrl("one"));
455         assertEquals(urlString + "two", secondPage.getFullyQualifiedUrl("/two"));
456         assertEquals(urlString + "foo/two?a=b", secondPage.getFullyQualifiedUrl("two?a=b"));
457 
458         final HtmlPage thirdPage = client.getPage("http://foo.com/dog/cat/one.html");
459         assertEquals("http://foo.com/dog/cat/one.html", thirdPage.getFullyQualifiedUrl(""));
460         assertEquals("http://foo.com/dog/cat/two.html", thirdPage.getFullyQualifiedUrl("two.html"));
461     }
462 
463     /**
464      * @throws Exception if the test fails
465      */
466     @Test
467     public void getFullQualifiedUrl_WithBase() throws Exception {
468         testGetFullQualifiedUrl_WithBase("http", "");
469         testGetFullQualifiedUrl_WithBase("http", ":8080");
470         testGetFullQualifiedUrl_WithBase("https", "");
471         testGetFullQualifiedUrl_WithBase("https", ":2005");
472     }
473 
474     /**
475      * @throws Exception if the test fails
476      */
477     private void testGetFullQualifiedUrl_WithBase(final String baseProtocol, final String basePortPart)
478         throws Exception {
479 
480         final String baseUrl = baseProtocol + "://second" + basePortPart;
481         final String htmlContent = DOCTYPE_HTML
482             + "<html><head><title>foo</title>\n"
483             + "<base href='" + baseUrl + "'>\n"
484             + "</head><body>\n"
485             + "<form id='form1'>\n"
486             + "<table><tr><td><input type='text' id='foo'/></td></tr></table>\n"
487             + "</form></body></html>";
488         final HtmlPage page = loadPage(htmlContent);
489 
490         assertEquals(baseUrl, page.getFullyQualifiedUrl(""));
491         assertEquals(baseUrl + "/foo", page.getFullyQualifiedUrl("foo"));
492         assertEquals(baseUrl + "/foo.js", page.getFullyQualifiedUrl("/foo.js"));
493         assertEquals("http://foo.com/bar", page.getFullyQualifiedUrl("http://foo.com/bar"));
494         assertEquals("mailto:me@foo.com", page.getFullyQualifiedUrl("mailto:me@foo.com"));
495 
496         assertEquals(baseUrl + "/bbb", page.getFullyQualifiedUrl("aaa/../bbb"));
497         assertEquals(baseUrl + "/c/d", page.getFullyQualifiedUrl("c/./d"));
498     }
499 
500     /**
501      * @throws Exception if the test fails
502      */
503     @Test
504     public void getFullQualifiedUrl_invalid() throws Exception {
505         final String htmlContent = DOCTYPE_HTML + "<html><body></body></html>";
506         final HtmlPage page = loadPage(htmlContent);
507 
508         Assertions.assertThrows(MalformedURLException.class, () -> page.getFullyQualifiedUrl("http://"));
509     }
510 
511     /**
512      * @throws Exception if an error occurs
513      */
514     @Test
515     public void testBase_Multiple() throws Exception {
516         final String html = DOCTYPE_HTML
517             + "<html>\n"
518             + "<head>\n"
519             + "<base href='" + URL_SECOND + "'>\n"
520             + "<base href='" + URL_THIRD + "'>\n"
521             + "</head>\n"
522             + "<body>\n"
523             + "  <a href='somepage.html'>\n"
524             + "</body></html>";
525 
526         final WebClient webClient = getWebClient();
527         final List<String> collectedIncorrectness = new ArrayList<>();
528         final IncorrectnessListener listener = new IncorrectnessListener() {
529             @Override
530             public void notify(final String message, final Object origin) {
531                 collectedIncorrectness.add(message);
532             }
533         };
534         webClient.setIncorrectnessListener(listener);
535 
536         final MockWebConnection webConnection = new MockWebConnection();
537         webClient.setWebConnection(webConnection);
538         webConnection.setDefaultResponse(html);
539         final HtmlPage page = webClient.getPage(URL_FIRST);
540         page.getAnchors().get(0).click();
541 
542         final String[] expectedIncorrectness = {"Multiple 'base' detected, only the first is used.",
543             "Multiple 'base' detected, only the first is used."};
544         assertEquals(expectedIncorrectness, collectedIncorrectness);
545     }
546 
547     /**
548      * @throws Exception if an error occurs
549      */
550     @Test
551     public void testBase_InsideBody() throws Exception {
552         final String html = DOCTYPE_HTML
553             + "<html><body>\n"
554             + "  <base href='" + URL_SECOND + "'>\n"
555             + "  <a href='somepage.html'>link</a>\n"
556             + "</body></html>";
557 
558         final WebClient webClient = getWebClient();
559 
560         final MockWebConnection webConnection = new MockWebConnection();
561         webClient.setWebConnection(webConnection);
562         webConnection.setDefaultResponse(html);
563         final HtmlPage page = webClient.getPage(URL_FIRST);
564         final HtmlAnchor anchor = page.getAnchors().get(0);
565         final HtmlPage secondPage = anchor.click();
566 
567         assertEquals(URL_SECOND + "somepage.html", secondPage.getUrl());
568     }
569 
570     /**
571      * @throws Exception if the test fails
572      */
573     @Test
574     public void onLoadHandler_BodyStatement() throws Exception {
575         final String htmlContent = DOCTYPE_HTML
576             + "<html><head><title>foo</title>\n"
577             + "</head><body onLoad='alert(\"foo\")'>\n"
578             + "</body></html>";
579         final List<String> collectedAlerts = new ArrayList<>();
580         final HtmlPage page = loadPage(htmlContent, collectedAlerts);
581         assertEquals("foo", page.getTitleText());
582 
583         final String[] expectedAlerts = {"foo"};
584         assertEquals(expectedAlerts, collectedAlerts);
585     }
586 
587     /**
588      * If the onload handler contains two statements then only the first would execute.
589      * @throws Exception if the test fails
590      */
591     @Test
592     public void onLoadHandler_TwoBodyStatements() throws Exception {
593         final String htmlContent = DOCTYPE_HTML
594             + "<html><head><title>foo</title>\n"
595             + "</head><body onLoad='alert(\"foo\");alert(\"bar\")'>\n"
596             + "</body></html>";
597         final List<String> collectedAlerts = new ArrayList<>();
598         final HtmlPage page = loadPage(htmlContent, collectedAlerts);
599         assertEquals("foo", page.getTitleText());
600 
601         final String[] expectedAlerts = {"foo", "bar"};
602         assertEquals(expectedAlerts, collectedAlerts);
603     }
604 
605     /**
606      * Regression test for bug #69.
607      * @throws Exception if the test fails
608      */
609     @Test
610     public void onLoadHandler_BodyName() throws Exception {
611         final String htmlContent = DOCTYPE_HTML
612             + "<html><head><title>foo</title>\n"
613             + "<script type='text/javascript'>\n"
614             + "  window.onload = function() {alert('foo')}</script>\n"
615             + "</head><body></body></html>";
616         final List<String> collectedAlerts = new ArrayList<>();
617         final HtmlPage page = loadPage(htmlContent, collectedAlerts);
618         assertEquals("foo", page.getTitleText());
619 
620         final String[] expectedAlerts = {"foo"};
621         assertEquals(expectedAlerts, collectedAlerts);
622     }
623 
624     /**
625      * Regression test for bug #69.
626      * @throws Exception if the test fails
627      */
628     @Test
629     public void onLoadHandler_BodyName_NotAFunction() throws Exception {
630         final String htmlContent = DOCTYPE_HTML
631             + "<html><head><title>foo</title></head>\n"
632             + "<body onLoad='foo=4711'>\n"
633             + "<a name='alert' href='javascript:alert(foo)'/>\n"
634             + "</body></html>";
635         final List<String> collectedAlerts = new ArrayList<>();
636         final HtmlPage page = loadPage(htmlContent, collectedAlerts);
637         assertEquals("foo", page.getTitleText());
638 
639         page.getAnchorByName("alert").click();
640 
641         final String[] expectedAlerts = {"4711"};
642         assertEquals(expectedAlerts, collectedAlerts);
643     }
644 
645     /**
646      * Regression test for window.onload property.
647      * @throws Exception if the test fails
648      */
649     @Test
650     public void onLoadHandler_ScriptName() throws Exception {
651         final String htmlContent = DOCTYPE_HTML
652             + "<html><head><title>foo</title>\n"
653             + "<script type='text/javascript'>\n"
654             + "load=function() {alert('foo')};\n"
655             + "onload=load\n"
656             + "</script></head><body></body></html>";
657         final List<String> collectedAlerts = new ArrayList<>();
658         final HtmlPage page = loadPage(htmlContent, collectedAlerts);
659         assertEquals("foo", page.getTitleText());
660 
661         final String[] expectedAlerts = {"foo"};
662         assertEquals(expectedAlerts, collectedAlerts);
663     }
664 
665     /**
666      * @throws Exception if the test fails
667      */
668     @Test
669     public void embeddedMetaTag_Regression() throws Exception {
670         final String htmlContent = DOCTYPE_HTML
671             + "<html><head><title>foo</title>\n"
672             + "</head><body>\n"
673             + "<table><tr><td>\n"
674             + "<meta name=vs_targetSchema content=\"http://schemas.microsoft.com/intellisense/ie5\">\n"
675             + "<form name='form1'>\n"
676             + "  <input type='text' name='textfield1' id='textfield1' value='foo' />\n"
677             + "  <input type='text' name='textfield2' id='textfield2'/>\n"
678             + "</form>\n"
679             + "</td></tr></table>\n"
680             + "</body></html>";
681         final List<String> collectedAlerts = new ArrayList<>();
682 
683         // This used to blow up on page load
684         final HtmlPage page = loadPage(htmlContent, collectedAlerts);
685         assertEquals("foo", page.getTitleText());
686 
687         final List<?> expectedAlerts = Collections.EMPTY_LIST;
688         assertEquals(expectedAlerts, collectedAlerts);
689     }
690 
691     /**
692      * Verifies that an empty charset in a content-type meta tag is ignored. See bug #752.
693      * @throws Exception if an error occurs
694      */
695     @Test
696     public void getPageEncoding_EmptyCharset() throws Exception {
697         final String html = DOCTYPE_HTML
698             + "<html><head>\n"
699             + "<meta http-equiv='Content-Type' content='text/html; charset='>\n"
700             + "</head><body>abc</body></html>";
701         final HtmlPage page = loadPage(html);
702         assertEquals(ISO_8859_1, page.getCharset());
703     }
704 
705     /**
706      * Bug #914.
707      *
708      * @throws Exception if an error occurs
709      */
710     @Test
711     public void getPageEncoding_HeaderHasPrecedenceOverMetaTag() throws Exception {
712         final String html = DOCTYPE_HTML
713             + "<html><head><meta content='text/html; charset=iso-8859-1' http-equiv='Content-Type'/>\n"
714             + "</head><body></body></html>";
715         final MockWebConnection conn = new MockWebConnection();
716         conn.setResponse(URL_FIRST, html, "text/html; charset=UTF-8");
717         final WebClient client = getWebClient();
718         client.setWebConnection(conn);
719         final HtmlPage page = client.getPage(URL_FIRST);
720         assertSame(UTF_8, page.getCharset());
721         assertEquals(page.getWebResponse().getContentCharset(), page.getCharset());
722     }
723 
724     /**
725      * @throws Exception if the test fails
726      */
727     @Test
728     public void getForms() throws Exception {
729         final String htmlContent = DOCTYPE_HTML
730             + "<html>\n"
731             + "<head><title>foo</title></head>\n"
732             + "<body>\n"
733             + "<form name='one'>\n"
734             + "<a id='c' accesskey='c'>foo</a>\n"
735             + "</form>\n"
736             + "<form name='two'>\n"
737             + "<a id='c' accesskey='c'>foo</a>\n"
738             + "</form>\n"
739             + "<input name='foo' type='submit' disabled='disabled' id='foo' accesskey='f'/>\n"
740             + "<input name='bar' type='submit' id='bar'/>\n"
741             + "</form></body></html>";
742 
743         final HtmlPage page = loadPage(htmlContent);
744 
745         final List<HtmlForm> expectedForms = Arrays.asList(new HtmlForm[] {page.getFormByName("one"),
746                 page.getFormByName("two")});
747         assertEquals(expectedForms, page.getForms());
748     }
749 
750     /**
751      * Test auto-refresh from a meta tag.
752      * @throws Exception if the test fails
753      */
754     @Test
755     public void refresh_MetaTag_DefaultRefreshHandler() throws Exception {
756         testRefresh_MetaTag("<META HTTP-EQUIV=\"Refresh\" CONTENT=\"2;URL=§§URL§§\">");
757     }
758 
759     /**
760      * @throws Exception if the test fails
761      */
762     @Test
763     public void refresh_MetaTag_caseSensitivity() throws Exception {
764         testRefresh_MetaTag("<META HTTP-EQUIV=\"Refresh\" CONTENT=\"2;Url=§§URL§§\">");
765     }
766 
767     /**
768      * Regression test for bug #954.
769      * @throws Exception if the test fails
770      */
771     @Test
772     public void refresh_MetaTag_spaceSeparator() throws Exception {
773         testRefresh_MetaTag("<META HTTP-EQUIV=\"Refresh\" CONTENT=\"2 Url=§§URL§§\">");
774         testRefresh_MetaTag("<META HTTP-EQUIV=\"Refresh\" CONTENT=\"2\nUrl=§§URL§§\">");
775     }
776 
777     /**
778      * Test auto-refresh from a meta tag with no URL.
779      * @throws Exception if the test fails
780      */
781     @Test
782     public void refresh_MetaTag_NoUrl() throws Exception {
783         final String firstContent = DOCTYPE_HTML
784             + "<html><head><title>first</title>\n"
785             + "<META HTTP-EQUIV=\"Refresh\" CONTENT=\"1\">\n"
786             + "</head><body></body></html>";
787 
788         final WebClient client = getWebClient();
789         final List<Object> collectedItems = new ArrayList<>();
790         client.setRefreshHandler(new LoggingRefreshHandler(collectedItems));
791 
792         final MockWebConnection webConnection = new MockWebConnection();
793         webConnection.setResponse(URL_FIRST, firstContent);
794         client.setWebConnection(webConnection);
795 
796         client.getPage(URL_FIRST);
797 
798         // avoid using equals() on URL because it takes to much time (due to ip resolution)
799         assertEquals("first", collectedItems.get(0));
800         assertEquals(URL_FIRST, (URL) collectedItems.get(1));
801         assertEquals(Integer.valueOf(1), collectedItems.get(2));
802     }
803 
804     /**
805      * Ensures that if a page is supposed to refresh itself every certain amount of
806      * time, and the ImmediateRefreshHandler is being used, an OOME is avoided by
807      * not performing the refresh.
808      * @throws Exception if the test fails
809      */
810     @Test
811     public void refresh_ImmediateRefresh_AvoidOOME() throws Exception {
812         final String firstContent = DOCTYPE_HTML
813             + "<html><head><title>first</title>\n"
814             + "<META HTTP-EQUIV=\"Refresh\" CONTENT=\"1\">\n"
815             + "</head><body></body></html>";
816 
817         final WebClient client = getWebClient();
818         assertTrue(ImmediateRefreshHandler.class.isInstance(client.getRefreshHandler()));
819         try {
820             loadPage(firstContent);
821             Assertions.fail("should have thrown");
822         }
823         catch (final RuntimeException e) {
824             assertTrue(e.getMessage().indexOf("could have caused an OutOfMemoryError") > -1);
825         }
826         Thread.sleep(1000);
827     }
828 
829     /**
830      * Test auto-refresh from a meta tag with URL quoted.
831      * @throws Exception if the test fails
832      */
833     @Test
834     public void refresh_MetaTagQuoted() throws Exception {
835         testRefresh_MetaTag("<META HTTP-EQUIV='Refresh' CONTENT='0;URL=\"§§URL§§\"'>");
836     }
837 
838     /**
839      * Test auto-refresh from a meta tag with URL partly quoted.
840      * @throws Exception if the test fails
841      */
842     @Test
843     public void refresh_MetaTagPartlyQuoted() throws Exception {
844         testRefresh_MetaTag("<META HTTP-EQUIV='Refresh' CONTENT=\"0;URL='§§URL§§\">");
845     }
846 
847     private void testRefresh_MetaTag(final String metaTag) throws Exception {
848         final String firstContent = DOCTYPE_HTML
849             + "<html><head><title>first</title>\n"
850             + metaTag.replace("§§URL§§", URL_SECOND.toString()) + "\n"
851             + "<META HTTP-EQUIV='Refresh' CONTENT=\"0;URL='" + URL_SECOND + "\">\n"
852             + "</head><body></body></html>";
853         final String secondContent = DOCTYPE_HTML
854                 + "<html><head><title>second</title></head><body></body></html>";
855 
856         final WebClient client = getWebClient();
857 
858         final MockWebConnection webConnection = new MockWebConnection();
859         webConnection.setResponse(URL_FIRST, firstContent);
860         webConnection.setResponse(URL_SECOND, secondContent);
861         client.setWebConnection(webConnection);
862 
863         final HtmlPage page = client.getPage(URL_FIRST);
864 
865         assertEquals("second", page.getTitleText());
866     }
867 
868     /**
869      * Test auto-refresh from a meta tag inside noscript.
870      * @throws Exception if the test fails
871      */
872     @Test
873     public void refresh_MetaTagNoScript() throws Exception {
874         final String firstContent = DOCTYPE_HTML
875             + "<html><head><title>first</title>\n"
876             + "<noscript>\n"
877             + "<META HTTP-EQUIV=\"Refresh\" CONTENT=\"0;URL=" + URL_SECOND + "\">\n"
878             + "</noscript>\n"
879             + "</head><body></body></html>";
880         final String secondContent = DOCTYPE_HTML + "<html><head><title>second</title></head><body></body></html>";
881 
882         final WebClient client = getWebClient();
883 
884         final MockWebConnection webConnection = new MockWebConnection();
885         webConnection.setResponse(URL_FIRST, firstContent);
886         webConnection.setResponse(URL_SECOND, secondContent);
887         client.setWebConnection(webConnection);
888 
889         HtmlPage page = client.getPage(URL_FIRST);
890         assertEquals("first", page.getTitleText());
891 
892         client.getOptions().setJavaScriptEnabled(false);
893         page = client.getPage(URL_FIRST);
894         assertEquals("second", page.getTitleText());
895     }
896 
897     /**
898      * Test auto-refresh from a meta tag with a refresh handler that doesn't refresh.
899      * @throws Exception if the test fails
900      */
901     @Test
902     public void refresh_MetaTag_CustomRefreshHandler() throws Exception {
903         final String firstContent = DOCTYPE_HTML
904             + "<html><head><title>first</title>\n"
905             + "<META HTTP-EQUIV=\"Refresh\" CONTENT=\"3;URL=" + URL_SECOND + "\">\n"
906             + "</head><body></body></html>";
907         final String secondContent = DOCTYPE_HTML + "<html><head><title>second</title></head><body></body></html>";
908 
909         final WebClient client = getWebClient();
910         final List<Object> collectedItems = new ArrayList<>();
911         client.setRefreshHandler(new LoggingRefreshHandler(collectedItems));
912 
913         final MockWebConnection webConnection = new MockWebConnection();
914         webConnection.setResponse(URL_FIRST, firstContent);
915         webConnection.setResponse(URL_SECOND, secondContent);
916         client.setWebConnection(webConnection);
917 
918         final HtmlPage page = client.getPage(URL_FIRST);
919 
920         assertEquals("first", page.getTitleText());
921 
922         // avoid using equals() on URL because it takes to much time (due to ip resolution)
923         assertEquals("first", collectedItems.get(0));
924         assertEquals(URL_SECOND, (URL) collectedItems.get(1));
925         assertEquals(Integer.valueOf(3), collectedItems.get(2));
926     }
927 
928     /**
929      * Test that whitespace before and after ';' is permitted.
930      *
931      * @throws Exception if the test fails
932      */
933     @Test
934     public void refresh_MetaTag_Whitespace() throws Exception {
935         testRefresh_MetaTag("<META HTTP-EQUIV='Refresh' CONTENT='0  ;  URL=§§URL§§'>");
936     }
937 
938     /**
939      * Test that the refresh time can be a double ("3.4", for example), not just an integer.
940      *
941      * @throws Exception if an error occurs
942      */
943     @Test
944     public void refresh_MetaTag_Double() throws Exception {
945         testRefresh_MetaTag("<META HTTP-EQUIV='Refresh' CONTENT='1.2  ;  URL=§§URL§§'>");
946     }
947 
948     /**
949      * Test auto-refresh from a response header.
950      * @throws Exception if the test fails
951      */
952     @Test
953     public void refresh_HttpResponseHeader() throws Exception {
954         final String firstContent = DOCTYPE_HTML
955             + "<html><head><title>first</title>\n"
956             + "</head><body></body></html>";
957         final String secondContent = DOCTYPE_HTML + "<html><head><title>second</title></head><body></body></html>";
958 
959         final WebClient client = getWebClient();
960 
961         final MockWebConnection webConnection = new MockWebConnection();
962         webConnection.setResponse(URL_FIRST, firstContent, 200, "OK", MimeType.TEXT_HTML, Collections
963                 .singletonList(new NameValuePair("Refresh", "2;URL=" + URL_SECOND)));
964         webConnection.setResponse(URL_SECOND, secondContent);
965         client.setWebConnection(webConnection);
966 
967         final HtmlPage page = client.getPage(URL_FIRST);
968 
969         assertEquals("second", page.getTitleText());
970     }
971 
972     /**
973      * Test that the parent of the DOM Document (HtmlPage) is null.
974      * @throws Exception if the test fails
975      */
976     @Test
977     public void documentParentIsNull() throws Exception {
978         final String htmlContent = DOCTYPE_HTML
979             + "<html>\n"
980             + "<head><title>foo</title></head>\n"
981             + "<body>\n"
982             + "</body></html>";
983         final HtmlPage page = loadPage(htmlContent);
984 
985         assertNotNull(page);
986         assertNull(page.getParentNode());
987     }
988 
989     /**
990      * @throws Exception if the test fails
991      */
992     @Test
993     public void documentElement() throws Exception {
994         final String htmlContent = DOCTYPE_HTML
995             + "<html>\n"
996             + "<head><title>foo</title></head>\n"
997             + "<body>\n"
998             + "</body></html>";
999         final HtmlPage page = loadPage(htmlContent);
1000 
1001         final DomElement root = page.getDocumentElement();
1002 
1003         assertNotNull(root);
1004         assertEquals("html", root.getTagName());
1005         assertSame(page, root.getParentNode());
1006     }
1007 
1008     /**
1009      * @throws Exception if the test fails
1010      */
1011     @Test
1012     public void documentNodeType() throws Exception {
1013         final String htmlContent = DOCTYPE_HTML
1014             + "<html>\n"
1015             + "<head><title>foo</title></head>\n"
1016             + "<body>\n"
1017             + "</body></html>";
1018         final HtmlPage page = loadPage(htmlContent);
1019 
1020         final DomElement root = page.getDocumentElement();
1021 
1022         assertEquals(org.w3c.dom.Node.DOCUMENT_NODE, page.getNodeType());
1023         assertEquals(org.w3c.dom.Node.ELEMENT_NODE, root.getNodeType());
1024         assertEquals("#document", page.getNodeName());
1025     }
1026 
1027     /**
1028      * @throws Exception if the test fails
1029      */
1030     @Test
1031     public void deregisterFrameWithoutSrc() throws Exception {
1032         final String htmlContent = DOCTYPE_HTML
1033             + "<html>\n"
1034             + "<head><title>foo</title></head>\n"
1035             + "<body>\n"
1036             + "<iframe></iframe>\n"
1037             + "<a href='about:blank'>link</a>\n"
1038             + "</body></html>";
1039 
1040         final HtmlPage page = loadPage(htmlContent);
1041         final HtmlAnchor link = page.getAnchors().get(0);
1042         link.click();
1043     }
1044 
1045     /**
1046      * Tests that a return statement in onload doesn't throw any exception.
1047      * @throws Exception if the test fails
1048      */
1049     @Test
1050     public void onLoadReturn() throws Exception {
1051         final String htmlContent = DOCTYPE_HTML
1052             + "<html><head><title>foo</title></head>\n"
1053             + "<body onload='return true'>\n"
1054             + "</body></html>";
1055 
1056         loadPage(htmlContent);
1057     }
1058 
1059     /**
1060      * @exception Exception If the test fails
1061      */
1062     @Test
1063     public void asXml() throws Exception {
1064         final String htmlContent =
1065             "<html><head><title>foo</title></head>"
1066             + "<body><p>helloworld</p></body>"
1067             + "</html>";
1068 
1069         final HtmlPage page = loadPage(htmlContent);
1070         String xml = page.asXml();
1071         final String prefix = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>";
1072         assertTrue(xml.startsWith(prefix));
1073         xml = xml.substring(prefix.length());
1074         assertEquals(htmlContent, xml.replaceAll("\\s", ""));
1075     }
1076 
1077     /**
1078      * Tests that the generated XML is valid as HTML code too.
1079      * @exception Exception If the test fails
1080      */
1081     @Test
1082     public void asXmlValidHtmlOutput() throws Exception {
1083         final String html =
1084             "<html><head><title>foo</title>"
1085             + "<script src='script.js'></script></head>"
1086             + "<body><div></div><iframe src='about:blank'></iframe></body>"
1087             + "</html>";
1088 
1089         final WebClient client = getWebClient();
1090         final MockWebConnection webConnection = new MockWebConnection();
1091         webConnection.setDefaultResponse(html);
1092         webConnection.setResponse(new URL(URL_FIRST, "script.js"), "", "text/javascript");
1093         client.setWebConnection(webConnection);
1094 
1095         final HtmlPage page = client.getPage(URL_FIRST);
1096 
1097         String xml = page.asXml();
1098         final String prefix = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>";
1099         assertTrue(xml.startsWith(prefix));
1100         xml = xml.substring(prefix.length());
1101         assertEquals(html, xml
1102                 .replaceAll("[\\n\\r]", "")
1103                 .replaceAll("\\s\\s+", "")
1104                 .replaceAll("\"", "'"));
1105     }
1106 
1107     /**
1108      * @exception Exception If the test fails
1109      */
1110     @Test
1111     public void asXml2() throws Exception {
1112         final String htmlContent = DOCTYPE_HTML
1113             + "<html><head><title>foo</title>\n"
1114             + "<script>var test = 15 < 16;</script></head>\n"
1115             + "</head>\n"
1116             + "<body onload='test=(1 > 2) && (45 < 78)'><p>helloworld &amp;amp; helloall</p></body>\n"
1117             + "</html>";
1118 
1119         final HtmlPage page = loadPage(htmlContent);
1120         assertNotNull("xml document could not be parsed", page.asXml());
1121         final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
1122         final DocumentBuilder builder = factory.newDocumentBuilder();
1123         builder.parse(IOUtils.toInputStream(page.asXml(), StandardCharsets.ISO_8859_1));
1124     }
1125 
1126     /**
1127      * @throws Exception if the test fails
1128      */
1129     @Test
1130     public void asXml_unicode() throws Exception {
1131         final String unicodeString = "\u064A\u0627 \u0644\u064A\u064A\u0644";
1132         final String html = DOCTYPE_HTML
1133             + "<html>\n"
1134             + "<head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8'></head>\n"
1135             + "<body><span id='foo'>" + unicodeString + "</span></body></html>";
1136 
1137         final WebClient client = getWebClient();
1138         final MockWebConnection webConnection = new MockWebConnection();
1139 
1140         webConnection.setDefaultResponse(StringUtils.toByteArray(html, UTF_8), 200, "OK", MimeType.TEXT_HTML);
1141         client.setWebConnection(webConnection);
1142 
1143         final HtmlPage page = client.getPage(URL_FIRST);
1144         final String xml = page.asXml();
1145         assertTrue(xml.contains("<?xml "));
1146         assertTrue(xml.contains(unicodeString));
1147     }
1148 
1149     /**
1150      * @throws Exception if the test fails
1151      */
1152     @Test
1153     public void asXml_noscript() throws Exception {
1154         final String html = DOCTYPE_HTML
1155             + "<html>"
1156             + "<body>"
1157             + "<noscript><p><strong>your browser does not support JavaScript</strong></p></noscript>"
1158             + "</body></html>";
1159 
1160         final String expected = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\r\n"
1161                 + "<html>\r\n"
1162                 + "  <head/>\r\n"
1163                 + "  <body>\r\n"
1164                 + "    <noscript>"
1165                             + "&lt;p&gt;&lt;strong&gt;your browser does not support JavaScript&lt;/strong&gt;&lt;/p&gt;"
1166                             + "</noscript>\r\n"
1167                 + "  </body>\r\n"
1168                 + "</html>";
1169 
1170         final HtmlPage page = loadPage(html);
1171         assertEquals(expected, page.asXml());
1172     }
1173 
1174     /**
1175      * @exception Exception if the test fails
1176      */
1177     @Test
1178     public void getElementsById() throws Exception {
1179         final String html = DOCTYPE_HTML
1180                 + "<html><body>"
1181                 + "<div id='a'>foo</div>"
1182                 + "<div id='b'/><div id='b'/>"
1183                 + "<div id='c'><div id='c'/></div>"
1184                 + "</body></html>";
1185         final HtmlPage page = loadPage(html);
1186         assertEquals(1, page.getElementsById("a").size());
1187         assertEquals(2, page.getElementsById("b").size());
1188         assertEquals(2, page.getElementsById("c").size());
1189         assertEquals(0, page.getElementsById("d").size());
1190 
1191         final DomElement a = page.getElementsById("a").get(0);
1192         a.remove();
1193         assertEquals(0, page.getElementsById("a").size());
1194 
1195         final DomElement b1 = page.getElementsById("b").get(0);
1196         b1.appendChild(a);
1197 
1198         assertEquals(1, page.getElementsById("a").size());
1199     }
1200 
1201     /**
1202      * @exception Exception if the test fails
1203      */
1204     @Test
1205     public void getElementsByName() throws Exception {
1206         final String html = DOCTYPE_HTML
1207                 + "<html><body><div name='a'>foo</div><div name='b'/><div name='b'/></body></html>";
1208         final HtmlPage page = loadPage(html);
1209         assertEquals(1, page.getElementsByName("a").size());
1210         assertEquals(2, page.getElementsByName("b").size());
1211         assertEquals(0, page.getElementsByName("c").size());
1212 
1213         final DomElement a = page.getElementsByName("a").get(0);
1214         a.remove();
1215         assertEquals(0, page.getElementsByName("a").size());
1216 
1217         final DomElement b1 = page.getElementsByName("b").get(0);
1218         b1.appendChild(a);
1219         assertEquals(1, page.getElementsByName("a").size());
1220     }
1221 
1222     /**
1223      * @exception Exception if the test fails
1224      */
1225     @Test
1226     public void getElementByName() throws Exception {
1227         final String html = DOCTYPE_HTML
1228             + "<html><body>\n"
1229             + "<div id='a' name='a'>foo</div>\n"
1230             + "<div id='b1' name='b'>bar</div>\n"
1231             + "<div id='b2' name='b'>baz</div></body></html>";
1232         final HtmlPage page = loadPage(html);
1233         assertEquals(page.getElementById("a"), page.getElementByName("a"));
1234         assertEquals(page.getElementById("b1"), page.getElementByName("b"));
1235 
1236         page.getElementByName("b").remove();
1237         assertEquals(page.getElementById("b2"), page.getElementByName("b"));
1238     }
1239 
1240     /**
1241      * @exception Exception if the test fails
1242      */
1243     @Test
1244     public void getElementByNameNotfound() throws Exception {
1245         final String html = DOCTYPE_HTML
1246             + "<html><body>\n"
1247             + "<div id='a' name='a'>foo</div>\n"
1248             + "<div id='b1' name='b'>bar</div>\n"
1249             + "<div id='b2' name='b'>baz</div></body></html>";
1250         final HtmlPage page = loadPage(html);
1251 
1252         Assertions.assertThrows(ElementNotFoundException.class, () -> page.getElementByName("c"));
1253     }
1254 
1255     /**
1256      * @exception Exception if the test fails
1257      */
1258     @Test
1259     public void getHtmlElementsByIdAndOrName() throws Exception {
1260         final String html = DOCTYPE_HTML
1261                 + "<html><body><div name='a' id='a'>foo</div><div name='b' id='c'>bar</div>"
1262                 + "<div name='b' id='d'>bar</div></body></html>";
1263         final HtmlPage page = loadPage(html);
1264         assertEquals(1, page.getElementsByIdAndOrName("a").size());
1265         assertEquals(2, page.getElementsByIdAndOrName("b").size());
1266         assertEquals(1, page.getElementsByIdAndOrName("c").size());
1267         assertEquals(1, page.getElementsByIdAndOrName("d").size());
1268 
1269         final DomElement a = page.getElementsByIdAndOrName("a").get(0);
1270         a.remove();
1271         assertEquals(0, page.getElementsByIdAndOrName("a").size());
1272 
1273         final DomElement b1 = page.getElementsByIdAndOrName("b").get(0);
1274         b1.appendChild(a);
1275         assertEquals(1, page.getElementsByIdAndOrName("a").size());
1276     }
1277 
1278     /**
1279      * Regression test for bug #287.
1280      * @exception Exception If the test fails
1281      */
1282     @Test
1283     public void getHtmlElementByIdAfterRemove() throws Exception {
1284         final String htmlContent = DOCTYPE_HTML
1285             + "<html><head><title>foo</title></head>\n"
1286             + "<body>\n"
1287             + "<div id='div1'>\n"
1288             + "<div id='div2'>\n"
1289             + "</div>\n"
1290             + "</div>\n"
1291             + "</body>\n"
1292             + "</html>";
1293 
1294         final HtmlPage page = loadPage(htmlContent);
1295         final HtmlElement div1 = page.getHtmlElementById("div1");
1296         page.getHtmlElementById("div2"); // would throw if not found
1297         div1.remove();
1298         try {
1299             page.getHtmlElementById("div1"); // throws if not found
1300             Assertions.fail("div1 should have been removed");
1301         }
1302         catch (final ElementNotFoundException e) {
1303             // nothing
1304         }
1305 
1306         try {
1307             page.getHtmlElementById("div2"); // throws if not found
1308             Assertions.fail("div2 should have been removed");
1309         }
1310         catch (final ElementNotFoundException e) {
1311             // nothing
1312         }
1313     }
1314 
1315     /**
1316      * Test getHtmlElementById() when 2 elements have the same id and the first one
1317      * is removed.
1318      * @exception Exception If the test fails
1319      */
1320     @Test
1321     public void getHtmlElementById_idTwice() throws Exception {
1322         final String htmlContent = DOCTYPE_HTML
1323             + "<html><head><title>foo</title></head>\n"
1324             + "<body>\n"
1325             + "<div id='id1'>foo</div>\n"
1326             + "<span id='id1'>bla</span>\n"
1327             + "</body>\n"
1328             + "</html>";
1329 
1330         final HtmlPage page = loadPage(htmlContent);
1331         final HtmlElement elt1 = page.getHtmlElementById("id1");
1332         assertEquals("div", elt1.getNodeName());
1333         elt1.remove();
1334         assertEquals("span", page.getHtmlElementById("id1").getNodeName());
1335     }
1336 
1337     /**
1338      * Test the "set-cookie" meta tag.
1339      * @throws Exception if the test fails
1340      */
1341     @Test
1342     @Alerts("webm=none")
1343     public void setCookieMetaTag() throws Exception {
1344         final String content = DOCTYPE_HTML
1345             + "<html><head>\n"
1346             + "<meta http-equiv='set-cookie' content='webm=none; path=/;'>\n"
1347             + "</head><body>\n"
1348             + "<script>document.title = document.cookie</script>\n"
1349             + "</body></html>";
1350 
1351         final HtmlPage page = loadPage(content);
1352         assertEquals(getExpectedAlerts()[0], page.getTitleText());
1353 
1354         final Set<Cookie> cookies = page.getWebClient().getCookieManager().getCookies();
1355         assertEquals(1, cookies.size());
1356         final Cookie cookie = cookies.iterator().next();
1357         assertEquals(page.getUrl().getHost(), cookie.getDomain());
1358         assertEquals("webm", cookie.getName());
1359         assertEquals("none", cookie.getValue());
1360         assertEquals("/", cookie.getPath());
1361     }
1362 
1363     /**
1364      * Test for bug #474.
1365      * @throws Exception if the test fails
1366      */
1367     @Test
1368     public void noSlashURL() throws Exception {
1369         testNoSlashURL("http:/second");
1370         testNoSlashURL("http:second");
1371     }
1372 
1373     private void testNoSlashURL(final String url) throws Exception {
1374         final String firstContent = DOCTYPE_HTML
1375             + "<html><body>\n"
1376             + "<iframe id='myIFrame' src='" + url + "'></iframe>\n"
1377             + "</body></html>";
1378 
1379         final String secondContent = DOCTYPE_HTML + "<html><body></body></html>";
1380         final WebClient client = getWebClient();
1381 
1382         final URL secondURL = new URL("http://second/");
1383         final MockWebConnection webConnection = new MockWebConnection();
1384         webConnection.setResponse(URL_FIRST, firstContent);
1385         webConnection.setResponse(secondURL, secondContent);
1386 
1387         client.setWebConnection(webConnection);
1388 
1389         final HtmlPage firstPage = client.getPage(URL_FIRST);
1390         final HtmlInlineFrame iframe = firstPage.getHtmlElementById("myIFrame");
1391 
1392         assertEquals(secondURL, iframe.getEnclosedPage().getUrl());
1393     }
1394 
1395     /**
1396      * @throws Exception failure
1397      */
1398     @Test
1399     public void metaTagWithEmptyURL() throws Exception {
1400         final WebClient client = getWebClient();
1401         client.setRefreshHandler(new ImmediateRefreshHandler());
1402 
1403         // connection will return a page with <meta ... refresh> for the first call
1404         // and the same page without it for the other calls
1405         final MockWebConnection webConnection = new MockWebConnection() {
1406             private int nbCalls_ = 0;
1407             @Override
1408             public WebResponse getResponse(final WebRequest request) throws IOException {
1409                 String content = DOCTYPE_HTML + "<html><head>\n";
1410                 if (nbCalls_ == 0) {
1411                     content += "<meta http-equiv='refresh' content='1; URL='>\n";
1412                 }
1413                 content += "</head><body></body></html>";
1414                 nbCalls_++;
1415 
1416                 final StringWebResponse response = new StringWebResponse(content, request.getUrl());
1417                 response.getWebRequest().setHttpMethod(request.getHttpMethod());
1418                 return response;
1419             }
1420         };
1421         client.setWebConnection(webConnection);
1422 
1423         final WebRequest request = new WebRequest(URL_FIRST);
1424         request.setHttpMethod(HttpMethod.POST);
1425         client.getPage(request);
1426     }
1427 
1428     /**
1429      * @throws Exception if the test fails
1430      */
1431     @Test
1432     public void serialization() throws Exception {
1433         // The document.all and form.elements calls are important because they trigger the creation
1434         // of HTMLCollections, which have caused serialization problems in the past (see bug #606).
1435 
1436         final String content = DOCTYPE_HTML
1437             + "<html><body>\n"
1438             + "<div id='myId'>Hello there!</div>\n"
1439             + "<script>\n"
1440             + "  var x = document.all;\n"
1441             + "  window.onload = function() {alert('foo')};\n"
1442 
1443             // this tests 3103703
1444             // we don't store the jobs are pending at the moment of serialization
1445             + "  var aktiv = window.setInterval('foo()', 1000);\n"
1446             + "  var i = 0;\n"
1447             + "  function foo() {\n"
1448             + "    i = i + 1;\n"
1449             + "    if (i >= 10)\n"
1450             + "      window.clearInterval(aktiv);\n"
1451             + "  }\n"
1452             + "</script>\n"
1453             + "<form name='f' id='f'></form>\n"
1454             + "<script>var y = document.getElementById('f').elements;</script>\n"
1455             + "</body></html>";
1456 
1457         // waiting for the alerts creates some more js objects associated with the page
1458         // this tests 3103703
1459         final List<String> expectedAlerts = new LinkedList<>();
1460         expectedAlerts.add("foo");
1461 
1462         final HtmlPage page1 = loadPage(content, expectedAlerts);
1463         final byte[] bytes = SerializationUtils.serialize(page1);
1464 
1465         final HtmlPage page2 = (HtmlPage) SerializationUtils.deserialize(bytes);
1466 
1467         final Iterator<HtmlElement> iterator1 = page1.getHtmlElementDescendants().iterator();
1468         final Iterator<HtmlElement> iterator2 = page2.getHtmlElementDescendants().iterator();
1469         while (iterator1.hasNext()) {
1470             assertTrue(iterator2.hasNext());
1471             final HtmlElement element1 = iterator1.next();
1472             final HtmlElement element2 = iterator2.next();
1473             assertEquals(element1.getNodeName(), element2.getNodeName());
1474         }
1475         assertFalse(iterator2.hasNext());
1476         assertEquals("Hello there!", page2.getHtmlElementById("myId").getFirstChild().getNodeValue());
1477     }
1478 
1479     /**
1480      * Test issue 513.
1481      * @throws Exception if the test fails
1482      */
1483     @Test
1484     public void serializationLambda() throws Exception {
1485         final String content = DOCTYPE_HTML
1486             + "<html><body>\n"
1487             + "<div id='myId'>Hello there!</div>\n"
1488             + "<form name='f' id='f'></form>\n"
1489             + "<script>var y = document.body.getElementsByTagName('form').elements;</script>\n"
1490             + "</body></html>";
1491 
1492         final HtmlPage page1 = loadPage(content);
1493         final byte[] bytes = SerializationUtils.serialize(page1);
1494 
1495         final HtmlPage page2 = (HtmlPage) SerializationUtils.deserialize(bytes);
1496 
1497         final Iterator<HtmlElement> iterator1 = page1.getHtmlElementDescendants().iterator();
1498         final Iterator<HtmlElement> iterator2 = page2.getHtmlElementDescendants().iterator();
1499         while (iterator1.hasNext()) {
1500             assertTrue(iterator2.hasNext());
1501             final HtmlElement element1 = iterator1.next();
1502             final HtmlElement element2 = iterator2.next();
1503             assertEquals(element1.getNodeName(), element2.getNodeName());
1504         }
1505         assertFalse(iterator2.hasNext());
1506         assertEquals("Hello there!", page2.getHtmlElementById("myId").getFirstChild().getNodeValue());
1507     }
1508 
1509     /**
1510      * @throws Exception if the test fails
1511      */
1512     @Test
1513     public void serializationStaticDomNodeList() throws Exception {
1514         final String content = DOCTYPE_HTML
1515             + "<html><body>\n"
1516             + "<div id='myId'>Hello there!</div>\n"
1517             + "<form name='f' id='f'></form>\n"
1518             + "<script>var y = document.querySelectorAll('*');</script>\n"
1519             + "</body></html>";
1520 
1521         final HtmlPage page1 = loadPage(content);
1522         final byte[] bytes = SerializationUtils.serialize(page1);
1523 
1524         final HtmlPage page2 = (HtmlPage) SerializationUtils.deserialize(bytes);
1525 
1526         final Iterator<HtmlElement> iterator1 = page1.getHtmlElementDescendants().iterator();
1527         final Iterator<HtmlElement> iterator2 = page2.getHtmlElementDescendants().iterator();
1528         while (iterator1.hasNext()) {
1529             assertTrue(iterator2.hasNext());
1530             final HtmlElement element1 = iterator1.next();
1531             final HtmlElement element2 = iterator2.next();
1532             assertEquals(element1.getNodeName(), element2.getNodeName());
1533         }
1534         assertFalse(iterator2.hasNext());
1535         assertEquals("Hello there!", page2.getHtmlElementById("myId").getFirstChild().getNodeValue());
1536     }
1537 
1538     /**
1539      * Verifies that a cloned HtmlPage has its own "idMap_".
1540      * @throws Exception if the test fails
1541      */
1542     @Test
1543     public void clonedPageHasOwnIdMap() throws Exception {
1544         final String content = DOCTYPE_HTML
1545             + "<html><head><title>foo</title>"
1546             + "<body>"
1547             + "<div id='id1' class='cl1'><div id='id2' class='cl2'></div></div>"
1548             + "</body></html>";
1549 
1550         final HtmlPage page = loadPage(content);
1551         final HtmlElement id1 = (HtmlElement) page.getDocumentElement().getLastChild().getLastChild();
1552         assertEquals("id1", id1.getId());
1553         assertSame(id1, page.getHtmlElementById("id1"));
1554         final HtmlPage clone = page.cloneNode(true);
1555         assertSame(id1, page.getHtmlElementById("id1"));
1556         final HtmlElement id1clone = (HtmlElement) clone.getDocumentElement().getLastChild().getLastChild();
1557         assertNotSame(id1, id1clone);
1558         assertEquals("id1", id1clone.getId());
1559         assertSame(id1clone, clone.getHtmlElementById("id1"));
1560         assertNotSame(id1clone, page.getHtmlElementById("id1"));
1561         assertNotSame(id1, clone.getHtmlElementById("id1"));
1562 
1563         page.getHtmlElementById("id2").remove();
1564         try {
1565             page.getHtmlElementById("id2");
1566             Assertions.fail("should have thrown ElementNotFoundException");
1567         }
1568         catch (final ElementNotFoundException expected) {
1569         }
1570         assertNotNull(clone.getHtmlElementById("id2"));
1571     }
1572 
1573     /**
1574      * Verifies that a cloned HtmlPage has its own "documentElement".
1575      * @throws Exception if the test fails
1576      */
1577     @Test
1578     public void clonedPageHasOwnDocumentElement() throws Exception {
1579         final String content = DOCTYPE_HTML
1580             + "<html><head><title>foo</title>\n"
1581             + "<body>\n"
1582             + "<div id='id1' class='cl1'><div id='id2' class='cl2'></div></div>\n"
1583             + "</body></html>";
1584 
1585         final HtmlPage page = loadPage(content);
1586         final HtmlPage clone = page.cloneNode(true);
1587         assertTrue(page != clone);
1588         final DomElement doc = page.getDocumentElement();
1589         final DomElement docclone = clone.getDocumentElement();
1590         assertTrue(doc != docclone);
1591     }
1592 
1593     /**
1594      * @throws Exception if the test fails
1595      */
1596     @Test
1597     public void htmlAttributeChangeListener_AddAttribute() throws Exception {
1598         final String htmlContent = DOCTYPE_HTML
1599             + "<html><head><title>foo</title>\n"
1600             + "<script>\n"
1601             + "  function clickMe() {\n"
1602             + "    var p1 = document.getElementById('p1');\n"
1603             + "    p1.setAttribute('title', 'myTitle');\n"
1604             + "  }\n"
1605             + "</script>\n"
1606             + "</head>\n"
1607             + "<body>\n"
1608             + "<p id='p1'></p>\n"
1609             + "<input id='myButton' type='button' onclick='clickMe()'>\n"
1610             + "</body></html>";
1611 
1612         final String[] expectedValues = {"attributeAdded: p,title,myTitle"};
1613         final HtmlPage page = loadPage(htmlContent);
1614         final HtmlAttributeChangeListenerTestImpl listenerImpl = new HtmlAttributeChangeListenerTestImpl();
1615         page.addHtmlAttributeChangeListener(listenerImpl);
1616         final HtmlButtonInput myButton = page.getHtmlElementById("myButton");
1617 
1618         myButton.click();
1619         assertEquals(expectedValues, listenerImpl.getCollectedValues());
1620     }
1621 
1622     /**
1623      * @throws Exception if the test fails
1624      */
1625     @Test
1626     public void htmlAttributeChangeListener_ReplaceAttribute() throws Exception {
1627         final String htmlContent = DOCTYPE_HTML
1628             + "<html><head><title>foo</title>\n"
1629             + "<script>\n"
1630             + "  function clickMe() {\n"
1631             + "    var p1 = document.getElementById('p1');\n"
1632             + "    p1.setAttribute('title', p1.getAttribute('title') + 'a');\n"
1633             + "  }\n"
1634             + "</script>\n"
1635             + "</head>\n"
1636             + "<body>\n"
1637             + "<p id='p1' title='myTitle'></p>\n"
1638             + "<input id='myButton' type='button' onclick='clickMe()'>\n"
1639             + "</body></html>";
1640 
1641         final String[] expectedValues = {"attributeReplaced: p,title,myTitle"};
1642         final HtmlPage page = loadPage(htmlContent);
1643         final HtmlAttributeChangeListenerTestImpl listenerImpl = new HtmlAttributeChangeListenerTestImpl();
1644         page.addHtmlAttributeChangeListener(listenerImpl);
1645         final HtmlButtonInput myButton = page.getHtmlElementById("myButton");
1646 
1647         myButton.click();
1648         assertEquals(expectedValues, listenerImpl.getCollectedValues());
1649     }
1650 
1651     /**
1652      * @throws Exception if the test fails
1653      */
1654     @Test
1655     public void htmlAttributeChangeListener_RemoveAttribute() throws Exception {
1656         final String htmlContent = DOCTYPE_HTML
1657             + "<html><head><title>foo</title>\n"
1658             + "<script>\n"
1659             + "  function clickMe() {\n"
1660             + "    var p1 = document.getElementById('p1');\n"
1661             + "    p1.removeAttribute('title');\n"
1662             + "  }\n"
1663             + "</script>\n"
1664             + "</head>\n"
1665             + "<body>\n"
1666             + "<p id='p1' title='myTitle'></p>\n"
1667             + "<input id='myButton' type='button' onclick='clickMe()'>\n"
1668             + "</body></html>";
1669 
1670         final String[] expectedValues = {"attributeRemoved: p,title,myTitle"};
1671         final HtmlPage page = loadPage(htmlContent);
1672         final HtmlAttributeChangeListenerTestImpl listenerImpl = new HtmlAttributeChangeListenerTestImpl();
1673         page.addHtmlAttributeChangeListener(listenerImpl);
1674         final HtmlButtonInput myButton = page.getHtmlElementById("myButton");
1675 
1676         myButton.click();
1677         assertEquals(expectedValues, listenerImpl.getCollectedValues());
1678     }
1679 
1680     /**
1681      * @throws Exception if the test fails
1682      */
1683     @Test
1684     public void htmlAttributeChangeListener_RemoveListener() throws Exception {
1685         final String htmlContent = DOCTYPE_HTML
1686             + "<html><head><title>foo</title>\n"
1687             + "<script>\n"
1688             + "  function clickMe() {\n"
1689             + "    var p1 = document.getElementById('p1');\n"
1690             + "    p1.setAttribute('title', p1.getAttribute('title') + 'a');\n"
1691             + "  }\n"
1692             + "</script>\n"
1693             + "</head>\n"
1694             + "<body>\n"
1695             + "<p id='p1' title='myTitle'></p>\n"
1696             + "<input id='myButton' type='button' onclick='clickMe()'>\n"
1697             + "</body></html>";
1698 
1699         final String[] expectedValues = {"attributeReplaced: p,title,myTitle"};
1700         final HtmlPage page = loadPage(htmlContent);
1701         final HtmlAttributeChangeListenerTestImpl listenerImpl = new HtmlAttributeChangeListenerTestImpl();
1702         page.addHtmlAttributeChangeListener(listenerImpl);
1703         final HtmlButtonInput myButton = page.getHtmlElementById("myButton");
1704 
1705         myButton.click();
1706         page.removeHtmlAttributeChangeListener(listenerImpl);
1707         myButton.click();
1708         assertEquals(expectedValues, listenerImpl.getCollectedValues());
1709     }
1710 
1711     /**
1712      * @throws Exception if the test fails
1713      */
1714     @Test
1715     public void htmlAttributeChangeListener_ListenerRegistersNewListener() throws Exception {
1716         final String htmlContent = DOCTYPE_HTML
1717             + "<html><head><title>foo</title>\n"
1718             + "</head>\n"
1719             + "<body>\n"
1720             + "<p id='p1' title='myTitle'></p>\n"
1721             + "</body></html>";
1722 
1723         final HtmlPage page = loadPage(htmlContent);
1724 
1725         final List<String> collector = new ArrayList<>();
1726         final HtmlAttributeChangeListener listener2 = new HtmlAttributeChangeListenerTestImpl() {
1727             @Override
1728             public void attributeReplaced(final HtmlAttributeChangeEvent event) {
1729                 collector.add("in listener 2");
1730             }
1731         };
1732         final HtmlAttributeChangeListener listener1 = new HtmlAttributeChangeListenerTestImpl() {
1733             @Override
1734             public void attributeReplaced(final HtmlAttributeChangeEvent event) {
1735                 collector.add("in listener 1");
1736                 page.addHtmlAttributeChangeListener(listener2);
1737             }
1738         };
1739 
1740         page.addHtmlAttributeChangeListener(listener1);
1741 
1742         final HtmlElement p = page.getHtmlElementById("p1");
1743         p.setAttribute("title", "new title");
1744 
1745         final String[] expectedValues = {"in listener 1"};
1746         assertEquals(expectedValues, collector);
1747         collector.clear();
1748 
1749         p.setAttribute("title", "new new title");
1750         final String[] expectedValues2 = {"in listener 1", "in listener 2"};
1751         assertEquals(expectedValues2, collector);
1752     }
1753 
1754     /**
1755      * @throws Exception if the test fails
1756      */
1757     @Test
1758     public void caseInsensitiveRegexReplacement() throws Exception {
1759         final String html = DOCTYPE_HTML
1760             + "<html><body><script>\n"
1761             + "var r = /^([#.]?)([a-z0-9\\*_-]*)/i;\n"
1762             + "var s = '#userAgent';\n"
1763             + "s = s.replace(r, '');\n"
1764             + "alert(s.length);\n"
1765             + "</script></body></html>";
1766         final String[] expectedAlerts = {"0"};
1767         final List<String> actualAlerts = new ArrayList<>();
1768         loadPage(html, actualAlerts);
1769         assertEquals(expectedAlerts, actualAlerts);
1770     }
1771 
1772     /**
1773      * @throws Exception if the test fails
1774      */
1775     @Test
1776     public void regexReplacementWithFunction() throws Exception {
1777         final String html = DOCTYPE_HTML
1778             + "<html><body><script>\n"
1779             + "var r = /-([a-z])/ig;\n"
1780             + "var s = 'font-size';\n"
1781             + "s = s.replace(r, function(z,b){return b.toUpperCase();});\n"
1782             + "alert(s);\n"
1783             + "</script></body></html>";
1784         final String[] expectedAlerts = {"fontSize"};
1785         final List<String> collectedAlerts = new ArrayList<>();
1786         loadPage(html, collectedAlerts);
1787         assertEquals(expectedAlerts, collectedAlerts);
1788     }
1789 
1790     /**
1791      * @throws Exception if the test fails
1792      */
1793     @Test
1794     public void title_EmptyXmlTagExpanded() throws Exception {
1795         final String content = DOCTYPE_HTML
1796             + "<html><head><title/></head>\n"
1797             + "<body>Hello World!</body></html>";
1798         final HtmlPage page = loadPage(content);
1799         assertTrue(page.asXml().indexOf("</title>") != -1);
1800     }
1801 
1802     /**
1803      * @throws Exception if the test fails
1804      */
1805     @Test
1806     public void onunLoadHandler() throws Exception {
1807         final String htmlContent = DOCTYPE_HTML
1808             + "<html><head><title>foo</title>\n"
1809             + "</head><body onunload='alert(\"foo\");alert(\"bar\")'>\n"
1810             + "</body></html>";
1811 
1812         final String[] expectedAlerts = {"foo", "bar"};
1813         final List<String> collectedAlerts = new ArrayList<>();
1814         final WebClient client = getWebClient();
1815         client.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
1816         final MockWebConnection conn = new MockWebConnection();
1817         conn.setResponse(URL_FIRST, htmlContent);
1818         client.setWebConnection(conn);
1819 
1820         client.getPage(URL_FIRST);
1821         client.getPage(URL_FIRST);
1822 
1823         assertEquals(expectedAlerts, collectedAlerts);
1824     }
1825 
1826     /**
1827      * @throws Exception if the test fails
1828      */
1829     @Test
1830     public void onbeforeunloadHandler_ok() throws Exception {
1831         testOnbeforeunloadHandler(true, "second");
1832     }
1833 
1834     /**
1835      * @throws Exception if the test fails
1836      */
1837     @Test
1838     public void onbeforeunloadHandler_cancel() throws Exception {
1839         testOnbeforeunloadHandler(false, "first");
1840     }
1841 
1842     /**
1843      * @param browserVersion the browser version to use
1844      * @param handlerOk whether <tt>OnbeforeunloadHandler.handleEvent</tt> will return {@code true} of {@code false}
1845      * @param expectedPageTitle the expected title of the page after clicking
1846      */
1847     private void testOnbeforeunloadHandler(final boolean handlerOk, final String expectedPageTitle) throws Exception {
1848         final WebClient webClient = getWebClient();
1849         final MockWebConnection webConnection = new MockWebConnection();
1850         final List<String> collectedConfirms = new ArrayList<>();
1851 
1852         webClient.setOnbeforeunloadHandler(new OnbeforeunloadHandler() {
1853             @Override
1854             public boolean handleEvent(final Page page, final String message) {
1855                 collectedConfirms.add(message);
1856                 return handlerOk;
1857             }
1858         });
1859 
1860         final String expectedMessage = "Any string value here forces a dialog box to appear before closing the window.";
1861         final String firstContent = DOCTYPE_HTML
1862             + "<html><head><title>first</title>\n"
1863             + "<script>\n"
1864             + "  function closeIt(event) {\n"
1865             + "    event.returnValue = '" + expectedMessage + "';\n"
1866             + "  }\n"
1867             + "</script>\n"
1868             + "</head><body onbeforeunload='closeIt(event)'>\n"
1869             + "  <a href='" + URL_SECOND + "'>Second page</a>\n"
1870             + "</body></html>";
1871 
1872         final String secondContent = DOCTYPE_HTML
1873             + "<html><head><title>second</title>\n"
1874             + "</head><body>\n"
1875             + "</body></html>";
1876 
1877         webConnection.setResponse(URL_FIRST, firstContent);
1878         webConnection.setResponse(URL_SECOND, secondContent);
1879         webClient.setWebConnection(webConnection);
1880 
1881         final HtmlPage page = webClient.getPage(URL_FIRST);
1882         final HtmlAnchor anchor = page.getAnchors().get(0);
1883         final HtmlPage secondPage = anchor.click();
1884 
1885         assertEquals(new String[] {expectedMessage}, collectedConfirms);
1886         assertEquals(expectedPageTitle, secondPage.getTitleText());
1887     }
1888 
1889     /**
1890      * @throws Exception if an error occurs
1891      */
1892     @Test
1893     public void srcJavaScript() throws Exception {
1894         final String htmlContent = DOCTYPE_HTML
1895             + "<html><head><title>foo</title></head><body>\n"
1896             + "<script id='ie_ready' src='javascript:void(0)'></script>\n"
1897             + "</body></html>";
1898 
1899         loadPage(htmlContent);
1900     }
1901 
1902     /**
1903      * Regression test for asNormalizedText() which would blow up.
1904      *
1905      * @exception Exception If the test fails
1906      */
1907     @Test
1908     public void asNormalizedText() throws Exception {
1909         final String htmlContent = DOCTYPE_HTML
1910             + "<html><head><title>test</title></head>\n"
1911             + "<body><table>\n"
1912             + "<tr><form><td>a</td></form></tr>\n"
1913             + "</table></body></html>";
1914 
1915         final HtmlPage page = loadPage(htmlContent);
1916         assertEquals("test\na", page.asNormalizedText());
1917     }
1918 
1919     /**
1920      * @throws Exception if the test fails
1921      */
1922     @Test
1923     public void getElementsByTagName() throws Exception {
1924         final String firstContent = DOCTYPE_HTML
1925             + "<html><head><title>First</title></head>\n"
1926             + "<body>\n"
1927             + "<form><input type='button' name='button1' value='pushme'></form>\n"
1928             + "<div>a</div> <div>b</div> <div>c</div>\n"
1929             + "</body></html>";
1930 
1931         final HtmlPage page = loadPage(firstContent);
1932 
1933         NodeList inputs = page.getElementsByTagName("input");
1934         assertEquals(1, inputs.getLength());
1935         assertEquals("button", inputs.item(0).getAttributes().getNamedItem("type").getNodeValue());
1936 
1937         final NodeList divs = page.getElementsByTagName("div");
1938         assertEquals(3, divs.getLength());
1939 
1940         final HtmlDivision newDiv = new HtmlDivision(HtmlDivision.TAG_NAME, page, null);
1941         page.getBody().appendChild(newDiv);
1942         assertEquals(4, divs.getLength());
1943 
1944         // case sensitive
1945         inputs = page.getElementsByTagName("inPUT");
1946         assertEquals(1, inputs.getLength());
1947 
1948         // empty
1949         inputs = page.getElementsByTagName("");
1950         assertEquals(0, inputs.getLength());
1951 
1952         // null
1953         inputs = page.getElementsByTagName(null);
1954         assertEquals(0, inputs.getLength());
1955     }
1956 
1957     /**
1958      * HtmlPage.getReadyState() should give the same information than the document element.
1959      * @see <a href="http://sourceforge.net/p/htmlunit/bugs/402/">402</a>
1960      * @exception Exception If the test fails
1961      */
1962     @Test
1963     public void readyState() throws Exception {
1964         final String htmlContent = DOCTYPE_HTML
1965             + "<html><head><title>test</title></head>\n"
1966             + "<body><table>\n"
1967             + "<tr><form><td>a</td></form></tr>\n"
1968             + "</table></body></html>";
1969 
1970         final HtmlPage page = loadPage(htmlContent);
1971         assertEquals(DomNode.READY_STATE_COMPLETE, page.getReadyState());
1972     }
1973 
1974     /**
1975      * @exception Exception If the test fails
1976      */
1977     @Test
1978     public void cloneNode() throws Exception {
1979         final String html = DOCTYPE_HTML
1980             + "<html>\n"
1981             + "<head><title>foo</title></head>\n"
1982             + "<body>\n"
1983             + "<p>hello world</p>\n"
1984             + "</body></html>";
1985         final HtmlPage page = loadPage(html);
1986         page.getByXPath("//p");
1987         final HtmlPage clonedPage = page.cloneNode(true);
1988         clonedPage.getByXPath("//p");
1989     }
1990 
1991     /**
1992      * @exception Exception If the test fails
1993      */
1994     @Test
1995     public void cloneHtmlPageWithFrame() throws Exception {
1996         final String html = DOCTYPE_HTML
1997                 + "<html>\n"
1998                 + "<head></head><body>\n"
1999                 + "<div id='content'>"
2000                 + "  <iframe name='content' src='frame1.html'></iframe>"
2001                 + "</div>"
2002                 + "</body></html>";
2003 
2004         final String frameContent = DOCTYPE_HTML
2005                 + "<html>\n"
2006                 + "<head></head>\n"
2007                 + "<body>"
2008                 + "  <p>frame1</p>"
2009                 + "</body></html>";
2010 
2011         final MockWebConnection webConnection = getMockWebConnection();
2012         webConnection.setResponse(new URL("http://example/index.html"), html);
2013         webConnection.setResponse(new URL("http://example/frame1.html"), frameContent);
2014 
2015         final WebClient webClient = getWebClientWithMockWebConnection();
2016 
2017         final HtmlPage page = webClient.getPage("http://example/index.html");
2018 
2019         // check frame on page
2020         final List<FrameWindow> frames = page.getFrames();
2021         assertEquals(1, frames.size());
2022         assertEquals("frame1", ((HtmlPage) frames.get(0).getEnclosedPage()).asNormalizedText());
2023 
2024         // clone page with deep false
2025         HtmlPage clonedPage = page.cloneNode(false);
2026 
2027         assertEquals(1, page.getFrames().size());
2028         assertEquals(1, clonedPage.getFrames().size());
2029 
2030         // clone page with deep true
2031         clonedPage = page.cloneNode(true);
2032 
2033         // must be equals 1
2034         assertEquals(1, page.getFrames().size());
2035         assertEquals(1, clonedPage.getFrames().size());
2036 
2037         // clone page with deep true
2038         page.executeJavaScript("document.cloneNode(true)");
2039 
2040         // must be equals 1
2041         assertEquals(1, page.getFrames().size());
2042         assertEquals(1, clonedPage.getFrames().size());
2043     }
2044 
2045     /**
2046      * @exception Exception If the test fails
2047      */
2048     @Test
2049     public void addAutoCloseable() throws Exception {
2050         final String html = "";
2051         final HtmlPage page = loadPage(html);
2052         page.addAutoCloseable(new AutoCloseable() {
2053             @Override
2054             public void close() throws Exception {
2055                 page.addAutoCloseable(new AutoCloseable() {
2056                     @Override
2057                     public void close() throws Exception {
2058                         throw new NullPointerException("close failed");
2059                     }
2060                 });
2061             }
2062         });
2063         page.cleanUp();
2064     }
2065 
2066     /**
2067      * @exception Exception If the test fails
2068      */
2069     @Test
2070     public void addAutoCloseableNull() throws Exception {
2071         final String html = "";
2072         final HtmlPage page = loadPage(html);
2073         page.addAutoCloseable(null);
2074         page.cleanUp();
2075     }
2076 
2077     /**
2078      * @exception Exception If the test fails
2079      */
2080     @Test
2081     public void getBaseUrl() throws Exception {
2082         final String html = DOCTYPE_HTML
2083                 + "<html>\n"
2084                 + "<head></head>\n"
2085                 + "<body>body</body>\n"
2086                 + "</html>";
2087 
2088         HtmlPage page = loadPage(getBrowserVersion(), html, null, URL_FIRST);
2089         assertEquals(URL_FIRST.toExternalForm(), page.getBaseURL().toExternalForm());
2090 
2091         // see also org.htmlunit.javascript.host.html.HTMLDocumentTest.baseURI_noBaseTag()
2092         String path = "details/abc";
2093         page = loadPage(getBrowserVersion(), html, null, new URL(URL_FIRST.toString() + path));
2094         assertEquals(URL_FIRST.toExternalForm() + path, page.getBaseURL().toExternalForm());
2095 
2096         path = "details/abc?x=y&z=z";
2097         page = loadPage(getBrowserVersion(), html, null, new URL(URL_FIRST.toString() + path));
2098         assertEquals(URL_FIRST.toExternalForm() + path, page.getBaseURL().toExternalForm());
2099 
2100         path = "details/abc;jsessionid=42?x=y&z=z";
2101         page = loadPage(getBrowserVersion(), html, null, new URL(URL_FIRST.toString() + path));
2102         assertEquals(URL_FIRST.toExternalForm() + path, page.getBaseURL().toExternalForm());
2103     }
2104 }