Upload Kmake

This commit is contained in:
Gorochu
2026-05-26 23:36:42 -07:00
parent ba051b2f74
commit 555ec72358
41615 changed files with 13344630 additions and 1 deletions

View File

@ -0,0 +1,3 @@
features:
- name: broadcast-channel
files: "**"

View File

@ -0,0 +1,128 @@
test(function() {
assert_throws_js(
TypeError,
() => BroadcastChannel(""),
"Calling BroadcastChannel constructor without 'new' must throw"
);
}, "BroadcastChannel constructor called as normal function");
async_test(t => {
let c1 = new BroadcastChannel('eventType');
let c2 = new BroadcastChannel('eventType');
c2.onmessage = t.step_func(e => {
assert_true(e instanceof MessageEvent);
assert_equals(e.target, c2);
assert_equals(e.type, 'message');
assert_equals(e.origin, location.origin, 'origin');
assert_equals(e.data, 'hello world');
assert_equals(e.source, null, 'source');
t.done();
});
c1.postMessage('hello world');
}, 'postMessage results in correct event');
async_test(t => {
let c1 = new BroadcastChannel('order');
let c2 = new BroadcastChannel('order');
let c3 = new BroadcastChannel('order');
let events = [];
let doneCount = 0;
let handler = t.step_func(e => {
events.push(e);
if (e.data == 'done') {
doneCount++;
if (doneCount == 2) {
assert_equals(events.length, 6);
assert_equals(events[0].target, c2, 'target for event 0');
assert_equals(events[0].data, 'from c1');
assert_equals(events[1].target, c3, 'target for event 1');
assert_equals(events[1].data, 'from c1');
assert_equals(events[2].target, c1, 'target for event 2');
assert_equals(events[2].data, 'from c3');
assert_equals(events[3].target, c2, 'target for event 3');
assert_equals(events[3].data, 'from c3');
assert_equals(events[4].target, c1, 'target for event 4');
assert_equals(events[4].data, 'done');
assert_equals(events[5].target, c3, 'target for event 5');
assert_equals(events[5].data, 'done');
t.done();
}
}
});
c1.onmessage = handler;
c2.onmessage = handler;
c3.onmessage = handler;
c1.postMessage('from c1');
c3.postMessage('from c3');
c2.postMessage('done');
}, 'messages are delivered in port creation order');
async_test(t => {
let c1 = new BroadcastChannel('closed');
let c2 = new BroadcastChannel('closed');
let c3 = new BroadcastChannel('closed');
c2.onmessage = t.unreached_func();
c2.close();
c3.onmessage = t.step_func(() => t.done());
c1.postMessage('test');
}, 'messages aren\'t delivered to a closed port');
async_test(t => {
let c1 = new BroadcastChannel('closed');
let c2 = new BroadcastChannel('closed');
let c3 = new BroadcastChannel('closed');
c2.onmessage = t.unreached_func();
c3.onmessage = t.step_func(() => t.done());
c1.postMessage('test');
c2.close();
}, 'messages aren\'t delivered to a port closed after calling postMessage.');
async_test(t => {
let c1 = new BroadcastChannel('create-in-onmessage');
let c2 = new BroadcastChannel('create-in-onmessage');
c2.onmessage = t.step_func(e => {
assert_equals(e.data, 'first');
c2.close();
let c3 = new BroadcastChannel('create-in-onmessage');
c3.onmessage = t.step_func(event => {
assert_equals(event.data, 'done');
t.done();
});
c1.postMessage('done');
});
c1.postMessage('first');
c2.postMessage('second');
}, 'closing and creating channels during message delivery works correctly');
async_test(t => {
let c1 = new BroadcastChannel('close-in-onmessage');
let c2 = new BroadcastChannel('close-in-onmessage');
let c3 = new BroadcastChannel('close-in-onmessage');
let events = [];
c1.onmessage = e => events.push('c1: ' + e.data);
c2.onmessage = e => events.push('c2: ' + e.data);
c3.onmessage = e => events.push('c3: ' + e.data);
// c2 closes itself when it receives the first message
c2.addEventListener('message', e => {
c2.close();
});
c3.addEventListener('message', t.step_func(e => {
if (e.data == 'done') {
assert_array_equals(events, [
'c2: first',
'c3: first',
'c3: done']);
t.done();
}
}));
c1.postMessage('first');
c1.postMessage('done');
}, 'Closing a channel in onmessage prevents already queued tasks from firing onmessage events');

View File

@ -0,0 +1,82 @@
<!DOCTYPE html>
<meta charset=utf-8>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/gc.js"></script>
<script>
async_test(t => {
const c1 = new BroadcastChannel('blob');
const c2 = new BroadcastChannel('blob');
const c3 = new BroadcastChannel('blob');
let readCount = 0;
c2.onmessage = t.step_func(e => {
// check blob
assert_true('blob' in e.data);
assert_true(e.data.blob instanceof Blob);
assert_equals(e.data.blob.size, 6);
const reader = new FileReader();
reader.onerror = t.unreached_func();
reader.onload = t.step_func(() => {
assert_equals(reader.result, 'foobar');
if (++readCount == 2)
t.done();
});
reader.readAsText(e.data.blob);
});
c3.onmessage = c2.onmessage;
(() => {
c1.postMessage({blob: new Blob(['foo', 'bar'])});
})();
garbageCollect();
}, 'Blobs work on BroadcastChannel');
async_test(t => {
const c1 = new BroadcastChannel('blobworker');
const c2 = new BroadcastChannel('blobworker');
const events = [];
const verifyEvents = function() {
assert_equals(events.length, 5);
assert_equals(events[0], 'from worker');
assert_equals(events[1], 'from worker');
assert_true(events[2].blob instanceof Blob);
assert_equals(events[2].blob.size, 11);
assert_true(events[3].blob instanceof Blob);
assert_equals(events[3].blob.size, 11);
assert_equals(events[4], 'done');
const reader = new FileReader();
reader.onerror = t.unreached_func();
reader.onload = t.step_func(() => {
assert_equals(reader.result, 'hello-world');
t.done();
});
reader.readAsText(events[3].blob);
};
let receivedDone = false;
let receivedWorkerDone = false;
c1.onmessage = e => events.push(e.data);
c2.onmessage = e => events.push(e.data);
c2.addEventListener('message', t.step_func(e => {
if (e.data.blob)
c1.postMessage('done');
if (e.data === 'done')
receivedDone = true;
if (receivedDone && receivedWorkerDone)
verifyEvents();
}));
const worker = new Worker('resources/worker.js');
worker.onmessage = t.step_func(e => {
receivedWorkerDone = true;
if (receivedDone && receivedWorkerDone)
verifyEvents();
});
worker.postMessage({channel: 'blobworker'});
worker.postMessage({blob: ['hello-world']});
}, 'Blobs work with workers on BroadcastChannel');
</script>

