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.javascript.host;
16  
17  import org.htmlunit.WebDriverTestCase;
18  import org.htmlunit.junit.annotation.Alerts;
19  import org.junit.jupiter.api.Test;
20  import org.openqa.selenium.By;
21  import org.openqa.selenium.WebDriver;
22  
23  /**
24   * Tests postMessage within the Windows context.
25   *
26   * @author Marc Guillemot
27   * @author Ahmed Ashour
28   * @author Ronald Brill
29   * @author Thorsten Wendelmuth
30   */
31  public class WindowPostMessageTest extends WebDriverTestCase {
32  
33      /**
34       * @throws Exception if the test fails
35       */
36      @Test
37      @Alerts({"type: message", "bubbles: false", "cancelable: false", "data: hello",
38               "origin: ", "source: true false", "lastEventId: "})
39      public void postMessage() throws Exception {
40          final String[] expectedAlerts = getExpectedAlerts();
41          expectedAlerts[4] += "http://127.0.0.1:" + PORT;
42          setExpectedAlerts(expectedAlerts);
43  
44          final String html = DOCTYPE_HTML
45              + "<html>\n"
46              + "<head></head>\n"
47              + "<body>\n"
48              + "  <iframe id='myFrame' src='" + URL_THIRD + "'></iframe>\n"
49              + "<script>\n"
50              + LOG_TITLE_FUNCTION
51              + "  var win = document.getElementById('myFrame').contentWindow;\n"
52  
53              + "  function receiveMessage(event) {\n"
54              + "    log('type: ' + event.type);\n"
55              + "    log('bubbles: ' + event.bubbles);\n"
56              + "    log('cancelable: ' + event.cancelable);\n"
57              + "    log('data: ' + event.data);\n"
58              + "    log('origin: ' + event.origin);\n"
59              + "    log('source: ' + (event.source === win) + ' ' + (event.source === window));\n"
60              + "    log('lastEventId: ' + event.lastEventId);\n"
61              + "  }\n"
62  
63              + "  window.addEventListener('message', receiveMessage, false);\n"
64              + "</script>\n"
65              + "</body></html>";
66  
67          final String iframe = DOCTYPE_HTML
68              + "<html><body><script>\n"
69              + "  top.postMessage('hello', '*');\n"
70              + "</script></body></html>";
71  
72          getMockWebConnection().setResponse(URL_THIRD, iframe);
73          loadPageVerifyTitle2(html);
74      }
75  
76      /**
77       * @throws Exception if the test fails
78       */
79      @Test
80      @Alerts({"type: message", "bubbles: false", "cancelable: false", "data: hello",
81               "origin: ", "source: false true", "lastEventId: "})
82      public void postMessageFromIframe() throws Exception {
83          final String[] expectedAlerts = getExpectedAlerts();
84          expectedAlerts[4] += "http://localhost:" + PORT;
85          setExpectedAlerts(expectedAlerts);
86  
87          final String html = DOCTYPE_HTML
88              + "<html>\n"
89              + "<head></head>\n"
90              + "<body>\n"
91              + "  <iframe id='myFrame' src='" + URL_THIRD + "'></iframe>\n"
92  
93              + "<script>\n"
94              + LOG_TITLE_FUNCTION
95              + "  var win = document.getElementById('myFrame').contentWindow;\n"
96  
97              + "  function receiveMessage(event) {\n"
98              + "    log('type: ' + event.type);\n"
99              + "    log('bubbles: ' + event.bubbles);\n"
100             + "    log('cancelable: ' + event.cancelable);\n"
101             + "    log('data: ' + event.data);\n"
102             + "    log('origin: ' + event.origin);\n"
103             + "    log('source: ' + (event.source === win) + ' ' + (event.source === window));\n"
104             + "    log('lastEventId: ' + event.lastEventId);\n"
105             + "  }\n"
106 
107             + "  win.addEventListener('message', receiveMessage, false);\n"
108             + "  win.postMessage('hello', '*');\n"
109             + "</script>\n"
110             + "</body></html>";
111 
112         final String iframe = DOCTYPE_HTML
113                 + "<html><body><p>inside frame</p></body></html>";
114 
115         getMockWebConnection().setResponse(URL_SECOND, iframe);
116         loadPageVerifyTitle2(html);
117     }
118 
119     /**
120      * @throws Exception if the test fails
121      */
122     @Test
123     @Alerts("TypeError")
124     public void postMessageMissingParameters() throws Exception {
125         final String html = DOCTYPE_HTML
126             + "<html>\n"
127             + "<head></head>\n"
128             + "<body>\n"
129             + "<script>\n"
130             + LOG_TITLE_FUNCTION
131             + "  try {\n"
132             + "    window.postMessage();\n"
133             + "  } catch(e) {\n"
134             + "    logEx(e);\n"
135             + "  }\n"
136             + "</script>\n"
137             + "</body></html>";
138 
139         loadPageVerifyTitle2(html);
140     }
141 
142     /**
143      * @throws Exception if the test fails
144      */
145     @Test
146     @Alerts({"type: message", "bubbles: false", "cancelable: false", "data: hello",
147              "origin: ", "source: false true", "lastEventId: "})
148     public void postMessageWithoutTargetOrigin() throws Exception {
149         final String[] expectedAlerts = getExpectedAlerts();
150         expectedAlerts[4] += "http://localhost:" + PORT;
151         setExpectedAlerts(expectedAlerts);
152 
153         final String html = DOCTYPE_HTML
154             + "<html>\n"
155             + "<head></head>\n"
156             + "<body>\n"
157             + "  <iframe id='myFrame' src='" + URL_THIRD + "'></iframe>\n"
158 
159             + "<script>\n"
160             + LOG_TITLE_FUNCTION
161             + "  var win = document.getElementById('myFrame').contentWindow;\n"
162 
163             + "  function receiveMessage(event) {\n"
164             + "    log('type: ' + event.type);\n"
165             + "    log('bubbles: ' + event.bubbles);\n"
166             + "    log('cancelable: ' + event.cancelable);\n"
167             + "    log('data: ' + event.data);\n"
168             + "    log('origin: ' + event.origin);\n"
169             + "    log('source: ' + (event.source === win) + ' ' + (event.source === window));\n"
170             + "    log('lastEventId: ' + event.lastEventId);\n"
171             + "  }\n"
172 
173             + "  win.addEventListener('message', receiveMessage, false);\n"
174             + "  win.postMessage('hello');\n"
175             + "</script>\n"
176             + "</body></html>";
177 
178         final String iframe = DOCTYPE_HTML
179                 + "<html><body><p>inside frame</p></body></html>";
180 
181         getMockWebConnection().setResponse(URL_THIRD, iframe);
182         loadPageVerifyTitle2(html);
183     }
184 
185     /**
186      * Test for #1589 NullPointerException because of missing context.
187      *
188      * @throws Exception if the test fails
189      */
190     @Test
191     @Alerts({"data: hello", "source: true false"})
192     public void postMessageFromClick() throws Exception {
193         final String html = DOCTYPE_HTML
194             + "<html>\n"
195             + "<head></head>\n"
196             + "<body>\n"
197             + "<script>\n"
198             + LOG_TITLE_FUNCTION
199             + "  function receiveMessage(event) {\n"
200             + "    log('data: ' + event.data);\n"
201             + "    var win = document.getElementById('myFrame').contentWindow;\n"
202             + "    log('source: ' + (event.source === win) + ' ' + (event.source === window));\n"
203             + "  }\n"
204 
205             + "  window.addEventListener('message', receiveMessage, false);\n"
206             + "</script>\n"
207             + "  <iframe id='myFrame' src='" + URL_THIRD + "'></iframe>\n"
208             + "</body></html>";
209 
210         final String iframe = "<html><body>\n"
211             + "  <button id='clickme' onclick='top.postMessage(\"hello\", \"*\");'>Click me</a>\n"
212             + "</body></html>";
213 
214         getMockWebConnection().setResponse(URL_THIRD, iframe);
215         final WebDriver driver = loadPage2(html);
216         driver.switchTo().frame("myFrame");
217         driver.findElement(By.id("clickme")).click();
218 
219         verifyTitle2(driver, getExpectedAlerts());
220     }
221 
222     /**
223      * @throws Exception if the test fails
224      */
225     @Test
226     @Alerts("sync: false")
227     public void postMessageSyncOrAsync() throws Exception {
228         final String html = DOCTYPE_HTML
229             + "<html>\n"
230             + "<head></head>\n"
231             + "<body>\n"
232             + "<script>\n"
233             + LOG_TITLE_FUNCTION
234             + "  var sync = true;\n"
235             + "  function receiveMessage(event) {\n"
236             + "    log('sync: ' + sync);\n"
237             + "  }\n"
238             + "  window.addEventListener('message', receiveMessage, false);\n"
239             + "</script>\n"
240             + "  <iframe src='" + URL_SECOND + "'></iframe>\n"
241             + "</body></html>";
242 
243         final String iframe = DOCTYPE_HTML
244             + "<html><body><script>\n"
245             + "  top.postMessage('hello', '*');\n"
246             + "  top.sync = false;\n"
247             + "</script></body></html>";
248 
249         getMockWebConnection().setResponse(URL_SECOND, iframe);
250         loadPageVerifyTitle2(html);
251     }
252 
253     /**
254      * @throws Exception if the test fails
255      */
256     @Test
257     @Alerts("received")
258     public void postMessage_exactURL() throws Exception {
259         postMessage(URL_FIRST.toExternalForm());
260     }
261 
262     /**
263      * @throws Exception if the test fails
264      */
265     @Test
266     @Alerts({})
267     public void postMessage_slash() throws Exception {
268         postMessage("/");
269     }
270 
271     /**
272      * @throws Exception if the test fails
273      */
274     @Test
275     @Alerts("received")
276     public void postMessageSameOrigin_slash() throws Exception {
277         postMessageSameOrigin("/");
278     }
279 
280     /**
281      * @throws Exception if the test fails
282      */
283     @Test
284     @Alerts({})
285     public void postMessage_otherHost() throws Exception {
286         postMessage("http://127.0.0.1:" + PORT + "/");
287     }
288 
289     /**
290      * @throws Exception if the test fails
291      */
292     @Test
293     @Alerts({})
294     public void postMessageSameOrigin_otherHost() throws Exception {
295         postMessageSameOrigin("http://127.0.0.1:" + PORT + "/");
296     }
297 
298     /**
299      * @throws Exception if the test fails
300      */
301     @Test
302     @Alerts({})
303     public void postMessage_otherPort() throws Exception {
304         postMessage("http://localhost:" + (PORT + 1) + "/");
305     }
306 
307     /**
308      * @throws Exception if the test fails
309      */
310     @Test
311     @Alerts({})
312     public void postMessageSameOrigin_otherPort() throws Exception {
313         postMessageSameOrigin("http://localhost:" + (PORT + 1) + "/");
314     }
315 
316     /**
317      * @throws Exception if the test fails
318      */
319     @Test
320     @Alerts({})
321     public void postMessage_otherProtocol() throws Exception {
322         postMessage("https://localhost:" + PORT + "/");
323     }
324 
325     /**
326      * @throws Exception if the test fails
327      */
328     @Test
329     @Alerts({})
330     public void postMessageSameOrigin_otherProtocol() throws Exception {
331         postMessageSameOrigin("https://localhost:" + PORT + "/");
332     }
333 
334     private void postMessage(final String url) throws Exception {
335         final String html = DOCTYPE_HTML
336             + "<html>\n"
337             + "<head></head>\n"
338             + "<body>\n"
339             + "<script>\n"
340             + LOG_TITLE_FUNCTION
341             + "  function receiveMessage(event) {\n"
342             + "    log('received');\n"
343             + "  }\n"
344             + "  window.addEventListener('message', receiveMessage, false);\n"
345             + "</script>\n"
346             + "  <iframe src='" + URL_THIRD + "'></iframe>\n"
347             + "</body></html>";
348 
349         final String iframe = DOCTYPE_HTML
350             + "<html><body><script>\n"
351             + "  top.postMessage('hello', '" + url + "');\n"
352             + "</script></body></html>";
353 
354         getMockWebConnection().setResponse(URL_THIRD, iframe);
355         loadPageVerifyTitle2(html, getExpectedAlerts());
356     }
357 
358     private void postMessageSameOrigin(final String url) throws Exception {
359         final String html = DOCTYPE_HTML
360             + "<html>\n"
361             + "<head></head>\n"
362             + "<body>\n"
363             + "<script>\n"
364             + LOG_TITLE_FUNCTION
365             + "  function receiveMessage(event) {\n"
366             + "    log('received');\n"
367             + "  }\n"
368             + "  window.addEventListener('message', receiveMessage, false);\n"
369             + "</script>\n"
370             + "  <iframe src='" + URL_SECOND + "'></iframe>\n"
371             + "</body></html>";
372 
373         final String iframe = DOCTYPE_HTML
374             + "<html><body><script>\n"
375             + "  top.postMessage('hello', '" + url + "');\n"
376             + "</script></body></html>";
377 
378         getMockWebConnection().setResponse(URL_SECOND, iframe);
379         loadPageVerifyTitle2(html, getExpectedAlerts());
380     }
381 
382     /**
383      * @throws Exception if the test fails
384      */
385     @Test
386     @Alerts("SyntaxError/DOMException")
387     public void postMessageTargetOriginNotUrl() throws Exception {
388         postMessageInvalidTargetOrigin("abcd");
389     }
390 
391     /**
392      * @throws Exception if the test fails
393      */
394     @Test
395     @Alerts("SyntaxError/DOMException")
396     public void postMessageTargetOriginEmpty() throws Exception {
397         postMessageInvalidTargetOrigin("");
398     }
399 
400     /**
401      * @throws Exception if the test fails
402      */
403     @Test
404     @Alerts("SyntaxError/DOMException")
405     public void postMessageTargetOriginSubpath() throws Exception {
406         postMessageInvalidTargetOrigin("/abc");
407     }
408 
409     private void postMessageInvalidTargetOrigin(final String targetOrigin) throws Exception {
410         final String html = DOCTYPE_HTML
411             + "<html>\n"
412             + "<head></head>\n"
413             + "<body>\n"
414             + "<script>\n"
415             + LOG_TITLE_FUNCTION
416             + "  try {\n"
417             + "    window.postMessage('hello', '" + targetOrigin + "');\n"
418             + "  } catch(e) {\n"
419             + "    logEx(e);\n"
420             + "  }\n"
421             + "</script>\n"
422             + "</body></html>";
423 
424         loadPageVerifyTitle2(html);
425     }
426 
427     /**
428      * @throws Exception if an error occurs
429      */
430     @Test
431     @Alerts("data: 2")
432     public void postMessage_jsonPayload() throws Exception {
433         final String html = DOCTYPE_HTML
434             + "<html>\n"
435             + "<head></head>\n"
436             + "<body>\n"
437             + "<script>\n"
438             + LOG_TITLE_FUNCTION
439             + "  function receiveMessage(event) {\n"
440             + "    log('data: ' + event.data.outer);\n"
441             + "  }\n"
442 
443             + "  window.addEventListener('message', receiveMessage, false);\n"
444             + "</script>\n"
445             + "  <iframe src='" + URL_THIRD + "'></iframe>\n"
446             + "</body></html>";
447 
448         final String iframe = DOCTYPE_HTML
449             + "<html><body><script>\n"
450             + "  top.postMessage({outer: 2}, '*');\n"
451             + "</script></body></html>";
452 
453         getMockWebConnection().setResponse(URL_THIRD, iframe);
454         loadPageVerifyTitle2(html);
455     }
456 
457     /**
458      * @throws Exception if an error occurs
459      */
460     @Test
461     @Alerts("data: innerProperty")
462     public void postMessage_jsonPayloadWithNestedObjects() throws Exception {
463         final String html = DOCTYPE_HTML
464             + "<html>\n"
465             + "<head></head>\n"
466             + "<body>\n"
467             + "<script>\n"
468             + LOG_TITLE_FUNCTION
469             + "  function receiveMessage(event) {\n"
470             + "    log('data: ' + event.data.inner.property);\n"
471             + "  }\n"
472 
473             + "  window.addEventListener('message', receiveMessage, false);\n"
474             + "</script>\n"
475             + "  <iframe src='" + URL_THIRD + "'></iframe>\n"
476             + "</body></html>";
477 
478         final String iframe = DOCTYPE_HTML
479             + "<html><body><script>\n"
480             + "  top.postMessage({inner: {property: 'innerProperty'}}, '*');\n"
481             + "</script></body></html>";
482 
483         getMockWebConnection().setResponse(URL_THIRD, iframe);
484         loadPageVerifyTitle2(html);
485     }
486 
487 }