tor-browser

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

commit 108746012f3a80eb57110fe8a5414d48d43e441c
parent c73a5034930ffb851950b1aa73d0115c15ece9d4
Author: Elissa Cha <echa@mozilla.com>
Date:   Tue,  6 Jan 2026 18:30:06 +0000

Bug 2000982 - add message level config to allow showing message in AI Window. r=pdahiya,omc-reviewers,ngrato,aminomancer

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

Diffstat:
Mbrowser/components/asrouter/docs/targeting-attributes.md | 31+++++++++++++++++++++++++++++++
Mbrowser/components/asrouter/modules/ASRouter.sys.mjs | 5+++++
Mbrowser/components/asrouter/modules/ASRouterTargeting.sys.mjs | 26+++++++++++++++++++-------
Mbrowser/components/asrouter/tests/browser/browser_asrouter_menu_messages.js | 2++
Mbrowser/components/asrouter/tests/unit/ASRouter.test.js | 15+++++++--------
Mbrowser/components/asrouter/tests/unit/ASRouterTargeting.test.js | 52+++++++++++++++++++++++++++++++++++++++++++++++++++-
Mbrowser/components/asrouter/tests/unit/TargetingDocs.test.js | 1+
7 files changed, 116 insertions(+), 16 deletions(-)

diff --git a/browser/components/asrouter/docs/targeting-attributes.md b/browser/components/asrouter/docs/targeting-attributes.md @@ -48,6 +48,7 @@ Please note that some targeting attributes require stricter controls on the tele * [hasSelectableProfiles](#hasselectableprofiles) * [homePageSettings](#homepagesettings) * [isBackgroundTaskMode](#isbackgroundtaskmode) +* [isAIWindow] (#isaiwindow) * [isChinaRepack](#ischinarepack) * [isDefaultBrowser](#isdefaultbrowser) * [isDefaultBrowserUncached](#isdefaultbrowseruncached) @@ -855,6 +856,36 @@ actually emit from tabs, this is always true. For other triggers, like declare const browserIsSelected: boolean; ``` +### `isAIWindow` + +A context property included for all triggers that evaluates to `true` when the +message comes from an AI Window, and `false` otherwise. + +#### Definition + +```ts +declare const isAIWindow: boolean; +``` + +#### Examples + +* Target AI Windows only: +```javascript +isAIWindow +``` + +* Target Classic Windows only: +```javascript +!isAIWindow +``` + +* Target both AI Windows and Classic Windows: +```javascript +isAIWindow == isAIWindow +or equivalently +(isAIWindow || !isAIWindow) +``` + ### `isChinaRepack` Does the user use [the partner repack distributed by Mozilla Online](https://github.com/mozilla-partners/mozillaonline), diff --git a/browser/components/asrouter/modules/ASRouter.sys.mjs b/browser/components/asrouter/modules/ASRouter.sys.mjs @@ -60,6 +60,8 @@ ChromeUtils.defineESModuleGetters(lazy, { Spotlight: "resource:///modules/asrouter/Spotlight.sys.mjs", ToastNotification: "resource:///modules/asrouter/ToastNotification.sys.mjs", ToolbarBadgeHub: "resource:///modules/asrouter/ToolbarBadgeHub.sys.mjs", + AIWindow: + "moz-src:///browser/components/aiwindow/ui/modules/AIWindow.sys.mjs", }); XPCOMUtils.defineLazyPreferenceGetter( @@ -2342,6 +2344,9 @@ export class _ASRouter { trigger.context = {}; } if (typeof trigger.context === "object") { + trigger.context.isAIWindow = !!lazy.AIWindow?.isAIWindowActive?.( + browser.ownerGlobal + ); trigger.context.browserIsSelected = trigger.context.browserIsSelected || browser === browser.ownerGlobal.gBrowser?.selectedBrowser; diff --git a/browser/components/asrouter/modules/ASRouterTargeting.sys.mjs b/browser/components/asrouter/modules/ASRouterTargeting.sys.mjs @@ -1390,6 +1390,19 @@ const TargetingGetters = { }, }; +function addAIWindowTargeting(targeting) { + if (!targeting || targeting === "true") { + // Default behavior: Classic-only if no targeting is specified + return `!isAIWindow`; + } + + if (/\bisAIWindow\b/.test(targeting)) { + return targeting; + } + + return `((${targeting}) && !isAIWindow)`; +} + export const ASRouterTargeting = { Environment: TargetingGetters, @@ -1531,14 +1544,13 @@ export const ASRouterTargeting = { Array.from(arguments) // eslint-disable-line prefer-rest-params ); - // If no targeting is specified, - if (!message.targeting) { - return true; - } + let { targeting } = message; + targeting = addAIWindowTargeting(targeting); + let result; try { if (shouldCache) { - result = this.getCachedEvaluation(message.targeting); + result = this.getCachedEvaluation(targeting); if (result) { return result.value; } @@ -1546,9 +1558,9 @@ export const ASRouterTargeting = { // Used to report the source of the targeting error in the case of // undesired events targetingContext.setTelemetrySource(message.id); - result = await targetingContext.evalWithDefault(message.targeting); + result = await targetingContext.evalWithDefault(targeting); if (shouldCache) { - jexlEvaluationCache.set(message.targeting, { + jexlEvaluationCache.set(targeting, { timestamp: Date.now(), value: result, }); diff --git a/browser/components/asrouter/tests/browser/browser_asrouter_menu_messages.js b/browser/components/asrouter/tests/browser/browser_asrouter_menu_messages.js @@ -486,6 +486,7 @@ add_task(async function test_trigger() { context: { source: MenuMessage.SOURCES.APP_MENU, browserIsSelected: true, + isAIWindow: false, }, }), "sendTriggerMessage was called when opening the AppMenu panel." @@ -501,6 +502,7 @@ add_task(async function test_trigger() { context: { source: MenuMessage.SOURCES.PXI_MENU, browserIsSelected: true, + isAIWindow: false, }, }), "sendTriggerMessage was called when opening the PXI panel." diff --git a/browser/components/asrouter/tests/unit/ASRouter.test.js b/browser/components/asrouter/tests/unit/ASRouter.test.js @@ -1801,15 +1801,14 @@ describe("ASRouter", () => { id: "firstRun", }); + const [{ trigger }] = + ASRouterTargeting.findMatchingMessage.firstCall.args; + assert.calledOnce(ASRouterTargeting.findMatchingMessage); - assert.deepEqual( - ASRouterTargeting.findMatchingMessage.firstCall.args[0].trigger, - { - id: "firstRun", - param: undefined, - context: { browserIsSelected: true }, - } - ); + assert.strictEqual(trigger.id, "firstRun"); + assert.strictEqual(trigger.param, undefined); + assert.isObject(trigger.context); + assert.strictEqual(trigger.context.browserIsSelected, true); }); it("should record telemetry information", async () => { const fakeTimerId = 42; diff --git a/browser/components/asrouter/tests/unit/ASRouterTargeting.test.js b/browser/components/asrouter/tests/unit/ASRouterTargeting.test.js @@ -322,7 +322,10 @@ describe("ASRouterTargeting", () => { false ); assert.calledOnce(fakeTargetingContext.evalWithDefault); - assert.calledWithExactly(fakeTargetingContext.evalWithDefault, "true"); + assert.include( + fakeTargetingContext.evalWithDefault.firstCall.args[0], + "!isAIWindow" + ); assert.calledWithExactly( fakeTargetingContext.setTelemetrySource, "message" @@ -404,6 +407,53 @@ describe("ASRouterTargeting", () => { assert.calledTwice(evalStub); }); + it("defaults to Classic-only targeting when no targeting is specified", async () => { + evalStub.resolves(true); + const targetingContext = new global.TargetingContext(); + const message = { id: "test-message" }; + + await ASRouterTargeting.checkMessageTargeting( + message, + targetingContext, + null, + false + ); + + assert.calledOnce(fakeTargetingContext.evalWithDefault); + assert.calledWith(fakeTargetingContext.evalWithDefault, "!isAIWindow"); + }); + it("blocks messages in AI windows by default via !isAIWindow", async () => { + evalStub.resolves(true); + const targetingContext = new global.TargetingContext(); + targetingContext.isAIWindow = false; + const message = { id: "test-message" }; + + await ASRouterTargeting.checkMessageTargeting( + message, + targetingContext, + null, + false + ); + + assert.calledOnce(fakeTargetingContext.evalWithDefault); + assert.calledWith(fakeTargetingContext.evalWithDefault, "!isAIWindow"); + }); + it("does not modify targeting that explicitly references isAIWindow", async () => { + evalStub.resolves(true); + const targetingContext = new global.TargetingContext(); + targetingContext.isAIWindow = true; + const message = { id: "test-message", targeting: "isAIWindow" }; + + await ASRouterTargeting.checkMessageTargeting( + message, + targetingContext, + null, + false + ); + + assert.calledOnce(fakeTargetingContext.evalWithDefault); + assert.calledWith(fakeTargetingContext.evalWithDefault, "isAIWindow"); + }); describe("#findMatchingMessage", () => { let matchStub; diff --git a/browser/components/asrouter/tests/unit/TargetingDocs.test.js b/browser/components/asrouter/tests/unit/TargetingDocs.test.js @@ -74,6 +74,7 @@ describe("ASRTargeting docs", () => { "messageImpressions", "screenImpressions", "browserIsSelected", + "isAIWindow", ]; for (const targetingParam of DOCS_TARGETING_HEADINGS.filter( doc => !allow.includes(doc)