View File

@ -0,0 +1,38 @@
<!DOCTYPE html>
<meta charset=utf-8>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<!-- Pull in the with_iframe helper function from the service worker tests -->
<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
<body>
<script>
const events = [];
function testCompletion(t) {
return new Promise((resolve) => {
window.addEventListener("message", t.step_func(e => {
if (e.data == 'done') {
assert_equals(events.length, 0);
resolve();
}
}));
});
}
promise_test(async t => {
const bc0 = new BroadcastChannel('no-cross-origin-messages');
bc0.onmessage = e => {window.events.push(e);};
const testResults = testCompletion(t);
const url = get_host_info().HTTPS_NOTSAMESITE_ORIGIN +
'/webmessaging/broadcastchannel/resources/cross-origin.html';
await with_iframe(url);
return testResults;
}, "Messages aren't delivered across origins");
</script>
</body>

View File

@ -0,0 +1,356 @@
<!DOCTYPE html>
<meta charset=utf-8>
<meta name="timeout" content="long">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="/common/utils.js"></script>
<script src="/common/dispatcher/dispatcher.js"></script>
<!-- Pull in executor_path needed by newPopup / newIframe -->
<script src="/html/cross-origin-embedder-policy/credentialless/resources/common.js"></script>
<!-- Pull in importScript / newPopup / newIframe -->
<script src="/html/anonymous-iframe/resources/common.js"></script>
<body>
<script>
const bc_postmessage_js = (channel_name, message, done_queue_name) => `
const bc = new BroadcastChannel("${channel_name}");
bc.postMessage("${message}");
send("${done_queue_name}", "done");
`;
const add_iframe_js = (iframe_origin, response_queue_uuid) => `
const importScript = ${importScript};
await importScript("/html/cross-origin-embedder-policy/credentialless" +
"/resources/common.js");
await importScript("/html/anonymous-iframe/resources/common.js");
await importScript("/common/utils.js");
send("${response_queue_uuid}", newIframe("${iframe_origin}"));
`;
const same_site_origin = get_host_info().HTTPS_ORIGIN;
const cross_site_origin = get_host_info().HTTPS_NOTSAMESITE_ORIGIN;
async function create_test_iframes(t, response_queue_uuid) {
// Create a same-origin iframe in a cross-site popup.
const not_same_site_popup_uuid = newPopup(t, cross_site_origin);
send(not_same_site_popup_uuid,
add_iframe_js(same_site_origin, response_queue_uuid));
const iframe_1_uuid = await receive(response_queue_uuid);
// Create a same-origin iframe in a same-site popup.
const same_origin_popup_uuid = newPopup(t, same_site_origin);
send(same_origin_popup_uuid,
add_iframe_js(same_site_origin, response_queue_uuid));
const iframe_2_uuid = await receive(response_queue_uuid);
return [iframe_1_uuid, iframe_2_uuid];
}
promise_test(t => {
return new Promise(async (resolve, reject) => {
const response_queue_uuid = token();
const [iframe_1_uuid, iframe_2_uuid] =
await create_test_iframes(t, response_queue_uuid);
const channel_name = token();
const bc = new BroadcastChannel(channel_name);
bc.onmessage = resolve;
// Instruct the not-same-top-level-site iframe to send a message on the BC
// channel we are listening on. This message should not be received since
// the iframe should be in a different partition.
send(iframe_1_uuid,
bc_postmessage_js(channel_name, "iframe1 msg", response_queue_uuid));
if (await receive(response_queue_uuid) != "done") {
reject("Unable to trigger iframe1 BroadcastChannel message creation");
}
// Now instruct the same-top-level-site iframe to send a BC message. By
// the time we send the script to execute, have it send the BC message,
// and then receive the BC message in our BC instance, it should be
// reasonable to assume that the message from the first iframe would have
// been delivered if it was going to be.
send(iframe_2_uuid,
bc_postmessage_js(channel_name, "iframe2 msg", response_queue_uuid));
if (await receive(response_queue_uuid) != "done") {
reject("Unable to trigger iframe2 BroadcastChannel message creation");
}
}).then(event => {
assert_equals(event.data, "iframe2 msg");
});
}, "BroadcastChannel messages aren't received from a cross-partition iframe");
// Optional Test: Checking for partitioned BroadcastChannels in an A->B->A
// (nested-iframe with cross-site ancestor chain) scenario.
promise_test(t => {
return new Promise(async (resolve, reject) => {
const response_queue_uuid = token();
const [cross_site_iframe_uuid, all_same_parent_iframe_uuid] =
await create_test_iframes(t, response_queue_uuid);
// Create a same-origin iframe in a cross-site iframe in a same-site popup.
// Create the same-site child iframe of the cross-site iframe (A->B->A).
send(cross_site_iframe_uuid,
add_iframe_js(same_site_origin, response_queue_uuid));
const same_site_iframe_uuid = await receive(response_queue_uuid);
// Create a same-origin iframe in a same-site iframe in a same-site popup.
// Create the same-site child iframe of the same-site parent iframe (A->A->A).
send(all_same_parent_iframe_uuid,
add_iframe_js(same_site_origin, response_queue_uuid));
const all_same_child_iframe_uuid = await receive(response_queue_uuid);
const channel_name = token();
const bc = new BroadcastChannel(channel_name);
bc.onmessage = resolve;
// Instruct the A->B->A child iframe to send a message on the BC
// channel we are listening on. This message should not be received since
// the iframe should be in a different partition.
send(same_site_iframe_uuid,
bc_postmessage_js(channel_name, "iframe1 msg", response_queue_uuid));
if (await receive(response_queue_uuid) != "done") {
reject("Unable to trigger A->B->A BroadcastChannel message creation");
}
// Now instruct the A->A->A child iframe to send a BC message. By
// the time we send the script to execute, send the BC message,
// and receive the BC message in our BC instance, it should be
// reasonable to assume that the message from the first iframe would have
// been delivered if it was going to be.
send(all_same_child_iframe_uuid,
bc_postmessage_js(channel_name, "iframe2 msg", response_queue_uuid));
if (await receive(response_queue_uuid) != "done") {
reject("Unable to trigger A->A->A BroadcastChannel message creation");
}
}).then(event => {
assert_equals(event.data, "iframe2 msg");
});
}, "BroadcastChannel messages aren't received from a nested iframe with a cross-site ancestor");
const newWorker = (origin) => {
const worker_token = token();
const worker_url = origin + executor_worker_path + `&uuid=${worker_token}`;
const worker = new Worker(worker_url);
return worker_token;
}
promise_test(t => {
return new Promise(async (resolve, reject) => {
const response_queue_uuid = token();
const create_worker_js = (origin) => `
const importScript = ${importScript};
await importScript("/html/cross-origin-embedder-policy/credentialless" +
"/resources/common.js");
await importScript("/html/anonymous-iframe/resources/common.js");
await importScript("/common/utils.js");
const newWorker = ${newWorker};
send("${response_queue_uuid}", newWorker("${origin}"));
`;
const [iframe_1_uuid, iframe_2_uuid] =
await create_test_iframes(t, response_queue_uuid);
// Create a dedicated worker in the cross-top-level-site iframe.
send(iframe_1_uuid, create_worker_js(same_site_origin));
const worker_1_uuid = await receive(response_queue_uuid);
// Create a dedicated worker in the same-top-level-site iframe.
send(iframe_2_uuid, create_worker_js(same_site_origin));
const worker_2_uuid = await receive(response_queue_uuid);
const channel_name = token();
const bc = new BroadcastChannel(channel_name);
bc.onmessage = async (e) => {
await send(worker_1_uuid, "self.close();");
await send(worker_2_uuid, "self.close();");
resolve(e);
};
// Instruct the not-same-top-level-site worker to send a message on the BC
// channel we are listening on. This message should not be received since
// the worker should be in a different partition.
send(worker_1_uuid,
bc_postmessage_js(channel_name, "worker1 msg", response_queue_uuid));
if (await receive(response_queue_uuid) != "done") {
reject("Unable to trigger worker1 BroadcastChannel message creation");
}
// Now instruct the same-top-level-site worker to send a BC message. By
// the time we send the script to execute, have it send the BC message,
// and then receive the BC message in our BC instance, it should be
// reasonable to assume that the message from the first worker would have
// been delivered if it was going to be.
send(worker_2_uuid,
bc_postmessage_js(channel_name, "worker2 msg", response_queue_uuid));
if (await receive(response_queue_uuid) != "done") {
reject("Unable to trigger worker2 BroadcastChannel message creation");
}
}).then(event => {
assert_equals(event.data, "worker2 msg");
});
}, "BroadcastChannel messages aren't received from a cross-partition dedicated worker");
const newSharedWorker = (origin) => {
const worker_token = token();
const worker_url = origin + executor_worker_path + `&uuid=${worker_token}`;
const worker = new SharedWorker(worker_url, worker_token);
return worker_token;
}
promise_test(t => {
return new Promise(async (resolve, reject) => {
const response_queue_uuid = token();
const create_worker_js = (origin) => `
const importScript = ${importScript};
await importScript("/html/cross-origin-embedder-policy/credentialless" +
"/resources/common.js");
await importScript("/html/anonymous-iframe/resources/common.js");
await importScript("/common/utils.js");
const newSharedWorker = ${newSharedWorker};
send("${response_queue_uuid}", newSharedWorker("${origin}"));
`;
const [iframe_1_uuid, iframe_2_uuid] =
await create_test_iframes(t, response_queue_uuid);
// Create a shared worker in the cross-top-level-site iframe.
send(iframe_1_uuid, create_worker_js(same_site_origin));
const worker_1_uuid = await receive(response_queue_uuid);
// Create a shared worker in the same-top-level-site iframe.
send(iframe_2_uuid, create_worker_js(same_site_origin));
const worker_2_uuid = await receive(response_queue_uuid);
const channel_name = token();
const bc = new BroadcastChannel(channel_name);
bc.onmessage = async (e) => {
await send(worker_1_uuid, "self.close();");
await send(worker_2_uuid, "self.close();");
resolve(e);
};
// Instruct the not-same-top-level-site worker to send a message on the BC
// channel we are listening on. This message should not be received since
// the worker should be in a different partition.
send(worker_1_uuid,
bc_postmessage_js(channel_name, "worker1 msg", response_queue_uuid));
if (await receive(response_queue_uuid) != "done") {
reject("Unable to trigger worker1 BroadcastChannel message creation");
}
// Now instruct the same-top-level-site worker to send a BC message. By
// the time we send the script to execute, have it send the BC message,
// and then receive the BC message in our BC instance, it should be
// reasonable to assume that the message from the first worker would have
// been delivered if it was going to be.
send(worker_2_uuid,
bc_postmessage_js(channel_name, "worker2 msg", response_queue_uuid));
if (await receive(response_queue_uuid) != "done") {
reject("Unable to trigger worker2 BroadcastChannel message creation");
}
}).then(event => {
assert_equals(event.data, "worker2 msg");
});
}, "BroadcastChannel messages aren't received from a cross-partition shared worker");
const newServiceWorker = async (origin) => {
const worker_token = token();
const worker_url = origin + executor_service_worker_path +
`&uuid=${worker_token}`;
const worker_url_path = executor_service_worker_path.substring(0,
executor_service_worker_path.lastIndexOf('/'));
const scope = worker_url_path + "/not-used/";
const reg = await navigator.serviceWorker.register(worker_url,
{'scope': scope});
return worker_token;
}
promise_test(t => {
return new Promise(async (resolve, reject) => {
const response_queue_uuid = token();
const create_worker_js = (origin) => `
const importScript = ${importScript};
await importScript("/html/cross-origin-embedder-policy/credentialless" +
"/resources/common.js");
await importScript("/html/anonymous-iframe/resources/common.js");
await importScript("/common/utils.js");
const newServiceWorker = ${newServiceWorker};
send("${response_queue_uuid}", await newServiceWorker("${origin}"));
`;
const [iframe_1_uuid, iframe_2_uuid] =
await create_test_iframes(t, response_queue_uuid);
// Create a service worker in the cross-top-level-site iframe.
send(iframe_1_uuid, create_worker_js(same_site_origin));
var worker_1_uuid = await receive(response_queue_uuid);
const channel_name = token();
const bc = new BroadcastChannel(channel_name);
bc.onmessage = async (e) => {
if (worker_1_uuid) {
await send(worker_1_uuid, "self.registration.unregister();");
}
if (worker_2_uuid) {
await send(worker_2_uuid, "self.registration.unregister();");
}
resolve(e);
};
// Instruct the not-same-top-level-site worker to send a message on the BC
// channel we are listening on. This message should not be received since
// the worker should be in a different partition.
send(worker_1_uuid,
bc_postmessage_js(channel_name, "worker1 msg", response_queue_uuid));
if (await receive(response_queue_uuid) != "done") {
reject("Unable to trigger worker1 BroadcastChannel message creation");
}
await send(worker_1_uuid, "await self.registration.unregister();");
worker_1_uuid = undefined;
// Create a service worker in the same-top-level-site iframe. Note that
// if service workers are unpartitioned then this new service worker would
// replace the one created above. This is why we wait to create the second
// service worker until after we are done with the first one.
send(iframe_2_uuid, create_worker_js(same_site_origin));
var worker_2_uuid = await receive(response_queue_uuid);
// Now instruct the same-top-level-site worker to send a BC message. By
// the time we send the script to execute, have it send the BC message,
// and then receive the BC message in our BC instance, it should be
// reasonable to assume that the message from the first worker would have
// been delivered if it was going to be.
send(worker_2_uuid,
bc_postmessage_js(channel_name, "worker2 msg", response_queue_uuid));
if (await receive(response_queue_uuid) != "done") {
reject("Unable to trigger worker2 BroadcastChannel message creation");
}
await send(worker_2_uuid, "await self.registration.unregister();");
worker_2_uuid = undefined;
}).then(event => {
assert_equals(event.data, "worker2 msg");
});
}, "BroadcastChannel messages aren't received from a cross-partition service worker");
</script>
</body>

