Skip to content

Commit

Permalink
Dispatch a close event
Browse files Browse the repository at this point in the history
We want to dispatch a close event when an entangled MessagePort is disconnected.
Given a pair of entangled ports, port1 and port2, if port2 is closed at any point,
a port1’s error handler is run.
So we can change an error handler to dispatch a close event.

The tests of close event are as follows:
1) port was explicitly closed.
2) owing document was destroyed.
3) owing document crashed.
4) port was garbage collected.

Bug: 1495616
Change-Id: I99f9f5a0d7cc63f0916da316ec666ba793215019
  • Loading branch information
nononokam authored and chromium-wpt-export-bot committed Nov 15, 2023
1 parent 9d3be8a commit 96cebb0
Show file tree
Hide file tree
Showing 7 changed files with 210 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<!doctype html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/utils.js"></script>
<script src="/common/dispatcher/dispatcher.js"></script>
<script src="resources/helper.sub.js"></script>
<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
<script>
promise_test(async t => {
const pageA = new RemoteContext(token());
const pageB = new RemoteContext(token());

const urlA = location.origin + executorPath + pageA.context_id;
const urlB = originCrossSite + executorPath + pageB.context_id;

// Register a service worker and make this page controlled.
const workerUrl =
'resources/service-worker.js?pipe=header(Service-Worker-Allowed,../)';
const registration =
await service_worker_unregister_and_register(t, workerUrl, './');
t.add_cleanup(_ => registration.unregister());
await wait_for_state(t, registration.installing, 'activated');

window.open(urlA, '_blank', 'noopener');
await pageA.execute_script(waitForPageShow);

assert_true(
await pageA.execute_script(
() => (navigator.serviceWorker.controller !== null)),
'pageA should be controlled before navigation');

// Send MessagePort to the service worker.
await pageA.execute_script(() => {
const {port1,port2} = new MessageChannel();
self.port = port1;
self.port.start();
const ctrl = navigator.serviceWorker.controller;
ctrl.postMessage({type: "storeMessagePort"}, [ port2 ])})

// Navigate away to url B and back.
await navigateAndThenBack(pageA, pageB, urlB);
await assert_bfcached(pageA);

// Confirm MessagePort can still work after the page is restored from
// BFCache.
assert_equals(
await pageA.execute_script(
() => new Promise((resolve, reject) => {
self.port.addEventListener('message', (event) => {
resolve(event.data);
});
self.port.postMessage(
"postMessage from the page");
})),
"receive message");
}, 'MessagePort still works after the page is restored from BFCache');
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ self.addEventListener('message', function(event) {
client.postMessage("dummyValue");
}
event.data.port.postMessage("PASS");
} else if (event.data.type == 'storeMessagePort') {
self.port = event.ports[0];
self.port.start();
self.port.onmessage = (event) => {
if (event.data == 'postMessage from the page') {
self.port.postMessage('receive message');
}
}
}
});

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// META: title=Close event test when the document is destroyed.
// META: script=/common/dispatcher/dispatcher.js
// META: script=/common/get-host-info.sub.js
// META: script=/common/utils.js
// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js

async function AddWindow() {
const helper = new RemoteContextHelper();
return await helper.addWindow();
}

function CreateMessagePromise(window) {
return new Promise(resolve => {
window.onmessage = e => {
e.ports[0].start();
resolve(e.ports[0]);
};
});
}

async function CreateMessageChannelAndSendPort(rc1) {
await rc1.executeScript(() => {
const {port1, port2} = new MessageChannel();
port1.start();
window.opener.postMessage({}, '*', [port2]);
});
}

function CreateCloseEventPromise(port) {
return new Promise(resolve => {
port.onclose = resolve;
});
}

promise_test(async t => {
let rc1 = await AddWindow();
let wait_port = CreateMessagePromise(window);
await CreateMessageChannelAndSendPort(rc1);
const event_promise = CreateCloseEventPromise(await wait_port);
rc1.navigateToNew();
await event_promise;
}, 'the context is navigated to a new document and a close event is fired.')

promise_test(async t => {
let rc1 = await AddWindow();
let wait_port = CreateMessagePromise(window);
await CreateMessageChannelAndSendPort(rc1);
const event_promise = CreateCloseEventPromise(await wait_port);
rc1.executeScript(() => {
window.close();
});
await event_promise;
}, 'the window is closed and a close event is fired.')

promise_test(async t => {
let iframe;
let load_iframe = new Promise(function(resolve) {
iframe = document.createElement('iframe');
iframe.onload = function() {
resolve(iframe);
};
iframe.src = './resources/empty.html';
document.documentElement.appendChild(iframe);
});
iframe = await load_iframe;

let wait_port = CreateMessagePromise(iframe.contentWindow);
const {port1, port2} = new MessageChannel();
port1.start();
iframe.contentWindow.postMessage('', '*', [port2]);
await wait_port;
const event_promise = CreateCloseEventPromise(port1);
iframe.remove();
await event_promise;
}, 'the iframe is deleted and a close event is fired.')
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// META: title=Close event test when an entangled port is explicitly closed.
// META: script=/common/dispatcher/dispatcher.js
// META: script=/common/get-host-info.sub.js
// META: script=/common/utils.js
// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js

async_test(t => {
const channel = new MessageChannel();
channel.port1.start();
channel.port2.start();
channel.port2.onclose = t.step_func_done();
channel.port1.close();
}, 'Close event on port2 is fired when port1 is explicitly closed');

async_test(t => {
const channel = new MessageChannel();
channel.port1.start();
channel.port2.start();
channel.port1.onclose =
t.unreached_func('Should not fire a close event on port1');
channel.port1.close();
setTimeout(t.step_func_done(), 100);
}, 'Close event on port1 is not fired when port1 is explicitly closed');

promise_test(async t => {
let wait_port = new Promise(resolve => {
window.onmessage =
e => {
e.ports[0].start();
resolve(e.ports[0]);
}
});
const helper = new RemoteContextHelper();
const rc1 = await helper.addWindow();

await rc1.executeScript(() => {
const channel = new MessageChannel();
window.port = channel.port1;
window.opener.postMessage({}, '*', [channel.port2]);
});
const port = await wait_port;
const event_promise = new Promise(resolve => {
port.onclose = resolve;
});
rc1.executeScript(() => {
window.port.close();
});
await event_promise;
}, 'Close event on port1 is fired when port2, which is in a different window, is explicitly closed.')
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// META: title=Close event test when an entangled port is GCed.
// META: script=/wpt_internal/dom/abort/resources/run-async-gc.js

promise_test(async t => {
let port;
let wr2;
(function() {
const {port1, port2} = new MessageChannel();
port = port1;
port.start();
wr2 = new WeakRef(port2);
})()
const watcher = new EventWatcher(t, port, ['close']);
const event_promise = watcher.wait_for('close');

runAsyncGC();
await event_promise;
assert_equals(wr2.deref(), undefined, 'port2 should be GCed');
}, 'entangled port is garbage collected, and the close event is fired.')
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<!DOCTYPE html>
<html></html>

0 comments on commit 96cebb0

Please sign in to comment.