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.htmlunit.junit.annotation.HtmlUnitNYI;
20  import org.junit.jupiter.api.Test;
21  import org.openqa.selenium.WebDriver;
22  import org.openqa.selenium.WindowType;
23  
24  /**
25   * Tests for {@link BroadcastChannel}.
26   *
27   * @author Ronald Brill
28   */
29  public class BroadcastChannelTest extends WebDriverTestCase {
30  
31      /**
32       * @throws Exception if an error occurs
33       */
34      @Test
35      @Alerts({"channel1", "i got [object MessageEvent]", "i got data Hello from main page!",
36               "i got origin http://localhost:22222", "i got lastEventId ",
37               "i got source null", "i got ports ",
38               "got [object MessageEvent]", "got data post: Response from iframe",
39               "got origin http://localhost:22222", "got lastEventId ", "got source null", "got ports "})
40      public void basicBroadcastTest() throws Exception {
41          final String html = DOCTYPE_HTML
42                  + "<html><body>\n"
43                  + "<iframe src='" + URL_SECOND + "'></iframe>\n"
44                  + "<script>\n"
45                  + LOG_SESSION_STORAGE_FUNCTION
46                  + "  var bc = new BroadcastChannel('channel1');\n"
47                  + "  log(bc.name);\n"
48                  + "  var ifr = document.querySelector('iframe');\n"
49                  + "  function iframeLoaded() {\n"
50                  + "    bc.postMessage('Hello from main page!');\n"
51                  + "  }\n"
52                  + "  ifr.addEventListener('load', iframeLoaded, false);\n"
53                  + "  bc.onmessage = function(e) {\n"
54                  + "    log('got ' + e);\n"
55                  + "    log('got data ' + e.data);\n"
56                  + "    log('got origin ' + e.origin);\n"
57                  + "    log('got lastEventId ' + e.lastEventId);\n"
58                  + "    log('got source ' + e.source);\n"
59                  + "    log('got ports ' + e.ports);\n"
60                  + "  };\n"
61                  + "</script>\n"
62                  + "</body></html>";
63  
64          final String html2 = DOCTYPE_HTML
65                  + "<html><body>\n"
66                  + "<script>\n"
67                  + LOG_SESSION_STORAGE_FUNCTION
68                  + "  var bc = new BroadcastChannel('channel1');\n"
69                  + "  bc.onmessage = function(e) {\n"
70                  + "    log('i got ' + e);\n"
71                  + "    log('i got data ' + e.data);\n"
72                  + "    log('i got origin ' + e.origin);\n"
73                  + "    log('i got lastEventId ' + e.lastEventId);\n"
74                  + "    log('i got source ' + e.source);\n"
75                  + "    log('i got ports ' + e.ports);\n"
76                  + "    bc.postMessage('post: Response from iframe');\n"
77                  + "  };\n"
78                  + "</script>\n"
79                  + "</body></html>";
80  
81          getMockWebConnection().setResponse(URL_SECOND, html2);
82  
83          final WebDriver driver = loadPage2(html);
84          verifySessionStorage2(driver, getExpectedAlerts());
85          // to start always with fresh session storage
86          shutDownAll();
87      }
88  
89      /**
90       * @throws Exception if an error occurs
91       */
92      @Test
93      @Alerts({"channel1", "channel2", "bc1: Message for channel1", "bc2: Message for channel2"})
94      public void differentChannelsTest() throws Exception {
95          final String html = DOCTYPE_HTML
96                  + "<html><body>\n"
97                  + "<iframe src='" + URL_SECOND + "'></iframe>\n"
98                  + "<script>\n"
99                  + LOG_SESSION_STORAGE_FUNCTION
100                 + "  var bc1 = new BroadcastChannel('channel1');\n"
101                 + "  log(bc1.name);\n"
102 
103                 + "  var bc2 = new BroadcastChannel('channel2');\n"
104                 + "  log(bc2.name);\n"
105 
106                 + "  bc1.onmessage = function(e) {\n"
107                 + "    log('bc1: ' + e.data);\n"
108                 + "  };\n"
109                 + "  bc2.onmessage = function(e) {\n"
110                 + "    log('bc2: ' + e.data);\n"
111                 + "  };\n"
112                 + "</script>\n"
113                 + "</body></html>";
114 
115         final String html2 = DOCTYPE_HTML
116                 + "<html><body>\n"
117                 + "<script>\n"
118                 + LOG_SESSION_STORAGE_FUNCTION
119                 + "  var bc1 = new BroadcastChannel('channel1');\n"
120                 + "  bc1.postMessage('Message for channel1');\n"
121 
122                 + "  var bc2 = new BroadcastChannel('channel2');\n"
123                 + "  bc2.postMessage('Message for channel2');\n"
124 
125                 + "  var bc3 = new BroadcastChannel('channel3');\n"
126                 + "  bc3.postMessage('Message for channel3');\n"
127                 + "</script>\n"
128                 + "</body></html>";
129 
130         getMockWebConnection().setResponse(URL_SECOND, html2);
131 
132         final WebDriver driver = loadPage2(html);
133         verifySessionStorage2(driver, getExpectedAlerts());
134         // to start always with fresh session storage
135         shutDownAll();
136     }
137 
138     /**
139      * @throws Exception if an error occurs
140      */
141     @Test
142     @Alerts({"Trigger close()", "postMessage() done", "done"})
143     public void closeChannelTest() throws Exception {
144         final String html = DOCTYPE_HTML
145                 + "<html><body>\n"
146                 + "<iframe src='" + URL_SECOND + "'></iframe>\n"
147                 + "<script>\n"
148                 + LOG_SESSION_STORAGE_FUNCTION
149                 + "  var bc = new BroadcastChannel('test');\n"
150                 + "  var ifr = document.querySelector('iframe');\n"
151                 + "  bc.onmessage = function(e) {\n"
152                 + "    log(e.data);\n"
153                 + "    bc.close();\n"
154                 + "  };\n"
155                 + "</script>\n"
156                 + "</body></html>";
157 
158         final String html2 = DOCTYPE_HTML
159                 + "<html><body>\n"
160                 + "<script>\n"
161                 + LOG_SESSION_STORAGE_FUNCTION
162                 + "  var bc = new BroadcastChannel('test');\n"
163                 + "  // This message should not be received after close\n"
164                 + "  bc.postMessage('Trigger close()');\n"
165                 + "  setTimeout(function() {\n"
166                 + "    bc.postMessage('Should not receive this');\n"
167                 + "    log('postMessage() done');\n"
168                 + "  }, 50);\n"
169                 + "  setTimeout(function() {\n"
170                 + "    log('done');\n"
171                 + "  }, 100);\n"
172                 + "</script>\n"
173                 + "</body></html>";
174 
175         getMockWebConnection().setResponse(URL_SECOND, html2);
176 
177         final WebDriver driver = loadPage2(html);
178         Thread.sleep(DEFAULT_WAIT_TIME.dividedBy(2).toMillis());
179         verifySessionStorage2(driver, getExpectedAlerts());
180         // to start always with fresh session storage
181         shutDownAll();
182     }
183 
184     /**
185      * @throws Exception if an error occurs
186      */
187     @Test
188     @Alerts("cross-origin")
189     public void sameOrigin() throws Exception {
190         final String html = DOCTYPE_HTML
191                 + "<html><head><script>\n"
192                 + LOG_TITLE_FUNCTION
193                 + "  var bc = new BroadcastChannel('test');\n"
194                 + "  bc.onmessage = function(e) {\n"
195                 + "    log(e.data);\n"
196                 + "  };\n"
197                 + "</script>\n"
198                 + "</head>\n"
199                 + "<body></body>\n"
200                 + "</html>";
201 
202         final String html2 = DOCTYPE_HTML
203                 + "<html><body>\n"
204                 + "<script>\n"
205                 + "  var bc = new BroadcastChannel('test');\n"
206                 + "  bc.postMessage('cross-origin');\n"
207                 + "</script>\n"
208                 + "</body></html>";
209 
210         getMockWebConnection().setResponse(URL_SECOND, html2);
211 
212         final WebDriver driver = loadPage2(html);
213         final Object[] windowHandles = driver.getWindowHandles().toArray();
214         driver.switchTo().newWindow(WindowType.TAB);
215         driver.get(URL_SECOND.toExternalForm());
216         driver.close();
217         driver.switchTo().window((String) windowHandles[0]);
218 
219         verifyTitle2(driver, getExpectedAlerts());
220         // to start always with fresh session storage
221         shutDownAll();
222     }
223 
224     /**
225      * @throws Exception if an error occurs
226      */
227     @Test
228     @Alerts({})
229     public void crossOrigin() throws Exception {
230         final String html = DOCTYPE_HTML
231                 + "<html><head>\n"
232                 + "<script>\n"
233                 + LOG_TITLE_FUNCTION
234                 + "  var bc = new BroadcastChannel('test');\n"
235                 + "  bc.onmessage = function(e) {\n"
236                 + "    log(e.data);\n"
237                 + "  };\n"
238                 + "</script>\n"
239                 + "</head>\n"
240                 + "<body></body>\n"
241                 + "</html>";
242 
243         final String html2 = DOCTYPE_HTML
244                 + "<html><body>\n"
245                 + "<script>\n"
246                 + "  var bc = new BroadcastChannel('test');\n"
247                 + "  bc.postMessage('cross-origin');\n"
248                 + "</script>\n"
249                 + "</body></html>";
250 
251         getMockWebConnection().setResponse(URL_THIRD, html2);
252 
253         final WebDriver driver = loadPage2(html);
254         final Object[] windowHandles = driver.getWindowHandles().toArray();
255         driver.switchTo().newWindow(WindowType.TAB);
256         driver.get(URL_THIRD.toExternalForm());
257         driver.close();
258         driver.switchTo().window((String) windowHandles[0]);
259 
260         verifyTitle2(driver, getExpectedAlerts());
261         // to start always with fresh session storage
262         shutDownAll();
263     }
264 
265     /**
266      * @throws Exception if an error occurs
267      */
268     @Test
269     @Alerts("postMessage done")
270     public void noSelfMessageTest() throws Exception {
271         final String html = DOCTYPE_HTML
272                 + "<html><body>\n"
273                 + "<script>\n"
274                 + LOG_TITLE_FUNCTION
275                 + "  var bc = new BroadcastChannel('selftest');\n"
276                 + "  bc.onmessage = function(e) {\n"
277                 + "    log('received: ' + e.data);\n"
278                 + "  };\n"
279                 + "  bc.postMessage('self message');\n"
280                 + "  log('postMessage done');\n"
281                 + "</script>\n"
282                 + "</body></html>";
283 
284         loadPageVerifyTitle2(html);
285     }
286 
287     /**
288      * @throws Exception if an error occurs
289      */
290     @Test
291     @Alerts("null")
292     public void constructorWithNullTest() throws Exception {
293         final String html = DOCTYPE_HTML
294                 + "<html><body>\n"
295                 + "<script>\n"
296                 + LOG_TITLE_FUNCTION
297                 + "  try {\n"
298                 + "    var bc = new BroadcastChannel(null);\n"
299                 + "    log(bc.name);\n"
300                 + "  } catch (e) { logEx(e); }\n"
301                 + "</script>\n"
302                 + "</body></html>";
303 
304         loadPageVerifyTitle2(html);
305     }
306 
307     /**
308      * @throws Exception if an error occurs
309      */
310     @Test
311     @Alerts("undefined")
312     @HtmlUnitNYI(CHROME = "TypeError",
313             EDGE = "TypeError",
314             FF = "TypeError",
315             FF_ESR = "TypeError")
316     public void constructorWithUndefinedTest() throws Exception {
317         final String html = DOCTYPE_HTML
318                 + "<html><body>\n"
319                 + "<script>\n"
320                 + LOG_TITLE_FUNCTION
321                 + "  try {\n"
322                 + "    var bc = new BroadcastChannel(undefined);\n"
323                 + "    log(bc.name);\n"
324                 + "  } catch (e) { logEx(e); }\n"
325                 + "</script>\n"
326                 + "</body></html>";
327 
328         loadPageVerifyTitle2(html);
329     }
330 
331     /**
332      * @throws Exception if an error occurs
333      */
334     @Test
335     @Alerts("")
336     public void constructorWithEmptyStringTest() throws Exception {
337         final String html = DOCTYPE_HTML
338                 + "<html><body>\n"
339                 + "<script>\n"
340                 + LOG_TITLE_FUNCTION
341                 + "  try {\n"
342                 + "    var bc = new BroadcastChannel('');\n"
343                 + "    log(bc.name);\n"
344                 + "  } catch (e) { logEx(e); }\n"
345                 + "</script>\n"
346                 + "</body></html>";
347 
348         loadPageVerifyTitle2(html);
349     }
350 
351     /**
352      * @throws Exception if an error occurs
353      */
354     @Test
355     @Alerts("\\s\\sa\\sb\\s\\sc\\texu\\s\\s\\s\\s\\t\\s")
356     public void constructorWithStringWhitespaceTest() throws Exception {
357         final String html = DOCTYPE_HTML
358                 + "<html><body>\n"
359                 + "<script>\n"
360                 + LOG_TITLE_FUNCTION_NORMALIZE
361                 + "  try {\n"
362                 + "    var bc = new BroadcastChannel('  a b  c\texu    \t ');\n"
363                 + "    log(bc.name);\n"
364                 + "  } catch (e) { logEx(e); }\n"
365                 + "</script>\n"
366                 + "</body></html>";
367 
368         loadPageVerifyTitle2(html);
369     }
370 
371     /**
372      * @throws Exception if an error occurs
373      */
374     @Test
375     @Alerts("123")
376     public void constructorWithNumberTest() throws Exception {
377         final String html = DOCTYPE_HTML
378                 + "<html><body>\n"
379                 + "<script>\n"
380                 + LOG_TITLE_FUNCTION
381                 + "  try {\n"
382                 + "    var bc = new BroadcastChannel(123);\n"
383                 + "    log(bc.name);\n"
384                 + "  } catch (e) { logEx(e); }\n"
385                 + "</script>\n"
386                 + "</body></html>";
387 
388         loadPageVerifyTitle2(html);
389     }
390 
391     /**
392      * @throws Exception if an error occurs
393      */
394     @Test
395     @Alerts("custom")
396     public void constructorWithObjectTest() throws Exception {
397         final String html = DOCTYPE_HTML
398                 + "<html><body>\n"
399                 + "<script>\n"
400                 + LOG_TITLE_FUNCTION
401                 + "  try {\n"
402                 + "    var bc = new BroadcastChannel({toString: function() { return 'custom'; }});\n"
403                 + "    log(bc.name);\n"
404                 + "  } catch (e) { logEx(e); }\n"
405                 + "</script>\n"
406                 + "</body></html>";
407 
408         loadPageVerifyTitle2(html);
409     }
410 
411     /**
412      * @throws Exception if an error occurs
413      */
414     @Test
415     @Alerts("special!@#$%^&*()_+-=[]{}|;':\",./&lt;&gt;?`~")
416     public void constructorWithSpecialCharactersTest() throws Exception {
417         final String html = DOCTYPE_HTML
418                 + "<html><body>\n"
419                 + "<script>\n"
420                 + LOG_TITLE_FUNCTION
421                 + "  try {\n"
422                 + "    var bc = new BroadcastChannel('special!@#$%^&*()_+-=[]{}|;\\':\\\",./&lt;&gt;?`~');\n"
423                 + "    log(bc.name);\n"
424                 + "  } catch (e) { logEx(e); }\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("TypeError")
436     public void constructorWithNoArgumentsTest() throws Exception {
437         final String html = DOCTYPE_HTML
438                 + "<html><body>\n"
439                 + "<script>\n"
440                 + LOG_TITLE_FUNCTION
441                 + "  try {\n"
442                 + "    var bc = new BroadcastChannel();\n"
443                 + "    log('success: ' + bc.name);\n"
444                 + "  } catch (e) { logEx(e); }\n"
445                 + "</script>\n"
446                 + "</body></html>";
447 
448         loadPageVerifyTitle2(html);
449     }
450 
451     /**
452      * @throws Exception if an error occurs
453      */
454     @Test
455     @Alerts("message received")
456     public void constructorWithWhitespaceChannelsTest() throws Exception {
457         final String html = DOCTYPE_HTML
458                 + "<html><body>\n"
459                 + "<iframe src='" + URL_SECOND + "'></iframe>\n"
460                 + "<script>\n"
461                 + LOG_TITLE_FUNCTION
462                 + "  var bc = new BroadcastChannel('  spaced  ');\n"
463                 + "  var ifr = document.querySelector('iframe');\n"
464                 + "  function iframeLoaded() {\n"
465                 + "    bc.postMessage('test message');\n"
466                 + "  }\n"
467                 + "  ifr.addEventListener('load', iframeLoaded, false);\n"
468                 + "  bc.onmessage = function(e) {\n"
469                 + "    log('message received');\n"
470                 + "  };\n"
471                 + "</script>\n"
472                 + "</body></html>";
473 
474         final String html2 = DOCTYPE_HTML
475                 + "<html><body>\n"
476                 + "<script>\n"
477                 + "  var bc = new BroadcastChannel('  spaced  ');\n"
478                 + "  bc.onmessage = function(e) {\n"
479                 + "    bc.postMessage('response');\n"
480                 + "  };\n"
481                 + "</script>\n"
482                 + "</body></html>";
483 
484         getMockWebConnection().setResponse(URL_SECOND, html2);
485         loadPageVerifyTitle2(html);
486     }
487 }