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 java.net.URL;
18  import java.text.SimpleDateFormat;
19  import java.util.ArrayList;
20  import java.util.Arrays;
21  import java.util.Date;
22  import java.util.List;
23  import java.util.Map;
24  
25  import org.apache.commons.lang3.StringUtils;
26  import org.apache.commons.lang3.time.DateUtils;
27  import org.htmlunit.HttpHeader;
28  import org.htmlunit.WebClient;
29  import org.htmlunit.WebDriverTestCase;
30  import org.htmlunit.WebResponse;
31  import org.htmlunit.junit.BrowserRunner;
32  import org.htmlunit.junit.annotation.Alerts;
33  import org.htmlunit.util.MimeType;
34  import org.htmlunit.util.NameValuePair;
35  import org.junit.Test;
36  import org.junit.runner.RunWith;
37  import org.openqa.selenium.By;
38  import org.openqa.selenium.WebDriver;
39  import org.openqa.selenium.WebElement;
40  import org.openqa.selenium.htmlunit.HtmlUnitDriver;
41  
42  /**
43   * Tests for {@link HtmlLink}.
44   *
45   * @author Ahmed Ashour
46   * @author Marc Guillemot
47   * @author Ronald Brill
48   */
49  @RunWith(BrowserRunner.class)
50  public class HtmlLink2Test extends WebDriverTestCase {
51  
52      /**
53       * @throws Exception if the test fails
54       */
55      @Test
56      @Alerts("[object HTMLLinkElement]")
57      public void simpleScriptable() throws Exception {
58          final String html = DOCTYPE_HTML
59              + "<html><head>\n"
60              + "<link id='myId' href='file1.css'>\n"
61              + "<script>\n"
62              + LOG_TITLE_FUNCTION
63              + "  function test() {\n"
64              + "    log(document.getElementById('myId'));\n"
65              + "  }\n"
66              + "</script>\n"
67              + "</head><body onload='test()'>\n"
68              + "</body></html>";
69  
70          final WebDriver driver = loadPageVerifyTitle2(html);
71          if (driver instanceof HtmlUnitDriver) {
72              final HtmlPage page = (HtmlPage) getEnclosedPage();
73              assertTrue(HtmlLink.class.isInstance(page.getHtmlElementById("myId")));
74          }
75      }
76  
77      /**
78       * Verifies getVisibleText().
79       * @throws Exception if the test fails
80       */
81      @Test
82      @Alerts("")
83      public void getVisibleText() throws Exception {
84          final String htmlContent = DOCTYPE_HTML
85              + "<html>\n"
86              + "<head>\n"
87              + "  <link id='tester' href='file1.css'>\n"
88              + "</head>\n"
89              + "<body>\n"
90              + "</body></html>";
91  
92          final WebDriver driver = loadPage2(htmlContent);
93          final String text = driver.findElement(By.id("tester")).getText();
94          assertEquals(getExpectedAlerts()[0], text);
95  
96          if (driver instanceof HtmlUnitDriver) {
97              final HtmlPage page = (HtmlPage) getEnclosedPage();
98              assertEquals(getExpectedAlerts()[0], page.getBody().getVisibleText());
99          }
100     }
101 
102     /**
103      * @throws Exception if an error occurs
104      */
105     @Test
106     public void isDisplayed() throws Exception {
107         final String html = DOCTYPE_HTML
108                 + "<html>\n"
109                 + "<head>\n"
110                 + "  <link id='l' href='file1.css'>\n"
111                 + "</head>\n"
112                 + "<body>\n"
113                 + "</body>\n"
114                 + "</html>";
115 
116         final WebDriver driver = loadPage2(html);
117         final boolean displayed = driver.findElement(By.id("l")).isDisplayed();
118         assertFalse(displayed);
119     }
120 
121     /**
122      * @throws Exception if an error occurs
123      */
124     @Test
125     @Alerts({"2", "onLoad", "body onLoad"})
126     public void onLoad() throws Exception {
127         getMockWebConnection().setResponse(new URL(URL_FIRST, "simple.css"), "", MimeType.TEXT_CSS);
128         onLoadOnError("rel='stylesheet' href='simple.css'");
129     }
130 
131     /**
132      * @throws Exception if an error occurs
133      */
134     @Test
135     @Alerts({"2", "onLoad", "body onLoad"})
136     public void onLoadRelCase() throws Exception {
137         getMockWebConnection().setResponse(new URL(URL_FIRST, "simple.css"), "", MimeType.TEXT_CSS);
138         onLoadOnError("rel='sTYLeSheet' href='simple.css'");
139     }
140 
141     /**
142      * @throws Exception if an error occurs
143      */
144     @Test
145     @Alerts({"2", "onLoad", "body onLoad"})
146     public void onLoadMediaScreen() throws Exception {
147         getMockWebConnection().setResponse(new URL(URL_FIRST, "simple.css"), "", MimeType.TEXT_CSS);
148         onLoadOnError("rel='stylesheet' href='simple.css' media='screen'");
149     }
150 
151     /**
152      * @throws Exception if an error occurs
153      */
154     @Test
155     @Alerts({"2", "onLoad", "body onLoad"})
156     public void onLoadMediaPrint() throws Exception {
157         getMockWebConnection().setResponse(new URL(URL_FIRST, "simple.css"), "", MimeType.TEXT_CSS);
158         onLoadOnError("rel='stylesheet' href='simple.css' media='print'");
159     }
160 
161     /**
162      * @throws Exception if an error occurs
163      */
164     @Test
165     @Alerts({"2", "onLoad", "body onLoad"})
166     public void onLoadMediaQueryMatch() throws Exception {
167         getMockWebConnection().setResponse(new URL(URL_FIRST, "simple.css"), "", MimeType.TEXT_CSS);
168         onLoadOnError("rel='stylesheet' href='simple.css' media='(min-width: 100px)'");
169     }
170 
171     /**
172      * @throws Exception if an error occurs
173      */
174     @Test
175     @Alerts({"2", "onLoad", "body onLoad"})
176     public void onLoadMediaQueryNotMatch() throws Exception {
177         getMockWebConnection().setResponse(new URL(URL_FIRST, "simple.css"), "", MimeType.TEXT_CSS);
178         onLoadOnError("rel='stylesheet' href='simple.css' media='(max-width: 10px)'");
179     }
180 
181     /**
182      * @throws Exception if an error occurs
183      */
184     @Test
185     @Alerts({"2", "onLoad", "body onLoad"})
186     public void onLoadRelWhitespace() throws Exception {
187         getMockWebConnection().setResponse(new URL(URL_FIRST, "simple.css"), "", MimeType.TEXT_CSS);
188         onLoadOnError("rel='\t stylesheet     ' href='simple.css'");
189     }
190 
191     /**
192      * @throws Exception if an error occurs
193      */
194     @Test
195     @Alerts({"2", "onLoad", "body onLoad"})
196     public void onLoadTypeCss() throws Exception {
197         getMockWebConnection().setResponse(new URL(URL_FIRST, "simple.css"), "", MimeType.TEXT_CSS);
198         onLoadOnError("rel='stylesheet' href='simple.css' type='" + MimeType.TEXT_CSS + "'");
199     }
200 
201     /**
202      * @throws Exception if an error occurs
203      */
204     @Test
205     @Alerts(DEFAULT = {"1", "body onLoad"},
206             FF = {"2", "body onLoad"},
207             FF_ESR = {"2", "body onLoad"})
208     public void onLoadTypePlain() throws Exception {
209         getMockWebConnection().setResponse(new URL(URL_FIRST, "simple.css"), "", MimeType.TEXT_CSS);
210         onLoadOnError("rel='stylesheet' href='simple.css' type='" + MimeType.TEXT_PLAIN + "'");
211     }
212 
213     /**
214      * @throws Exception if an error occurs
215      */
216     @Test
217     @Alerts(DEFAULT = {"1", "body onLoad"},
218             FF = {"2", "body onLoad"},
219             FF_ESR = {"2", "body onLoad"})
220     public void onLoadTypeHtml() throws Exception {
221         getMockWebConnection().setResponse(new URL(URL_FIRST, "simple.css"), "", MimeType.TEXT_CSS);
222         onLoadOnError("rel='stylesheet' href='simple.css' type='" + MimeType.TEXT_HTML + "'");
223     }
224 
225     /**
226      * @throws Exception if an error occurs
227      */
228     @Test
229     @Alerts(DEFAULT = {"1", "body onLoad"},
230             FF = {"2", "body onLoad"},
231             FF_ESR = {"2", "body onLoad"})
232     public void onLoadTypeJs() throws Exception {
233         getMockWebConnection().setResponse(new URL(URL_FIRST, "simple.css"), "", MimeType.TEXT_CSS);
234         onLoadOnError("rel='stylesheet' href='simple.css' type='" + MimeType.TEXT_JAVASCRIPT + "'");
235     }
236 
237     /**
238      * @throws Exception if an error occurs
239      */
240     @Test
241     @Alerts(DEFAULT = {"1", "body onLoad"},
242             FF = {"2", "body onLoad"},
243             FF_ESR = {"2", "body onLoad"})
244     public void onLoadTypeGif() throws Exception {
245         getMockWebConnection().setResponse(new URL(URL_FIRST, "simple.css"), "", MimeType.TEXT_CSS);
246         onLoadOnError("rel='stylesheet' href='simple.css' type='" + MimeType.IMAGE_GIF + "'");
247     }
248 
249 
250     /**
251      * @throws Exception if an error occurs
252      */
253     @Test
254     @Alerts(DEFAULT = {"2", "onLoad", "body onLoad"},
255             FF = {"2", "onError", "body onLoad"},
256             FF_ESR = {"2", "onError", "body onLoad"})
257     public void onLoadResponseTypePlain() throws Exception {
258         getMockWebConnection().setResponse(new URL(URL_FIRST, "simple.css"), "", MimeType.TEXT_PLAIN);
259         onLoadOnError("rel='stylesheet' href='simple.css'");
260     }
261 
262     /**
263      * @throws Exception if an error occurs
264      */
265     @Test
266     @Alerts(DEFAULT = {"2", "onLoad", "body onLoad"},
267             FF = {"2", "onError", "body onLoad"},
268             FF_ESR = {"2", "onError", "body onLoad"})
269     public void onLoadResponseTypeHtml() throws Exception {
270         getMockWebConnection().setResponse(new URL(URL_FIRST, "simple.css"), "", MimeType.TEXT_HTML);
271         onLoadOnError("rel='stylesheet' href='simple.css'");
272     }
273 
274     /**
275      * @throws Exception if an error occurs
276      */
277     @Test
278     @Alerts(DEFAULT = {"2", "onLoad", "body onLoad"},
279             FF = {"2", "onError", "body onLoad"},
280             FF_ESR = {"2", "onError", "body onLoad"})
281     public void onLoadResponseTypeJs() throws Exception {
282         getMockWebConnection().setResponse(new URL(URL_FIRST, "simple.css"), "", MimeType.TEXT_JAVASCRIPT);
283         onLoadOnError("rel='stylesheet' href='simple.css'");
284     }
285 
286     /**
287      * @throws Exception if an error occurs
288      */
289     @Test
290     @Alerts(DEFAULT = {"2", "onLoad", "body onLoad"},
291             FF = {"2", "onError", "body onLoad"},
292             FF_ESR = {"2", "onError", "body onLoad"})
293     public void onLoadResponseTypeGif() throws Exception {
294         getMockWebConnection().setResponse(new URL(URL_FIRST, "simple.css"), "", MimeType.IMAGE_GIF);
295         onLoadOnError("rel='stylesheet' href='simple.css'");
296     }
297 
298     /**
299      * @throws Exception if an error occurs
300      */
301     @Test
302     @Alerts({"2", "onError", "body onLoad"})
303     public void onError() throws Exception {
304         onLoadOnError("rel='stylesheet' href='unknown.css'");
305     }
306 
307     /**
308      * @throws Exception if an error occurs
309      */
310     @Test
311     @Alerts({"1", "body onLoad"})
312     public void onLoadOnErrorWithoutRel() throws Exception {
313         onLoadOnError("href='unknown.css'");
314     }
315 
316     private void onLoadOnError(final String attribs) throws Exception {
317         final String html = DOCTYPE_HTML
318                 + "<html>\n"
319                 + "<head>\n"
320                 + "  <script>\n"
321                 + LOG_TITLE_FUNCTION
322                 + "  </script>\n"
323                 + "  <link " + attribs
324                         + " onload='log(\"onLoad\")' onerror='log(\"onError\")'>\n"
325                 + "</head>\n"
326                 + "<body onload='log(\"body onLoad\")'>\n"
327                 + "</body>\n"
328                 + "</html>";
329         getMockWebConnection().setDefaultResponse("Error: not found", 404, "Not Found", MimeType.TEXT_HTML);
330 
331         final int requests = getMockWebConnection().getRequestCount();
332         final String[] expected = getExpectedAlerts();
333         setExpectedAlerts(Arrays.copyOfRange(expected, 1, expected.length));
334         loadPageVerifyTitle2(html);
335 
336         final int count = Integer.parseInt(expected[0]);
337         assertEquals(count, getMockWebConnection().getRequestCount() - requests);
338     }
339 
340     /**
341      * @throws Exception if an error occurs
342      */
343     @Test
344     @Alerts(DEFAULT = {"4", "onLoad1", "onLoadJs1", "onLoad2", "body onLoad"},
345             FF = {"4", "onLoadJs1", "onLoad2", "body onLoad"},
346             FF_ESR = {"4", "onLoadJs1", "onLoad2", "body onLoad"})
347     public void onLoadOrder() throws Exception {
348         getMockWebConnection().setResponse(new URL(URL_FIRST, "simple1.css"), "");
349         getMockWebConnection().setResponse(new URL(URL_FIRST, "simple2.css"), "", MimeType.TEXT_CSS);
350         getMockWebConnection().setResponse(new URL(URL_FIRST, "simple1.js"), "var x=1;", MimeType.TEXT_JAVASCRIPT);
351 
352         final String html = DOCTYPE_HTML
353                 + "<html>\n"
354                 + "<head>\n"
355                 + "  <script>\n"
356                 + LOG_TITLE_FUNCTION
357                 + "  </script>\n"
358                 + "  <link rel='stylesheet' href='simple1.css' onload='log(\"onLoad1\")'>\n"
359                 + "  <script type='text/javascript' src='simple1.js' onload='log(\"onLoadJs1\")'></script>\n"
360                 + "  <link rel='stylesheet' href='simple2.css' onload='log(\"onLoad2\")'>\n"
361                 + "</head>\n"
362                 + "<body onload='log(\"body onLoad\")'>\n"
363                 + "</body>\n"
364                 + "</html>";
365 
366         final int requests = getMockWebConnection().getRequestCount();
367         final String[] expected = getExpectedAlerts();
368         setExpectedAlerts(Arrays.copyOfRange(expected, 1, expected.length));
369         loadPageVerifyTitle2(html);
370 
371         final int count = Integer.parseInt(expected[0]);
372         assertEquals(count, getMockWebConnection().getRequestCount() - requests);
373     }
374 
375     /**
376      * @throws Exception if an error occurs
377      */
378     @Test
379     @Alerts(DEFAULT = "onLoad [object Event]",
380             FF = "onError [object Event]",
381             FF_ESR = "onError [object Event]")
382     public void onLoadDynamic() throws Exception {
383         getMockWebConnection().setResponse(new URL(URL_FIRST, "simple.css"), "");
384         final String html = DOCTYPE_HTML
385                 + "<html>\n"
386                 + "<head>\n"
387                 + "  <script>\n"
388                 + LOG_TEXTAREA_FUNCTION
389                 + "    function test() {\n"
390                 + "      var dynLink = document.createElement('link');\n"
391                 + "      dynLink.rel = 'stylesheet';\n"
392                 + "      dynLink.type = 'text/css';\n"
393                 + "      dynLink.href = 'simple.css';"
394                 + "      dynLink.onload = function (e) { log(\"onLoad \" + e) };\n"
395                 + "      dynLink.onerror = function (e) { log(\"onError \" + e) };\n"
396                 + "      document.head.appendChild(dynLink);\n"
397                 + "    }\n"
398                 + "  </script>\n"
399                 + "</head>\n"
400                 + "<body onload='test()'></body>\n"
401                 + LOG_TEXTAREA
402                 + "</body>\n"
403                 + "</html>";
404 
405         loadPageVerifyTextArea2(html);
406     }
407 
408     /**
409      * @throws Exception if an error occurs
410      */
411     @Test
412     @Alerts("onError [object Event]")
413     public void onLoadDynamicUnknown() throws Exception {
414         getMockWebConnection().setResponse(new URL(URL_FIRST, "simple.css"), "");
415         final String html = DOCTYPE_HTML
416                 + "<html>\n"
417                 + "<head>\n"
418                 + "  <script>\n"
419                 + LOG_TEXTAREA_FUNCTION
420                 + "    function test() {\n"
421                 + "      var dynLink = document.createElement('link');\n"
422                 + "      dynLink.rel = 'stylesheet';\n"
423                 + "      dynLink.type = 'text/css';\n"
424                 + "      dynLink.href = 'unknown.css';"
425                 + "      dynLink.onload = function (e) { log(\"onLoad \" + e) };\n"
426                 + "      dynLink.onerror = function (e) { log(\"onError \" + e) };\n"
427                 + "      document.head.appendChild(dynLink);\n"
428                 + "    }\n"
429                 + "  </script>\n"
430                 + "</head>\n"
431                 + "<body onload='test()'></body>\n"
432                 + LOG_TEXTAREA
433                 + "</body>\n"
434                 + "</html>";
435         getMockWebConnection().setDefaultResponse("Error: not found", 404, "Not Found", MimeType.TEXT_HTML);
436 
437         loadPageVerifyTextArea2(html);
438     }
439 
440     /**
441      * Test for issue #2011.
442      * @throws Exception if an error occurs
443      */
444     @Test
445     @Alerts({"§§URL§§sample?string=a&int=1", "/sample?string=a&int=1"})
446     public void testEntityRefWithoutSemicolon() throws Exception {
447         final String html = DOCTYPE_HTML
448             + "<html>\n"
449             + "<head></head>\n"
450             + "<body>\n"
451             + "  <a id='target' href='/sample?string=a&int=1'>/sample?string=a&int=1</a>\n"
452             + "</body>\n"
453             + "</html>";
454 
455         expandExpectedAlertsVariables(URL_FIRST);
456         final WebDriver driver = loadPage2(html);
457 
458         final WebElement element = driver.findElement(By.id("target"));
459 
460         assertEquals(getExpectedAlerts()[0], element.getAttribute("href"));
461         assertEquals(getExpectedAlerts()[1], element.getText());
462     }
463 
464     /**
465      * @throws Exception if an error occurs
466      */
467     @Test
468     @Alerts({"§§URL§§sample?string=a&iexcl=1", "/sample?string=a\u00A1=1"})
469     public void testEntityRefWithoutSemicolonReplaceInAttrib() throws Exception {
470         final String html = DOCTYPE_HTML
471             + "<html>\n"
472             + "<head></head>\n"
473             + "<body>\n"
474             + "  <a id='target' href='/sample?string=a&iexcl=1'>/sample?string=a&iexcl=1</a>\n"
475             + "</body>\n"
476             + "</html>";
477         getMockWebConnection().setDefaultResponse("Error: not found", 404, "Not Found", MimeType.TEXT_HTML);
478 
479         expandExpectedAlertsVariables(URL_FIRST);
480         final WebDriver driver = loadPage2(html);
481 
482         final WebElement element = driver.findElement(By.id("target"));
483 
484         assertEquals(getExpectedAlerts()[0], element.getAttribute("href"));
485         assertEquals(getExpectedAlerts()[1], element.getText());
486     }
487 
488     /**
489      * This is a WebDriver test because we need an OnFile
490      * cache entry.
491      *
492      * @throws Exception if the test fails
493      */
494     @Test
495     public void testReloadAfterRemovalFromCache() throws Exception {
496         final WebDriver driver = getWebDriver();
497 
498         int maxInMemory = 0;
499         if (driver instanceof HtmlUnitDriver) {
500             final WebClient webClient = getWebClient();
501             maxInMemory = webClient.getOptions().getMaxInMemory();
502         }
503 
504         final List<NameValuePair> headers = new ArrayList<>();
505         headers.add(new NameValuePair("Expires", new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz").format(new Date(
506                 System.currentTimeMillis()
507                     + (12 * DateUtils.MILLIS_PER_MINUTE)))));
508         final String bigContent = ".someRed { color: red; }" + StringUtils.repeat(' ', maxInMemory);
509 
510         getMockWebConnection().setResponse(new URL(URL_FIRST, "simple.css"),
511                 bigContent,  200, "OK", MimeType.TEXT_CSS, headers);
512 
513         final String html = DOCTYPE_HTML
514             + "<html><head>\n"
515             + "<link id='myId' href='simple.css'></link>\n"
516             + "</head><body>\n"
517             + "</body></html>";
518 
519         loadPage2(html);
520 
521         if (driver instanceof HtmlUnitDriver) {
522             final WebClient webClient = getWebClient();
523 
524             final HtmlPage page = (HtmlPage) getEnclosedPage();
525             final HtmlLink link = page.getFirstByXPath("//link");
526 
527             assertTrue(webClient.getCache().getSize() == 0);
528             WebResponse respCss = link.getWebResponse(true);
529             assertEquals(bigContent, respCss.getContentAsString());
530 
531             assertTrue(webClient.getCache().getSize() > 0);
532 
533             webClient.getCache().clear();
534 
535             respCss = link.getWebResponse(true);
536             // assertTrue(getWebClient().getCache().getSize() > 1);
537             assertEquals(bigContent, respCss.getContentAsString());
538         }
539     }
540 
541     /**
542      * @throws Exception if the test fails
543      */
544     @Test
545     @Alerts("false")
546     public void polymerImportCheck() throws Exception {
547         final String html = DOCTYPE_HTML
548             + "<html><head>\n"
549             + "<script>\n"
550             + LOG_TITLE_FUNCTION
551             + "  function test() {\n"
552             + "    log('import' in document.createElement('link'));\n"
553             + "  }\n"
554             + "</script>\n"
555             + "</head>\n"
556             + "<body onload='test()'>\n"
557             + "</body></html>";
558 
559         loadPageVerifyTitle2(html);
560     }
561 
562     /**
563      * @throws Exception if the test fails
564      */
565     @Test
566     @Alerts("§§URL§§index.html?test")
567     public void getResponse_referer() throws Exception {
568         final String html = DOCTYPE_HTML
569             + "<html><head>\n"
570             + "<link rel='stylesheet' href='" + URL_SECOND + "'></link>\n"
571             + "</head><body>\n"
572             + "</body></html>";
573 
574         expandExpectedAlertsVariables(URL_FIRST);
575 
576         final URL indexUrl = new URL(URL_FIRST.toString() + "index.html");
577 
578         getMockWebConnection().setResponse(indexUrl, html);
579         getMockWebConnection().setResponse(URL_SECOND, "");
580 
581         loadPage2(html, new URL(URL_FIRST.toString() + "index.html?test#ref"));
582 
583         assertEquals(2, getMockWebConnection().getRequestCount());
584 
585         final Map<String, String> lastAdditionalHeaders = getMockWebConnection().getLastAdditionalHeaders();
586         assertEquals(getExpectedAlerts()[0], lastAdditionalHeaders.get(HttpHeader.REFERER));
587     }
588 }