commit fb0d97f9ef6adfcb656439d5594f09d340144c63
parent a4680f6b5f497e6f59014c5294cfdfa11d9ae5eb
Author: Duncan McIntosh <dmcintosh@mozilla.com>
Date: Wed, 1 Oct 2025 13:51:38 +0000
Bug 1989093 - Add ASRoutingTargeting attribute to check whether the current tab is installed as a web app. r=nrishel,emcminn,omc-reviewers
Differential Revision: https://phabricator.services.mozilla.com/D265901
Diffstat:
3 files changed, 82 insertions(+), 0 deletions(-)
diff --git a/browser/components/asrouter/docs/targeting-attributes.md b/browser/components/asrouter/docs/targeting-attributes.md
@@ -25,6 +25,7 @@ Please note that some targeting attributes require stricter controls on the tele
* [creditCardsSaved](#creditcardssaved)
* [currentDate](#currentdate)
* [currentTabGroups](#currenttabgroups)
+* [currentTabInstalledAsWebApp](#currenttabinstalledaswebapp)
* [currentProfileId](#currentprofileid)
* [defaultPDFHandler](#defaultpdfhandler)
* [devToolsOpenedCount](#devtoolsopenedcount)
@@ -666,6 +667,17 @@ Pref used by system administrators to disallow add-ons from installed altogether
```ts
declare const xpinstallEnabled: boolean;
```
+
+### `currentTabInstalledAsWebApp`
+
+Returns whether the current tab has a matching Web App (Taskbar Tab) installed.
+
+#### Definition
+
+```ts
+declare const currentTabInstalledAsWebApp: Promise<boolean>;
+```
+
### `currentTabGroups`
Returns the number of currently open tab groups.
diff --git a/browser/components/asrouter/modules/ASRouterTargeting.sys.mjs b/browser/components/asrouter/modules/ASRouterTargeting.sys.mjs
@@ -71,6 +71,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
"resource:///modules/profiles/SelectableProfileService.sys.mjs",
SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs",
TargetingContext: "resource://messaging-system/targeting/Targeting.sys.mjs",
+ TaskbarTabs: "resource:///modules/taskbartabs/TaskbarTabs.sys.mjs",
TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.sys.mjs",
TelemetrySession: "resource://gre/modules/TelemetrySession.sys.mjs",
WindowsLaunchOnLogin: "resource://gre/modules/WindowsLaunchOnLogin.sys.mjs",
@@ -829,6 +830,27 @@ const TargetingGetters = {
let totalTabGroups = win.gBrowser.getAllTabGroups().length;
return totalTabGroups;
},
+ get currentTabInstalledAsWebApp() {
+ let win = lazy.BrowserWindowTracker.getTopWindow({
+ allowFromInactiveWorkspace: true,
+ });
+ if (!win) {
+ // There is no active tab, so it isn't a web app.
+ return false;
+ }
+
+ // Note: this is a promise!
+ return (
+ lazy.TaskbarTabs.findTaskbarTab(
+ win.gBrowser.selectedBrowser.currentURI,
+ win.gBrowser.selectedTab.userContextId
+ )
+ .then(aTaskbarTab => aTaskbarTab !== null)
+ // If this is not an nsIURL (e.g. if it's about:blank), then this will
+ // throw; in that case there isn't a matching web app.
+ .catch(() => false)
+ );
+ },
get hasPinnedTabs() {
for (let win of Services.wm.getEnumerator("navigator:browser")) {
if (win.closed || !win.ownerGlobal.gBrowser) {
diff --git a/browser/components/asrouter/tests/browser/browser_asrouter_targeting.js b/browser/components/asrouter/tests/browser/browser_asrouter_targeting.js
@@ -25,8 +25,11 @@ ChromeUtils.defineESModuleGetters(this, {
QueryCache: "resource:///modules/asrouter/ASRouterTargeting.sys.mjs",
Region: "resource://gre/modules/Region.sys.mjs",
ShellService: "moz-src:///browser/components/shell/ShellService.sys.mjs",
+ sinon: "resource://testing-common/Sinon.sys.mjs",
Spotlight: "resource:///modules/asrouter/Spotlight.sys.mjs",
TargetingContext: "resource://messaging-system/targeting/Targeting.sys.mjs",
+ TaskbarTabs: "resource:///modules/taskbartabs/TaskbarTabs.sys.mjs",
+ TaskbarTabsPin: "resource:///modules/taskbartabs/TaskbarTabsPin.sys.mjs",
TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.sys.mjs",
TelemetrySession: "resource://gre/modules/TelemetrySession.sys.mjs",
});
@@ -966,6 +969,51 @@ add_task(async function check_xpinstall_enabled() {
is(await ASRouterTargeting.Environment.xpinstallEnabled, true);
});
+add_task(async function check_current_tab_installed_as_web_app() {
+ // By default, Taskbar Tabs will try and pin this to the taskbar, but we
+ // don't want to do that in this test.
+ const sandbox = sinon.createSandbox();
+ sandbox.stub(TaskbarTabsPin, "pinTaskbarTab");
+ sandbox.stub(TaskbarTabsPin, "unpinTaskbarTab");
+
+ const kUri = "https://example.com";
+
+ await BrowserTestUtils.withNewTab(kUri, async () => {
+ is(
+ await ASRouterTargeting.Environment.currentTabInstalledAsWebApp,
+ false,
+ "no taskbar tab exists yet"
+ );
+
+ const tt = await TaskbarTabs.findOrCreateTaskbarTab(
+ Services.io.newURI(kUri),
+ 0
+ );
+ is(
+ await ASRouterTargeting.Environment.currentTabInstalledAsWebApp,
+ true,
+ "should be true when a Taskbar Tab exists for this tab"
+ );
+
+ await BrowserTestUtils.withNewTab("mochi.test:8888", async () => {
+ is(
+ await ASRouterTargeting.Environment.currentTabInstalledAsWebApp,
+ false,
+ "should be false even if a Taskbar Tab exists for a different tab"
+ );
+ });
+
+ await TaskbarTabs.removeTaskbarTab(tt.id);
+ is(
+ await ASRouterTargeting.Environment.currentTabInstalledAsWebApp,
+ false,
+ "should be false after removing the Taskbar Tab"
+ );
+ });
+
+ sandbox.restore();
+});
+
add_task(async function check_pinned_tabs() {
await BrowserTestUtils.withNewTab(
{ gBrowser, url: "about:blank" },