зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1788659 - [bidi] Implement "script.realmDestroyed" event. r=webdriver-reviewers,jdescottes
Differential Revision: https://phabricator.services.mozilla.com/D187550
This commit is contained in:
Родитель
497ea8ec6a
Коммит
f76d7e94f9
|
@ -263,15 +263,15 @@ export class MessageHandler extends EventEmitter {
|
|||
}
|
||||
|
||||
/**
|
||||
* Apply the initial session data items provided to this MessageHandler on
|
||||
* startup. Implementation is specific to each MessageHandler class.
|
||||
* Execute the required initialization steps, inlcluding apply the initial session data items
|
||||
* provided to this MessageHandler on startup. Implementation is specific to each MessageHandler class.
|
||||
*
|
||||
* By default the implementation is a no-op.
|
||||
*
|
||||
* @param {Array<SessionDataItem>} sessionDataItems
|
||||
* Initial session data items for this MessageHandler.
|
||||
*/
|
||||
async applyInitialSessionDataItems(sessionDataItems) {}
|
||||
async initialize(sessionDataItems) {}
|
||||
|
||||
/**
|
||||
* Returns the module path corresponding to this MessageHandler class.
|
||||
|
|
|
@ -202,7 +202,7 @@ export class MessageHandlerRegistry extends EventEmitter {
|
|||
);
|
||||
messageHandler.on("message-handler-event", this._onMessageHandlerEvent);
|
||||
|
||||
messageHandler.applyInitialSessionDataItems(sessionDataItems);
|
||||
messageHandler.initialize(sessionDataItems);
|
||||
|
||||
this._messageHandlersMap.set(sessionId, messageHandler);
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
|||
*/
|
||||
export class RootMessageHandler extends MessageHandler {
|
||||
#navigationManager;
|
||||
#realms;
|
||||
#rootTransport;
|
||||
#sessionData;
|
||||
|
||||
|
@ -67,12 +68,25 @@ export class RootMessageHandler extends MessageHandler {
|
|||
this.#sessionData = new lazy.SessionData(this);
|
||||
this.#navigationManager = new lazy.NavigationManager();
|
||||
this.#navigationManager.startMonitoring();
|
||||
|
||||
// Map with inner window ids as keys, and sets of realm ids, assosiated with
|
||||
// this window as values.
|
||||
this.#realms = new Map();
|
||||
// In the general case, we don't get notified that realms got destroyed,
|
||||
// because there is no communication between content and parent process at this moment,
|
||||
// so we have to listen to the this notification to clean up the internal
|
||||
// map and trigger the events.
|
||||
Services.obs.addObserver(this, "window-global-destroyed");
|
||||
}
|
||||
|
||||
get navigationManager() {
|
||||
return this.#navigationManager;
|
||||
}
|
||||
|
||||
get realms() {
|
||||
return this.#realms;
|
||||
}
|
||||
|
||||
get sessionData() {
|
||||
return this.#sessionData;
|
||||
}
|
||||
|
@ -80,6 +94,10 @@ export class RootMessageHandler extends MessageHandler {
|
|||
destroy() {
|
||||
this.#sessionData.destroy();
|
||||
this.#navigationManager.destroy();
|
||||
|
||||
Services.obs.removeObserver(this, "window-global-destroyed");
|
||||
this.#realms = null;
|
||||
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
|
@ -96,6 +114,22 @@ export class RootMessageHandler extends MessageHandler {
|
|||
return this.updateSessionData([sessionData]);
|
||||
}
|
||||
|
||||
emitEvent(name, eventPayload, contextInfo) {
|
||||
// Intercept realm created and destroyed events to update internal map.
|
||||
if (name === "realm-created") {
|
||||
this.#onRealmCreated(eventPayload);
|
||||
}
|
||||
// We receive this events in the case of moving the page to BFCache.
|
||||
if (name === "windowglobal-pagehide") {
|
||||
this.#cleanUpRealmsForWindow(
|
||||
eventPayload.innerWindowId,
|
||||
eventPayload.context
|
||||
);
|
||||
}
|
||||
|
||||
super.emitEvent(name, eventPayload, contextInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a public protocol event. This event will be sent over to the client.
|
||||
*
|
||||
|
@ -137,6 +171,17 @@ export class RootMessageHandler extends MessageHandler {
|
|||
return true;
|
||||
}
|
||||
|
||||
observe(subject, topic) {
|
||||
if (topic !== "window-global-destroyed") {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#cleanUpRealmsForWindow(
|
||||
subject.innerWindowId,
|
||||
subject.browsingContext
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove session data items of a given module, category and
|
||||
* contextDescriptor.
|
||||
|
@ -160,4 +205,33 @@ export class RootMessageHandler extends MessageHandler {
|
|||
async updateSessionData(sessionData = []) {
|
||||
await this.#sessionData.updateSessionData(sessionData);
|
||||
}
|
||||
|
||||
#cleanUpRealmsForWindow(innerWindowId, context) {
|
||||
const realms = this.#realms.get(innerWindowId);
|
||||
|
||||
if (!realms) {
|
||||
return;
|
||||
}
|
||||
|
||||
realms.forEach(realm => {
|
||||
this.#realms.get(innerWindowId).delete(realm);
|
||||
|
||||
this.emitEvent("realm-destroyed", {
|
||||
context,
|
||||
realm,
|
||||
});
|
||||
});
|
||||
|
||||
this.#realms.delete(innerWindowId);
|
||||
}
|
||||
|
||||
#onRealmCreated = data => {
|
||||
const { innerWindowId, realmId } = data;
|
||||
|
||||
if (!this.#realms.has(innerWindowId)) {
|
||||
this.#realms.set(innerWindowId, new Set());
|
||||
}
|
||||
|
||||
this.#realms.get(innerWindowId).add(realmId);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -34,15 +34,33 @@ export class WindowGlobalMessageHandler extends MessageHandler {
|
|||
|
||||
this.#innerWindowId = this.context.window.windowGlobalChild.innerWindowId;
|
||||
|
||||
// Maps sandbox names to instances of window realms,
|
||||
// the default realm is mapped to an empty string sandbox name.
|
||||
this.#realms = new Map([["", new lazy.WindowRealm(this.context.window)]]);
|
||||
// Maps sandbox names to instances of window realms.
|
||||
this.#realms = new Map();
|
||||
}
|
||||
|
||||
initialize(sessionDataItems) {
|
||||
// Create the default realm, it is mapped to an empty string sandbox name.
|
||||
this.#realms.set("", this.#createRealm());
|
||||
|
||||
// This method, even though being async, is not awaited on purpose,
|
||||
// since for now the sessionDataItems are passed in response to an event in a for loop.
|
||||
this.#applyInitialSessionDataItems(sessionDataItems);
|
||||
|
||||
// With the session data applied the handler is now ready to be used.
|
||||
this.emitEvent("window-global-handler-created", {
|
||||
contextId: this.contextId,
|
||||
innerWindowId: this.#innerWindowId,
|
||||
});
|
||||
}
|
||||
|
||||
destroy() {
|
||||
for (const realm of this.#realms.values()) {
|
||||
realm.destroy();
|
||||
}
|
||||
this.emitEvent("windowglobal-pagehide", {
|
||||
context: this.context,
|
||||
innerWindowId: this.innerWindowId,
|
||||
});
|
||||
this.#realms = null;
|
||||
|
||||
super.destroy();
|
||||
|
@ -92,6 +110,19 @@ export class WindowGlobalMessageHandler extends MessageHandler {
|
|||
return this.context.window;
|
||||
}
|
||||
|
||||
#createRealm(sandboxName = null) {
|
||||
const realm = new lazy.WindowRealm(this.context.window, {
|
||||
sandboxName,
|
||||
});
|
||||
|
||||
this.emitEvent("realm-created", {
|
||||
realmId: realm.id,
|
||||
innerWindowId: this.innerWindowId,
|
||||
});
|
||||
|
||||
return realm;
|
||||
}
|
||||
|
||||
#getRealmFromSandboxName(sandboxName = null) {
|
||||
if (sandboxName === null || sandboxName === "") {
|
||||
return this.#realms.get("");
|
||||
|
@ -101,16 +132,14 @@ export class WindowGlobalMessageHandler extends MessageHandler {
|
|||
return this.#realms.get(sandboxName);
|
||||
}
|
||||
|
||||
const realm = new lazy.WindowRealm(this.context.window, {
|
||||
sandboxName,
|
||||
});
|
||||
const realm = this.#createRealm(sandboxName);
|
||||
|
||||
this.#realms.set(sandboxName, realm);
|
||||
|
||||
return realm;
|
||||
}
|
||||
|
||||
async applyInitialSessionDataItems(sessionDataItems) {
|
||||
async #applyInitialSessionDataItems(sessionDataItems) {
|
||||
if (!Array.isArray(sessionDataItems)) {
|
||||
return;
|
||||
}
|
||||
|
@ -163,12 +192,6 @@ export class WindowGlobalMessageHandler extends MessageHandler {
|
|||
}
|
||||
|
||||
await Promise.all(sessionDataPromises);
|
||||
|
||||
// With the session data applied the handler is now ready to be used.
|
||||
this.emitEvent("window-global-handler-created", {
|
||||
contextId: this.contextId,
|
||||
innerWindowId: this.#innerWindowId,
|
||||
});
|
||||
}
|
||||
|
||||
forwardCommand(command) {
|
||||
|
|
|
@ -16,6 +16,7 @@ prefs =
|
|||
[browser_handle_command_retry.js]
|
||||
[browser_handle_simple_command.js]
|
||||
[browser_navigation_manager.js]
|
||||
[browser_realms.js]
|
||||
[browser_registry.js]
|
||||
[browser_session_data.js]
|
||||
[browser_session_data_browser_element.js]
|
||||
|
|
|
@ -24,19 +24,38 @@ add_task(async function test_event() {
|
|||
const browsingContext = tab.linkedBrowser.browsingContext;
|
||||
|
||||
const rootMessageHandler = createRootMessageHandler("session-id-event");
|
||||
let messageHandlerEvent;
|
||||
let registryEvent;
|
||||
|
||||
// Events are emitted both as generic message-handler-event events as well
|
||||
// as under their own name. We expect to receive the event for both.
|
||||
const onHandlerEvent = rootMessageHandler.once("message-handler-event");
|
||||
const _onMessageHandlerEvent = (eventName, eventData) => {
|
||||
if (eventData.name === "event-from-window-global") {
|
||||
messageHandlerEvent = eventData;
|
||||
}
|
||||
};
|
||||
rootMessageHandler.on("message-handler-event", _onMessageHandlerEvent);
|
||||
const onNamedEvent = rootMessageHandler.once("event-from-window-global");
|
||||
// MessageHandlerRegistry should forward all the message-handler-events.
|
||||
const onRegistryEvent = RootMessageHandlerRegistry.once(
|
||||
"message-handler-registry-event"
|
||||
const _onMessageHandlerRegistryEvent = (eventName, eventData) => {
|
||||
if (eventData.name === "event-from-window-global") {
|
||||
registryEvent = eventData;
|
||||
}
|
||||
};
|
||||
RootMessageHandlerRegistry.on(
|
||||
"message-handler-registry-event",
|
||||
_onMessageHandlerRegistryEvent
|
||||
);
|
||||
|
||||
callTestEmitEvent(rootMessageHandler, browsingContext.id);
|
||||
|
||||
const messageHandlerEvent = await onHandlerEvent;
|
||||
const namedEvent = await onNamedEvent;
|
||||
is(
|
||||
namedEvent.text,
|
||||
`event from ${browsingContext.id}`,
|
||||
"Received the expected payload"
|
||||
);
|
||||
|
||||
is(
|
||||
messageHandlerEvent.name,
|
||||
"event-from-window-global",
|
||||
|
@ -48,20 +67,16 @@ add_task(async function test_event() {
|
|||
"Received the expected payload"
|
||||
);
|
||||
|
||||
const namedEvent = await onNamedEvent;
|
||||
is(
|
||||
namedEvent.text,
|
||||
`event from ${browsingContext.id}`,
|
||||
"Received the expected payload"
|
||||
);
|
||||
|
||||
const registryEvent = await onRegistryEvent;
|
||||
is(
|
||||
registryEvent,
|
||||
messageHandlerEvent,
|
||||
"The event forwarded by the MessageHandlerRegistry is identical to the MessageHandler event"
|
||||
);
|
||||
|
||||
rootMessageHandler.off("message-handler-event", _onMessageHandlerEvent);
|
||||
RootMessageHandlerRegistry.off(
|
||||
"message-handler-registry-event",
|
||||
_onMessageHandlerRegistryEvent
|
||||
);
|
||||
rootMessageHandler.destroy();
|
||||
gBrowser.removeTab(tab);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,152 @@
|
|||
/* 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_tab_is_removed() {
|
||||
const tab = await addTab("https://example.com/document-builder.sjs?html=tab");
|
||||
const sessionId = "realms";
|
||||
const browsingContext = tab.linkedBrowser.browsingContext;
|
||||
const contextDescriptor = {
|
||||
type: ContextDescriptorType.TopBrowsingContext,
|
||||
id: browsingContext.browserId,
|
||||
};
|
||||
|
||||
const rootMessageHandler = createRootMessageHandler(sessionId);
|
||||
|
||||
const onRealmCreated = rootMessageHandler.once("realm-created");
|
||||
|
||||
// Add a new session data item to get window global handlers created
|
||||
await rootMessageHandler.addSessionDataItem({
|
||||
moduleName: "command",
|
||||
category: "browser_realms",
|
||||
contextDescriptor,
|
||||
values: [true],
|
||||
});
|
||||
|
||||
const realmCreatedEvent = await onRealmCreated;
|
||||
const createdRealmId = realmCreatedEvent.realmId;
|
||||
|
||||
is(rootMessageHandler.realms.size, 1, "Realm is added in the internal map");
|
||||
|
||||
const onRealmDestroyed = rootMessageHandler.once("realm-destroyed");
|
||||
|
||||
gBrowser.removeTab(tab);
|
||||
|
||||
const realmDestroyedEvent = await onRealmDestroyed;
|
||||
|
||||
is(
|
||||
realmDestroyedEvent.realm,
|
||||
createdRealmId,
|
||||
"Received a correct realm id in realm-destroyed event"
|
||||
);
|
||||
is(rootMessageHandler.realms.size, 0, "The realm map is cleaned up");
|
||||
|
||||
rootMessageHandler.destroy();
|
||||
});
|
||||
|
||||
add_task(async function test_same_origin_navigation() {
|
||||
const tab = await addTab("https://example.com/document-builder.sjs?html=tab");
|
||||
const sessionId = "realms";
|
||||
const browsingContext = tab.linkedBrowser.browsingContext;
|
||||
const contextDescriptor = {
|
||||
type: ContextDescriptorType.TopBrowsingContext,
|
||||
id: browsingContext.browserId,
|
||||
};
|
||||
|
||||
const rootMessageHandler = createRootMessageHandler(sessionId);
|
||||
|
||||
const onRealmCreated = rootMessageHandler.once("realm-created");
|
||||
|
||||
// Add a new session data item to get window global handlers created
|
||||
await rootMessageHandler.addSessionDataItem({
|
||||
moduleName: "command",
|
||||
category: "browser_realms",
|
||||
contextDescriptor,
|
||||
values: [true],
|
||||
});
|
||||
|
||||
const realmCreatedEvent = await onRealmCreated;
|
||||
const createdRealmId = realmCreatedEvent.realmId;
|
||||
|
||||
is(rootMessageHandler.realms.size, 1, "Realm is added in the internal map");
|
||||
|
||||
const onRealmDestroyed = rootMessageHandler.once("realm-destroyed");
|
||||
const onNewRealmCreated = rootMessageHandler.once("realm-created");
|
||||
|
||||
// Navigate to another page with the same origin
|
||||
await loadURL(
|
||||
tab.linkedBrowser,
|
||||
"https://example.com/document-builder.sjs?html=othertab"
|
||||
);
|
||||
|
||||
const realmDestroyedEvent = await onRealmDestroyed;
|
||||
|
||||
is(
|
||||
realmDestroyedEvent.realm,
|
||||
createdRealmId,
|
||||
"Received a correct realm id in realm-destroyed event"
|
||||
);
|
||||
|
||||
await onNewRealmCreated;
|
||||
|
||||
is(rootMessageHandler.realms.size, 1, "Realm is added in the internal map");
|
||||
|
||||
gBrowser.removeTab(tab);
|
||||
rootMessageHandler.destroy();
|
||||
});
|
||||
|
||||
add_task(async function test_cross_origin_navigation() {
|
||||
const tab = await addTab("https://example.com/document-builder.sjs?html=tab");
|
||||
const sessionId = "realms";
|
||||
const browsingContext = tab.linkedBrowser.browsingContext;
|
||||
const contextDescriptor = {
|
||||
type: ContextDescriptorType.TopBrowsingContext,
|
||||
id: browsingContext.browserId,
|
||||
};
|
||||
|
||||
const rootMessageHandler = createRootMessageHandler(sessionId);
|
||||
|
||||
const onRealmCreated = rootMessageHandler.once("realm-created");
|
||||
|
||||
// Add a new session data item to get window global handlers created
|
||||
await rootMessageHandler.addSessionDataItem({
|
||||
moduleName: "command",
|
||||
category: "browser_realms",
|
||||
contextDescriptor,
|
||||
values: [true],
|
||||
});
|
||||
|
||||
const realmCreatedEvent = await onRealmCreated;
|
||||
const createdRealmId = realmCreatedEvent.realmId;
|
||||
|
||||
is(rootMessageHandler.realms.size, 1, "Realm is added in the internal map");
|
||||
|
||||
const onRealmDestroyed = rootMessageHandler.once("realm-destroyed");
|
||||
const onNewRealmCreated = rootMessageHandler.once("realm-created");
|
||||
|
||||
// Navigate to another page with the different origin
|
||||
await loadURL(
|
||||
tab.linkedBrowser,
|
||||
"https://example.com/document-builder.sjs?html=otherorigin"
|
||||
);
|
||||
|
||||
const realmDestroyedEvent = await onRealmDestroyed;
|
||||
|
||||
is(
|
||||
realmDestroyedEvent.realm,
|
||||
createdRealmId,
|
||||
"Received a correct realm id in realm-destroyed event"
|
||||
);
|
||||
|
||||
await onNewRealmCreated;
|
||||
|
||||
is(rootMessageHandler.realms.size, 1, "Realm is added in the internal map");
|
||||
|
||||
gBrowser.removeTab(tab);
|
||||
rootMessageHandler.destroy();
|
||||
});
|
|
@ -186,7 +186,9 @@ add_task(async function test_sessionDataRootOnlyModule() {
|
|||
"https://example.com/document-builder.sjs?html=tab"
|
||||
);
|
||||
|
||||
const windowGlobalCreated = rootMessageHandler.once("message-handler-event");
|
||||
const windowGlobalCreated = rootMessageHandler.once(
|
||||
"window-global-handler-created"
|
||||
);
|
||||
|
||||
info("Test that adding SessionData items works the root module");
|
||||
// Updating the session data on the root message handler should not cause
|
||||
|
|
|
@ -40,6 +40,7 @@ const ScriptEvaluateResultType = {
|
|||
|
||||
class ScriptModule extends Module {
|
||||
#preloadScriptMap;
|
||||
#subscribedEvents;
|
||||
|
||||
constructor(messageHandler) {
|
||||
super(messageHandler);
|
||||
|
@ -48,10 +49,14 @@ class ScriptModule extends Module {
|
|||
// with an item named expression, which is a string,
|
||||
// and an item named sandbox which is a string or null.
|
||||
this.#preloadScriptMap = new Map();
|
||||
|
||||
// Set of event names which have active subscriptions.
|
||||
this.#subscribedEvents = new Set();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.#preloadScriptMap = null;
|
||||
this.#subscribedEvents = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -746,8 +751,77 @@ class ScriptModule extends Module {
|
|||
.filter(realm => realm.context !== null);
|
||||
}
|
||||
|
||||
#onRealmDestroyed = (eventName, { realm, context }) => {
|
||||
// This event is emitted from the parent process but for a given browsing
|
||||
// context. Set the event's contextInfo to the message handler corresponding
|
||||
// to this browsing context.
|
||||
const contextInfo = {
|
||||
contextId: context.id,
|
||||
type: lazy.WindowGlobalMessageHandler.type,
|
||||
};
|
||||
|
||||
this.emitEvent("script.realmDestroyed", { realm }, contextInfo);
|
||||
};
|
||||
|
||||
#startListingOnRealmDestroyed() {
|
||||
if (!this.#subscribedEvents.has("script.realmDestroyed")) {
|
||||
this.messageHandler.on("realm-destroyed", this.#onRealmDestroyed);
|
||||
}
|
||||
}
|
||||
|
||||
#stopListingOnRealmDestroyed() {
|
||||
if (this.#subscribedEvents.has("script.realmDestroyed")) {
|
||||
this.messageHandler.off("realm-destroyed", this.#onRealmDestroyed);
|
||||
}
|
||||
}
|
||||
|
||||
#subscribeEvent(event) {
|
||||
switch (event) {
|
||||
case "script.realmDestroyed": {
|
||||
this.#startListingOnRealmDestroyed();
|
||||
this.#subscribedEvents.add(event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#unsubscribeEvent(event) {
|
||||
switch (event) {
|
||||
case "script.realmDestroyed": {
|
||||
this.#stopListingOnRealmDestroyed();
|
||||
this.#subscribedEvents.delete(event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_applySessionData(params) {
|
||||
// TODO: Bug 1775231. Move this logic to a shared module or an abstract
|
||||
// class.
|
||||
const { category } = params;
|
||||
if (category === "event") {
|
||||
const filteredSessionData = params.sessionData.filter(item =>
|
||||
this.messageHandler.matchesContext(item.contextDescriptor)
|
||||
);
|
||||
for (const event of this.#subscribedEvents.values()) {
|
||||
const hasSessionItem = filteredSessionData.some(
|
||||
item => item.value === event
|
||||
);
|
||||
// If there are no session items for this context, we should unsubscribe from the event.
|
||||
if (!hasSessionItem) {
|
||||
this.#unsubscribeEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
// Subscribe to all events, which have an item in SessionData.
|
||||
for (const { value } of filteredSessionData) {
|
||||
this.#subscribeEvent(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static get supportedEvents() {
|
||||
return ["script.message"];
|
||||
return ["script.message", "script.realmDestroyed"];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче