204 lines
6.5 KiB
JavaScript
204 lines
6.5 KiB
JavaScript
/* 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/. */
|
|
|
|
const lazy = {};
|
|
|
|
ChromeUtils.defineESModuleGetters(lazy, {
|
|
clearTimeout: "resource://gre/modules/Timer.sys.mjs",
|
|
setTimeout: "resource://gre/modules/Timer.sys.mjs",
|
|
|
|
error: "chrome://remote/content/shared/messagehandler/Errors.sys.mjs",
|
|
Log: "chrome://remote/content/shared/Log.sys.mjs",
|
|
RootMessageHandlerRegistry:
|
|
"chrome://remote/content/shared/messagehandler/RootMessageHandlerRegistry.sys.mjs",
|
|
WindowGlobalMessageHandler:
|
|
"chrome://remote/content/shared/messagehandler/WindowGlobalMessageHandler.sys.mjs",
|
|
});
|
|
|
|
ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());
|
|
|
|
ChromeUtils.defineLazyGetter(lazy, "WebDriverError", () => {
|
|
return ChromeUtils.importESModule(
|
|
"chrome://remote/content/shared/webdriver/Errors.sys.mjs"
|
|
).error.WebDriverError;
|
|
});
|
|
|
|
// Set the timeout delay before a command is considered as potentially timing
|
|
// out. This can be customized by a preference mostly for tests. Regular
|
|
// implementation should use DEFAULT_COMMAND_DELAY;
|
|
const DEFAULT_COMMAND_DELAY = 10000;
|
|
const PREF_REMOTE_COMMAND_DELAY = "remote.messagehandler.test.command.delay";
|
|
|
|
ChromeUtils.defineLazyGetter(lazy, "commandDelay", () =>
|
|
Services.prefs.getIntPref(PREF_REMOTE_COMMAND_DELAY, DEFAULT_COMMAND_DELAY)
|
|
);
|
|
|
|
const PING_DELAY = 1000;
|
|
const PING_TIMEOUT = Symbol();
|
|
|
|
/**
|
|
* Parent actor for the MessageHandlerFrame JSWindowActor. The
|
|
* MessageHandlerFrame actor is used by RootTransport to communicate between
|
|
* ROOT MessageHandlers and WINDOW_GLOBAL MessageHandlers.
|
|
*/
|
|
export class MessageHandlerFrameParent extends JSWindowActorParent {
|
|
#destroyed;
|
|
|
|
constructor() {
|
|
super();
|
|
this.#destroyed = false;
|
|
}
|
|
|
|
didDestroy() {
|
|
this.#destroyed = true;
|
|
}
|
|
|
|
async receiveMessage(message) {
|
|
switch (message.name) {
|
|
case "MessageHandlerFrameChild:sendCommand": {
|
|
return this.#handleSendCommandMessage(message.data);
|
|
}
|
|
case "MessageHandlerFrameChild:messageHandlerEvent": {
|
|
return this.#handleMessageHandlerEventMessage(message.data);
|
|
}
|
|
default:
|
|
throw new Error("Unsupported message:" + message.name);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Send a command to the corresponding MessageHandlerFrameChild actor via a
|
|
* JSWindowActor query.
|
|
*
|
|
* @param {Command} command
|
|
* The command to forward. See type definition in MessageHandler.js
|
|
* @param {string} sessionId
|
|
* ID of the session that sent the command.
|
|
* @returns {Promise}
|
|
* Promise that will resolve with the result of query sent to the
|
|
* MessageHandlerFrameChild actor.
|
|
*/
|
|
async sendCommand(command, sessionId) {
|
|
const timer = lazy.setTimeout(
|
|
() => this.#sendPing(command),
|
|
lazy.commandDelay
|
|
);
|
|
|
|
const result = await this.sendQuery(
|
|
"MessageHandlerFrameParent:sendCommand",
|
|
{
|
|
command,
|
|
sessionId,
|
|
}
|
|
);
|
|
|
|
lazy.clearTimeout(timer);
|
|
|
|
if (result?.error) {
|
|
if (result.isMessageHandlerError) {
|
|
throw lazy.error.MessageHandlerError.fromJSON(result.error);
|
|
}
|
|
|
|
// TODO: Do not assume WebDriver is the session protocol, see Bug 1779026.
|
|
throw lazy.WebDriverError.fromJSON(result.error);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
async #handleMessageHandlerEventMessage(messageData) {
|
|
const { name, contextInfo, data, sessionId } = messageData;
|
|
const [moduleName] = name.split(".");
|
|
|
|
// Re-emit the event on the RootMessageHandler.
|
|
const messageHandler =
|
|
lazy.RootMessageHandlerRegistry.getExistingMessageHandler(sessionId);
|
|
// TODO: getModuleInstance expects a CommandDestination in theory,
|
|
// but only uses the MessageHandler type in practice, see Bug 1776389.
|
|
const module = messageHandler.moduleCache.getModuleInstance(moduleName, {
|
|
type: lazy.WindowGlobalMessageHandler.type,
|
|
});
|
|
let eventPayload = data;
|
|
|
|
// Modify an event payload if there is a special method in the targeted module.
|
|
// If present it can be found in windowglobal-in-root module.
|
|
if (module?.interceptEvent) {
|
|
eventPayload = await module.interceptEvent(name, data);
|
|
|
|
if (eventPayload === null) {
|
|
lazy.logger.trace(
|
|
`${moduleName}.interceptEvent returned null, skipping event: ${name}, data: ${data}`
|
|
);
|
|
return;
|
|
}
|
|
// Make sure that an event payload is returned.
|
|
if (!eventPayload) {
|
|
throw new Error(
|
|
`${moduleName}.interceptEvent doesn't return the event payload`
|
|
);
|
|
}
|
|
}
|
|
messageHandler.emitEvent(name, eventPayload, contextInfo);
|
|
}
|
|
|
|
async #handleSendCommandMessage(messageData) {
|
|
const { sessionId, command } = messageData;
|
|
const messageHandler =
|
|
lazy.RootMessageHandlerRegistry.getExistingMessageHandler(sessionId);
|
|
try {
|
|
return await messageHandler.handleCommand(command);
|
|
} catch (e) {
|
|
if (e?.isRemoteError) {
|
|
return {
|
|
error: e.toJSON(),
|
|
isMessageHandlerError: e.isMessageHandlerError,
|
|
};
|
|
}
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
async #sendPing(command) {
|
|
const commandName = `${command.moduleName}.${command.commandName}`;
|
|
const destination = command.destination.id;
|
|
|
|
if (this.#destroyed) {
|
|
// If the JSWindowActor was destroyed already, no need to send a ping.
|
|
return;
|
|
}
|
|
|
|
lazy.logger.trace(
|
|
`MessageHandlerFrameParent command ${commandName} to ${destination} ` +
|
|
`takes more than ${lazy.commandDelay / 1000} seconds to resolve, sending ping`
|
|
);
|
|
|
|
try {
|
|
const result = await Promise.race([
|
|
this.sendQuery("MessageHandlerFrameParent:sendPing"),
|
|
new Promise(r => lazy.setTimeout(() => r(PING_TIMEOUT), PING_DELAY)),
|
|
]);
|
|
|
|
if (result === PING_TIMEOUT) {
|
|
lazy.logger.warn(
|
|
`MessageHandlerFrameParent ping for command ${commandName} to ${destination} timed out`
|
|
);
|
|
} else {
|
|
lazy.logger.trace(
|
|
`MessageHandlerFrameParent ping for command ${commandName} to ${destination} was successful`
|
|
);
|
|
}
|
|
} catch (e) {
|
|
if (!this.#destroyed) {
|
|
// Only swallow errors if the JSWindowActor pair was destroyed while
|
|
// waiting for the ping response.
|
|
throw e;
|
|
}
|
|
|
|
lazy.logger.trace(
|
|
`MessageHandlerFrameParent ping for command ${commandName} to ${destination}` +
|
|
` lost after JSWindowActor was destroyed`
|
|
);
|
|
}
|
|
}
|
|
}
|