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 org.htmlunit.MockWebConnection;
21  import org.htmlunit.SimpleWebTestCase;
22  import org.htmlunit.WebClient;
23  import org.htmlunit.javascript.background.JavaScriptJobManager;
24  import org.junit.jupiter.api.Test;
25  
26  /**
27   * Unit tests for {@link HtmlInlineFrame}.
28   *
29   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
30   * @author Ahmed Ashour
31   * @author Marc Guillemot
32   * @author Daniel Gredler
33   * @author Ronald Brill
34   */
35  public class HtmlInlineFrameTest extends SimpleWebTestCase {
36  
37      /**
38       * @throws Exception if the test fails
39       */
40      @Test
41      public void setSrcAttribute() throws Exception {
42          final String firstContent = DOCTYPE_HTML
43              + "<html><head><title>First</title></head><body>\n"
44              + "<iframe id='iframe1' src='" + URL_SECOND + "'>\n"
45              + "</body></html>";
46          final String secondContent = DOCTYPE_HTML + "<html><head><title>Second</title></head><body></body></html>";
47          final String thirdContent = DOCTYPE_HTML + "<html><head><title>Third</title></head><body></body></html>";
48          final WebClient client = getWebClientWithMockWebConnection();
49  
50          final MockWebConnection webConnection = getMockWebConnection();
51          webConnection.setResponse(URL_FIRST, firstContent);
52          webConnection.setResponse(URL_SECOND, secondContent);
53          webConnection.setResponse(URL_THIRD, thirdContent);
54  
55          final HtmlPage page = client.getPage(URL_FIRST);
56          assertEquals("First", page.getTitleText());
57  
58          final HtmlInlineFrame iframe = page.getHtmlElementById("iframe1");
59          assertEquals(URL_SECOND.toExternalForm(), iframe.getSrcAttribute());
60          assertEquals("Second", ((HtmlPage) iframe.getEnclosedPage()).getTitleText());
61  
62          iframe.setSrcAttribute(URL_THIRD.toExternalForm());
63          assertEquals(URL_THIRD.toExternalForm(), iframe.getSrcAttribute());
64          assertEquals("Third", ((HtmlPage) iframe.getEnclosedPage()).getTitleText());
65      }
66  
67      /**
68       * @throws Exception if the test fails
69       */
70      @Test
71      public void setSrcAttributeWithWhiteSpaces() throws Exception {
72          final String firstContent = DOCTYPE_HTML
73              + "<html><head><title>First</title></head><body>\n"
74              + "<iframe id='iframe1' src='\n" + URL_SECOND + "\n'>\n"
75              + "</body></html>";
76          final String secondContent = DOCTYPE_HTML + "<html><head><title>Second</title></head><body></body></html>";
77          final String thirdContent = DOCTYPE_HTML + "<html><head><title>Third</title></head><body></body></html>";
78          final WebClient client = getWebClientWithMockWebConnection();
79  
80          final MockWebConnection webConnection = getMockWebConnection();
81          webConnection.setResponse(URL_FIRST, firstContent);
82          webConnection.setResponse(URL_SECOND, secondContent);
83          webConnection.setResponse(URL_THIRD, thirdContent);
84  
85          final HtmlPage page = client.getPage(URL_FIRST);
86          assertEquals("First", page.getTitleText());
87  
88          final HtmlInlineFrame iframe = page.getHtmlElementById("iframe1");
89          assertEquals(URL_SECOND.toExternalForm(), iframe.getSrcAttribute());
90          assertEquals("Second", ((HtmlPage) iframe.getEnclosedPage()).getTitleText());
91  
92          iframe.setSrcAttribute(URL_THIRD.toExternalForm());
93          assertEquals(URL_THIRD.toExternalForm(), iframe.getSrcAttribute());
94          assertEquals("Third", ((HtmlPage) iframe.getEnclosedPage()).getTitleText());
95      }
96  
97      /**
98       * Tests that a recursive src attribute (i.e. src="#xyz") doesn't result in an
99       * infinite loop (bug #459).
100      *
101      * @throws Exception if an error occurs
102      */
103     @Test
104     public void recursiveSrcAttribute() throws Exception {
105         final String html = DOCTYPE_HTML + "<html><body><iframe id='a' src='#abc'></body></html>";
106         final HtmlPage page = loadPage(html);
107         final HtmlInlineFrame iframe = page.getHtmlElementById("a");
108         assertNotNull(iframe.getEnclosedPage());
109     }
110 
111     /**
112      * Tests that a recursive src is prevented.
113      * @throws Exception if an error occurs
114      */
115     @Test
116     public void recursiveNestedFrames() throws Exception {
117         final String firstContent = DOCTYPE_HTML
118             + "<html><head><title>First</title></head><body>\n"
119             + "<iframe id='iframe1' src='" + URL_SECOND + "'>\n"
120             + "</body></html>";
121         final String secondContent = DOCTYPE_HTML
122             + "<html><head><title>Second</title></head>\n"
123             + "<body><iframe id='iframe2_1' src='" + URL_FIRST + "'></iframe></body></html>";
124         final WebClient client = getWebClientWithMockWebConnection();
125 
126         final MockWebConnection webConnection = getMockWebConnection();
127         webConnection.setResponse(URL_FIRST, firstContent);
128         webConnection.setResponse(URL_SECOND, secondContent);
129 
130         final HtmlPage page = client.getPage(URL_FIRST);
131         assertEquals("First", page.getTitleText());
132 
133         final HtmlInlineFrame iframe = page.getHtmlElementById("iframe1");
134         assertEquals(URL_SECOND.toExternalForm(), iframe.getSrcAttribute());
135         final HtmlPage iframePage = (HtmlPage) iframe.getEnclosedPage();
136         assertEquals("Second", iframePage.getTitleText());
137 
138         // the nested frame should not have been loaded
139         final HtmlInlineFrame iframeIn2 = iframePage.getHtmlElementById("iframe2_1");
140         assertEquals(URL_FIRST.toExternalForm(), iframeIn2.getSrcAttribute());
141         final HtmlPage iframePage2 = (HtmlPage) iframeIn2.getEnclosedPage();
142         assertEquals(URL_FIRST.toExternalForm(), iframeIn2.getEnclosedPage().getUrl());
143 
144         // the nested nested frame should not have been loaded
145         final HtmlInlineFrame iframeIn3 = iframePage2.getHtmlElementById("iframe1");
146         assertEquals(URL_SECOND.toExternalForm(), iframeIn3.getSrcAttribute());
147         assertEquals("about:blank", iframeIn3.getEnclosedPage().getUrl());
148     }
149 
150     /**
151      * Tests that an invalid src attribute (i.e. src="foo://bar") doesn't result
152      * in a NPE (bug #458).
153      *
154      * @throws Exception if an error occurs
155      */
156     @Test
157     public void invalidSrcAttribute() throws Exception {
158         final String html = DOCTYPE_HTML + "<html><body><iframe id='a' src='foo://bar'></body></html>";
159         final HtmlPage page = loadPage(html);
160         final HtmlInlineFrame iframe = page.getHtmlElementById("a");
161         assertNotNull(iframe.getEnclosedPage());
162     }
163 
164     /**
165      * @throws Exception if the test fails
166      */
167     @Test
168     public void setSrcAttribute_ViaJavaScript() throws Exception {
169         final String firstContent = DOCTYPE_HTML
170             + "<html><head><title>First</title></head><body>\n"
171             + "<iframe id='iframe1' src='" + URL_SECOND + "'></iframe>\n"
172             + "<script type='text/javascript'>document.getElementById('iframe1').src = '" + URL_THIRD + "';\n"
173             + "</script></body></html>";
174         final String secondContent = DOCTYPE_HTML + "<html><head><title>Second</title></head><body></body></html>";
175         final String thirdContent = DOCTYPE_HTML + "<html><head><title>Third</title></head><body></body></html>";
176         final WebClient client = getWebClientWithMockWebConnection();
177 
178         final MockWebConnection webConnection = getMockWebConnection();
179         webConnection.setResponse(URL_FIRST, firstContent);
180         webConnection.setResponse(URL_SECOND, secondContent);
181         webConnection.setResponse(URL_THIRD, thirdContent);
182 
183         final HtmlPage page = client.getPage(URL_FIRST);
184         assertEquals("First", page.getTitleText());
185 
186         final HtmlInlineFrame iframe = page.getHtmlElementById("iframe1");
187         assertEquals(URL_THIRD.toExternalForm(), iframe.getSrcAttribute());
188         assertEquals("Third", ((HtmlPage) iframe.getEnclosedPage()).getTitleText());
189     }
190 
191     /**
192      * Verifies that cloned frames do no reload their content (bug #613).
193      * @throws Exception if an error occurs
194      */
195     @Test
196     public void frameCloneDoesNotReloadFrame() throws Exception {
197         final String html1 = DOCTYPE_HTML + "<html><body><iframe src='" + URL_SECOND + "'></iframe></body></html>";
198         final String html2 = DOCTYPE_HTML + "<html><body>abc</body></html>";
199 
200         final WebClient client = getWebClientWithMockWebConnection();
201 
202         final MockWebConnection conn = getMockWebConnection();
203         conn.setResponse(URL_FIRST, html1);
204         conn.setResponse(URL_SECOND, html2);
205 
206         final HtmlPage page = client.getPage(URL_FIRST);
207         assertEquals(2, conn.getRequestCount());
208 
209         page.cloneNode(true);
210         assertEquals(2, conn.getRequestCount());
211     }
212 
213     /**
214      * Verifies that frames added via document.write() don't get their contents loaded twice (bug #238).
215      * @throws Exception if an error occurs
216      */
217     @Test
218     public void frameWriteDoesNotReloadFrame() throws Exception {
219         final String html1 = DOCTYPE_HTML
220             + "<html><body>\n"
221             + "<script>document.write('<iframe id=\"f\" src=\"" + URL_SECOND + "\"></iframe>')</script>\n"
222             + "</body></html>";
223         final String html2 = DOCTYPE_HTML + "<html><body>iframe content</body></html>";
224 
225         final WebClient client = getWebClientWithMockWebConnection();
226 
227         final MockWebConnection conn = getMockWebConnection();
228         conn.setResponse(URL_FIRST, html1);
229         conn.setResponse(URL_SECOND, html2);
230 
231         final HtmlPage page = client.getPage(URL_FIRST);
232         final HtmlElement iFrame = page.getHtmlElementById("f");
233 
234         assertEquals("iframe", iFrame.getTagName());
235 
236         final HtmlPage enclosedPage = (HtmlPage) ((HtmlInlineFrame) iFrame).getEnclosedPage();
237         assertEquals("iframe content", enclosedPage.asNormalizedText());
238 
239         assertEquals(2, conn.getRequestCount());
240     }
241 
242     /**
243      * Verifies that frames added via element.innerHtml() are resolved.
244      * @throws Exception if an error occurs
245      */
246     @Test
247     public void frameSetInnerHtmlDoesLoadFrame() throws Exception {
248         final String html1 = DOCTYPE_HTML
249             + "<html><body>\n"
250             + "<iframe id='myFrame' src='" + URL_THIRD + "'></iframe>';\n"
251             + "<span id='A'></span>\n"
252             + "<script>\n"
253             + "  var frame='<iframe id=\"f\" src=\"" + URL_SECOND + "\"></iframe>';\n"
254             + "  document.getElementById('A').innerHTML = frame;\n"
255             + "</script>\n"
256             + "</body></html>";
257         final String html2 = DOCTYPE_HTML + "<html><body>iframe content</body></html>";
258         final String html3 = DOCTYPE_HTML + "<html><head></head><body>Third content</body></html>";
259 
260         final WebClient client = getWebClientWithMockWebConnection();
261 
262         final MockWebConnection conn = getMockWebConnection();
263         conn.setResponse(URL_FIRST, html1);
264         conn.setResponse(URL_SECOND, html2);
265         conn.setResponse(URL_THIRD, html3);
266 
267         final HtmlPage page = client.getPage(URL_FIRST);
268 
269         final HtmlElement myFrame = page.getHtmlElementById("myFrame");
270         assertEquals("iframe", myFrame.getTagName());
271 
272         HtmlPage enclosedPage = (HtmlPage) ((HtmlInlineFrame) myFrame).getEnclosedPage();
273         assertEquals("Third content", enclosedPage.asNormalizedText());
274 
275         final HtmlElement iFrame = page.getHtmlElementById("f");
276         assertEquals("iframe", iFrame.getTagName());
277 
278         enclosedPage = (HtmlPage) ((HtmlInlineFrame) iFrame).getEnclosedPage();
279         assertEquals("iframe content", enclosedPage.asNormalizedText());
280 
281         assertEquals(3, conn.getRequestCount());
282     }
283 
284     /**
285      * Verifies that frames added via element.innerHtml() are resolved.
286      * @throws Exception if an error occurs
287      */
288     @Test
289     public void frameSetInnerHtmlDoesLoadFrameContentTimeout() throws Exception {
290         final String html1 = DOCTYPE_HTML
291             + "<html><body>\n"
292             + "<iframe id='myFrame' src='" + URL_THIRD + "'></iframe>';\n"
293             + "<span id='A'></span>\n"
294             + "<script>\n"
295             + "  function createIframe() {\n"
296             + "    var frame='<iframe id=\"f\" src=\"" + URL_SECOND + "\"></iframe>';\n"
297             + "    document.getElementById('A').innerHTML = frame;\n"
298             + "  }\n"
299             + "  setTimeout('createIframe()', 100);\n"
300             + "</script>\n"
301             + "</body></html>";
302         final String html2 = DOCTYPE_HTML + "<html><body>iframe content</body></html>";
303         final String html3 = DOCTYPE_HTML + "<html><head></head><body>Third content</body></html>";
304 
305         final WebClient client = getWebClientWithMockWebConnection();
306 
307         final MockWebConnection conn = getMockWebConnection();
308         conn.setResponse(URL_FIRST, html1);
309         conn.setResponse(URL_SECOND, html2);
310         conn.setResponse(URL_THIRD, html3);
311 
312         final HtmlPage page = client.getPage(URL_FIRST);
313 
314         final HtmlElement myFrame = page.getHtmlElementById("myFrame");
315         assertEquals("iframe", myFrame.getTagName());
316 
317         HtmlPage enclosedPage = (HtmlPage) ((HtmlInlineFrame) myFrame).getEnclosedPage();
318         assertEquals("Third content", enclosedPage.asNormalizedText());
319 
320         // wait for the timer
321         final JavaScriptJobManager jobManager = page.getEnclosingWindow().getJobManager();
322         jobManager.waitForJobs(1000);
323 
324         final HtmlElement iFrame = page.getHtmlElementById("f");
325         assertEquals("iframe", iFrame.getTagName());
326 
327         enclosedPage = (HtmlPage) ((HtmlInlineFrame) iFrame).getEnclosedPage();
328         assertEquals("iframe content", enclosedPage.asNormalizedText());
329 
330         assertEquals(3, conn.getRequestCount());
331     }
332 
333     /**
334      * Issue 3046109.
335      * The iframe has no source and is filled from javascript.
336      * @throws Exception if an error occurs
337      */
338     @Test
339     public void frameContentCreationViaJavascript() throws Exception {
340         final String html = DOCTYPE_HTML
341             + "<html><head><title>frames</title></head>\n"
342             + "<body>\n"
343             + "<iframe name='foo'></iframe>\n"
344             + "<script type='text/javascript'>\n"
345             + "var doc = window.frames['foo'].document;\n"
346             + "doc.open();\n"
347             + "doc.write('<html><body><div id=\"myContent\">Hi Folks!</div></body></html>');\n"
348             + "doc.close();\n"
349             + "</script>\n"
350             + "<body>\n"
351             + "</html>";
352 
353         final WebClient webClient = getWebClientWithMockWebConnection();
354         final MockWebConnection conn = getMockWebConnection();
355 
356         conn.setDefaultResponse(html);
357         final HtmlPage page = webClient.getPage(URL_FIRST);
358 
359         final HtmlPage enclosedPage = (HtmlPage) page.getFrames().get(0).getEnclosedPage();
360         final HtmlElement element = enclosedPage.getHtmlElementById("myContent");
361         final String content = element.asNormalizedText();
362         assertEquals("Hi Folks!", content);
363     }
364 
365     /**
366      * The iframe has no source and is filled from javascript.
367      * The javascript writes unicode charactes into the iframe content.
368      * @throws Exception if an error occurs
369      */
370     @Test
371     public void frameContentCreationViaJavascriptUnicode() throws Exception {
372         final String html = DOCTYPE_HTML
373             + "<html><head><title>frames</title></head>\n"
374             + "<body>\n"
375             + "<iframe name='foo'></iframe>\n"
376             + "<script type='text/javascript'>\n"
377             + "var doc = window.frames['foo'].document;\n"
378             + "doc.open();\n"
379             + "doc.write('<html><body><div id=\"myContent\">Hello worl\u0414</div></body></html>');\n"
380             + "doc.close();\n"
381             + "</script>\n"
382             + "<body>\n"
383             + "</html>";
384 
385         final WebClient webClient = getWebClientWithMockWebConnection();
386         final MockWebConnection conn = getMockWebConnection();
387 
388         conn.setResponse(URL_FIRST, html, "text/html; charset=UTF-8", UTF_8);
389         final HtmlPage page = webClient.getPage(URL_FIRST);
390 
391         final HtmlPage enclosedPage = (HtmlPage) page.getFrames().get(0).getEnclosedPage();
392         final HtmlElement element = enclosedPage.getHtmlElementById("myContent");
393         final String content = element.asNormalizedText();
394         assertEquals("Hello worl\u0414", content);
395     }
396 
397     /**
398      * The iframe has no source and is filled from javascript.
399      * The javascript writes windows characters into the iframe content.
400      * @throws Exception if an error occurs
401      */
402     @Test
403     public void frameContentCreationViaJavascriptISO_8859_1() throws Exception {
404         final String html = DOCTYPE_HTML
405             + "<html><head><title>frames</title></head>\n"
406             + "<body>\n"
407             + "<iframe name='foo'></iframe>\n"
408             + "<script type='text/javascript'>\n"
409             + "var doc = window.frames['foo'].document;\n"
410             + "doc.open();\n"
411             + "doc.write('<html><body><div id=\"myContent\">\u00e4\u00f6\u00fc</div></body></html>');\n"
412             + "doc.close();\n"
413             + "</script>\n"
414             + "<body>\n"
415             + "</html>";
416 
417         final WebClient webClient = getWebClientWithMockWebConnection();
418         final MockWebConnection conn = getMockWebConnection();
419         webClient.setWebConnection(conn);
420 
421         conn.setResponse(URL_FIRST, html, "text/html; charset=ISO-8859-1", ISO_8859_1);
422         final HtmlPage page = webClient.getPage(URL_FIRST);
423 
424         final HtmlPage enclosedPage = (HtmlPage) page.getFrames().get(0).getEnclosedPage();
425         final HtmlElement element = enclosedPage.getHtmlElementById("myContent");
426         final String content = element.asNormalizedText();
427         assertEquals("\u00e4\u00f6\u00fc", content);
428     }
429 
430     /**
431      * Issue 3046109.
432      * The iframe has a source but is filled from javascript before.
433      * @throws Exception if an error occurs
434      */
435     @Test
436     public void frameContentCreationViaJavascriptBeforeFrameResolved() throws Exception {
437         final String html = DOCTYPE_HTML
438             + "<html><head><title>frames</title></head>\n"
439             + "<body>\n"
440             + "<iframe name='foo' src='" + URL_SECOND + "'></iframe>\n"
441             + "<script type='text/javascript'>\n"
442             + "var doc = window.frames['foo'].document;\n"
443             + "doc.open();\n"
444             + "doc.write('<html><body><div id=\"myContent\">Hi Folks!</div></body></html>');\n"
445             + "doc.close();\n"
446             + "</script>\n"
447             + "<body>\n"
448             + "</html>";
449 
450         final String frameHtml = DOCTYPE_HTML
451             + "<html><head><title>inside frame</title></head>\n"
452             + "<body>\n"
453             + "<div id=\"myContent\">inside frame</div>\n"
454             + "<body>\n"
455             + "</html>";
456 
457         final WebClient webClient = getWebClient();
458         final MockWebConnection conn = new MockWebConnection();
459         webClient.setWebConnection(conn);
460 
461         conn.setResponse(URL_FIRST, html);
462         conn.setResponse(URL_SECOND, frameHtml);
463         final HtmlPage page = webClient.getPage(URL_FIRST);
464 
465         final HtmlPage enclosedPage = (HtmlPage) page.getFrames().get(0).getEnclosedPage();
466         final HtmlElement element = enclosedPage.getHtmlElementById("myContent");
467         final String content = element.asNormalizedText();
468         assertEquals("Hi Folks!", content);
469     }
470 
471     /**
472      * @throws Exception if the test fails
473      */
474     @Test
475     public void asNormalizedText() throws Exception {
476         final String firstContent = DOCTYPE_HTML
477             + "<html><head><title>First</title></head><body>\n"
478             + "before<iframe id='iframe1' src='" + URL_SECOND + "'></iframe>after\n"
479             + "</body></html>";
480         final String secondContent = DOCTYPE_HTML + "<html><head></head><body>Second content</body></html>";
481         final WebClient client = getWebClientWithMockWebConnection();
482 
483         final MockWebConnection webConnection = getMockWebConnection();
484         webConnection.setResponse(URL_FIRST, firstContent);
485         webConnection.setResponse(URL_SECOND, secondContent);
486 
487         final HtmlPage page = client.getPage(URL_FIRST);
488         assertEquals("First\nbefore\nSecond content\nafter", page.asNormalizedText());
489     }
490 
491     /**
492      * See issue #1825.
493      * @exception Exception If the test fails
494      */
495     @Test
496     public void brokenIframe() throws Exception {
497         final String html = DOCTYPE_HTML
498                 + "<html>\n"
499                 + "<head></head>\n"
500                 + "<body>"
501                 + "1<div>2<iframe/>3</div>4"
502                 + "</body>\n"
503                 + "</html>";
504 
505         final HtmlPage page = loadPage(html);
506         assertEquals("1\n2", page.asNormalizedText());
507     }
508 }