1<!DOCTYPE html>
2<meta charset=utf-8>
3<script src="/resources/testharness.js"></script>
4<script src="/resources/testharnessreport.js"></script>
5<script>
6
7async_test(t => {
8    let c1 = new BroadcastChannel('worker');
9    let c2 = new BroadcastChannel('worker');
10    let events = [];
11
12    c1.onmessage = e => events.push(e);
13    c2.onmessage = e => events.push(e);
14
15    let doneCount = 0;
16    c2.addEventListener('message', t.step_func(e => {
17        if (e.data == 'from worker') {
18          c2.postMessage('from c2');
19          c1.postMessage('done');
20        } else if (e.data == 'done') {
21          assert_equals(events.length, 4);
22          assert_equals(events[0].data, 'from worker');
23          assert_equals(events[0].target, c1);
24          assert_equals(events[1].data, 'from worker');
25          assert_equals(events[1].target, c2);
26          assert_equals(events[2].data, 'from c2');
27          assert_equals(events[3].data, 'done');
28          if (++doneCount == 2) t.done();
29        }
30      }));
31
32    let worker = new Worker('resources/worker.js');
33    worker.onmessage = t.step_func(e => {
34        assert_array_equals(e.data, ['from c2', 'done']);
35        if (++doneCount == 2) t.done();
36      });
37    worker.postMessage({channel: 'worker'});
38
39  }, 'BroadcastChannel works in workers');
40
41async_test(t => {
42    let c1 = new BroadcastChannel('shared worker');
43    let c2 = new BroadcastChannel('shared worker');
44    let events = [];
45
46    c1.onmessage = e => events.push(e);
47    c2.onmessage = e => events.push(e);
48
49    let doneCount = 0;
50    c2.addEventListener('message', t.step_func(e => {
51        if (e.data == 'from worker') {
52          c2.postMessage('from c2');
53          c1.postMessage('done');
54        } else if (e.data == 'done') {
55          assert_equals(events.length, 4);
56          assert_equals(events[0].data, 'from worker');
57          assert_equals(events[0].target, c1);
58          assert_equals(events[1].data, 'from worker');
59          assert_equals(events[1].target, c2);
60          assert_equals(events[2].data, 'from c2');
61          assert_equals(events[3].data, 'done');
62          if (++doneCount == 2) t.done();
63        }
64      }));
65
66    let worker = new SharedWorker('resources/worker.js');
67    worker.port.onmessage = t.step_func(e => {
68        assert_array_equals(e.data, ['from c2', 'done']);
69        if (++doneCount == 2) t.done();
70      });
71    worker.port.postMessage({channel: 'shared worker'});
72
73  }, 'BroadcastChannel works in shared workers');
74
75async_test(t => {
76    let c = new BroadcastChannel('worker-close');
77    let events = [];
78
79    c.onmessage = e => events.push('c1: ' + e.data);
80
81    let worker = new Worker('resources/worker.js');
82    worker.onmessage = t.step_func(e => {
83        assert_array_equals(events,
84                            ['c1: from worker', 'c2: ready', 'c2: echo'],
85                            'messages in document');
86        assert_array_equals(e.data, ['done'], 'messages in worker');
87        t.done();
88      });
89    worker.onmessagerror =
90        t.unreached_func('Worker\'s onmessageerror handler called');
91
92    c.addEventListener('message', e => {
93        if (e.data == 'from worker') {
94          c.close();
95          if (self.gc) self.gc();
96          window.setTimeout(() => {
97              let c2 = new BroadcastChannel('worker-close');
98              c2.onmessage = e => {
99                  events.push('c2: ' + e.data);
100                  if (e.data === 'ready') {
101                    worker.postMessage({ping: 'echo'});
102                  } else {
103                    c2.postMessage('done');
104                    c2.close();
105                  }
106                };
107              // For some implementations there may be a race condition between
108              // when the BroadcastChannel instance above is created / ready to
109              // receive messages and when the worker calls postMessage on it's
110              // BroadcastChannel instance. To avoid this, confirm that our
111              // instance can receive a message before indicating to the other
112              // thread that we are ready. For more details, see:
113              // https://github.com/whatwg/html/issues/7267
114              let c3 = new BroadcastChannel('worker-close');
115              c3.postMessage('ready');
116              c3.close();
117            }, 1);
118        }
119      });
120
121    worker.postMessage({channel: 'worker-close'});
122    t.add_cleanup(() => worker.terminate());
123
124  }, 'Closing and re-opening a channel works.');
125
126async_test(t => {
127  function workerCode() {
128    close();
129    try {
130      var bc = new BroadcastChannel('worker-create-after-close');
131    } catch (e) {
132      postMessage(e);
133      return;
134    }
135    postMessage(true);
136  }
137
138  var workerBlob = new Blob(
139      [workerCode.toString() + ';workerCode();'],
140      {type: 'application/javascript'});
141
142  var w = new Worker(URL.createObjectURL(workerBlob));
143  w.onmessage = t.step_func_done(function(e) {
144    assert_equals(
145        e.data, true,
146        'BroadcastChannel creation in closed worker triggered exception: ' +
147            e.data.message);
148  });
149  t.add_cleanup(() => w.terminate());
150}, 'BroadcastChannel created after a worker self.close()');
151
152
153function postMessageFromWorkerWorkerCode(workerName, channelName) {
154  if (workerName === 'close-before-create-worker') {
155    close();
156  }
157  let bc = new BroadcastChannel(channelName);
158  if (workerName === 'close-after-create-worker') {
159    close();
160  }
161  bc.postMessage(workerName + ' done');
162  postMessage(true);
163}
164
165function doPostMessageFromWorkerTest(t, workerName, channelName) {
166  var bc = new BroadcastChannel(channelName);
167  bc.onmessage = t.step_func_done(function(e) {
168    assert_equals(
169        e.data, 'done-worker done',
170        'BroadcastChannel message should only be received from the second worker');
171  });
172  t.add_cleanup(() => bc.close());
173
174  var testMessageHandler = t.step_func(function(e) {
175    assert_equals(
176        e.data, true,
177        'Worker sent postMessage indicating it sent a BroadcastChannel message');
178
179    var w = createWorker(
180        postMessageFromWorkerWorkerCode, 'done-worker', channelName);
181    t.add_cleanup(() => w.terminate());
182  });
183  createWorker(
184      postMessageFromWorkerWorkerCode, workerName, channelName,
185      testMessageHandler);
186
187  // To avoid calling t.step_timeout here, have the worker postMessage(true)
188  // once it is finished and then we'll instantiate another worker that
189  // performs the same test steps but doesn't close. By the time the
190  // BroadcastChannel message in that worker gets sent successfully it should
191  // be safe to assume that any BroadcastChannel messages from the previous
192  // worker would have been sent if they were going to be.
193}
194
195function createWorker(workerCode, workerName, channelName, handler = null) {
196  var workerCodeStr = workerCode.toString() +
197      `;${workerCode.name}("${workerName}", "${channelName}");`;
198  var workerBlob = new Blob([workerCodeStr], {type: 'application/javascript'});
199  var w = new Worker(URL.createObjectURL(workerBlob));
200  if (handler !== null) {
201    w.onmessage = handler;
202  }
203  return w;
204}
205
206async_test(t => {
207  const workerName = 'close-after-create-worker';
208  const channelName = workerName + '-postmessage-from-worker';
209  doPostMessageFromWorkerTest(t, workerName, channelName);
210}, 'BroadcastChannel messages from closed worker to parent should be ignored (BC created before closing)');
211
212async_test(t => {
213  const workerName = 'close-before-create-worker';
214  const channelName = workerName + '-postmessage-from-worker';
215  doPostMessageFromWorkerTest(t, workerName, channelName);
216}, 'BroadcastChannel messages from closed worker to parent should be ignored (BC created after closing)');
217
218
219function postMessageToWorkerWorkerCode(workerName, channelName) {
220  self.addEventListener('message', () => {
221    if (workerName === 'close-before-create-worker') {
222      close();
223    }
224    try {
225      let bc1 = new BroadcastChannel(channelName);
226      bc1.onmessage = e => {
227        if (e.data === 'ready') {
228          postMessage(e.data);
229        } else if (e.data === 'test') {
230          postMessage(workerName + ' done');
231        }
232      };
233      bc1.onmessageerror = () => {
234        postMessage('onmessageerror called from worker BroadcastChannel');
235      };
236      if (workerName === 'close-after-create-worker') {
237        close();
238      }
239    } catch (e) {
240      postMessage(e);
241      return;
242    }
243
244    if (workerName === 'done-worker') {
245      // For some implementations there may be a race condition between when
246      // the BroadcastChannel instance above is created / ready to receive
247      // messages and when the parent calls postMessage on it's
248      // BroadcastChannel instance. To avoid this, confirm that our instance
249      // can receive a message before indicating to the other thread that we
250      // are ready. For more details, see:
251      // https://github.com/whatwg/html/issues/7267
252      let bc2 = new BroadcastChannel(channelName);
253      bc2.postMessage('ready');
254      bc2.close();
255    } else {
256      // Since the worker has closed, it's not expected that the
257      // BroadcastChannel will receive messages (there's a separate test for
258      // that), so just indicate directly that it's ready to test receiving
259      // a message from the parent dispite the possibility of a race condition.
260      postMessage('ready');
261    }
262  });
263  self.addEventListener('messageerror', () => {
264    postMessage('onmessageerror called from worker');
265  });
266}
267
268function doPostMessageToWorkerTest(t, workerName, channelName) {
269  var bc = new BroadcastChannel(channelName);
270  t.add_cleanup(() => bc.close());
271
272  var doneMessageHandler = t.step_func(function(e) {
273    if (e.data === 'ready') {
274      bc.postMessage('test');
275    } else if (e.data === 'done-worker done') {
276      t.done();
277    } else {
278      assert_unreached(
279          'BroadcastChannel.postMessage triggered exception within second worker: ' +
280          e.data.message);
281    }
282  });
283  var testMessageHandler = t.step_func(function(e) {
284    assert_equals(
285        e.data, 'ready',
286        'Worker sent postMessage indicating its BroadcastChannel instance is ready');
287    bc.postMessage('test');
288
289    var doneWorker = createWorker(
290        postMessageToWorkerWorkerCode, 'done-worker', channelName,
291        doneMessageHandler);
292    t.add_cleanup(() => {
293      doneWorker.terminate();
294    });
295    doneWorker.postMessage('start');
296  });
297  var testWorker = createWorker(
298      postMessageToWorkerWorkerCode, workerName, channelName,
299      testMessageHandler);
300  testWorker.postMessage('start');
301}
302
303async_test(t => {
304  const workerName = 'close-after-create-worker';
305  const channelName = workerName + '-postmessage-to-worker';
306  doPostMessageToWorkerTest(t, workerName, channelName);
307}, 'BroadcastChannel messages from parent to closed worker should be ignored (BC created before closing)');
308
309async_test(t => {
310  const workerName = 'close-before-create-worker';
311  const channelName = workerName + '-postmessage-to-worker';
312  doPostMessageToWorkerTest(t, workerName, channelName);
313}, 'BroadcastChannel messages from parent to closed worker should be ignored (BC created after closing)');
314
315
316function postMessageWithinWorkerWorkerCode(workerName, channelName) {
317  if (workerName === 'close-before-create-worker') {
318    close();
319  }
320  try {
321    let bc1 = new BroadcastChannel(channelName);
322    let bc2 = new BroadcastChannel(channelName);
323    bc1.onmessage = e => {
324      postMessage(workerName + ' done')
325    };
326    if (workerName === 'close-after-create-worker') {
327      close();
328    }
329    bc2.postMessage(true);
330    postMessage(true);
331  } catch (e) {
332    postMessage(e);
333  }
334}
335
336function doPostMessageWithinWorkerTest(t, workerName, channelName) {
337  var doneMessageHandler = t.step_func(function(e) {
338    if (e.data === true) {
339      // Done worker has finished - no action needed
340    } else if (e.data === 'done-worker done') {
341      t.done();
342    } else {
343      assert_unreached(
344          'BroadcastChannel.postMessage triggered exception within second worker: ' +
345          e.data.message);
346    }
347  });
348  var testMessageHandler = t.step_func(function(e) {
349    assert_equals(
350        e.data, true,
351        'Worker indicated that the test procedures were executed successfully');
352
353    var w = createWorker(
354        postMessageWithinWorkerWorkerCode, 'done-worker', channelName,
355        doneMessageHandler);
356    t.add_cleanup(() => w.terminate());
357  });
358  createWorker(
359      postMessageWithinWorkerWorkerCode, workerName, channelName,
360      testMessageHandler);
361}
362
363async_test(t => {
364  const workerName = 'close-after-create-worker';
365  const channelName = workerName + '-postmessage-within-worker';
366  doPostMessageWithinWorkerTest(t, workerName, channelName);
367}, 'BroadcastChannel messages within closed worker should be ignored (BCs created before closing)');
368
369async_test(t => {
370  const workerName = 'close-before-create-worker';
371  const channelName = workerName + '-postmessage-within-worker';
372  doPostMessageWithinWorkerTest(t, workerName, channelName);
373}, 'BroadcastChannel messages within closed worker should be ignored (BCs created after closing)');
374
375</script>
376