commit 5eea86343d639895cb6d00b05270dae576f3bc4c
parent a091e7c4c132680be00a5021386dcad16f6b45df
Author: Henrik Skupin <mail@hskupin.info>
Date: Mon, 1 Dec 2025 20:43:58 +0000
Bug 2000801 - [remote] Update Navigable Manager for parent process browsing contexts. r=jdescottes
Differential Revision: https://phabricator.services.mozilla.com/D273539
Diffstat:
2 files changed, 157 insertions(+), 18 deletions(-)
diff --git a/remote/shared/NavigableManager.sys.mjs b/remote/shared/NavigableManager.sys.mjs
@@ -5,6 +5,7 @@
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
+ BiMap: "chrome://remote/content/shared/BiMap.sys.mjs",
BrowsingContextListener:
"chrome://remote/content/shared/listeners/BrowsingContextListener.sys.mjs",
generateUUID: "chrome://remote/content/shared/UUID.sys.mjs",
@@ -23,10 +24,11 @@ ChromeUtils.defineESModuleGetters(lazy, {
* that is not implemented in Firefox.
*/
class NavigableManagerClass {
- #tracking;
#browserIds;
+ #chromeNavigables;
#contextListener;
#navigableIds;
+ #tracking;
constructor() {
this.#tracking = false;
@@ -38,6 +40,10 @@ class NavigableManagerClass {
// context id from the formerly known browser.
this.#browserIds = new WeakMap();
+ // Maps canonical browsing contexts from the parent process
+ // to a uuid and vice versa.
+ this.#chromeNavigables = new lazy.BiMap();
+
// Maps browsing contexts to uuid: WeakMap.<BrowsingContext, string>.
this.#navigableIds = new WeakMap();
@@ -77,19 +83,23 @@ class NavigableManagerClass {
* @param {string} id
* A browsing context unique id (created by getIdForBrowsingContext).
*
- * @returns {BrowsingContext=}
+ * @returns {BrowsingContext|null}
* The browsing context found for this id, null if none was found or
* browsing context is discarded.
*/
getBrowsingContextById(id) {
let browsingContext;
- const browser = this.getBrowserById(id);
- if (browser) {
- // top-level browsing context
- browsingContext = browser.browsingContext;
+ if (this.#chromeNavigables.hasId(id)) {
+ browsingContext = this.#chromeNavigables.getObject(id);
} else {
- browsingContext = BrowsingContext.get(id);
+ const browser = this.getBrowserById(id);
+ if (browser) {
+ // top-level browsing context
+ browsingContext = browser.browsingContext;
+ } else {
+ browsingContext = BrowsingContext.get(id);
+ }
}
if (!browsingContext || browsingContext.isDiscarded) {
@@ -120,11 +130,10 @@ class NavigableManagerClass {
return null;
}
- const key = browser.permanentKey;
- if (!this.#browserIds.has(key)) {
- this.#browserIds.set(key, lazy.generateUUID());
- }
- return this.#browserIds.get(key);
+ return this.#browserIds.getOrInsertComputed(
+ browser.permanentKey,
+ lazy.generateUUID
+ );
}
/**
@@ -143,6 +152,12 @@ class NavigableManagerClass {
return null;
}
+ if (!browsingContext.isContent) {
+ // When the browsing context runs in the parent process we
+ // can use the browsing context as key because it's stable.
+ return this.#chromeNavigables.getOrInsert(browsingContext);
+ }
+
if (!browsingContext.parent) {
// For top-level browsing contexts always try to use the browser
// as navigable first because it survives a cross-process navigation.
@@ -194,14 +209,16 @@ class NavigableManagerClass {
return;
}
- lazy.TabManager.getBrowsers().forEach(browser =>
- this.#setIdForBrowsingContext(browser.browsingContext)
- );
-
this.#contextListener = new lazy.BrowsingContextListener();
this.#contextListener.on("attached", this.#onContextAttached);
+ this.#contextListener.on("discarded", this.#onContextDiscarded);
this.#contextListener.startListening();
+ // Register as well all browsing contexts from already open tabs.
+ lazy.TabManager.getBrowsers().forEach(browser =>
+ this.#setIdForBrowsingContext(browser.browsingContext)
+ );
+
this.#tracking = true;
}
@@ -210,9 +227,12 @@ class NavigableManagerClass {
return;
}
+ this.#contextListener.off("attached", this.#onContextAttached);
+ this.#contextListener.off("discarded", this.#onContextDiscarded);
this.#contextListener.stopListening();
this.#contextListener = null;
+ this.#chromeNavigables.clear();
this.#browserIds = new WeakMap();
this.#navigableIds = new WeakMap();
@@ -255,8 +275,19 @@ class NavigableManagerClass {
#onContextAttached = (_, data = {}) => {
const { browsingContext } = data;
- if (lazy.TabManager.isValidCanonicalBrowsingContext(browsingContext)) {
- this.#setIdForBrowsingContext(browsingContext);
+ if (!browsingContext.isContent) {
+ this.#chromeNavigables.getOrInsert(browsingContext);
+ return;
+ }
+
+ this.#setIdForBrowsingContext(browsingContext);
+ };
+
+ #onContextDiscarded = (_, data = {}) => {
+ const { browsingContext } = data;
+
+ if (!browsingContext.isContent) {
+ this.#chromeNavigables.deleteByValue(browsingContext);
}
};
}
diff --git a/remote/shared/test/browser/browser_NavigableManager.js b/remote/shared/test/browser/browser_NavigableManager.js
@@ -439,4 +439,112 @@ describe("NavigableManager", function () {
"Got a valid uuid for the top-level context"
);
});
+
+ it("Get the Navigable id for a chrome browsing context", async function test_getIdForChromeBrowsingContext() {
+ // Get the parent process browsing context (chrome scope)
+ const chromeContext = window.browsingContext;
+
+ ok(!chromeContext.isContent, "Chrome context is not a content context");
+
+ const chromeContextId =
+ NavigableManager.getIdForBrowsingContext(chromeContext);
+
+ Assert.stringMatches(
+ chromeContextId,
+ uuidRegex,
+ "Got a valid uuid for chrome browsing context"
+ );
+
+ is(
+ NavigableManager.getIdForBrowsingContext(chromeContext),
+ chromeContextId,
+ "Id is always the same for the same chrome browsing context"
+ );
+
+ const chromeContext2 = BrowsingContext.getFromWindow(
+ Services.wm.getMostRecentWindow("navigator:browser")
+ );
+ is(
+ NavigableManager.getIdForBrowsingContext(chromeContext2),
+ chromeContextId,
+ "Same chrome context returns same id when retrieved differently"
+ );
+ });
+
+ it("Get chrome browsing context by its Navigable id", async function test_getChromeBrowsingContextById() {
+ const chromeContext = BrowsingContext.getFromWindow(window);
+
+ ok(!chromeContext.isContent, "Chrome context is not a content context");
+
+ const chromeContextId =
+ NavigableManager.getIdForBrowsingContext(chromeContext);
+
+ is(
+ NavigableManager.getBrowsingContextById(chromeContextId),
+ chromeContext,
+ "Chrome browsing context can be retrieved by its id"
+ );
+ });
+
+ it("Chrome browsing contexts have different ids than content contexts", async function test_chromeVsContentIds() {
+ const { newContext } = testData;
+ const chromeContext = BrowsingContext.getFromWindow(window);
+
+ const chromeContextId =
+ NavigableManager.getIdForBrowsingContext(chromeContext);
+ const contentContextId =
+ NavigableManager.getIdForBrowsingContext(newContext);
+
+ Assert.stringMatches(
+ chromeContextId,
+ uuidRegex,
+ "Chrome context has valid uuid"
+ );
+ Assert.stringMatches(
+ contentContextId,
+ uuidRegex,
+ "Content context has valid uuid"
+ );
+
+ isnot(
+ chromeContextId,
+ contentContextId,
+ "Chrome and content contexts have different ids"
+ );
+ });
+
+ it("Chrome browsing context cleanup on discard", async function test_chromeBrowsingContextDiscard() {
+ // Open a new window to get a chrome browsing context we can close
+ const newWindow = await BrowserTestUtils.openNewBrowserWindow();
+ const newChromeContext = BrowsingContext.getFromWindow(newWindow);
+
+ ok(
+ !newChromeContext.isContent,
+ "New window's chrome context is not a content context"
+ );
+
+ const chromeContextId =
+ NavigableManager.getIdForBrowsingContext(newChromeContext);
+
+ Assert.stringMatches(
+ chromeContextId,
+ uuidRegex,
+ "Got a valid uuid for new window's chrome context"
+ );
+
+ is(
+ NavigableManager.getBrowsingContextById(chromeContextId),
+ newChromeContext,
+ "Chrome browsing context can be retrieved by id"
+ );
+
+ // Close the new window
+ await BrowserTestUtils.closeWindow(newWindow);
+
+ is(
+ NavigableManager.getBrowsingContextById(chromeContextId),
+ null,
+ "Discarded chrome browsing context has no navigable id"
+ );
+ });
});