Bug 1746598 - Route MS messages to PB newtab r=dmose,extension-reviewers,rpl

Differential Revision: https://phabricator.services.mozilla.com/D134157
This commit is contained in:
Andrei Oprea 2022-01-25 13:02:30 +00:00
Родитель 2d4b0eb9d6
Коммит c1807eee8c
18 изменённых файлов: 321 добавлений и 39 удалений

Просмотреть файл

@ -46,10 +46,8 @@ class AboutPrivateBrowsingChild extends RemotePageChild {
}
}
PrivateBrowsingFeatureConfig(defaultValues) {
const config = NimbusFeatures.privatebrowsing.getAllVariables({
defaultValues,
});
PrivateBrowsingFeatureConfig() {
const config = NimbusFeatures.privatebrowsing.getAllVariables() || {};
NimbusFeatures.privatebrowsing.recordExposureEvent();

Просмотреть файл

@ -1461,7 +1461,7 @@ pref("browser.newtabpage.activity-stream.asrouter.providers.message-groups", "{\
// this page over http opens us up to a man-in-the-middle attack that we'd rather not face. If you are a downstream
// repackager of this code using an alternate snippet url, please keep your users safe
pref("browser.newtabpage.activity-stream.asrouter.providers.snippets", "{\"id\":\"snippets\",\"enabled\":false,\"type\":\"remote\",\"url\":\"https://snippets.cdn.mozilla.net/%STARTPAGE_VERSION%/%NAME%/%VERSION%/%APPBUILDID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/\",\"updateCycleInMs\":14400000}");
pref("browser.newtabpage.activity-stream.asrouter.providers.messaging-experiments", "{\"id\":\"messaging-experiments\",\"enabled\":true,\"type\":\"remote-experiments\",\"messageGroups\":[\"cfr\",\"aboutwelcome\",\"infobar\",\"spotlight\",\"moments-page\"],\"updateCycleInMs\":3600000}");
pref("browser.newtabpage.activity-stream.asrouter.providers.messaging-experiments", "{\"id\":\"messaging-experiments\",\"enabled\":true,\"type\":\"remote-experiments\",\"messageGroups\":[\"cfr\",\"aboutwelcome\",\"infobar\",\"spotlight\",\"moments-page\",\"pbNewtab\"],\"updateCycleInMs\":3600000}");
// ASRouter user prefs
pref("browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons", true);

Просмотреть файл

@ -718,7 +718,12 @@ let JSWINDOWACTORS = {
DOMDocElementInserted: {},
},
},
matches: ["about:home*", "about:newtab*", "about:welcome*"],
matches: [
"about:home*",
"about:newtab*",
"about:welcome*",
"about:privatebrowsing",
],
remoteTypes: ["privilegedabout"],
},

Просмотреть файл

@ -118,7 +118,7 @@ add_task(async function test_no_show_hide_for_private_window() {
});
// Open and close a menu on the private window.
let menu = await openContextMenu("body", privateWindow);
let menu = await openContextMenu("body div", privateWindow);
// We should not see the "not_allowed" extension here.
ok(
!privateWindow.document.getElementById(extMenuId),

Просмотреть файл

@ -10,6 +10,8 @@ const MESSAGE_TYPE_LIST = [
"IMPRESSION",
"TRIGGER",
"NEWTAB_MESSAGE_REQUEST",
// PB is Private Browsing
"PBNEWTAB_MESSAGE_REQUEST",
"DOORHANGER_TELEMETRY",
"TOOLBAR_BADGE_TELEMETRY",
"TOOLBAR_PANEL_TELEMETRY",

Просмотреть файл

@ -0,0 +1,78 @@
{
"title": "PBNewtabPromoMessage",
"description": "Message shown on the private browsing newtab page.",
"version": "1.0.0",
"type": "object",
"properties": {
"infoEnabled": {
"type": "boolean",
"description": "Should we show the info section."
},
"infoIcon": {
"type": "string",
"description": "Icon shown in the left side of the info section. Default is the private browsing icon."
},
"infoTitle": {
"type": "string",
"description": "Is the title in the info section enabled."
},
"infoTitleEnabled": {
"type": "boolean",
"description": "Is the title in the info section enabled."
},
"infoBody": {
"type": "string",
"description": "Text content in the info section."
},
"infoLinkText": {
"type": "string",
"description": "Text for the link in the info section."
},
"infoLinkUrl": {
"type": "string",
"description": "URL for the info section link."
},
"promoEnabled": {
"type": "boolean",
"description": "Should we show the promo section."
},
"promoSectionStyle": {
"type": "string",
"description": "Sets the position of the promo section. Possible values are: top, below-search, bottom. Default bottom.",
"enum": ["top", "below-search", "bottom"]
},
"promoTitle": {
"type": "string",
"description": "The text content of the promo section."
},
"promoTitleEnabled": {
"type": "boolean",
"description": "Should we show text content in the promo section."
},
"promoLinkText": {
"type": "string",
"description": "The text of the link in the promo box."
},
"promoHeader": {
"type": "string",
"description": "The title of the promo section."
},
"promoLinkUrl": {
"type": "string",
"description": "URL for link in the promo box."
},
"promoLinkType": {
"type": "string",
"description": "Type of promo link type. Possible values: link, button. Default is link.",
"enum": ["link", "button"]
},
"promoImageLarge": {
"type": "string",
"description": "URL for image used on the left side of the promo box, larger, showcases some feature. Default off."
},
"promoImageSmall": {
"type": "string",
"description": "URL for image used on the right side of the promo box, smaller, usually a logo. Default off."
}
}
}

Просмотреть файл

@ -2363,7 +2363,8 @@ __webpack_require__.r(__webpack_exports__);
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const MESSAGE_TYPE_LIST = ["BLOCK_MESSAGE_BY_ID", "USER_ACTION", "IMPRESSION", "TRIGGER", "NEWTAB_MESSAGE_REQUEST", "DOORHANGER_TELEMETRY", "TOOLBAR_BADGE_TELEMETRY", "TOOLBAR_PANEL_TELEMETRY", "MOMENTS_PAGE_TELEMETRY", "INFOBAR_TELEMETRY", "SPOTLIGHT_TELEMETRY", "AS_ROUTER_TELEMETRY_USER_EVENT", // Admin types
const MESSAGE_TYPE_LIST = ["BLOCK_MESSAGE_BY_ID", "USER_ACTION", "IMPRESSION", "TRIGGER", "NEWTAB_MESSAGE_REQUEST", // PB is Private Browsing
"PBNEWTAB_MESSAGE_REQUEST", "DOORHANGER_TELEMETRY", "TOOLBAR_BADGE_TELEMETRY", "TOOLBAR_PANEL_TELEMETRY", "MOMENTS_PAGE_TELEMETRY", "INFOBAR_TELEMETRY", "SPOTLIGHT_TELEMETRY", "AS_ROUTER_TELEMETRY_USER_EVENT", // Admin types
"ADMIN_CONNECT_STATE", "UNBLOCK_MESSAGE_BY_ID", "UNBLOCK_ALL", "BLOCK_BUNDLE", "UNBLOCK_BUNDLE", "DISABLE_PROVIDER", "ENABLE_PROVIDER", "EVALUATE_JEXL_EXPRESSION", "EXPIRE_QUERY_CACHE", "FORCE_ATTRIBUTION", "FORCE_WHATSNEW_PANEL", "CLOSE_WHATSNEW_PANEL", "OVERRIDE_MESSAGE", "MODIFY_MESSAGE_JSON", "RESET_PROVIDER_PREF", "SET_PROVIDER_USER_PREF", "RESET_GROUPS_STATE"];
const MESSAGE_TYPE_HASH = MESSAGE_TYPE_LIST.reduce((hash, value) => {
hash[value] = value;

Просмотреть файл

@ -1582,6 +1582,30 @@ class _ASRouter {
return this.loadMessagesFromAllProviders();
}
async sendPBNewTabMessage({ tabId }) {
let message = null;
await this.loadMessagesFromAllProviders();
const telemetryObject = { tabId };
TelemetryStopwatch.start("MS_MESSAGE_REQUEST_TIME_MS", telemetryObject);
message = await this.handleMessageRequest({ template: "pb_newtab" });
TelemetryStopwatch.finish("MS_MESSAGE_REQUEST_TIME_MS", telemetryObject);
// Format urls if any are defined
["infoLinkUrl", "promoLinkUrl"].forEach(key => {
if (message?.content?.[key]) {
message.content[key] = Services.urlFormatter.formatURL(
message.content[key]
);
}
});
NimbusFeatures.pbNewtab.recordExposureEvent({ once: true });
return { message };
}
async sendNewTabMessage({ endpoint, tabId, browser }) {
let message;
@ -1662,6 +1686,7 @@ class _ASRouter {
spotlight: "spotlight",
infobar: "infobar",
update_action: "moments-page",
pb_newtab: "pbNewtab",
};
let feature = featureMap[nonReachMessages[0].template];
if (feature) {

Просмотреть файл

@ -75,6 +75,13 @@ class ASRouterParentProcessMessageHandler {
browser,
});
}
case msg.PBNEWTAB_MESSAGE_REQUEST: {
return this._router.sendPBNewTabMessage({
...data,
tabId,
browser,
});
}
case msg.NEWTAB_MESSAGE_REQUEST: {
return this._router.sendNewTabMessage({
...data,

Просмотреть файл

@ -37,6 +37,20 @@ const ONBOARDING_MESSAGES = () => [
},
trigger: { id: "protectionsPanelOpen" },
},
{
id: "PB_NEWTAB_INFO_SECTION",
template: "pb_newtab",
content: {
promoEnabled: false,
infoEnabled: true,
infoIcon: "",
infoTitle: "",
infoBody: "fluent:about-private-browsing-info-description-private-window",
infoLinkText: "fluent:about-private-browsing-learn-more-link",
infoTitleEnabled: false,
},
targeting: "true",
},
];
const OnboardingMessageProvider = {

Просмотреть файл

@ -311,6 +311,28 @@ const MESSAGES = () => [
frequency: { lifetime: 3 },
trigger: { id: "defaultBrowserCheck" },
},
{
id: "PB_NEWTAB_VPN_PROMO",
template: "pb_newtab",
content: {
promoEnabled: true,
infoEnabled: true,
infoIcon: "",
infoTitle: "",
infoBody: "fluent:about-private-browsing-info-description-private-window",
infoLinkText: "fluent:about-private-browsing-learn-more-link",
infoTitleEnabled: false,
promoLinkType: "button",
promoLinkText: "fluent:about-private-browsing-prominent-cta",
promoSectionStyle: "below-search",
promoHeader: "fluent:about-private-browsing-get-privacy",
promoTitle: "fluent:about-private-browsing-hide-activity-1",
promoTitleEnabled: true,
promoImageLarge: "chrome://browser/content/assets/moz-vpn.svg",
},
targeting: "region != 'CN' && !hasActiveEnterprisePolicies",
frequency: { lifetime: 3 },
},
];
const PanelTestProvider = {

Просмотреть файл

@ -14,7 +14,7 @@ describe("CFRMessageProvider", () => {
beforeEach(async () => {
messages = await CFRMessageProvider.getMessages();
});
it("should have a total of 10 messages", () => {
it("should have a total of 11 messages", () => {
assert.lengthOf(messages, 11);
});
it("should have one message each for the three regular addons", () => {

Просмотреть файл

@ -2,16 +2,17 @@ import { PanelTestProvider } from "lib/PanelTestProvider.jsm";
import update_schema from "content-src/asrouter/templates/OnboardingMessage/UpdateAction.schema.json";
import whats_new_schema from "content-src/asrouter/templates/OnboardingMessage/WhatsNewMessage.schema.json";
import spotlight_schema from "content-src/asrouter/templates/OnboardingMessage/Spotlight.schema.json";
import PBNewtabSchema from "content-src/asrouter/templates/PBNewtab/NewtabPromoMessage.schema.json";
describe("PanelTestProvider", () => {
let messages;
beforeEach(async () => {
messages = await PanelTestProvider.getMessages();
});
it("should have a message", () => {
it("should have 12 messages", () => {
// Careful: when changing this number make sure that new messages also go
// through schema verifications.
assert.lengthOf(messages, 11);
assert.lengthOf(messages, 12);
});
it("should be a valid message", () => {
const updateMessages = messages.filter(
@ -31,7 +32,7 @@ describe("PanelTestProvider", () => {
assert.property(message, "order");
}
});
it("should be a valid message", () => {
it("should be a valid spotlight message", () => {
const spotlightMessages = messages.filter(
({ template }) => template === "spotlight"
);
@ -39,4 +40,11 @@ describe("PanelTestProvider", () => {
assert.jsonSchema(message, spotlight_schema);
}
});
it("should be a valid pb newtab message", () => {
const pbNewtabMessages = messages.filter(
({ template }) => template === "pb_newtab"
);
assert.lengthOf(pbNewtabMessages, 1);
pbNewtabMessages.forEach(m => assert.jsonSchema(m, PBNewtabSchema));
});
});

Просмотреть файл

@ -60,7 +60,7 @@
</div>
</div>
<div class="promo">
<div class="promo" hidden>
<div class="promo-image-large">
<img src="" alt="" />
</div>

Просмотреть файл

@ -38,7 +38,7 @@ async function renderInfo({
infoLinkText,
infoLinkUrl,
infoIcon,
}) {
} = {}) {
const container = document.querySelector(".info");
if (infoEnabled === false) {
container.remove();
@ -75,7 +75,7 @@ async function renderInfo({
}
async function renderPromo({
promoEnabled,
promoEnabled = false,
promoTitle,
promoTitleEnabled,
promoLinkText,
@ -85,7 +85,7 @@ async function renderPromo({
promoHeader,
promoImageLarge,
promoImageSmall,
}) {
} = {}) {
const container = document.querySelector(".promo");
if (promoEnabled === false) {
container.remove();
@ -164,33 +164,26 @@ async function renderPromo({
[linkEl, promoLinkText],
[promoHeaderEl, promoHeader],
]);
// Only make promo section visible after adding content
// and translations to prevent layout shifting in page
container.classList.add("promo-visible");
}
const DEFAULT_PRIVATE_BROWSING_CONTENT = {
promoEnabled: true,
infoEnabled: true,
infoIcon: "",
infoTitle: "",
infoBody: "fluent:about-private-browsing-info-description-private-window",
infoLinkText: "fluent:about-private-browsing-learn-more-link",
infoTitleEnabled: false,
promoLinkType: "button",
promoLinkText: "fluent:about-private-browsing-prominent-cta",
promoSectionStyle: "below-search",
promoHeader: "fluent:about-private-browsing-get-privacy",
promoTitle: "fluent:about-private-browsing-hide-activity-1",
promoTitleEnabled: true,
promoImageLarge: "chrome://browser/content/assets/moz-vpn.svg",
};
async function setupFeatureConfig() {
// Setup experiment data
let config = {};
let config = null;
try {
config = window.PrivateBrowsingFeatureConfig(
DEFAULT_PRIVATE_BROWSING_CONTENT
);
config = window.PrivateBrowsingFeatureConfig();
} catch (e) {}
if (!Object.keys(config).length) {
try {
let response = await window.ASRouterMessage({
type: "PBNEWTAB_MESSAGE_REQUEST",
data: {},
});
config = response?.message?.content;
} catch (e) {}
}
await renderInfo(config);
await renderPromo(config);

Просмотреть файл

@ -8,6 +8,12 @@ const { ExperimentFakes } = ChromeUtils.import(
const { ExperimentAPI } = ChromeUtils.import(
"resource://nimbus/ExperimentAPI.jsm"
);
const { TelemetryTestUtils } = ChromeUtils.import(
"resource://testing-common/TelemetryTestUtils.jsm"
);
const { PanelTestProvider } = ChromeUtils.import(
"resource://activity-stream/lib/PanelTestProvider.jsm"
);
/**
* These tests ensure that the experiment and remote default capabilities
@ -46,10 +52,14 @@ function waitForTelemetryEvent(category) {
}
add_task(async function test_experiment_plain_text() {
const defaultMessageContent = (await PanelTestProvider.getMessages()).find(
m => m.template === "pb_newtab"
).content;
let doExperimentCleanup = await ExperimentFakes.enrollWithFeatureConfig({
featureId: "privatebrowsing",
enabled: true,
value: {
...defaultMessageContent,
infoTitle: "Hello world",
infoTitleEnabled: true,
infoBody: "This is some text",
@ -94,10 +104,14 @@ add_task(async function test_experiment_plain_text() {
});
add_task(async function test_experiment_fluent() {
const defaultMessageContent = (await PanelTestProvider.getMessages()).find(
m => m.template === "pb_newtab"
).content;
let doExperimentCleanup = await ExperimentFakes.enrollWithFeatureConfig({
featureId: "privatebrowsing",
enabled: true,
value: {
...defaultMessageContent,
infoBody: "fluent:about-private-browsing-info-title",
promoLinkText: "fluent:about-private-browsing-prominent-cta",
},
@ -180,6 +194,8 @@ add_task(async function test_experiment_format_urls() {
featureId: "privatebrowsing",
enabled: true,
value: {
infoEnabled: true,
promoEnabled: true,
infoLinkUrl: "http://foo.mozilla.com/%LOCALE%",
promoLinkUrl: "http://bar.mozilla.com/%LOCALE%",
},
@ -238,6 +254,7 @@ add_task(async function test_experiment_click_promo_telemetry() {
featureId: "privatebrowsing",
enabled: true,
value: {
promoEnabled: true,
promoLinkUrl: "http://example.com",
},
});
@ -263,9 +280,13 @@ add_task(async function test_experiment_click_promo_telemetry() {
});
add_task(async function test_experiment_bottom_promo() {
const defaultMessageContent = (await PanelTestProvider.getMessages()).find(
m => m.template === "pb_newtab"
).content;
let doExperimentCleanup = await ExperimentFakes.enrollWithFeatureConfig({
featureId: "privatebrowsing",
value: {
...defaultMessageContent,
enabled: true,
promoLinkType: "button",
promoSectionStyle: "bottom",
@ -316,9 +337,13 @@ add_task(async function test_experiment_bottom_promo() {
});
add_task(async function test_experiment_below_search_promo() {
const defaultMessageContent = (await PanelTestProvider.getMessages()).find(
m => m.template === "pb_newtab"
).content;
let doExperimentCleanup = await ExperimentFakes.enrollWithFeatureConfig({
featureId: "privatebrowsing",
value: {
...defaultMessageContent,
enabled: true,
promoLinkType: "button",
promoSectionStyle: "below-search",
@ -371,9 +396,13 @@ add_task(async function test_experiment_below_search_promo() {
});
add_task(async function test_experiment_top_promo() {
const defaultMessageContent = (await PanelTestProvider.getMessages()).find(
m => m.template === "pb_newtab"
).content;
let doExperimentCleanup = await ExperimentFakes.enrollWithFeatureConfig({
featureId: "privatebrowsing",
value: {
...defaultMessageContent,
enabled: true,
promoLinkType: "button",
promoSectionStyle: "top",
@ -421,3 +450,91 @@ add_task(async function test_experiment_top_promo() {
await doExperimentCleanup();
});
add_task(async function test_experiment_messaging_system() {
const LOCALE = Services.locale.appLocaleAsBCP47;
let doExperimentCleanup = await ExperimentFakes.enrollWithFeatureConfig({
featureId: "pbNewtab",
enabled: true,
value: {
id: "PB_NEWTAB_MESSAGING_SYSTEM",
template: "pb_newtab",
content: {
promoEnabled: true,
infoEnabled: true,
infoBody: "fluent:about-private-browsing-info-title",
promoLinkText: "fluent:about-private-browsing-prominent-cta",
infoLinkUrl: "http://foo.example.com/%LOCALE%",
promoLinkUrl: "http://bar.example.com/%LOCALE%",
},
// Priority ensures this message is picked over the one in
// OnboardingMessageProvider
priority: 5,
targeting: "true",
},
});
Services.prefs.setStringPref(
"browser.newtabpage.activity-stream.asrouter.providers.messaging-experiments",
'{"id":"messaging-experiments","enabled":true,"type":"remote-experiments","messageGroups":["pbNewtab"],"updateCycleInMs":0}'
);
const { ASRouter } = ChromeUtils.import(
"resource://activity-stream/lib/ASRouter.jsm"
);
// Reload the provider
await ASRouter._updateMessageProviders();
// Wait to load the messages from the messaging-experiments provider
await ASRouter.loadMessagesFromAllProviders();
Assert.ok(
ASRouter.state.messages.find(m => m.id === "PB_NEWTAB_MESSAGING_SYSTEM"),
"Experiment message found in ASRouter state"
);
Services.telemetry.clearEvents();
let { win, tab } = await openTabAndWaitForRender();
await SpecialPowers.spawn(tab, [LOCALE], async function(locale) {
const infoBody = content.document.getElementById("info-body");
const promoLink = content.document.getElementById(
"private-browsing-vpn-link"
);
// Check experiment values are rendered
is(
infoBody.textContent,
"Youre in a Private Window",
"should render infoBody with fluent"
);
is(
promoLink.textContent,
"Stay private with Mozilla VPN",
"should render promoLinkText with fluent"
);
is(
content.document.querySelector(".info a").getAttribute("href"),
"http://foo.example.com/" + locale,
"should format the infoLinkUrl url"
);
is(
content.document.querySelector(".promo a").getAttribute("href"),
"http://bar.example.com/" + locale,
"should format the promoLinkUrl url"
);
});
TelemetryTestUtils.assertEvents(
[
{
method: "expose",
extra: {
featureId: "pbNewtab",
},
},
],
{ category: "normandy" }
);
await BrowserTestUtils.closeWindow(win);
await doExperimentCleanup();
});

Просмотреть файл

@ -325,10 +325,13 @@ p {
.promo {
text-align: center;
display: flex;
align-items: center;
}
.promo-visible {
display: flex;
}
.promo-content {
width: 100%;
}

Просмотреть файл

@ -452,3 +452,12 @@ spotlight:
schema: >-
"browser/components/newtab/content-src/asrouter/templates/OnboardingMessage/Spotlight.schema.json"
variables: {}
pbNewtab:
description: Message shown on the PB newtab for Messaging System
hasExposure: true
exposureDescription: >-
Exposure is sent if the message is about to be shown after trigger and targeting conditions on the message matched.
isEarlyStartup: false
schema: >-
browser/components/newtab/content-src/asrouter/templates/PBNewtab/NewtabPromoMessage.schema.json
variables: {}