Source code

Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* eslint-disable mozilla/valid-lazy */
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
const lazy = XPCOMUtils.declareLazy({
actorMatches: {
pref: "extensions.wpt.matches",
default: "*://web-platform.test/*",
},
});
// Map events with positional params to WPT messages with named properties.
const EVENTS = {
"test-task-start": "test-started testName",
"test-task-done": "test-finished remainingTests",
"test-eq": "assert-equality result message expectedValue actualValue",
"test-result": "assert result message",
};
// Routes extension test results to web-platform.test child actors.
export class WPTMessagesParent extends JSWindowActorParent {
static init(apiManager) {
this.apiManager = apiManager;
apiManager.on("startup", this.#onStartup);
apiManager.on("shutdown", this.#onShutdown);
ChromeUtils.registerWindowActor("WPTMessages", {
matches: lazy.actorMatches.split(","),
allFrames: true,
child: {
esModuleURI: "resource://gre/modules/WPTMessagesChild.sys.mjs",
observers: ["content-document-global-created"],
},
parent: {
esModuleURI: "resource://gre/modules/WPTMessagesParent.sys.mjs",
},
});
}
static unload() {
this.apiManager.off("startup", this.#onStartup);
this.apiManager.off("shutdown", this.#onShutdown);
ChromeUtils.unregisterWindowActor("WPTMessages");
}
receiveMessage({ name }) {
switch (name) {
case "onMessage.addListener":
WPTMessagesParent.#setListener(this);
break;
case "onMessage.removeListener":
WPTMessagesParent.#removeListener(this);
break;
}
}
didDestroy() {
WPTMessagesParent.#removeListener(this);
}
static #setListener(actor) {
// The latest actor to subscribe becomes the only listener.
this.#listener = actor;
this.#queue.forEach(args => this.#emit(...args));
this.#queue.length = 0;
}
static #removeListener(actor) {
// Only clear the listener if the next one hasn't already subscribed.
if (this.#listener === actor) {
this.#listener = null;
}
}
static #onStartup(_, ext) {
for (let ev of Object.keys(EVENTS)) {
ext.on(ev, WPTMessagesParent.#onEvent);
}
}
static #onShutdown(_, ext) {
for (let ev of Object.keys(EVENTS)) {
ext.off(ev, WPTMessagesParent.#onEvent);
}
}
static #onEvent(event, ...args) {
let [ev, ...keys] = EVENTS[event].split(" ");
let data = keys.map((key, i) => [key, args[i]]);
WPTMessagesParent.#emit(ev, Object.fromEntries(data));
}
static #emit(event, data) {
if (this.#listener) {
return this.#listener.sendAsyncMessage(event, data);
}
// Queue messages sent while we don't have a listener.
this.#queue.push([event, data]);
}
// One listener at a time for reliable queing.
static #listener = null;
static #queue = [];
}