Bug 1963213 - Allow messaging on only one profile in a multiprofile selectable group r=pdahiya,jhirsch,omc-reviewers,profiles-reviewers
Differential Revision: https://phabricator.services.mozilla.com/D247459
This commit is contained in:
committed by
emcminn@mozilla.com
parent
9ea62cb6aa
commit
c75f64e314
@@ -23,7 +23,8 @@ Please note that some targeting attributes require stricter controls on the tele
|
||||
* [canCreateSelectableProfiles](#cancreateselectableprofiles)
|
||||
* [creditCardsSaved](#creditcardssaved)
|
||||
* [currentDate](#currentdate)
|
||||
* [currentTabGroups](#currentTabGroups)
|
||||
* [currentTabGroups](#currenttabgroups)
|
||||
* [currentProfileId](#currentprofileid)
|
||||
* [defaultPDFHandler](#defaultpdfhandler)
|
||||
* [devToolsOpenedCount](#devtoolsopenedcount)
|
||||
* [distributionId](#distributionid)
|
||||
@@ -1113,7 +1114,10 @@ declare const systemArch: string | null;
|
||||
|
||||
Returns the number of times a user has completed a search in the URL Bar. The number is arbitrarily capped at 100.
|
||||
|
||||
|
||||
### `profileGroupId`
|
||||
|
||||
Returns the stable profile group ID used for data reporting.
|
||||
|
||||
### `currentProfileId`
|
||||
|
||||
The integer-valued identifier of the current selectable profile, as reported by `SelectableProfileService`, converted to a string.
|
||||
|
||||
@@ -60,6 +60,20 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
||||
ToolbarBadgeHub: "resource:///modules/asrouter/ToolbarBadgeHub.sys.mjs",
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
lazy,
|
||||
"messagingProfileId",
|
||||
"messaging-system.profile.messagingProfileId",
|
||||
""
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
lazy,
|
||||
"disableSingleProfileMessaging",
|
||||
"messaging-system.profile.singleProfileMessaging.disable",
|
||||
false
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetters(lazy, {
|
||||
BrowserHandler: ["@mozilla.org/browser/clh;1", "nsIBrowserHandler"],
|
||||
});
|
||||
@@ -1658,6 +1672,29 @@ export class _ASRouter {
|
||||
return impressions;
|
||||
}
|
||||
|
||||
// Determine whether the current profile is using Selectable profiles;
|
||||
// if yes, ensure we only message a single profile in the group.
|
||||
shouldShowMessagesToProfile() {
|
||||
// If the pref for this mitigation is disabled, skip these checks.
|
||||
if (lazy.disableSingleProfileMessaging) {
|
||||
return true;
|
||||
}
|
||||
// If multiple profiles aren't enabled or aren't being used,
|
||||
// then always show messages.
|
||||
if (
|
||||
!lazy.ASRouterTargeting.Environment.canCreateSelectableProfiles ||
|
||||
!lazy.ASRouterTargeting.Environment.hasSelectableProfiles
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
// if multiple profiles exist and messagingProfileID is set,
|
||||
// then show messages when profileID matches.
|
||||
return (
|
||||
lazy.messagingProfileId ===
|
||||
lazy.ASRouterTargeting.Environment.currentProfileId
|
||||
);
|
||||
}
|
||||
|
||||
handleMessageRequest({
|
||||
messages: candidates,
|
||||
triggerId,
|
||||
@@ -1668,6 +1705,13 @@ export class _ASRouter {
|
||||
ordered = false,
|
||||
returnAll = false,
|
||||
}) {
|
||||
// If using a selectable profile, return no messages
|
||||
if (!this.shouldShowMessagesToProfile()) {
|
||||
lazy.ASRouterPreferences.console.debug(
|
||||
"Selectable profile in use; skip loading messages"
|
||||
);
|
||||
return returnAll ? [] : null;
|
||||
}
|
||||
let shouldCache;
|
||||
lazy.ASRouterPreferences.console.debug(
|
||||
"in handleMessageRequest, arguments = ",
|
||||
|
||||
@@ -2,6 +2,18 @@
|
||||
* 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/. */
|
||||
|
||||
// eslint-disable-next-line mozilla/use-static-import
|
||||
const { XPCOMUtils } = ChromeUtils.importESModule(
|
||||
"resource://gre/modules/XPCOMUtils.sys.mjs"
|
||||
);
|
||||
|
||||
const lazy = {};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
SelectableProfileService:
|
||||
"resource:///modules/profiles/SelectableProfileService.sys.mjs",
|
||||
});
|
||||
|
||||
const PROVIDER_PREF_BRANCH =
|
||||
"browser.newtabpage.activity-stream.asrouter.providers.";
|
||||
const DEVTOOLS_PREF =
|
||||
@@ -15,6 +27,9 @@ const DEVTOOLS_PREF =
|
||||
const DEBUG_PREF = "browser.newtabpage.activity-stream.asrouter.debugLogLevel";
|
||||
|
||||
const FXA_USERNAME_PREF = "services.sync.username";
|
||||
// To observe changes to Selectable Profiles
|
||||
const SELECTABLE_PROFILES_UPDATED = "sps-profiles-updated";
|
||||
const MESSAGING_PROFILE_ID_PREF = "messaging-system.profile.messagingProfileId";
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
_initialized: false,
|
||||
@@ -30,6 +45,30 @@ const USER_PREFERENCES = {
|
||||
"browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features",
|
||||
};
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
lazy,
|
||||
"messagingProfileId",
|
||||
MESSAGING_PROFILE_ID_PREF,
|
||||
""
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
lazy,
|
||||
"disableSingleProfileMessaging",
|
||||
"messaging-system.profile.singleProfileMessaging.disable",
|
||||
false,
|
||||
async prefVal => {
|
||||
if (!prefVal) {
|
||||
return;
|
||||
}
|
||||
// unset the user value of the profile ID pref
|
||||
Services.prefs.clearUserPref(MESSAGING_PROFILE_ID_PREF);
|
||||
await lazy.SelectableProfileService.flushSharedPrefToDatabase(
|
||||
MESSAGING_PROFILE_ID_PREF
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// Preferences that influence targeting attributes. When these change we need
|
||||
// to re-evaluate if the message targeting still matches
|
||||
export const TARGETING_PREFERENCES = [FXA_USERNAME_PREF];
|
||||
@@ -158,6 +197,48 @@ export class _ASRouterPreferences {
|
||||
}
|
||||
}
|
||||
|
||||
async _maybeSetMessagingProfileID() {
|
||||
// If the pref for this mitigation is disabled, skip these checks.
|
||||
if (lazy.disableSingleProfileMessaging) {
|
||||
return;
|
||||
}
|
||||
await lazy.SelectableProfileService.init();
|
||||
let currentProfileID =
|
||||
lazy.SelectableProfileService.currentProfile?.id?.toString();
|
||||
// if multiple profiles exist and messagingProfileID isn't set,
|
||||
// set it and copy it around to the rest of the profile group.
|
||||
try {
|
||||
if (!lazy.messagingProfileId && currentProfileID) {
|
||||
Services.prefs.setStringPref(
|
||||
MESSAGING_PROFILE_ID_PREF,
|
||||
currentProfileID
|
||||
);
|
||||
await lazy.SelectableProfileService.trackPref(
|
||||
MESSAGING_PROFILE_ID_PREF
|
||||
);
|
||||
}
|
||||
// if multiple profiles exist and messagingProfileID is set, make
|
||||
// sure that a profile with that ID exists.
|
||||
if (
|
||||
lazy.messagingProfileId &&
|
||||
lazy.SelectableProfileService.initialized
|
||||
) {
|
||||
let messagingProfile = await lazy.SelectableProfileService.getProfile(
|
||||
parseInt(lazy.messagingProfileId, 10)
|
||||
);
|
||||
if (!messagingProfile) {
|
||||
// the messaging profile got deleted; set the current profile instead
|
||||
Services.prefs.setStringPref(
|
||||
MESSAGING_PROFILE_ID_PREF,
|
||||
currentProfileID
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Could not set profile ID: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
get devtoolsEnabled() {
|
||||
if (!this._initialized || this._devtoolsEnabled === null) {
|
||||
this._devtoolsEnabled = Services.prefs.getBoolPref(
|
||||
@@ -213,12 +294,17 @@ export class _ASRouterPreferences {
|
||||
this._migrateProviderPrefs();
|
||||
Services.prefs.addObserver(this._providerPrefBranch, this);
|
||||
Services.prefs.addObserver(this._devtoolsPref, this);
|
||||
Services.obs.addObserver(
|
||||
this._maybeSetMessagingProfileID,
|
||||
SELECTABLE_PROFILES_UPDATED
|
||||
);
|
||||
for (const id of Object.keys(USER_PREFERENCES)) {
|
||||
Services.prefs.addObserver(USER_PREFERENCES[id], this);
|
||||
}
|
||||
for (const targetingPref of TARGETING_PREFERENCES) {
|
||||
Services.prefs.addObserver(targetingPref, this);
|
||||
}
|
||||
this._maybeSetMessagingProfileID();
|
||||
this._initialized = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1200,6 +1200,13 @@ const TargetingGetters = {
|
||||
return QueryCache.getters.profileGroupId.get();
|
||||
},
|
||||
|
||||
get currentProfileId() {
|
||||
if (!lazy.SelectableProfileService.currentProfile) {
|
||||
return "";
|
||||
}
|
||||
return lazy.SelectableProfileService.currentProfile.id.toString();
|
||||
},
|
||||
|
||||
get buildId() {
|
||||
return parseInt(AppConstants.MOZ_BUILDID, 10);
|
||||
},
|
||||
|
||||
@@ -170,6 +170,9 @@ describe("ASRouter", () => {
|
||||
scoreThreshold: 5000,
|
||||
isChinaRepack: false,
|
||||
userId: "adsf",
|
||||
currentProfileId: "1",
|
||||
canCreateSelectableProfiles: false,
|
||||
hasSelectableProfiles: false,
|
||||
},
|
||||
};
|
||||
gBrowser = {
|
||||
@@ -1129,6 +1132,38 @@ describe("ASRouter", () => {
|
||||
providers: [{ id: "cfr" }, { id: "badge" }],
|
||||
}));
|
||||
});
|
||||
it("should return no messages if shouldShowMessagesToProfile returns false", async () => {
|
||||
sandbox.stub(Router, "shouldShowMessagesToProfile").returns(false);
|
||||
await Router.setState(() => ({
|
||||
messages: [
|
||||
{ id: "foo", provider: "cfr", groups: ["cfr"] },
|
||||
{ id: "bar", provider: "cfr", groups: ["cfr"] },
|
||||
],
|
||||
}));
|
||||
const result = await Router.handleMessageRequest({
|
||||
provider: "cfr",
|
||||
});
|
||||
assert.isNull(result);
|
||||
});
|
||||
it("should return messages if shouldShowMessagesToProfile returns true", async () => {
|
||||
sandbox.stub(Router, "shouldShowMessagesToProfile").returns(true);
|
||||
await Router.setState(() => ({
|
||||
messages: [
|
||||
{ id: "foo", provider: "cfr", groups: ["cfr"] },
|
||||
{ id: "bar", provider: "cfr", groups: ["cfr"] },
|
||||
],
|
||||
}));
|
||||
const result = await Router.handleMessageRequest({
|
||||
provider: "cfr",
|
||||
});
|
||||
assert.isNotNull(result);
|
||||
assert.calledWithMatch(ASRouterTargeting.findMatchingMessage, {
|
||||
messages: [
|
||||
{ id: "foo", provider: "cfr", groups: ["cfr"] },
|
||||
{ id: "bar", provider: "cfr", groups: ["cfr"] },
|
||||
],
|
||||
});
|
||||
});
|
||||
it("should not return a blocked message", async () => {
|
||||
// Block all messages except the first
|
||||
await Router.setState(() => ({
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/* import-globals-from ../../../../../toolkit/profile/test/xpcshell/head.js */
|
||||
/* import-globals-from ../../../../../browser/components/profiles/tests/unit/head.js */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { XPCOMUtils } = ChromeUtils.importESModule(
|
||||
"resource://gre/modules/XPCOMUtils.sys.mjs"
|
||||
);
|
||||
const { sinon } = ChromeUtils.importESModule(
|
||||
"resource://testing-common/Sinon.sys.mjs"
|
||||
);
|
||||
|
||||
const lazy = {};
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
ChromeUtils.defineESModuleGetters(this, {
|
||||
JsonSchema: "resource://gre/modules/JsonSchema.sys.mjs",
|
||||
SelectableProfileService:
|
||||
"resource:///modules/profiles/SelectableProfileService.sys.mjs",
|
||||
});
|
||||
|
||||
function assertValidates(validator, obj, msg) {
|
||||
@@ -31,7 +38,7 @@ async function fetchSchema(uri) {
|
||||
|
||||
async function schemaValidatorFor(uri, { common = false } = {}) {
|
||||
const schema = await fetchSchema(uri);
|
||||
const validator = new lazy.JsonSchema.Validator(schema);
|
||||
const validator = new JsonSchema.Validator(schema);
|
||||
|
||||
if (common) {
|
||||
const commonSchema = await fetchSchema(
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const { ASRouterPreferences } = ChromeUtils.importESModule(
|
||||
"resource:///modules/asrouter/ASRouterPreferences.sys.mjs"
|
||||
);
|
||||
|
||||
add_task(async function test_maybeSetMessagingProfileID() {
|
||||
await initSelectableProfileService();
|
||||
let currentProfile = sinon
|
||||
.stub(SelectableProfileService, "currentProfile")
|
||||
.value({ id: 1 });
|
||||
sinon.stub(SelectableProfileService, "trackPref").resolves();
|
||||
|
||||
// If the Profile ID pref is unset and a profile exists, set it
|
||||
Services.prefs.setStringPref(
|
||||
"messaging-system.profile.messagingProfileId",
|
||||
""
|
||||
);
|
||||
await ASRouterPreferences._maybeSetMessagingProfileID();
|
||||
|
||||
Assert.equal(
|
||||
"1",
|
||||
Services.prefs.getStringPref("messaging-system.profile.messagingProfileId")
|
||||
);
|
||||
|
||||
// Once the ID has been set, check to see if a profile exists
|
||||
currentProfile.value({ id: 2 });
|
||||
let messagingProfile = sinon
|
||||
.stub(SelectableProfileService, "getProfile")
|
||||
.returns({ id: 1 });
|
||||
// If the profile exists, do nothing
|
||||
await ASRouterPreferences._maybeSetMessagingProfileID();
|
||||
|
||||
Assert.equal(
|
||||
"1",
|
||||
Services.prefs.getStringPref("messaging-system.profile.messagingProfileId")
|
||||
);
|
||||
// If the profile does not exist, reset the Profile ID pref
|
||||
messagingProfile.returns(null);
|
||||
|
||||
await ASRouterPreferences._maybeSetMessagingProfileID();
|
||||
|
||||
Assert.equal(
|
||||
"2",
|
||||
Services.prefs.getStringPref("messaging-system.profile.messagingProfileId")
|
||||
);
|
||||
});
|
||||
@@ -17,7 +17,6 @@ ChromeUtils.defineESModuleGetters(this, {
|
||||
NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
|
||||
JsonSchemaValidator:
|
||||
"resource://gre/modules/components-utils/JsonSchemaValidator.sys.mjs",
|
||||
sinon: "resource://testing-common/Sinon.sys.mjs",
|
||||
TelemetryController: "resource://gre/modules/TelemetryController.sys.mjs",
|
||||
UpdateUtils: "resource://gre/modules/UpdateUtils.sys.mjs",
|
||||
});
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const { ASRouter } = ChromeUtils.importESModule(
|
||||
"resource:///modules/asrouter/ASRouter.sys.mjs"
|
||||
);
|
||||
|
||||
const { ASRouterTargeting } = ChromeUtils.importESModule(
|
||||
"resource:///modules/asrouter/ASRouterTargeting.sys.mjs"
|
||||
);
|
||||
|
||||
add_task(async function test_shouldShowMessagesToProfile() {
|
||||
let sandbox = sinon.createSandbox();
|
||||
// shouldShowMessages should return true if the Selectable Profile Service is not enabled
|
||||
Services.prefs.setBoolPref("browser.profiles.enabled", false);
|
||||
|
||||
Assert.equal(ASRouter.shouldShowMessagesToProfile(), true);
|
||||
// should return true if the Selectable Profile Service is enabled but no profiles have been created
|
||||
Services.prefs.setBoolPref("browser.profiles.enabled", true);
|
||||
|
||||
Assert.equal(ASRouter.shouldShowMessagesToProfile(), true);
|
||||
// should return false if the Selectable Profile Service is enabled, and there is a profile but the profile IDs don't match
|
||||
await initSelectableProfileService();
|
||||
Services.prefs.setBoolPref("browser.profiles.created", true);
|
||||
Services.prefs.setStringPref(
|
||||
"messaging-system.profile.messagingProfileId",
|
||||
"2"
|
||||
);
|
||||
sandbox.replaceGetter(
|
||||
ASRouterTargeting.Environment,
|
||||
"currentProfileId",
|
||||
function () {
|
||||
return "1";
|
||||
}
|
||||
);
|
||||
Assert.equal(ASRouter.shouldShowMessagesToProfile(), false);
|
||||
// should return true if the Selectable Profile Service is enabled, and the profile IDs match
|
||||
Services.prefs.setStringPref(
|
||||
"messaging-system.profile.messagingProfileId",
|
||||
"1"
|
||||
);
|
||||
Assert.equal(ASRouter.shouldShowMessagesToProfile(), true);
|
||||
});
|
||||
@@ -4,9 +4,6 @@
|
||||
const { OnboardingMessageProvider } = ChromeUtils.importESModule(
|
||||
"resource:///modules/asrouter/OnboardingMessageProvider.sys.mjs"
|
||||
);
|
||||
const { sinon } = ChromeUtils.importESModule(
|
||||
"resource://testing-common/Sinon.sys.mjs"
|
||||
);
|
||||
|
||||
function getOnboardingScreenById(screens, screenId) {
|
||||
return screens.find(screen => {
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
[DEFAULT]
|
||||
head = "head.js"
|
||||
head = "../../../../../toolkit/profile/test/xpcshell/head.js ../../../../../browser/components/profiles/tests/unit/head.js head.js"
|
||||
firefox-appdir = "browser"
|
||||
|
||||
prefs = [
|
||||
"browser.profiles.enabled=true",
|
||||
"browser.profiles.created=false",
|
||||
]
|
||||
|
||||
["test_ASRouterTargeting_attribution.js"]
|
||||
run-if = ["os == 'mac'"] # osx specific tests
|
||||
|
||||
@@ -12,6 +17,10 @@ support-files = ["../schemas/*.schema.json"]
|
||||
|
||||
["test_ASRouter_getTargetingParameters.js"]
|
||||
|
||||
["test_ASRouter_shouldShowMessagesToProfile.js"]
|
||||
|
||||
["test_ASRouterPreferences_maybeSetMessagingProfileID.js"]
|
||||
|
||||
["test_CFRMessageProvider.js"]
|
||||
|
||||
["test_InflightAssetsMessageProvider.js"]
|
||||
|
||||
Reference in New Issue
Block a user