View File

@ -0,0 +1,174 @@
<!DOCTYPE html>
<meta charset=utf-8>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<!-- Pull in the with_iframe helper function from the service worker tests -->
<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
<body>
<script>
const TEST_IFRAME_CLASS_NAME = 'test-iframe';
const events = [];
var bc1;
const DONE_MSG = 'done';
function DetachedIframeTestCheckForOneMessage(t) {
return new Promise((resolve) => {
bc1.onmessage = t.step_func(e => {
events.push(e);
if (e.data == DONE_MSG) {
assert_equals(events.length, 1);
resolve();
}
});
});
}
const IframeAction = {
REMOVE_BEFORE_CREATION: 'remove-before-creation',
REMOVE_AFTER_CREATION: 'remove-after-creation',
};
async function doMessageSentTest(t, channelName, action) {
await with_iframe('about:blank');
const iframe = document.getElementsByClassName(TEST_IFRAME_CLASS_NAME)[0];
const iframe_BroadcastChannel = iframe.contentWindow.BroadcastChannel;
if (action === IframeAction.REMOVE_BEFORE_CREATION) {
iframe.remove();
}
events.length = 0;
bc1 = new BroadcastChannel(channelName);
const bc2 = new BroadcastChannel(channelName);
const iframe_bc = new iframe_BroadcastChannel(channelName);
if (action === IframeAction.REMOVE_AFTER_CREATION) {
iframe.remove();
}
const testResultsPromise = DetachedIframeTestCheckForOneMessage(t);
iframe_bc.postMessage('test');
bc2.postMessage(DONE_MSG);
bc2.close();
iframe_bc.close();
t.add_cleanup(() => bc1.close());
return testResultsPromise;
}
promise_test(async t => {
return doMessageSentTest(
t, 'postMessage-from-detached-iframe-pre',
IframeAction.REMOVE_AFTER_CREATION);
}, 'BroadcastChannel messages from detached iframe to parent should be ignored (BC created before detaching)');
promise_test(async t => {
return doMessageSentTest(
t, 'postMessage-from-detached-iframe-post',
IframeAction.REMOVE_BEFORE_CREATION);
}, 'BroadcastChannel messages from detached iframe to parent should be ignored (BC created after detaching)');
async function doMessageReceivedTest(t, channelName, action) {
await with_iframe('about:blank');
const iframe = document.getElementsByClassName(TEST_IFRAME_CLASS_NAME)[0];
const iframe_BroadcastChannel = iframe.contentWindow.BroadcastChannel;
if (action === IframeAction.REMOVE_BEFORE_CREATION) {
iframe.remove();
}
events.length = 0;
// `iframe_bc` must be created first so that it receives messages before
// `bc1`. That way we can tell whether `iframe_bc` received a message by
// inspecting `events` in the `bc1` message handler.
const iframe_bc = new iframe_BroadcastChannel(channelName);
iframe_bc.onmessage = e => {
events.push(e)
};
bc1 = new BroadcastChannel(channelName);
const bc2 = new BroadcastChannel(channelName);
if (action === IframeAction.REMOVE_AFTER_CREATION) {
iframe.remove();
}
const testResultsPromise = DetachedIframeTestCheckForOneMessage(t);
bc2.postMessage(DONE_MSG);
bc2.close();
iframe_bc.close();
t.add_cleanup(() => bc1.close());
}
promise_test(async t => {
return doMessageReceivedTest(
t, 'postMessage-to-detached-iframe-pre',
IframeAction.REMOVE_AFTER_CREATION);
}, 'BroadcastChannel messages from parent to detached iframe should be ignored (BC created before detaching)');
promise_test(async t => {
return doMessageReceivedTest(
t, 'postMessage-to-detached-iframe-post',
IframeAction.REMOVE_BEFORE_CREATION);
}, 'BroadcastChannel messages from parent to detached iframe should be ignored (BC created after detaching)');
async function doMessageSendReceiveTest(t, channelName, action) {
await with_iframe('about:blank');
const iframe = document.getElementsByClassName(TEST_IFRAME_CLASS_NAME)[0];
const iframe_BroadcastChannel = iframe.contentWindow.BroadcastChannel;
if (action === IframeAction.REMOVE_BEFORE_CREATION) {
iframe.remove();
}
const iframe_bc1 = new iframe_BroadcastChannel(channelName);
const iframe_bc2 = new iframe_BroadcastChannel(channelName);
iframe_bc1.onmessage = t.unreached_func(
'Detached iframe BroadcastChannel instance received message unexpectedly');
if (action === IframeAction.REMOVE_AFTER_CREATION) {
iframe.remove();
}
iframe_bc2.postMessage(DONE_MSG);
iframe_bc2.close();
t.add_cleanup(() => iframe_bc1.close());
// To avoid calling t.step_timeout here, instead just create two new
// BroadcastChannel instances and complete the test when a message is passed
// between them. Per the spec, all "BroadcastChannel objects whose relevant
// agents are the same" must have messages delivered to them in creation
// order, so if we get this message then it's safe to assume the earlier
// message would have been delivered if it was going to be.
const bc1 = new BroadcastChannel(channelName);
const bc2 = new BroadcastChannel(channelName);
return new Promise((resolve) => {
bc1.onmessage = t.step_func(e => {
resolve();
});
bc2.postMessage(DONE_MSG);
});
}
promise_test(async t => {
return doMessageSendReceiveTest(
t, 'postMessage-within-detached-iframe-pre',
IframeAction.REMOVE_AFTER_CREATION);
}, 'BroadcastChannel messages within detached iframe should be ignored (BCs created before detaching)');
promise_test(async t => {
return doMessageSendReceiveTest(
t, 'postMessage-within-detached-iframe-post',
IframeAction.REMOVE_BEFORE_CREATION);
}, 'BroadcastChannel messages within detached iframe should be ignored (BCs created after detaching)');
</script>
</body>

