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_THIRD, 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      * @throws Exception if the test fails
187      */
188     @Test
189     @Alerts({"type: message", "bubbles: false", "cancelable: false", "data: hello",
190              "origin: ", "source: false true", "lastEventId: "})
191     public void postMessageWithTransferableOnly() throws Exception {
192         final String[] expectedAlerts = getExpectedAlerts();
193         expectedAlerts[4] += "http://localhost:" + PORT;
194         setExpectedAlerts(expectedAlerts);
195 
196         final String html = DOCTYPE_HTML
197             + "<html>\n"
198             + "<head></head>\n"
199             + "<body>\n"
200             + "  <iframe id='myFrame' src='" + URL_THIRD + "'></iframe>\n"
201 
202             + "<script>\n"
203             + LOG_TITLE_FUNCTION
204             + "  var win = document.getElementById('myFrame').contentWindow;\n"
205 
206             + "  function receiveMessage(event) {\n"
207             + "    log('type: ' + event.type);\n"
208             + "    log('bubbles: ' + event.bubbles);\n"
209             + "    log('cancelable: ' + event.cancelable);\n"
210             + "    log('data: ' + event.data);\n"
211             + "    log('origin: ' + event.origin);\n"
212             + "    log('source: ' + (event.source === win) + ' ' + (event.source === window));\n"
213             + "    log('lastEventId: ' + event.lastEventId);\n"
214             + "  }\n"
215 
216             + "  win.addEventListener('message', receiveMessage, false);\n"
217 
218             + "  uInt8Array = new Uint8Array(1);\n"
219             + "  win.postMessage('hello', [uInt8Array.buffer]);\n"
220             + "</script>\n"
221             + "</body></html>";
222 
223         final String iframe = DOCTYPE_HTML
224                 + "<html><body><p>inside frame</p></body></html>";
225 
226         getMockWebConnection().setResponse(URL_THIRD, iframe);
227         loadPageVerifyTitle2(html);
228     }
229 
230     /**
231      * Test for #1589 NullPointerException because of missing context.
232      *
233      * @throws Exception if the test fails
234      */
235     @Test
236     @Alerts({"data: hello", "source: true false"})
237     public void postMessageFromClick() throws Exception {
238         final String html = DOCTYPE_HTML
239             + "<html>\n"
240             + "<head></head>\n"
241             + "<body>\n"
242             + "<script>\n"
243             + LOG_TITLE_FUNCTION
244             + "  function receiveMessage(event) {\n"
245             + "    log('data: ' + event.data);\n"
246             + "    var win = document.getElementById('myFrame').contentWindow;\n"
247             + "    log('source: ' + (event.source === win) + ' ' + (event.source === window));\n"
248             + "  }\n"
249 
250             + "  window.addEventListener('message', receiveMessage, false);\n"
251             + "</script>\n"
252             + "  <iframe id='myFrame' src='" + URL_THIRD + "'></iframe>\n"
253             + "</body></html>";
254 
255         final String iframe = "<html><body>\n"
256             + "  <button id='clickme' onclick='top.postMessage(\"hello\", \"*\");'>Click me</a>\n"
257             + "</body></html>";
258 
259         getMockWebConnection().setResponse(URL_THIRD, iframe);
260         final WebDriver driver = loadPage2(html);
261         driver.switchTo().frame("myFrame");
262         driver.findElement(By.id("clickme")).click();
263 
264         verifyTitle2(driver, getExpectedAlerts());
265     }
266 
267     /**
268      * @throws Exception if the test fails
269      */
270     @Test
271     @Alerts("sync: false")
272     public void postMessageSyncOrAsync() throws Exception {
273         final String html = DOCTYPE_HTML
274             + "<html>\n"
275             + "<head></head>\n"
276             + "<body>\n"
277             + "<script>\n"
278             + LOG_TITLE_FUNCTION
279             + "  var sync = true;\n"
280             + "  function receiveMessage(event) {\n"
281             + "    log('sync: ' + sync);\n"
282             + "  }\n"
283             + "  window.addEventListener('message', receiveMessage, false);\n"
284             + "</script>\n"
285             + "  <iframe src='" + URL_SECOND + "'></iframe>\n"
286             + "</body></html>";
287 
288         final String iframe = DOCTYPE_HTML
289             + "<html><body><script>\n"
290             + "  top.postMessage('hello', '*');\n"
291             + "  top.sync = false;\n"
292             + "</script></body></html>";
293 
294         getMockWebConnection().setResponse(URL_SECOND, iframe);
295         loadPageVerifyTitle2(html);
296     }
297 
298     /**
299      * @throws Exception if the test fails
300      */
301     @Test
302     @Alerts("received")
303     public void postMessage_exactURL() throws Exception {
304         postMessage(URL_FIRST.toExternalForm());
305     }
306 
307     /**
308      * @throws Exception if the test fails
309      */
310     @Test
311     @Alerts({})
312     public void postMessage_slash() throws Exception {
313         postMessage("/");
314     }
315 
316     /**
317      * @throws Exception if the test fails
318      */
319     @Test
320     @Alerts("received")
321     public void postMessageSameOrigin_slash() throws Exception {
322         postMessageSameOrigin("/");
323     }
324 
325     /**
326      * @throws Exception if the test fails
327      */
328     @Test
329     @Alerts({})
330     public void postMessage_otherHost() throws Exception {
331         postMessage("http://127.0.0.1:" + PORT + "/");
332     }
333 
334     /**
335      * @throws Exception if the test fails
336      */
337     @Test
338     @Alerts({})
339     public void postMessageSameOrigin_otherHost() throws Exception {
340         postMessageSameOrigin("http://127.0.0.1:" + PORT + "/");
341     }
342 
343     /**
344      * @throws Exception if the test fails
345      */
346     @Test
347     @Alerts({})
348     public void postMessage_otherPort() throws Exception {
349         postMessage("http://localhost:" + (PORT + 1) + "/");
350     }
351 
352     /**
353      * @throws Exception if the test fails
354      */
355     @Test
356     @Alerts({})
357     public void postMessageSameOrigin_otherPort() throws Exception {
358         postMessageSameOrigin("http://localhost:" + (PORT + 1) + "/");
359     }
360 
361     /**
362      * @throws Exception if the test fails
363      */
364     @Test
365     @Alerts({})
366     public void postMessage_otherProtocol() throws Exception {
367         postMessage("https://localhost:" + PORT + "/");
368     }
369 
370     /**
371      * @throws Exception if the test fails
372      */
373     @Test
374     @Alerts({})
375     public void postMessageSameOrigin_otherProtocol() throws Exception {
376         postMessageSameOrigin("https://localhost:" + PORT + "/");
377     }
378 
379     private void postMessage(final String url) throws Exception {
380         final String html = DOCTYPE_HTML
381             + "<html>\n"
382             + "<head></head>\n"
383             + "<body>\n"
384             + "<script>\n"
385             + LOG_TITLE_FUNCTION
386             + "  function receiveMessage(event) {\n"
387             + "    log('received');\n"
388             + "  }\n"
389             + "  window.addEventListener('message', receiveMessage, false);\n"
390             + "</script>\n"
391             + "  <iframe src='" + URL_THIRD + "'></iframe>\n"
392             + "</body></html>";
393 
394         final String iframe = DOCTYPE_HTML
395             + "<html><body><script>\n"
396             + "  top.postMessage('hello', '" + url + "');\n"
397             + "</script></body></html>";
398 
399         getMockWebConnection().setResponse(URL_THIRD, iframe);
400         loadPageVerifyTitle2(html, getExpectedAlerts());
401     }
402 
403     private void postMessageSameOrigin(final String url) throws Exception {
404         final String html = DOCTYPE_HTML
405             + "<html>\n"
406             + "<head></head>\n"
407             + "<body>\n"
408             + "<script>\n"
409             + LOG_TITLE_FUNCTION
410             + "  function receiveMessage(event) {\n"
411             + "    log('received');\n"
412             + "  }\n"
413             + "  window.addEventListener('message', receiveMessage, false);\n"
414             + "</script>\n"
415             + "  <iframe src='" + URL_SECOND + "'></iframe>\n"
416             + "</body></html>";
417 
418         final String iframe = DOCTYPE_HTML
419             + "<html><body><script>\n"
420             + "  top.postMessage('hello', '" + url + "');\n"
421             + "</script></body></html>";
422 
423         getMockWebConnection().setResponse(URL_SECOND, iframe);
424         loadPageVerifyTitle2(html, getExpectedAlerts());
425     }
426 
427     /**
428      * @throws Exception if the test fails
429      */
430     @Test
431     @Alerts("SyntaxError/DOMException")
432     public void postMessageTargetOriginNotUrl() throws Exception {
433         postMessageInvalidTargetOrigin("abcd");
434     }
435 
436     /**
437      * @throws Exception if the test fails
438      */
439     @Test
440     @Alerts("SyntaxError/DOMException")
441     public void postMessageTargetOriginEmpty() throws Exception {
442         postMessageInvalidTargetOrigin("");
443     }
444 
445     /**
446      * @throws Exception if the test fails
447      */
448     @Test
449     @Alerts("SyntaxError/DOMException")
450     public void postMessageTargetOriginSubpath() throws Exception {
451         postMessageInvalidTargetOrigin("/abc");
452     }
453 
454     private void postMessageInvalidTargetOrigin(final String targetOrigin) throws Exception {
455         final String html = DOCTYPE_HTML
456             + "<html>\n"
457             + "<head></head>\n"
458             + "<body>\n"
459             + "<script>\n"
460             + LOG_TITLE_FUNCTION
461             + "  try {\n"
462             + "    window.postMessage('hello', '" + targetOrigin + "');\n"
463             + "  } catch(e) {\n"
464             + "    logEx(e);\n"
465             + "  }\n"
466             + "</script>\n"
467             + "</body></html>";
468 
469         loadPageVerifyTitle2(html);
470     }
471 
472     /**
473      * @throws Exception if an error occurs
474      */
475     @Test
476     @Alerts("data: 2")
477     public void postMessage_jsonPayload() throws Exception {
478         final String html = DOCTYPE_HTML
479             + "<html>\n"
480             + "<head></head>\n"
481             + "<body>\n"
482             + "<script>\n"
483             + LOG_TITLE_FUNCTION
484             + "  function receiveMessage(event) {\n"
485             + "    log('data: ' + event.data.outer);\n"
486             + "  }\n"
487 
488             + "  window.addEventListener('message', receiveMessage, false);\n"
489             + "</script>\n"
490             + "  <iframe src='" + URL_THIRD + "'></iframe>\n"
491             + "</body></html>";
492 
493         final String iframe = DOCTYPE_HTML
494             + "<html><body><script>\n"
495             + "  top.postMessage({outer: 2}, '*');\n"
496             + "</script></body></html>";
497 
498         getMockWebConnection().setResponse(URL_THIRD, iframe);
499         loadPageVerifyTitle2(html);
500     }
501 
502     /**
503      * @throws Exception if an error occurs
504      */
505     @Test
506     @Alerts("data: innerProperty")
507     public void postMessage_jsonPayloadWithNestedObjects() throws Exception {
508         final String html = DOCTYPE_HTML
509             + "<html>\n"
510             + "<head></head>\n"
511             + "<body>\n"
512             + "<script>\n"
513             + LOG_TITLE_FUNCTION
514             + "  function receiveMessage(event) {\n"
515             + "    log('data: ' + event.data.inner.property);\n"
516             + "  }\n"
517 
518             + "  window.addEventListener('message', receiveMessage, false);\n"
519             + "</script>\n"
520             + "  <iframe src='" + URL_THIRD + "'></iframe>\n"
521             + "</body></html>";
522 
523         final String iframe = DOCTYPE_HTML
524             + "<html><body><script>\n"
525             + "  top.postMessage({inner: {property: 'innerProperty'}}, '*');\n"
526             + "</script></body></html>";
527 
528         getMockWebConnection().setResponse(URL_THIRD, iframe);
529         loadPageVerifyTitle2(html);
530     }
531 
532 }