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