View File

@ -0,0 +1,65 @@
test(() => assert_throws_js(TypeError, () => new BroadcastChannel()),
'Should throw if no name is provided');
test(() => {
let c = new BroadcastChannel(null);
assert_equals(c.name, 'null');
}, 'Null name should not throw');
test(() => {
let c = new BroadcastChannel(undefined);
assert_equals(c.name, 'undefined');
}, 'Undefined name should not throw');
test(() => {
let c = new BroadcastChannel('fooBar');
assert_equals(c.name, 'fooBar');
}, 'Non-empty name should not throw');
test(() => {
let c = new BroadcastChannel(123);
assert_equals(c.name, '123');
}, 'Non-string name should not throw');
test(() => {
let c = new BroadcastChannel('');
assert_throws_js(TypeError, () => c.postMessage());
}, 'postMessage without parameters should throw');
test(() => {
let c = new BroadcastChannel('');
c.postMessage(null);
}, 'postMessage with null should not throw');
test(() => {
let c = new BroadcastChannel('');
c.close();
}, 'close should not throw');
test(() => {
let c = new BroadcastChannel('');
c.close();
c.close();
}, 'close should not throw when called multiple times');
test(() => {
let c = new BroadcastChannel('');
c.close();
assert_throws_dom('InvalidStateError', () => c.postMessage(''));
}, 'postMessage after close should throw');
test(() => {
let c = new BroadcastChannel('');
assert_not_equals(c.onmessage, undefined);
}, 'BroadcastChannel should have an onmessage event');
test(() => {
let c = new BroadcastChannel('');
assert_throws_dom('DataCloneError', () => c.postMessage(Symbol()));
}, 'postMessage should throw with uncloneable data');
test(() => {
let c = new BroadcastChannel('');
c.close();
assert_throws_dom('InvalidStateError', () => c.postMessage(Symbol()));
}, 'postMessage should throw InvalidStateError after close, even with uncloneable data');

