<!DOCTYPE html>
<meta charset=utf-8>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
async_test(t => {
let c1 = new BroadcastChannel('worker');
let c2 = new BroadcastChannel('worker');
let events = [];
c1.onmessage = e => events.push(e);
c2.onmessage = e => events.push(e);
let doneCount = 0;
c2.addEventListener('message', t.step_func(e => {
if (e.data == 'from worker') {
c2.postMessage('from c2');
c1.postMessage('done');
} else if (e.data == 'done') {
assert_equals(events.length, 4);
assert_equals(events[0].data, 'from worker');
assert_equals(events[0].target, c1);
assert_equals(events[1].data, 'from worker');
assert_equals(events[1].target, c2);
assert_equals(events[2].data, 'from c2');
assert_equals(events[3].data, 'done');
if (++doneCount == 2) t.done();
}
}));
let worker = new Worker('resources/worker.js');
worker.onmessage = t.step_func(e => {
assert_array_equals(e.data, ['from c2', 'done']);
if (++doneCount == 2) t.done();
});
worker.postMessage({channel: 'worker'});
}, 'BroadcastChannel works in workers');
async_test(t => {
let c1 = new BroadcastChannel('shared worker');
let c2 = new BroadcastChannel('shared worker');
let events = [];
c1.onmessage = e => events.push(e);
c2.onmessage = e => events.push(e);
let doneCount = 0;
c2.addEventListener('message', t.step_func(e => {
if (e.data == 'from worker') {
c2.postMessage('from c2');
c1.postMessage('done');
} else if (e.data == 'done') {
assert_equals(events.length, 4);
assert_equals(events[0].data, 'from worker');
assert_equals(events[0].target, c1);
assert_equals(events[1].data, 'from worker');
assert_equals(events[1].target, c2);
assert_equals(events[2].data, 'from c2');
assert_equals(events[3].data, 'done');
if (++doneCount == 2) t.done();
}
}));
let worker = new SharedWorker('resources/worker.js');
worker.port.onmessage = t.step_func(e => {
assert_array_equals(e.data, ['from c2', 'done']);
if (++doneCount == 2) t.done();
});
worker.port.postMessage({channel: 'shared worker'});
}, 'BroadcastChannel works in shared workers');
async_test(t => {
let c = new BroadcastChannel('worker-close');
let events = [];
c.onmessage = e => events.push('c1: ' + e.data);
let worker = new Worker('resources/worker.js');
worker.onmessage = t.step_func(e => {
assert_array_equals(events,
['c1: from worker', 'c2: ready', 'c2: echo'],
'messages in document');
assert_array_equals(e.data, ['done'], 'messages in worker');
t.done();
});
worker.onmessagerror =
t.unreached_func('Worker\'s onmessageerror handler called');
c.addEventListener('message', e => {
if (e.data == 'from worker') {
c.close();
if (self.gc) self.gc();
window.setTimeout(() => {
let c2 = new BroadcastChannel('worker-close');
c2.onmessage = e => {
events.push('c2: ' + e.data);
if (e.data === 'ready') {
worker.postMessage({ping: 'echo'});
} else {
c2.postMessage('done');
c2.close();
}
};
// For some implementations there may be a race condition between
// when the BroadcastChannel instance above is created / ready to
// receive messages and when the worker calls postMessage on it's
// BroadcastChannel instance. To avoid this, confirm that our
// instance can receive a message before indicating to the other
// thread that we are ready. For more details, see:
// https://github.com/whatwg/html/issues/7267
let c3 = new BroadcastChannel('worker-close');
c3.postMessage('ready');
c3.close();
}, 1);
}
});
worker.postMessage({channel: 'worker-close'});
t.add_cleanup(() => worker.terminate());
}, 'Closing and re-opening a channel works.');
async_test(t => {
function workerCode() {
close();
try {
var bc = new BroadcastChannel('worker-create-after-close');
} catch (e) {
postMessage(e);
return;
}
postMessage(true);
}
var workerBlob = new Blob(
[workerCode.toString() + ';workerCode();'],
{type: 'application/javascript'});
var w = new Worker(URL.createObjectURL(workerBlob));
w.onmessage = t.step_func_done(function(e) {
assert_equals(
e.data, true,
'BroadcastChannel creation in closed worker triggered exception: ' +
e.data.message);
});
t.add_cleanup(() => w.terminate());
}, 'BroadcastChannel created after a worker self.close()');
function postMessageFromWorkerWorkerCode(workerName, channelName) {
if (workerName === 'close-before-create-worker') {
close();
}
let bc = new BroadcastChannel(channelName);
if (workerName === 'close-after-create-worker') {
close();
}
bc.postMessage(workerName + ' done');
postMessage(true);
}
function doPostMessageFromWorkerTest(t, workerName, channelName) {
var bc = new BroadcastChannel(channelName);
bc.onmessage = t.step_func_done(function(e) {
assert_equals(
e.data, 'done-worker done',
'BroadcastChannel message should only be received from the second worker');
});
t.add_cleanup(() => bc.close());
var testMessageHandler = t.step_func(function(e) {
assert_equals(
e.data, true,
'Worker sent postMessage indicating it sent a BroadcastChannel message');
var w = createWorker(
postMessageFromWorkerWorkerCode, 'done-worker', channelName);
t.add_cleanup(() => w.terminate());
});
createWorker(
postMessageFromWorkerWorkerCode, workerName, channelName,
testMessageHandler);
// To avoid calling t.step_timeout here, have the worker postMessage(true)
// once it is finished and then we'll instantiate another worker that
// performs the same test steps but doesn't close. By the time the
// BroadcastChannel message in that worker gets sent successfully it should
// be safe to assume that any BroadcastChannel messages from the previous
// worker would have been sent if they were going to be.
}
function createWorker(workerCode, workerName, channelName, handler = null) {
var workerCodeStr = workerCode.toString() +
`;${workerCode.name}("${workerName}", "${channelName}");`;
var workerBlob = new Blob([workerCodeStr], {type: 'application/javascript'});
var w = new Worker(URL.createObjectURL(workerBlob));
if (handler !== null) {
w.onmessage = handler;
}
return w;
}
async_test(t => {
const workerName = 'close-after-create-worker';
const channelName = workerName + '-postmessage-from-worker';
doPostMessageFromWorkerTest(t, workerName, channelName);
}, 'BroadcastChannel messages from closed worker to parent should be ignored (BC created before closing)');
async_test(t => {
const workerName = 'close-before-create-worker';
const channelName = workerName + '-postmessage-from-worker';
doPostMessageFromWorkerTest(t, workerName, channelName);
}, 'BroadcastChannel messages from closed worker to parent should be ignored (BC created after closing)');
function postMessageToWorkerWorkerCode(workerName, channelName) {
self.addEventListener('message', () => {
if (workerName === 'close-before-create-worker') {
close();
}
try {
let bc1 = new BroadcastChannel(channelName);
bc1.onmessage = e => {
if (e.data === 'ready') {
postMessage(e.data);
} else if (e.data === 'test') {
postMessage(workerName + ' done');
}
};
bc1.onmessageerror = () => {
postMessage('onmessageerror called from worker BroadcastChannel');
};
if (workerName === 'close-after-create-worker') {
close();
}
} catch (e) {
postMessage(e);
return;
}
if (workerName === 'done-worker') {
// For some implementations there may be a race condition between when
// the BroadcastChannel instance above is created / ready to receive
// messages and when the parent calls postMessage on it's
// BroadcastChannel instance. To avoid this, confirm that our instance
// can receive a message before indicating to the other thread that we
// are ready. For more details, see:
// https://github.com/whatwg/html/issues/7267
let bc2 = new BroadcastChannel(channelName);
bc2.postMessage('ready');
bc2.close();
} else {
// Since the worker has closed, it's not expected that the
// BroadcastChannel will receive messages (there's a separate test for
// that), so just indicate directly that it's ready to test receiving
// a message from the parent dispite the possibility of a race condition.
postMessage('ready');
}
});
self.addEventListener('messageerror', () => {
postMessage('onmessageerror called from worker');
});
}
function doPostMessageToWorkerTest(t, workerName, channelName) {
var bc = new BroadcastChannel(channelName);
t.add_cleanup(() => bc.close());
var doneMessageHandler = t.step_func(function(e) {
if (e.data === 'ready') {
bc.postMessage('test');
} else if (e.data === 'done-worker done') {
t.done();
} else {
assert_unreached(
'BroadcastChannel.postMessage triggered exception within second worker: ' +
e.data.message);
}
});
var testMessageHandler = t.step_func(function(e) {
assert_equals(
e.data, 'ready',
'Worker sent postMessage indicating its BroadcastChannel instance is ready');
bc.postMessage('test');
var doneWorker = createWorker(
postMessageToWorkerWorkerCode, 'done-worker', channelName,
doneMessageHandler);
t.add_cleanup(() => {
doneWorker.terminate();
});
doneWorker.postMessage('start');
});
var testWorker = createWorker(
postMessageToWorkerWorkerCode, workerName, channelName,
testMessageHandler);
testWorker.postMessage('start');
}
async_test(t => {
const workerName = 'close-after-create-worker';
const channelName = workerName + '-postmessage-to-worker';
doPostMessageToWorkerTest(t, workerName, channelName);
}, 'BroadcastChannel messages from parent to closed worker should be ignored (BC created before closing)');
async_test(t => {
const workerName = 'close-before-create-worker';
const channelName = workerName + '-postmessage-to-worker';
doPostMessageToWorkerTest(t, workerName, channelName);
}, 'BroadcastChannel messages from parent to closed worker should be ignored (BC created after closing)');
function postMessageWithinWorkerWorkerCode(workerName, channelName) {
if (workerName === 'close-before-create-worker') {
close();
}
try {
let bc1 = new BroadcastChannel(channelName);
let bc2 = new BroadcastChannel(channelName);
bc1.onmessage = e => {
postMessage(workerName + ' done')
};
if (workerName === 'close-after-create-worker') {
close();
}
bc2.postMessage(true);
postMessage(true);
} catch (e) {
postMessage(e);
}
}
function doPostMessageWithinWorkerTest(t, workerName, channelName) {
var doneMessageHandler = t.step_func(function(e) {
if (e.data === true) {
// Done worker has finished - no action needed
} else if (e.data === 'done-worker done') {
t.done();
} else {
assert_unreached(
'BroadcastChannel.postMessage triggered exception within second worker: ' +
e.data.message);
}
});
var testMessageHandler = t.step_func(function(e) {
assert_equals(
e.data, true,
'Worker indicated that the test procedures were executed successfully');
var w = createWorker(
postMessageWithinWorkerWorkerCode, 'done-worker', channelName,
doneMessageHandler);
t.add_cleanup(() => w.terminate());
});
createWorker(
postMessageWithinWorkerWorkerCode, workerName, channelName,
testMessageHandler);
}
async_test(t => {
const workerName = 'close-after-create-worker';
const channelName = workerName + '-postmessage-within-worker';
doPostMessageWithinWorkerTest(t, workerName, channelName);
}, 'BroadcastChannel messages within closed worker should be ignored (BCs created before closing)');
async_test(t => {
const workerName = 'close-before-create-worker';
const channelName = workerName + '-postmessage-within-worker';
doPostMessageWithinWorkerTest(t, workerName, channelName);
}, 'BroadcastChannel messages within closed worker should be ignored (BCs created after closing)');
</script>