diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index c4ae99a1ec4b..4306a7fa057d 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -180,6 +180,9 @@ pref("app.update.langpack.enabled", true); pref("app.update.background.timeoutSec", 600); // By default, check for updates when the browser is not running every 7 hours. pref("app.update.background.interval", 25200); + // By default, snapshot Firefox Messaging System targeting for use by the + // background update task every 30 minutes. + pref("app.update.background.messaging.targeting.snapshot.intervalSec", 1800); #endif #ifdef XP_MACOSX diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm index 9a445ff741b5..75d056c744bd 100644 --- a/browser/components/BrowserGlue.jsm +++ b/browser/components/BrowserGlue.jsm @@ -2796,6 +2796,14 @@ BrowserGlue.prototype = { lazy.RemoteAgent.running) && Services.prefs.getBoolPref("app.update.disabledForTesting", false); if (!disabledForTesting) { + try { + lazy.BackgroundUpdate.scheduleFirefoxMessagingSystemTargetingSnapshotting(); + } catch (e) { + Cu.reportError( + "There was an error scheduling Firefox Messaging System targeting snapshotting: " + + e + ); + } lazy.BackgroundUpdate.maybeScheduleBackgroundUpdateTask(); } }, diff --git a/browser/components/newtab/lib/ASRouterTargeting.jsm b/browser/components/newtab/lib/ASRouterTargeting.jsm index 28b81375caa4..e874918235d1 100644 --- a/browser/components/newtab/lib/ASRouterTargeting.jsm +++ b/browser/components/newtab/lib/ASRouterTargeting.jsm @@ -692,6 +692,40 @@ const TargetingGetters = { const ASRouterTargeting = { Environment: TargetingGetters, + /** + * Snapshot the current targeting environment. + * + * Asynchronous getters are handled. Getters that throw or reject + * are ignored. + * + * @param {object} target - the environment to snapshot. + * @return {object} snapshot of target with `environment` object and `version` + * integer. + */ + async getEnvironmentSnapshot(target = ASRouterTargeting.Environment) { + // One promise for each named property. Label promises with property name. + let promises = Object.keys(target).map(async name => [ + name, + await target[name], + ]); + + // Ignore properties that are rejected. + let results = await Promise.allSettled(promises); + + let environment = {}; + for (let result of results) { + if (result.status === "fulfilled") { + let [name, value] = result.value; + environment[name] = value; + } + } + + // Should we need to migrate in the future. + const snapshot = { environment, version: 1 }; + + return snapshot; + }, + isTriggerMatch(trigger = {}, candidateMessageTrigger = {}) { if (trigger.id !== candidateMessageTrigger.id) { return false; diff --git a/browser/components/newtab/test/xpcshell/test_ASRouterTargeting_snapshot.js b/browser/components/newtab/test/xpcshell/test_ASRouterTargeting_snapshot.js new file mode 100644 index 000000000000..d905f0bb3bf9 --- /dev/null +++ b/browser/components/newtab/test/xpcshell/test_ASRouterTargeting_snapshot.js @@ -0,0 +1,24 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +"use strict"; + +const { ASRouterTargeting } = ChromeUtils.import( + "resource://activity-stream/lib/ASRouterTargeting.jsm" +); + +add_task(async function should_ignore_rejections() { + let target = { + get foo() { + return new Promise(resolve => resolve(1)); + }, + + get bar() { + return new Promise((resolve, reject) => reject(new Error("unspecified"))); + }, + }; + + let snapshot = await ASRouterTargeting.getEnvironmentSnapshot(target); + deepEqual(snapshot, { environment: { foo: 1 }, version: 1 }); +}); diff --git a/browser/components/newtab/test/xpcshell/xpcshell.ini b/browser/components/newtab/test/xpcshell/xpcshell.ini index 5ca99b02d745..dd8ba7555158 100644 --- a/browser/components/newtab/test/xpcshell/xpcshell.ini +++ b/browser/components/newtab/test/xpcshell/xpcshell.ini @@ -18,6 +18,7 @@ skip-if = [test_AboutWelcomeAttribution.js] [test_ASRouterTargeting_attribution.js] skip-if = toolkit != "cocoa" # osx specific tests +[test_ASRouterTargeting_snapshot.js] [test_AboutWelcomeTelemetry.js] [test_OnboardingMessageProvider.js] [test_PanelTestProvider.js] diff --git a/toolkit/mozapps/update/BackgroundUpdate.jsm b/toolkit/mozapps/update/BackgroundUpdate.jsm index 2a0e56b787b7..d6b01fb09ac1 100644 --- a/toolkit/mozapps/update/BackgroundUpdate.jsm +++ b/toolkit/mozapps/update/BackgroundUpdate.jsm @@ -22,8 +22,10 @@ const lazy = {}; XPCOMUtils.defineLazyModuleGetters(lazy, { AddonManager: "resource://gre/modules/AddonManager.jsm", + ASRouterTargeting: "resource://activity-stream/lib/ASRouterTargeting.jsm", BackgroundTasksUtils: "resource://gre/modules/BackgroundTasksUtils.jsm", FileUtils: "resource://gre/modules/FileUtils.jsm", + JSONFile: "resource://gre/modules/JSONFile.jsm", TaskScheduler: "resource://gre/modules/TaskScheduler.jsm", UpdateUtils: "resource://gre/modules/UpdateUtils.jsm", }); @@ -614,6 +616,55 @@ var BackgroundUpdate = { ); Glean.update.canUsuallyUseBits.set(lazy.UpdateService.canUsuallyUseBits); }, + + /** + * Schedule periodic snapshotting of the Firefox Messaging System + * targeting configuration. + * + * The background update task will target messages based on the + * latest snapshot of the default profile's targeting configuration. + */ + async scheduleFirefoxMessagingSystemTargetingSnapshotting() { + let SLUG = "scheduleFirefoxMessagingSystemTargetingSnapshotting"; + let path = PathUtils.join(PathUtils.profileDir, "targeting.snapshot.json"); + + let snapshot = new lazy.JSONFile({ + beforeSave: async () => { + lazy.log.debug( + `${SLUG}: preparing to write Firefox Messaging System targeting information to ${path}` + ); + snapshot.data = await lazy.ASRouterTargeting.getEnvironmentSnapshot(); + }, + path, + }); + + // We don't `load`, since we don't care about reading existing (now stale) + // data. + snapshot.data = lazy.ASRouterTargeting.getEnvironmentSnapshot(); + + // Persist. + snapshot.saveSoon(); + + // Continue persisting periodically. `JSONFile.jsm` will also persist one + // last time before shutdown. + this._targetingSnapshottingTimer = Cc[ + "@mozilla.org/timer;1" + ].createInstance(Ci.nsITimer); + + // Hold a reference to prevent GC. + this._targetingSnapshottingTimer.initWithCallback( + () => { + snapshot.saveSoon(); + }, + // By default, snapshot Firefox Messaging System targeting for use by the + // background update task every 30 minutes. + Services.prefs.getIntPref( + "app.update.background.messaging.targeting.snapshot.intervalSec", + 1800 + ) * 1000, + Ci.nsITimer.TYPE_REPEATING_SLACK_LOW_PRIORITY + ); + }, }; BackgroundUpdate.REASON = { diff --git a/toolkit/mozapps/update/docs/BackgroundUpdates.rst b/toolkit/mozapps/update/docs/BackgroundUpdates.rst index a3223aa3bae3..5591d48282ea 100644 --- a/toolkit/mozapps/update/docs/BackgroundUpdates.rst +++ b/toolkit/mozapps/update/docs/BackgroundUpdates.rst @@ -131,7 +131,7 @@ Scheduling background tasks We use OS-level scheduling mechanisms to schedule the command ``firefox --backgroundtask backgroundupdate`` to run on a particular cadence. This cadence -is controlled by the ``app.background.update.interval`` preference, which +is controlled by the ``app.update.background.interval`` preference, which defaults to 7 hours. On Windows, we use the `Task Scheduler