View File

@ -0,0 +1,193 @@
<!doctype html>
<html>
<head>
<meta charset=utf-8>
<meta name="timeout" content="long">
<title></title>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
</head>
<body>
<script>
<!--
promise_test(t => {
return new Promise((resolve) => {
let ifr = document.createElement("iframe");
ifr.src =
"data:text/html,<script> let bc = new BroadcastChannel(\"test\");" +
"bc.onmessage = (e) => {" +
" if (e.data == \"ping\") bc.postMessage('pong');"+
" else parent.postMessage({workerMessageOrigin: e.data, messageOrigin: e.origin}, \"*\"); };" +
"new Worker(URL.createObjectURL(new Blob([\"" +
"let bc2 = new BroadcastChannel('test'); bc2.postMessage('ping'); " +
"bc2.onmessage = e => bc2.postMessage(e.origin);" +
"\"], {type: 'text/javascript'}))); </script>";
window.addEventListener("message", t.step_func(e => {
assert_equals(e.data.workerMessageOrigin, "null");
assert_equals(e.data.messageOrigin, "null");
resolve();
}), {once: true});
t.add_cleanup(() => { document.body.removeChild(ifr) });
document.body.appendChild(ifr);
});
}, "Opaque origin should be serialized to \"null\"");
const iframe_src = (channel_name, iframe_name) => `data:text/html,<script>
let bc2 = new BroadcastChannel("${channel_name}");
bc2.onmessage = (e) => {
if (e.data == "from-${iframe_name}") {
parent.postMessage("${iframe_name}-done", "*");
} else {
parent.postMessage("fail", "*");
}
};
let bc3 = new BroadcastChannel("${channel_name}");
bc3.postMessage("from-${iframe_name}");
</script>`;
promise_test(t => {
return new Promise((resolve, reject) => {
const channel_name = "opaque-origin-test-2";
const bc1 = new BroadcastChannel(channel_name);
bc1.onmessage = t.unreached_func("Received message from an opaque origin");
// We'll create an iframe and have it send a BroadcastChannel message
// between two instances. Once the message is received, it will postMessage
// back and we'll repeat this with another iframe. If the first
// BroadcastChannel message is received by `bc1`, or if the second
// BroadcastChannel message is received by `bc1` or `bc2` in the first
// iframe, then the test should fail.
window.addEventListener("message", e => {
if(e.data == "iframe1-done") {
let iframe2 = document.createElement("iframe");
iframe2.src = iframe_src(channel_name, "iframe2");
t.add_cleanup(() => { document.body.removeChild(iframe2) });
document.body.appendChild(iframe2);
} else if(e.data == "iframe2-done") {
resolve();
} else if(e.data == "fail") {
reject("One opaque origin received a message from the other");
} else {
reject("An unexpected error occurred");
}
});
let iframe1 = document.createElement("iframe");
iframe1.src = iframe_src(channel_name, "iframe1");
t.add_cleanup(() => { document.body.removeChild(iframe1) });
document.body.appendChild(iframe1);
});
}, "BroadcastChannel messages from opaque origins should be self-contained");
const data_url_worker_src = (channel_name, worker_name) => {
const source = `
const handler = (reply) => {
let bc2 = new BroadcastChannel("${channel_name}");
bc2.onmessage = (e) => {
if (e.data == "from-${worker_name}") {
reply("${worker_name}-done");
} else {
reply("fail");
}
};
let bc3 = new BroadcastChannel("${channel_name}");
bc3.postMessage("from-${worker_name}");
};
// For dedicated workers:
self.addEventListener("message", () => handler(self.postMessage));
// For shared workers:
self.addEventListener("connect", (e) => {
var port = e.ports[0];
port.onmessage = () => handler(msg => port.postMessage(msg));
port.start();
});
`;
return "data:,".concat(encodeURIComponent(source));
}
promise_test(t => {
return new Promise((resolve, reject) => {
const channel_name = "opaque-origin-test-3";
const bc1 = new BroadcastChannel(channel_name);
bc1.onmessage = e => { reject("Received message from an opaque origin"); };
// Same as the previous test but with data URL dedicated workers (which
// should have opaque origins per the HTML spec).
const worker_name_prefix = "data-url-dedicated-worker";
const worker_1_name = `${worker_name_prefix}-1`;
const worker_2_name = `${worker_name_prefix}-2`;
const handler = e => {
if(e.data == `${worker_1_name}-done`) {
const worker2 = new Worker(data_url_worker_src(channel_name, worker_2_name));
t.add_cleanup(() => worker2.terminate());
worker2.addEventListener("message", handler);
worker2.postMessage("go!");
} else if(e.data == `${worker_2_name}-done`) {
resolve();
} else if(e.data == "fail") {
reject("One opaque origin received a message from the other");
} else {
reject("An unexpected error occurred");
}
};
let worker1 = new Worker(data_url_worker_src(channel_name, worker_1_name));
t.add_cleanup(() => worker1.terminate());
worker1.addEventListener("message", handler);
worker1.postMessage("go!");
});
}, "BroadcastChannel messages from data URL dedicated workers should be self-contained");
promise_test(() => {
return new Promise((resolve, reject) => {
const channel_name = "opaque-origin-test-4";
const bc1 = new BroadcastChannel(channel_name);
// Same as the previous test but with data URL shared workers (which
// should have opaque origins per the HTML spec).
const worker_name_prefix = "data-url-shared-worker";
const worker_1_name = `${worker_name_prefix}-1`;
const worker_2_name = `${worker_name_prefix}-2`;
const handler = e => {
if (e.data == `${worker_1_name}-done`) {
const worker_script = data_url_worker_src(channel_name, worker_2_name);
const worker2 = new SharedWorker(worker_script, worker_2_name);
worker2.port.addEventListener("message", handler);
worker2.port.start();
worker2.port.postMessage("go!");
} else if(e.data == `${worker_2_name}-done`) {
resolve();
} else if(e.data == "fail") {
reject("One opaque origin received a message from the other");
} else {
reject("An unexpected error occurred");
}
};
bc1.onmessage = e => {
if (e.data == "go!") {
const worker_script = data_url_worker_src(channel_name, worker_1_name);
const worker1 = new SharedWorker(worker_script, worker_1_name);
worker1.port.addEventListener("message", handler);
worker1.port.start();
worker1.port.postMessage("go!");
} else {
reject("Received message from an opaque origin");
}
};
// Ensure that the BroadcastChannel instance above can receive messages
// before we create the first shared worker.
const bc2 = new BroadcastChannel(channel_name);
bc2.postMessage("go!");
});
}, "BroadcastChannel messages from data URL shared workers should be self-contained");
//-->
</script>
</body>
</html>

