commit 497a1a82431ad914d13ed0acb6ba0301f1a58959
parent bb40796bb2ff8be97e2adcdad78d9b9ea1d3ea18
Author: Luca Greco <lgreco@mozilla.com>
Date: Fri, 28 Nov 2025 15:36:07 +0000
Bug 2000866 - Define a custom `addons` Glean ping to submit addons list on startup, on updates and on a daily schedule. r=chutten,willdurand,toolkit-telemetry-reviewers
Differential Revision: https://phabricator.services.mozilla.com/D273032
Diffstat:
9 files changed, 307 insertions(+), 1 deletion(-)
diff --git a/toolkit/components/glean/metrics_index.py b/toolkit/components/glean/metrics_index.py
@@ -214,6 +214,7 @@ gecko_pings = [
"toolkit/components/gecko-trace/pings.yaml",
"toolkit/components/glean/pings.yaml",
"toolkit/components/resistfingerprinting/pings.yaml",
+ "toolkit/mozapps/extensions/pings.yaml",
]
# Pings that are sent by Firefox Desktop.
diff --git a/toolkit/mozapps/extensions/AddonManager.sys.mjs b/toolkit/mozapps/extensions/AddonManager.sys.mjs
@@ -40,6 +40,12 @@ const PREF_REMOTESETTINGS_DISABLED = "extensions.remoteSettings.disabled";
const PREF_USE_REMOTE = "extensions.webextensions.remote";
const PREF_AMTELEMETRY_ADDONS_BUILDER =
"extensions.telemetry.EnvironmentAddonBuilder";
+const PREF_GLEAN_PING_ADDONS_UPDATED_DELAY_MS =
+ "extensions.gleanPingAddons.updated.delay";
+const PREF_GLEAN_PING_ADDONS_UPDATED_IDLE_TIMEOUT_MS =
+ "extensions.gleanPingAddons.updated.idleTimeout";
+const PREF_GLEAN_PING_ADDONS_UPDATED_TESTING =
+ "extensions.gleanPingAddons.updated.testing";
const PREF_MIN_WEBEXT_PLATFORM_VERSION =
"extensions.webExtensionsMinPlatformVersion";
@@ -86,6 +92,7 @@ const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
AbuseReporter: "resource://gre/modules/AbuseReporter.sys.mjs",
AddonRepository: "resource://gre/modules/addons/AddonRepository.sys.mjs",
+ DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
Extension: "resource://gre/modules/Extension.sys.mjs",
ObjectUtils: "resource://gre/modules/ObjectUtils.sys.mjs",
RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
@@ -113,6 +120,31 @@ XPCOMUtils.defineLazyPreferenceGetter(
false
);
+// By default coalesce `addons` updated ping submission happening in a 5min interval.
+XPCOMUtils.defineLazyPreferenceGetter(
+ lazy,
+ "GLEAN_PING_ADDONS_UPDATED_DELAY_MS",
+ PREF_GLEAN_PING_ADDONS_UPDATED_DELAY_MS,
+ 1000 * 60 * 5
+);
+
+// By default wait for 1min for an idle slot after the delay time have already elapsed.
+XPCOMUtils.defineLazyPreferenceGetter(
+ lazy,
+ "GLEAN_PING_ADDONS_UPDATED_IDLE_TIMEOUT_MS",
+ PREF_GLEAN_PING_ADDONS_UPDATED_IDLE_TIMEOUT_MS,
+ 1000 * 60
+);
+
+// Whether EnvironmentAddonBuilder._scheduleGleanPingAddonsUpdated should
+// send the `test-glean-ping-addons-updated` observer service notification.
+XPCOMUtils.defineLazyPreferenceGetter(
+ lazy,
+ "GLEAN_PING_ADDONS_UPDATED_TESTING",
+ PREF_GLEAN_PING_ADDONS_UPDATED_TESTING,
+ false
+);
+
// Initialize the WebExtension process script service as early as possible,
// since it needs to be able to track things like new frameLoader globals that
// are created before other framework code has been initialized.
@@ -4841,6 +4873,10 @@ export class EnvironmentAddonBuilder {
// has had a chance to load. Do we have full data yet?
this._addonsAreFull = false;
+ // a DeferredTask coalescing multiple addons list updates into a single
+ // submission of the Glean Ping `addons` with the reason `updated`.
+ this._submitGleanPingAddonsUpdatedTask = null;
+
this._log = console.createInstance({
prefix: "EnvironmentAddonBuilder",
maxLogLevel: Services.prefs.getBoolPref(PREF_LOGGING_ENABLED, false)
@@ -4914,6 +4950,10 @@ export class EnvironmentAddonBuilder {
this._pendingTask = null;
this._shutdownState = "_pendingTask init complete. No longer blocking.";
this._log.debug("init - completed");
+ // Submit the addons Glean ping with reason "startup" right after
+ // the EnvironmentAddonBuilder has been initialized as part of the
+ // AddonManager and application startup.
+ GleanPings.addons.submit("startup");
}
})();
@@ -4924,6 +4964,7 @@ export class EnvironmentAddonBuilder {
if (this._shutdownCompleted) {
return;
}
+ this._finalizeGleanPingAddonsUpdatedTask();
await this._shutdownBlocker();
}
@@ -5035,6 +5076,7 @@ export class EnvironmentAddonBuilder {
this._shutdownState = "No longer blocking, _updateAddons resolved";
if (result.changed) {
this._onEnvironmentChange(changeReason, result.oldEnvironment);
+ this._scheduleGleanPingAddonsUpdated();
}
},
err => {
@@ -5045,6 +5087,48 @@ export class EnvironmentAddonBuilder {
);
}
+ _scheduleGleanPingAddonsUpdated() {
+ if (!this._submitGleanPingAddonsUpdatedTask) {
+ this._submitGleanPingAddonsUpdatedTask = new lazy.DeferredTask(
+ () => {
+ if (lazy.GLEAN_PING_ADDONS_UPDATED_TESTING) {
+ Services.obs.notifyObservers(
+ null,
+ "test-glean-ping-addons-updated"
+ );
+ }
+ // Submit the addons Glean ping with reason "updated" when
+ // the list of addons/theme/GMPlugins has changed.
+ GleanPings.addons.submit("updated");
+ },
+ lazy.GLEAN_PING_ADDONS_UPDATED_DELAY_MS,
+ lazy.GLEAN_PING_ADDONS_UPDATED_IDLE_TIMEOUT_MS
+ );
+ AddonManager.beforeShutdown.addBlocker(
+ "EnvironmentAddonBuilder::GleanPingAddonsUpdated",
+ () => this._finalizeGleanPingAddonsUpdatedTask(),
+ { fetchState: () => this._shutdownState }
+ );
+ }
+ this._submitGleanPingAddonsUpdatedTask.arm();
+ }
+
+ _finalizeGleanPingAddonsUpdatedTask() {
+ try {
+ this._submitGleanPingAddonsUpdatedTask?.disarm();
+ this._submitGleanPingAddonsUpdatedTask?.finalize();
+ } catch (err) {
+ this._log.error(
+ "Unexpected failure on disarming and finalizing _submitGleanPingAddonsUpdatedTask",
+ err
+ );
+ } finally {
+ if (this._submitGleanPingAddonsUpdatedTask?.isFinalized) {
+ this._submitGleanPingAddonsUpdatedTask = null;
+ }
+ }
+ }
+
async _shutdownBlocker() {
if (this._loaded) {
AddonManager.removeAddonListener(this);
diff --git a/toolkit/mozapps/extensions/amManager.sys.mjs b/toolkit/mozapps/extensions/amManager.sys.mjs
@@ -353,3 +353,17 @@ BlocklistService.prototype = {
"nsITimerCallback",
]),
};
+
+// This service is configured as a daily timer from extensions.manifest and
+// it is responsible for sending the `addons` Glean Ping on a daily schedule.
+export class amGleanDaily {
+ static classID = Components.ID("{867d65a0-9784-4496-9a2e-f168f960f7c7}");
+ static contractID = "@mozilla.org/addons/glean-daily-ping;1";
+ QueryInterface = ChromeUtils.generateQI([Ci.nsITimerCallback]);
+
+ notify() {
+ // Submit the addons Glean ping with reason "daily" when
+ // the timer is notify the amGleanDaily nsITimerCallback.
+ GleanPings.addons.submit("daily");
+ }
+}
diff --git a/toolkit/mozapps/extensions/components.conf b/toolkit/mozapps/extensions/components.conf
@@ -21,6 +21,13 @@ Classes = [
'constructor': 'amManager',
},
{
+ 'cid': '{867d65a0-9784-4496-9a2e-f168f960f7c7}',
+ 'contract_ids': ['@mozilla.org/addons/glean-daily-ping;1'],
+ 'esModule': 'resource://gre/modules/amManager.sys.mjs',
+ 'constructor': 'amGleanDaily',
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+ {
'cid': '{8866d8e3-4ea5-48b7-a891-13ba0ac15235}',
'contract_ids': ['@mozilla.org/addon-web-api/manager;1'],
'esModule': 'resource://gre/modules/amWebAPI.sys.mjs',
diff --git a/toolkit/mozapps/extensions/extensions.manifest b/toolkit/mozapps/extensions/extensions.manifest
@@ -1,3 +1,5 @@
+# Daily `addons` Glean Ping scheduler.
+category update-timer amGleanDaily @mozilla.org/addons/glean-daily-ping;1,getService,glean-addons-daily,extensions.gleanPingAddons.daily.interval,86400
#ifndef MOZ_WIDGET_ANDROID
category update-timer addonManager @mozilla.org/addons/integration;1,getService,addon-background-update-timer,extensions.update.interval,86400
#endif
diff --git a/toolkit/mozapps/extensions/metrics.yaml b/toolkit/mozapps/extensions/metrics.yaml
@@ -30,9 +30,11 @@ addons:
bugs:
- https://bugzilla.mozilla.org/1950416
- https://bugzilla.mozilla.org/1991924
+ - https://bugzilla.mozilla.org/2000866
data_reviews:
- https://bugzilla.mozilla.org/1950416
- https://bugzilla.mozilla.org/1991924
+ - https://bugzilla.mozilla.org/2000866
data_sensitivity:
- technical
- interaction
@@ -89,6 +91,7 @@ addons:
send_in_pings:
- metrics
- heartbeat
+ - addons
theme:
type: object
@@ -108,9 +111,11 @@ addons:
bugs:
- https://bugzilla.mozilla.org/1950416
- https://bugzilla.mozilla.org/1991924
+ - https://bugzilla.mozilla.org/2000866
data_reviews:
- https://bugzilla.mozilla.org/1950416
- https://bugzilla.mozilla.org/1991924
+ - https://bugzilla.mozilla.org/2000866
data_sensitivity:
- technical
- interaction
@@ -148,6 +153,9 @@ addons:
type: number
signedTypes:
type: string
+ send_in_pings:
+ - metrics
+ - addons
active_g_m_plugins:
type: object
@@ -167,9 +175,11 @@ addons:
bugs:
- https://bugzilla.mozilla.org/1950416
- https://bugzilla.mozilla.org/1991924
+ - https://bugzilla.mozilla.org/2000866
data_reviews:
- https://bugzilla.mozilla.org/1950416
- https://bugzilla.mozilla.org/1991924
+ - https://bugzilla.mozilla.org/2000866
data_sensitivity:
- technical
- interaction
@@ -191,7 +201,9 @@ addons:
type: boolean
applyBackgroundUpdates:
type: number
-
+ send_in_pings:
+ - metrics
+ - addons
addons_manager:
install: &install_update_event
diff --git a/toolkit/mozapps/extensions/pings.yaml b/toolkit/mozapps/extensions/pings.yaml
@@ -0,0 +1,24 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+---
+$schema: moz://mozilla.org/schemas/glean/pings/2-0-0
+
+addons:
+ description: |
+ Instrumentation related to the list of add-ons managed by the AddonManager.
+ include_client_id: true
+ reasons:
+ startup: |
+ AddonManager is fully started and initialized as part of the application startup.
+ daily: |
+ Recurring daily ping.
+ updated: |
+ The list of active addons/theme/GMPlugins has been updated.
+ bugs:
+ - https://bugzilla.mozilla.org/2000866
+ data_reviews:
+ - https://bugzilla.mozilla.org/2000866
+ notification_emails:
+ - addons-dev-internal@mozilla.com
diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_addons_glean_ping.js b/toolkit/mozapps/extensions/test/xpcshell/test_addons_glean_ping.js
@@ -0,0 +1,160 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { AMTelemetry } = ChromeUtils.importESModule(
+ "resource://gre/modules/AddonManager.sys.mjs"
+);
+const { ExtensionUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/ExtensionUtils.sys.mjs"
+);
+
+AddonTestUtils.init(this);
+AddonTestUtils.overrideCertDB();
+createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1");
+
+add_setup(() => {
+ do_get_profile();
+ Services.fog.initializeFOG();
+});
+
+add_task(
+ {
+ pref_set: [
+ // Enable AddonManager managed EnvironmentAddonBuilder on any build
+ // to make it easier to test the `addons` Glean Ping scheduling
+ // across Android and Desktop builds (whereas by default the
+ // EnvironmentAddonBuilder is still managed by the legacy
+ // TelemetryEnvironment on Firefox Desktop builds).
+ ["extensions.telemetry.EnvironmentAddonBuilder", true],
+ // Reduce the delay and idle timeout for the `addons` Glean Ping
+ // scheduled on add-ons list updates (delay to 2s from the 5m default,
+ // idle timeout disabled completely).
+ ["extensions.gleanPingAddons.updated.delay", 1000 * 2],
+ ["extensions.gleanPingAddons.updated.idleTimeout", -1],
+ // Enable the test-glean-ping-addons-updated observer notification
+ // sent right before GleanPings.addons.submit("updated") is actually
+ // called.
+ ["extensions.gleanPingAddons.updated.testing", true],
+ ],
+ },
+ async function test_addons_glean_ping() {
+ const addonDetailsToString = addon => `${addon.id}:${addon.version}`;
+
+ info("Verify GleanPing addons is submitted after AOM startup");
+ await GleanPings.addons.testSubmission(
+ reason => {
+ Assert.equal(
+ reason,
+ "startup",
+ "Expect addons GleanPing submittion to have reason 'startup'"
+ );
+ Assert.deepEqual(
+ Glean.addons.activeAddons.testGetValue()?.map(addonDetailsToString),
+ [],
+ "Expect activeAddons Glean metric to be set to an empty object"
+ );
+ },
+ async () => {
+ // Trigger the XPI Database to be fully loaded and expect the
+ // Glean Ping `addons` to be submitted with reason `startup`.
+ await AddonTestUtils.promiseStartupManager();
+ Services.obs.notifyObservers(null, "test-load-xpi-database");
+ await AMTelemetry.telemetryAddonBuilder._pendingTask;
+ }
+ );
+
+ info(
+ "Verify GleanPing addons is submitted after new test add-on is installed"
+ );
+ let extension = null;
+ let promiseStartupCompleted = null;
+ await GleanPings.addons.testSubmission(
+ reason => {
+ Assert.equal(
+ reason,
+ "updated",
+ "Expect addons GleanPing submittion to have reason 'updated'"
+ );
+ Assert.deepEqual(
+ Glean.addons.activeAddons.testGetValue()?.map(addonDetailsToString),
+ [addonDetailsToString(extension)],
+ "Expect activeAddons Glean metric to include the test extension"
+ );
+ },
+ async () => {
+ // Install a new extension and expect the Glean Ping `addons` to
+ // be submitted with reason `update`.
+ extension = ExtensionTestUtils.loadExtension({
+ useAddonManager: "permanent",
+ manifest: {
+ name: "test-extension",
+ },
+ });
+ let promiseSubmitGleanPingAddonsUpdatedCalled =
+ ExtensionUtils.promiseObserved("test-glean-ping-addons-updated");
+ promiseStartupCompleted = extension.startup();
+ await promiseSubmitGleanPingAddonsUpdatedCalled;
+ }
+ );
+
+ // Prevent intermittent failures that may be hit if we try to unload the test
+ // extension while it is still being started).
+ await promiseStartupCompleted;
+
+ // Mock the addons glean-daily-ping timer notification and expect
+ // the Glean Ping `addons` to be submitted with reason `daily`.
+ info("Verify the GleanPing is submitted on the daily timer");
+ await GleanPings.addons.testSubmission(
+ reason => {
+ Assert.equal(
+ reason,
+ "daily",
+ "Expect addons GleanPing submittion to have reason 'daily'"
+ );
+ Assert.deepEqual(
+ Glean.addons.activeAddons.testGetValue()?.map(addonDetailsToString),
+ [addonDetailsToString(extension)],
+ "Expect activeAddons Glean metric to include the test extension"
+ );
+ },
+ async () => {
+ const fakeTimer = () =>
+ Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ Cc["@mozilla.org/addons/glean-daily-ping;1"]
+ .getService(Ci.nsITimerCallback)
+ .notify(fakeTimer());
+ }
+ );
+
+ info(
+ "Verify GleanPing addons is submitted after the test add-on is uninstalled"
+ );
+ let promiseShutdownCompleted = null;
+ await GleanPings.addons.testSubmission(
+ reason => {
+ Assert.equal(
+ reason,
+ "updated",
+ "Expect addons GleanPing submittion to have reason 'updated'"
+ );
+ Assert.deepEqual(
+ Glean.addons.activeAddons.testGetValue()?.map(addonDetailsToString),
+ [],
+ "Expect activeAddons Glean metric to be set to an empty object"
+ );
+ },
+ async () => {
+ // Uninstall the test extension and expect the Glean Ping `addons` to
+ // be submitted with reason `update`.
+ let promiseSubmitGleanPingAddonsUpdatedCalled =
+ ExtensionUtils.promiseObserved("test-glean-ping-addons-updated");
+ promiseShutdownCompleted = extension.unload();
+ await promiseSubmitGleanPingAddonsUpdatedCalled;
+ }
+ );
+
+ // Prevent intermittent failure that may be hit if the test task is exiting
+ // before the test extension unload was completed.
+ await promiseShutdownCompleted;
+ }
+);
diff --git a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.toml b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.toml
@@ -77,6 +77,8 @@ skip-if = [
["test_addon_manager_telemetry_events.js"]
+["test_addons_glean_ping.js"]
+
["test_amo_stats_telemetry.js"]
skip-if = [
"os == 'android'", # bug 1866520 - missing Glean version of stats telemetry