tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 438a3ce10eb77fb50d968463b7741117aec5bb4a
parent 170744127aa75c8de003df70574f8a6d6fdf7432
Author: unifolia <jlewis@mozilla.com>
Date:   Fri, 12 Dec 2025 04:13:06 +0000

Bug 2005590 - Handle AI Window Opening From Within BrowserWindowTracker r=Mardak,ai-frontend-reviewers

Differential Revision: https://phabricator.services.mozilla.com/D276072

Diffstat:
Mbrowser/base/content/browser.js | 2--
Mbrowser/components/aiwindow/ui/modules/AIWindow.sys.mjs | 48++++++++++++++++++++++++++++--------------------
Mbrowser/components/aiwindow/ui/test/browser/browser_open_aiwindow.js | 37+++++++++++++++++++++++++++++++++++++
Mbrowser/modules/BrowserWindowTracker.sys.mjs | 28++++++++++++++++++----------
4 files changed, 83 insertions(+), 32 deletions(-)

diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js @@ -1692,8 +1692,6 @@ function OpenBrowserWindow(options) { options ??= {}; options.openerWindow ??= window; - AIWindow.handleAIWindowOptions(window, options); - let win = BrowserWindowTracker.openWindow(options); win.addEventListener( diff --git a/browser/components/aiwindow/ui/modules/AIWindow.sys.mjs b/browser/components/aiwindow/ui/modules/AIWindow.sys.mjs @@ -36,43 +36,51 @@ export const AIWindow = { /** * Sets options for new AI Window if new or inherited conditions are met * - * @param {object} win opener window - * @param {object} options options to be passed into BrowserWindowTracker.openWindow + * @param {object} options Used in BrowserWindowTracker.openWindow + * @param {object} options.openerWindow Window making the BrowserWindowTracker.openWindow call + * @param {object} options.args Array of arguments to pass to new window + * @param {boolean} options.aiWindow Should new window be AI Window + * @param {boolean} options.private Should new window be Private Window + * + * @returns {object} Modified arguments appended to the options object */ - handleAIWindowOptions(win, options = {}) { - const { openerWindow } = options; - + handleAIWindowOptions({ + openerWindow, + args, + aiWindow, + private: isPrivate, + } = {}) { + // Indicates whether the new window should inherit AI Window state from opener window const canInheritAIWindow = - this.isAIWindowActive(win) && - !options.private && - !Object.hasOwn(options, "aiWindow"); + this.isAIWindowEnabled() && + this.isAIWindowActive(openerWindow) && + !isPrivate && + !aiWindow; const willOpenAIWindow = - openerWindow && - openerWindow.AIWindow?.isAIWindowEnabled && - (options.aiWindow || canInheritAIWindow); + (aiWindow && this.isAIWindowEnabled()) || canInheritAIWindow; if (!willOpenAIWindow) { - return; + return args; } - options.args ??= Cc["@mozilla.org/array;1"].createInstance( - Ci.nsIMutableArray - ); + args ??= Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray); - if (!options.args.length) { + if (!args.length) { const aiWindowURI = Cc["@mozilla.org/supports-string;1"].createInstance( Ci.nsISupportsString ); aiWindowURI.data = "chrome://browser/content/aiwindow/aiWindow.html"; - options.args.appendElement(aiWindowURI); + args.appendElement(aiWindowURI); const aiOption = Cc["@mozilla.org/hash-property-bag;1"].createInstance( Ci.nsIWritablePropertyBag2 ); - aiOption.setPropertyAsBool("ai-window", options.aiWindow); - options.args.appendElement(aiOption); + aiOption.setPropertyAsBool("ai-window", aiWindow); + args.appendElement(aiOption); } + + return args; }, /** @@ -83,7 +91,7 @@ export const AIWindow = { */ isAIWindowActive(win) { - return win.document.documentElement.hasAttribute("ai-window"); + return !!win && win.document.documentElement.hasAttribute("ai-window"); }, /** diff --git a/browser/components/aiwindow/ui/test/browser/browser_open_aiwindow.js b/browser/components/aiwindow/ui/test/browser/browser_open_aiwindow.js @@ -147,6 +147,43 @@ add_task(async function test_button_actions() { await SpecialPowers.popPrefEnv(); }); +add_task(async function test_openNewBrowserWindow_and_ai_inherit() { + await SpecialPowers.pushPrefEnv({ + set: [["browser.aiwindow.enabled", true]], + }); + + const newAIWindow = await BrowserTestUtils.openNewBrowserWindow({ + openerWindow: null, + aiWindow: true, + }); + + Assert.ok( + newAIWindow.document.documentElement.hasAttribute("ai-window"), + "BrowserTestUtils.openNewBrowserWindow({ aiWindow: true }) should open an AI Window" + ); + + await SpecialPowers.popPrefEnv(); + await SpecialPowers.pushPrefEnv({ + set: [["browser.aiwindow.enabled", false]], + }); + + const newWindowAfterDisabledAI = await BrowserTestUtils.openNewBrowserWindow({ + openerWindow: newAIWindow, + aiWindow: false, + }); + + Assert.ok( + !newWindowAfterDisabledAI.document.documentElement.hasAttribute( + "ai-window" + ), + "BrowserTestUtils.openNewBrowserWindow({ aiWindow: false }) should not open a new AI Window from an existing AI Window" + ); + + await BrowserTestUtils.closeWindow(newAIWindow); + await BrowserTestUtils.closeWindow(newWindowAfterDisabledAI); + await SpecialPowers.popPrefEnv(); +}); + function checkMenuItemVisibility( aiWindowEnabled, aiOpenerButton, diff --git a/browser/modules/BrowserWindowTracker.sys.mjs b/browser/modules/BrowserWindowTracker.sys.mjs @@ -19,6 +19,8 @@ XPCOMUtils.defineLazyServiceGetters(lazy, { }); ChromeUtils.defineESModuleGetters(lazy, { + AIWindow: + "moz-src:///browser/components/aiwindow/ui/modules/AIWindow.sys.mjs", HomePage: "resource:///modules/HomePage.sys.mjs", PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs", }); @@ -301,6 +303,8 @@ export const BrowserWindowTracker = { * An existing browser window to open the new one from. * @param {boolean} [options.private] * True to make the window a private browsing window. + * @param {boolean} [options.aiWindow] + * True to make the window an AI browsing window. * @param {string} [options.features] * Additional window features to give the new window. * @param {boolean} [options.all] @@ -317,16 +321,20 @@ export const BrowserWindowTracker = { * * @returns {Window} */ - openWindow({ - openerWindow = undefined, - private: isPrivate = false, - aiWindow = false, - features = undefined, - all = true, - args = null, - remote = undefined, - fission = undefined, - } = {}) { + openWindow(options = {}) { + let { + openerWindow = undefined, + private: isPrivate = false, + aiWindow = false, + features = undefined, + all = true, + args = null, + remote = undefined, + fission = undefined, + } = options; + + args = lazy.AIWindow.handleAIWindowOptions(options); + let windowFeatures = "chrome,dialog=no"; if (all) { windowFeatures += ",all";