View File

@ -0,0 +1,116 @@
<!DOCTYPE html>
<meta charset=utf-8>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<!-- Pull in the with_iframe helper function from the service worker tests -->
<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
<body>
<script>
const BC0_FIRST_MSG = 'from BC0 - first';
const BC1_FIRST_MSG = 'from BC1 - first';
const BC2_FIRST_MSG = 'from BC2 - first';
const BC3_FIRST_MSG = 'from BC3 - first';
const BC0_SECOND_MSG = 'from BC0 - second';
const BC1_SECOND_MSG = 'from BC1 - second';
const BC2_SECOND_MSG = 'from BC2 - second';
const BC3_SECOND_MSG = 'done';
const BC0_TARGET_NAME = 'BC1';
const BC1_TARGET_NAME = 'BC1';
const BC2_TARGET_NAME = 'BC2';
const BC3_TARGET_NAME = 'BC3';
const MULTI_FRAME_ORDERING_TEST_CHANNEL_NAME = 'multi-frame-order';
const bc0 = new BroadcastChannel(MULTI_FRAME_ORDERING_TEST_CHANNEL_NAME);
const messages = [];
function logReceivedMessage(targetname, e) {
messages.push({'target': targetname, 'data': e.data});
}
function postMessagesToChannel() {
return new Promise((resolve) => {
bc0.postMessage(BC0_FIRST_MSG);
bc0.postMessage(BC0_SECOND_MSG);
resolve();
});
}
// Expected flow of messages between the BroadcastChannel objects (based on
// the requirement that messages get delivered to BroadcastChannel objects
// "in creation order, oldest first") and comments describing the actions
// taken in response to each event
const EXPECTED_RESULTS = [
// -> BC0 sends two messages, BC1 and BC2 are connected to the channel
{'data': BC0_FIRST_MSG, 'target': BC1_TARGET_NAME},
// -> BC1 Creates BC3 and sends first message
{'data': BC0_FIRST_MSG, 'target': BC2_TARGET_NAME},
// -> BC2 sends two messages
// BC3 isn't expected to receive the messages sent before it was created, so
// no corresponding entries here for messages from BC0.
{'data': BC0_SECOND_MSG, 'target': BC1_TARGET_NAME},
// -> BC1 sends second message
{'data': BC0_SECOND_MSG, 'target': BC2_TARGET_NAME},
// -> BC2 closes
{'data': BC1_FIRST_MSG, 'target': BC0_TARGET_NAME},
{'data': BC1_FIRST_MSG, 'target': BC3_TARGET_NAME},
// -> BC3 sends first message
{'data': BC2_FIRST_MSG, 'target': BC0_TARGET_NAME},
{'data': BC2_FIRST_MSG, 'target': BC1_TARGET_NAME},
// -> BC1 closes
{'data': BC2_FIRST_MSG, 'target': BC3_TARGET_NAME},
// -> BC3 sends second message
{'data': BC2_SECOND_MSG, 'target': BC0_TARGET_NAME},
{'data': BC2_SECOND_MSG, 'target': BC3_TARGET_NAME},
// -> BC3 closes
{'data': BC1_SECOND_MSG, 'target': BC0_TARGET_NAME},
{'data': BC3_FIRST_MSG, 'target': BC0_TARGET_NAME},
{'data': BC3_SECOND_MSG, 'target': BC0_TARGET_NAME},
];
function testCompletion(t) {
return new Promise((resolve) => {
bc0.onmessage = t.step_func(e => {
logReceivedMessage(BC0_TARGET_NAME, e);
if (e.data == BC3_SECOND_MSG) {
assert_equals(messages.length, EXPECTED_RESULTS.length);
for(var i = 0; i < messages.length; i++) {
assert_equals(messages[i].target, EXPECTED_RESULTS[i].target, `Message ${i+1} has unexpected target`);
assert_equals(messages[i].data, EXPECTED_RESULTS[i].data, `Message ${i+1} has unexpected message contents`);
}
resolve();
}
});
});
}
promise_test(async t => {
const testResults = testCompletion(t);
// Await them sequentially because we need the BroadcastChannel object in
// iframe1 to be created first, we need the BroadcastChannel object in
// iframe2 to be created second, and then we only want to call
// postMessagesToChannel once both BroadcastChannels have been created.
await with_iframe('resources/ordering.html?id=iframe1');
await with_iframe('resources/ordering.html?id=iframe2');
await postMessagesToChannel();
return testResults;
}, "Messages are delivered in port creation order across multiple frames");
</script>
</body>

