tor-browser

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

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:
Mtoolkit/components/glean/metrics_index.py | 1+
Mtoolkit/mozapps/extensions/AddonManager.sys.mjs | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtoolkit/mozapps/extensions/amManager.sys.mjs | 14++++++++++++++
Mtoolkit/mozapps/extensions/components.conf | 7+++++++
Mtoolkit/mozapps/extensions/extensions.manifest | 2++
Mtoolkit/mozapps/extensions/metrics.yaml | 14+++++++++++++-
Atoolkit/mozapps/extensions/pings.yaml | 24++++++++++++++++++++++++
Atoolkit/mozapps/extensions/test/xpcshell/test_addons_glean_ping.js | 160+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtoolkit/mozapps/extensions/test/xpcshell/xpcshell.toml | 2++
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