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
This commit is contained in:
Elissa Cha authored and [email protected] committed 2026-01-06 18:35:17 +00:00
1 parent c73a503493
commit 108746012f
7 files changed
+116 -16

No files matched your search

@@ -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),
@@ -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;
@@ -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,
});
@@ -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."
@@ -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;
@@ -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;
@@ -74,6 +74,7 @@ describe("ASRTargeting docs", () => {
"messageImpressions",
"screenImpressions",
"browserIsSelected",
"isAIWindow",
];
for (const targetingParam of DOCS_TARGETING_HEADINGS.filter(
doc => !allow.includes(doc)