View File

@ -0,0 +1,10 @@
async_test(t => {
const crossOriginURL = new URL("resources/origin.html", self.location.href).href.replace("://", "://天気の良い日."),
frame = document.createElement("iframe");
frame.src = crossOriginURL;
document.body.appendChild(frame);
t.add_cleanup(() => frame.remove());
self.onmessage = t.step_func_done(e => {
assert_equals(e.data, self.origin.replace("://", "://xn--n8j6ds53lwwkrqhv28a."));
});
}, "Serialization of BroadcastChannel origin");

View File

@ -0,0 +1,15 @@
<body></body>
<script>
window.onload = function() {
bc1 = new BroadcastChannel('no-cross-origin-messages');
bc2 = new BroadcastChannel('no-cross-origin-messages');
bc2.onmessage = e => {
parent.postMessage('done', "*");
};
// Post a message on bc1 and once we receive it in bc2, we know that the
// message should have been sent to bc0 if messages were being passed
// across origin (assuming compliance with the spec regarding message
// delivery in port creation order).
bc1.postMessage('ignition');
}
</script>

View File

@ -0,0 +1,78 @@
<body></body>
<script>
const BC0_FIRST_MSG = 'from BC0 - first';
const BC1_FIRST_MSG = 'from BC1 - first';
const BC2_FIRST_MSG = 'from BC2 - first';
const BC3_FIRST_MSG = 'from BC3 - first';
const BC0_SECOND_MSG = 'from BC0 - second';
const BC1_SECOND_MSG = 'from BC1 - second';
const BC2_SECOND_MSG = 'from BC2 - second';
const BC3_SECOND_MSG = 'done';
const BC0_TARGET_NAME = 'BC1';
const BC1_TARGET_NAME = 'BC1';
const BC2_TARGET_NAME = 'BC2';
const BC3_TARGET_NAME = 'BC3';
const MULTI_FRAME_ORDERING_TEST_CHANNEL_NAME = 'multi-frame-order';
var bc1, bc2, bc3;
var sentMessageCountForBc1 = 0;
var sentMessageCountForBc2 = 0;
var sentMessageCountForBc3 = 0;
var bc1_handler = e => {
window.top.logReceivedMessage(BC1_TARGET_NAME, e);
switch(sentMessageCountForBc1) {
case 0:
bc3 = new BroadcastChannel(MULTI_FRAME_ORDERING_TEST_CHANNEL_NAME);
bc3.onmessage = bc3_handler;
bc1.postMessage(BC1_FIRST_MSG);
break;
case 1:
bc1.postMessage(BC1_SECOND_MSG);
break;
case 2:
bc1.close();
return;
}
sentMessageCountForBc1 += 1;
}
var bc2_handler = e => {
window.top.logReceivedMessage(BC2_TARGET_NAME, e);
switch(sentMessageCountForBc2) {
case 0:
bc2.postMessage(BC2_FIRST_MSG);
bc2.postMessage(BC2_SECOND_MSG);
sentMessageCountForBc2 += 2;
break;
case 2:
bc2.close();
return;
}
};
var bc3_handler = e => {
window.top.logReceivedMessage(BC3_TARGET_NAME, e);
switch(sentMessageCountForBc3) {
case 0:
bc3.postMessage(BC3_FIRST_MSG);
break;
case 1:
bc3.postMessage(BC3_SECOND_MSG);
break;
case 2:
bc3.close();
return;
}
sentMessageCountForBc3 += 1;
};
window.onload = function() {
const params = new URLSearchParams(window.location.search);
if (params.get('id') === 'iframe1') {
bc1 = new BroadcastChannel(MULTI_FRAME_ORDERING_TEST_CHANNEL_NAME);
bc1.onmessage = bc1_handler;
} else if (params.get('id') === 'iframe2') {
bc2 = new BroadcastChannel(MULTI_FRAME_ORDERING_TEST_CHANNEL_NAME);
bc2.onmessage = bc2_handler;
}
}
</script>

