test: run webmessaging/broadcastchannel WPT

Refs: https://github.com/nodejs/node/issues/38803

PR-URL: https://github.com/nodejs/node/pull/41962
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Mestery <mestery@protonmail.com>
This commit is contained in:
Ben Noordhuis authored and GitHub committed 2022-02-17 13:22:51 +00:00
1 parent fdab137e64
commit 9caceb28aa
20 files changed
+1353

No files matched your search

@@ -0,0 +1,120 @@
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');
@@ -0,0 +1,83 @@
<!DOCTYPE html>
<meta charset=utf-8>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.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'])});
})();
// TODO(https://github.com/web-platform-tests/wpt/issues/7899): Change to
// some sort of cross-browser GC trigger.
if (self.gc) self.gc();
}, '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>
@@ -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>
@@ -0,0 +1,72 @@
<!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>
<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 newPopup / newIframe -->
<script src="/html/cross-origin-embedder-policy/anonymous-iframe/resources/common.js"></script>
<body>
<script>
const emit_script = (channel_name, message, done_queue_name) => `
const bc = new BroadcastChannel("${channel_name}");
bc.postMessage("${message}");
send("${done_queue_name}", "done");
`;
promise_test(async t => {
const origin = get_host_info().HTTPS_ORIGIN;
const not_same_site_origin = get_host_info().HTTPS_NOTSAMESITE_ORIGIN;
const response_queue_uuid = token();
const popup_init_script = `
const importScript = ${importScript};
await importScript("/html/cross-origin-embedder-policy/credentialless" +
"/resources/common.js");
await importScript("/html/cross-origin-embedder-policy/anonymous-iframe" +
"/resources/common.js");
await importScript("/common/utils.js");
send("${response_queue_uuid}", newIframe("${origin}"));
`;
// Create a same-origin iframe in a cross-site popup.
const not_same_site_popup_uuid = newPopup(t, not_same_site_origin);
send(not_same_site_popup_uuid, popup_init_script);
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, origin);
send(same_origin_popup_uuid, popup_init_script);
const iframe_2_uuid = await receive(response_queue_uuid);
const channel_name = token();
const bc = new BroadcastChannel(channel_name);
bc.onmessage = t.step_func(e => {
assert_equals(e.data, "msg from iframe2");
t.done();
});
// 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,
emit_script(channel_name, "msg from iframe1", response_queue_uuid));
assert_equals(await receive(response_queue_uuid), "done");
// 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,
emit_script(channel_name, "msg from iframe2", response_queue_uuid));
assert_equals(await receive(response_queue_uuid), "done");
}, "BroadcastChannel messages aren't received from a cross-partition iframe");
</script>
</body>
@@ -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>
@@ -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');
@@ -0,0 +1,35 @@
<!doctype html>
<html>
<head>
<meta charset=utf-8>
<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});
document.body.appendChild(ifr);
});
}, "Opaque origin should be serialized to \"null\"");
//-->
</script>
</body>
</html>
@@ -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>
@@ -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");
@@ -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>
@@ -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>
@@ -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>
@@ -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>
@@ -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);
});
@@ -0,0 +1,37 @@
var c;
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)});
})();
// TODO(https://github.com/web-platform-tests/wpt/issues/7899): Change to
// some sort of cross-browser GC trigger.
if (self.gc) self.gc();
}
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));
};
@@ -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>
@@ -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>
@@ -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>
@@ -0,0 +1,26 @@
{
"basics.any.js": {
"fail": {
"unexpected": [
"assert_equals: origin expected \"https://example.com\" but got \"\"",
"assert_equals: target for event 0 expected object \"[object EventTarget]\" but got object \"[object EventTarget]\""
]
}
},
"interface.any.js": {
"fail": {
"unexpected": [
"assert_throws_dom: function \"() => c.postMessage('')\" threw object \"Error: BroadcastChannel is closed.\" that is not a DOMException InvalidStateError: property \"code\" is equal to 0, expected 11",
"assert_not_equals: got disallowed value undefined",
"assert_throws_dom: function \"() => c.postMessage(new Symbol())\" threw object \"Error: BroadcastChannel is closed.\" that is not a DOMException InvalidStateError: property \"code\" is equal to 0, expected 11"
]
}
},
"origin.window.js": {
"fail": {
"expected": [
"document is not defined"
]
}
}
}
+13
View File
@@ -0,0 +1,13 @@
'use strict';
require('../common');
const { WPTRunner } = require('../common/wpt');
const runner = new WPTRunner('webmessaging/broadcastchannel');
runner.setInitScript(`
const { BroadcastChannel } = require('worker_threads');
global.BroadcastChannel = BroadcastChannel;
`);
runner.runJsTests();