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