View File

@ -0,0 +1,8 @@
<script>
const bc1 = new BroadcastChannel("ladila"),
bc2 = new BroadcastChannel("ladila");
bc2.onmessage = e => {
parent.postMessage(e.origin, "*");
}
bc1.postMessage("does not matter");
</script>

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<meta charset=utf-8>
<script>
try {
let c = new BroadcastChannel('foo');
parent.postMessage('Created', '*');
} catch (e) {
parent.postMessage('Exception: ' + e.name, '*');
}
</script>

View File

@ -0,0 +1,15 @@
let promise_func = null;
let promise = new Promise(resolve => promise_func = resolve);
const SERVICE_WORKER_TEST_CHANNEL_NAME = 'service worker';
const bc3 = new BroadcastChannel(SERVICE_WORKER_TEST_CHANNEL_NAME);
bc3.onmessage = e => {
bc3.postMessage('done');
promise_func();
};
bc3.postMessage('from worker');
// Ensure that the worker stays alive for the duration of the test
self.addEventListener('install', evt => {
evt.waitUntil(promise);
});

View File

@ -0,0 +1,37 @@
importScripts("/common/gc.js");
var c;
async function handler(e, reply) {
if (e.data.ping) {
c.postMessage(e.data.ping);
return;
}
if (e.data.blob) {
(() => {
c.postMessage({blob: new Blob(e.data.blob)});
})();
await garbageCollect();
}
c = new BroadcastChannel(e.data.channel);
let messages = [];
c.onmessage = e => {
if (e.data === 'ready') {
// Ignore any 'ready' messages from the other thread since there could
// be some race conditions between this BroadcastChannel instance
// being created / ready to receive messages and the message being sent.
return;
}
messages.push(e.data);
if (e.data == 'done')
reply(messages);
};
c.postMessage('from worker');
}
onmessage = e => handler(e, postMessage);
onconnect = e => {
let port = e.ports[0];
port.onmessage = e => handler(e, msg => port.postMessage(msg));
};

View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<meta charset=utf-8>
<title>Creating BroadcastChannel in an opaque origin</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<body>
<script>
async_test(t => {
self.onmessage = t.step_func(e => {
assert_equals(e.data, 'Created');
t.done();
});
});
</script>
<iframe sandbox="allow-scripts" src="resources/sandboxed.html"></iframe>
</body>

View File

@ -0,0 +1,47 @@
<!DOCTYPE html>
<meta charset=utf-8>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
const SERVICE_WORKER_TEST_CHANNEL_NAME = 'service worker';
const events = [];
const c1 = new BroadcastChannel(SERVICE_WORKER_TEST_CHANNEL_NAME);
const c2 = new BroadcastChannel(SERVICE_WORKER_TEST_CHANNEL_NAME);
c1.onmessage = e => events.push(e);
c2.onmessage = e => events.push(e);
function testCompletion(t) {
return new Promise((resolve) => {
c2.addEventListener("message", t.step_func(e => {
if (e.data == 'from worker') {
c2.postMessage('from c2');
} else if (e.data == 'done') {
assert_equals(events.length, 5);
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');
assert_equals(events[3].target, c1);
assert_equals(events[4].data, 'done');
assert_equals(events[4].target, c2);
resolve();
}
}));
});
}
promise_test(async t => {
const testResults = testCompletion(t);
const SCRIPT = "resources/service-worker.js";
const SCOPE = "/webmessaging/broadcastchannel/resources/not-used/";
const reg = await navigator.serviceWorker.register(SCRIPT, {'scope': SCOPE});
t.add_cleanup(() => reg.unregister());
return testResults;
}, 'BroadcastChannel works in service workers');
</script>

View File

@ -0,0 +1,375 @@
<!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>