Bug 1830404 - [remote] Support sending message handler commands from windowglobal to root r=webdriver-reviewers,whimboo

Depends on D176713

Differential Revision: https://phabricator.services.mozilla.com/D176714
This commit is contained in:
Julian Descottes 2023-05-02 20:11:50 +00:00
Родитель 9fa2ad7bd9
Коммит fea56ca2b2
10 изменённых файлов: 228 добавлений и 36 удалений

Просмотреть файл

@ -88,6 +88,7 @@ export class MessageHandler extends EventEmitter {
#contextId;
#eventsDispatcher;
#moduleCache;
#registry;
#sessionId;
/**
@ -97,8 +98,10 @@ export class MessageHandler extends EventEmitter {
* ID of the session the handler is used for.
* @param {object} context
* The context linked to this MessageHandler instance.
* @param {MessageHandlerRegistry} registry
* The MessageHandlerRegistry which owns this MessageHandler instance.
*/
constructor(sessionId, context) {
constructor(sessionId, context, registry) {
super();
this.#moduleCache = new lazy.ModuleCache(this);
@ -107,6 +110,7 @@ export class MessageHandler extends EventEmitter {
this.#context = context;
this.#contextId = this.constructor.getIdFromContext(context);
this.#eventsDispatcher = new lazy.EventsDispatcher(this);
this.#registry = registry;
}
get context() {
@ -129,6 +133,10 @@ export class MessageHandler extends EventEmitter {
return [this.sessionId, this.constructor.type, this.contextId].join("-");
}
get registry() {
return this.#registry;
}
get sessionId() {
return this.#sessionId;
}

Просмотреть файл

@ -195,7 +195,8 @@ export class MessageHandlerRegistry extends EventEmitter {
_createMessageHandler(sessionId, sessionDataItems) {
const messageHandler = new this._messageHandlerClass(
sessionId,
this._context
this._context,
this
);
messageHandler.on(

Просмотреть файл

@ -192,12 +192,23 @@ export class ModuleCache {
}
_getModuleFolder(originType, destinationType) {
// root messages should always target the root layer.
// NB: The idea here is just to avoid confusing the module cache when
// trying to send to `root` from `windowglobal`. The general rule should
// normally be "if the destination has a higher level than the origin, just
// use the destination as target folder", but as we don't support other
// levels than root & windowglobal, we can simplify this to this for now.
if (destinationType === "root") {
return "root";
}
const originPath = lazy.getMessageHandlerClass(originType).modulePath;
if (originType === destinationType) {
// If the command is targeting the current type, the module is expected to
// be in eg "windowglobal/${moduleName}.jsm".
return originPath;
}
// If the command is targeting another type, the module is expected to
// be in a composed folder eg "windowglobal-in-root/${moduleName}.jsm".
const destinationPath = lazy.getMessageHandlerClass(destinationType)

Просмотреть файл

@ -11,6 +11,10 @@ const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
getMessageHandlerFrameChildActor:
"chrome://remote/content/shared/messagehandler/transports/js-window-actors/MessageHandlerFrameChild.sys.mjs",
RootMessageHandler:
"chrome://remote/content/shared/messagehandler/RootMessageHandler.sys.mjs",
WindowRealm: "chrome://remote/content/shared/Realm.sys.mjs",
});
@ -171,9 +175,16 @@ export class WindowGlobalMessageHandler extends MessageHandler {
}
forwardCommand(command) {
throw new Error(
`Cannot forward commands from a "WINDOW_GLOBAL" MessageHandler`
);
switch (command.destination.type) {
case lazy.RootMessageHandler.type:
return lazy
.getMessageHandlerFrameChildActor(this)
.sendCommand(command, this.sessionId);
default:
throw new Error(
`Cannot forward command to "${command.destination.type}" from "${this.constructor.type}".`
);
}
}
/**
@ -214,4 +225,21 @@ export class WindowGlobalMessageHandler extends MessageHandler {
contextDescriptor.id === this.context.browserId)
);
}
/**
* Send a command to the root MessageHandler.
*
* @param {Command} command
* The command to send to the root MessageHandler.
* @returns {Promise}
* A promise which resolves with the return value of the command.
*/
sendRootCommand(command) {
return this.handleCommand({
...command,
destination: {
type: lazy.RootMessageHandler.type,
},
});
}
}

Просмотреть файл

@ -22,3 +22,4 @@ prefs =
[browser_session_data_update.js]
[browser_session_data_update_categories.js]
[browser_session_data_update_contexts.js]
[browser_windowglobal_to_root.js]

Просмотреть файл

@ -0,0 +1,39 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const { RootMessageHandler } = ChromeUtils.importESModule(
"chrome://remote/content/shared/messagehandler/RootMessageHandler.sys.mjs"
);
add_task(async function test_windowGlobal_to_root_command() {
const browsingContextId = gBrowser.selectedBrowser.browsingContext.id;
const rootMessageHandler = createRootMessageHandler(
"session-id-windowglobal-to-rootModule"
);
for (const commandName of [
"testHandleCommandToRoot",
"testSendRootCommand",
]) {
const valueFromRoot = await rootMessageHandler.handleCommand({
moduleName: "windowglobaltoroot",
commandName,
destination: {
type: WindowGlobalMessageHandler.type,
id: browsingContextId,
},
});
is(
valueFromRoot,
"root-value-called-from-windowglobal",
"Retrieved the expected value from windowglobaltoroot using " +
commandName
);
}
rootMessageHandler.destroy();
});

Просмотреть файл

@ -0,0 +1,19 @@
/* 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/. */
import { Module } from "chrome://remote/content/shared/messagehandler/Module.sys.mjs";
class WindowGlobalToRootModule extends Module {
destroy() {}
/**
* Commands
*/
getValueFromRoot() {
return "root-value-called-from-windowglobal";
}
}
export const windowglobaltoroot = WindowGlobalToRootModule;

Просмотреть файл

@ -0,0 +1,33 @@
/* 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/. */
import { Module } from "chrome://remote/content/shared/messagehandler/Module.sys.mjs";
import { RootMessageHandler } from "chrome://remote/content/shared/messagehandler/RootMessageHandler.sys.mjs";
class WindowGlobalToRootModule extends Module {
destroy() {}
/**
* Commands
*/
testHandleCommandToRoot(params, destination) {
return this.messageHandler.handleCommand({
moduleName: "windowglobaltoroot",
commandName: "getValueFromRoot",
destination: {
type: RootMessageHandler.type,
},
});
}
testSendRootCommand(params, destination) {
return this.messageHandler.sendRootCommand({
moduleName: "windowglobaltoroot",
commandName: "getValueFromRoot",
});
}
}
export const windowglobaltoroot = WindowGlobalToRootModule;

Просмотреть файл

@ -13,6 +13,26 @@ ChromeUtils.defineESModuleGetters(lazy, {
"chrome://remote/content/shared/messagehandler/WindowGlobalMessageHandler.sys.mjs",
});
/**
* Map from MessageHandlerRegistry to MessageHandlerFrameChild actor. This will
* allow a WindowGlobalMessageHandler to find the JSWindowActorChild instance to
* use to send commands.
*/
const registryToActor = new WeakMap();
/**
* Retrieve the MessageHandlerFrameChild which is linked to the provided
* WindowGlobalMessageHandler instance.
*
* @param {WindowGlobalMessageHandler} messageHandler
* The WindowGlobalMessageHandler for which to get the JSWindowActor.
* @returns {MessageHandlerFrameChild}
* The corresponding MessageHandlerFrameChild instance.
*/
export function getMessageHandlerFrameChildActor(messageHandler) {
return registryToActor.get(messageHandler.registry);
}
/**
* Child actor for the MessageHandlerFrame JSWindowActor. The
* MessageHandlerFrame actor is used by RootTransport to communicate between
@ -24,6 +44,8 @@ export class MessageHandlerFrameChild extends JSWindowActorChild {
this.context = this.manager.browsingContext;
this._registry = new lazy.MessageHandlerRegistry(this.type, this.context);
registryToActor.set(this._registry, this);
this._onRegistryEvent = this._onRegistryEvent.bind(this);
// MessageHandlerFrameChild is responsible for forwarding events from
@ -69,6 +91,13 @@ export class MessageHandlerFrameChild extends JSWindowActorChild {
return null;
}
sendCommand(command, sessionId) {
return this.sendQuery("MessageHandlerFrameChild:sendCommand", {
command,
sessionId,
});
}
_onRegistryEvent(eventName, wrappedEvent) {
this.sendAsyncMessage(
"MessageHandlerFrameChild:messageHandlerEvent",

Просмотреть файл

@ -28,37 +28,12 @@ XPCOMUtils.defineLazyGetter(lazy, "WebDriverError", () => {
export class MessageHandlerFrameParent extends JSWindowActorParent {
async receiveMessage(message) {
switch (message.name) {
case "MessageHandlerFrameChild:messageHandlerEvent":
const { name, contextInfo, data, sessionId } = message.data;
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);
// 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);
break;
case "MessageHandlerFrameChild:sendCommand": {
return this.#handleSendCommandMessage(message.data);
}
case "MessageHandlerFrameChild:messageHandlerEvent": {
return this.#handleMessageHandlerEventMessage(message.data);
}
default:
throw new Error("Unsupported message:" + message.name);
}
@ -96,4 +71,52 @@ export class MessageHandlerFrameParent extends JSWindowActorParent {
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);
// 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;
}
}
}