Bug 1754832 - Update PBM to support opening spotlight tab modal r=Mardak,mviar,fluent-reviewers,flod

Differential Revision: https://phabricator.services.mozilla.com/D140565
This commit is contained in:
Punam Dahiya
2022-03-17 06:41:47 +00:00
parent 0d7fa051a3
commit 241b77481c
17 changed files with 443 additions and 246 deletions

View File

@@ -31,6 +31,8 @@ XPCOMUtils.defineLazyPreferenceGetter(
XPCOMUtils.defineLazyModuleGetters(this, { XPCOMUtils.defineLazyModuleGetters(this, {
UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm", UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm",
SpecialMessageActions:
"resource://messaging-system/lib/SpecialMessageActions.jsm",
}); });
// We only show the private search banner once per browser session. // We only show the private search banner once per browser session.
@@ -165,6 +167,9 @@ class AboutPrivateBrowsingParent extends JSWindowActorParent {
case "ShouldShowVPNPromo": { case "ShouldShowVPNPromo": {
return BrowserUtils.shouldShowVPNPromo(); return BrowserUtils.shouldShowVPNPromo();
} }
case "SpecialMessageActionDispatch": {
SpecialMessageActions.handleAction(aMessage.data, browser);
}
} }
return undefined; return undefined;

View File

@@ -126,7 +126,9 @@ function renderMultistage(ready) {
window.AWGetImportableSites = () => "[]"; window.AWGetImportableSites = () => "[]";
window.AWGetRegion = receive("GET_REGION"); window.AWGetRegion = receive("GET_REGION");
window.AWGetSelectedTheme = receive("GET_SELECTED_THEME"); window.AWGetSelectedTheme = receive("GET_SELECTED_THEME");
window.AWSendEventTelemetry = receive("TELEMETRY_EVENT"); // Do not send telemetry if message (e.g. spotlight in PBM) config sets metrics as 'block'.
window.AWSendEventTelemetry =
CONFIG?.metrics === "block" ? () => {} : receive("TELEMETRY_EVENT");
window.AWSendToParent = (name, data) => receive(name)(data); window.AWSendToParent = (name, data) => receive(name)(data);
window.AWFinish = () => { window.AWFinish = () => {
window.close(); window.close();

View File

@@ -125,10 +125,6 @@ var whitelist = [
// browser/components/preferences/moreFromMozilla.js // browser/components/preferences/moreFromMozilla.js
// These files URLs are constructed programatically at run time. // These files URLs are constructed programatically at run time.
{
file:
"chrome://browser/content/preferences/more-from-mozilla-qr-code-advanced.svg",
},
{ {
file: file:
"chrome://browser/content/preferences/more-from-mozilla-qr-code-simple.svg", "chrome://browser/content/preferences/more-from-mozilla-qr-code-simple.svg",

View File

@@ -37,6 +37,84 @@ const ONBOARDING_MESSAGES = () => [
}, },
trigger: { id: "protectionsPanelOpen" }, trigger: { id: "protectionsPanelOpen" },
}, },
{
id: "PB_NEWTAB_FOCUS_PROMO",
template: "pb_newtab",
groups: ["pbNewtab"],
content: {
infoBody: "fluent:about-private-browsing-info-description-simplified",
infoEnabled: true,
infoIcon: "chrome://global/skin/icons/indicator-private-browsing.svg",
infoLinkText: "fluent:about-private-browsing-learn-more-link",
infoTitle: "",
infoTitleEnabled: false,
promoEnabled: true,
promoHeader: "fluent:about-private-browsing-focus-promo-header",
promoImageLarge: "chrome://browser/content/assets/focus-promo.png",
promoLinkText: "fluent:about-private-browsing-focus-promo-cta",
promoLinkType: "button",
promoSectionStyle: "below-search",
promoTitle: "fluent:about-private-browsing-focus-promo-text",
promoTitleEnabled: true,
promoButton: {
action: {
type: "SHOW_SPOTLIGHT",
data: {
content: {
id: "FOCUS_PROMO",
template: "multistage",
modal: "tab",
metrics: "block",
backdrop: "transparent",
screens: [
{
id: "DEFAULT_MODAL_UI",
order: 0,
content: {
logo: {
imageURL:
"chrome://browser/content/preferences/more-from-mozilla-qr-code-advanced.svg",
height: "100px",
},
title: "Get Firefox Focus",
subtitle: "Scan the QR Code to Download",
primary_button: {
label: "Email yourself a link",
action: {
type: "OPEN_URL",
data: {
args:
"https://www.mozilla.org/firefox/mobile/get-app/",
where: "tabshifted",
},
},
},
secondary_button: {
label: "Close",
action: {
navigate: true,
},
},
},
},
],
},
},
},
},
},
priority: 2,
frequency: {
custom: [
{
cap: 1,
period: 604800000, // Max 1 per week
},
],
lifetime: 3,
},
targeting: "true",
},
{ {
id: "PB_NEWTAB_INFO_SECTION", id: "PB_NEWTAB_INFO_SECTION",
template: "pb_newtab", template: "pb_newtab",

View File

@@ -25,12 +25,19 @@ const Spotlight = {
}); });
}, },
async showSpotlightDialog(browser, message, dispatchCFRAction) { /**
* Shows spotlight tab or window modal specific to the given browser
* @param browser The browser for spotlight display
* @param message Message containing content to show
* @param dispatchCFRAction A function to dispatch resulting actions
* @return boolean value capturing if spotlight was displayed
*/
async showSpotlightDialog(browser, message, dispatchCFRAction = () => {}) {
const win = browser.ownerGlobal; const win = browser.ownerGlobal;
if (win.gDialogBox.isOpen) { if (win.gDialogBox.isOpen) {
return false; return false;
} }
const spotlight_url = "chrome://browser/content/spotlight.html";
let params = { primaryBtn: false, secondaryBtn: false }; let params = { primaryBtn: false, secondaryBtn: false };
// There are two events named `IMPRESSION` the first one refers to telemetry // There are two events named `IMPRESSION` the first one refers to telemetry
@@ -40,10 +47,18 @@ const Spotlight = {
const unload = await RemoteImages.patchMessage(message.content.logo); const unload = await RemoteImages.patchMessage(message.content.logo);
await win.gDialogBox.open("chrome://browser/content/spotlight.html", [ if (message.content?.modal === "tab") {
message.content, await win.gBrowser.getTabDialogBox(browser).open(
params, spotlight_url,
]); {
features: "resizable=no",
allowDuplicateDialogs: false,
},
[message.content, params]
);
} else {
await win.gDialogBox.open(spotlight_url, [message.content, params]);
}
if (unload) { if (unload) {
unload(); unload();

View File

@@ -87,6 +87,7 @@ async function renderPromo({
promoHeader, promoHeader,
promoImageLarge, promoImageLarge,
promoImageSmall, promoImageSmall,
promoButton = null,
} = {}) { } = {}) {
const container = document.querySelector(".promo"); const container = document.querySelector(".promo");
if (promoEnabled === false) { if (promoEnabled === false) {
@@ -102,20 +103,22 @@ async function renderPromo({
const promoImageSmallEl = document.querySelector(".promo-image-small img"); const promoImageSmallEl = document.querySelector(".promo-image-small img");
const dismissBtn = document.querySelector("#dismiss-btn"); const dismissBtn = document.querySelector("#dismiss-btn");
// Setup the private browsing VPN link.
const vpnPromoUrl =
promoLinkUrl || RPMGetFormatURLPref("browser.privatebrowsing.vpnpromourl");
if (promoLinkType === "button") { if (promoLinkType === "button") {
linkEl.classList.add("button"); linkEl.classList.add("button");
} }
if (vpnPromoUrl) { if (promoLinkUrl) {
linkEl.setAttribute("href", vpnPromoUrl); linkEl.setAttribute("href", promoLinkUrl);
linkEl.setAttribute("target", "_blank"); linkEl.setAttribute("target", "_blank");
linkEl.addEventListener("click", () => { linkEl.addEventListener("click", () => {
window.PrivateBrowsingRecordClick("promo_link"); window.PrivateBrowsingRecordClick("promo_link");
}); });
} else if (promoButton?.action?.type === "SHOW_SPOTLIGHT") {
linkEl.setAttribute("href", "#");
linkEl.addEventListener("click", async () => {
window.PrivateBrowsingRecordClick("promo_link");
await RPMSendQuery("SpecialMessageActionDispatch", promoButton.action);
});
} else { } else {
// If the link is undefined, remove the promo completely // If the link is undefined, remove the promo completely
container.remove(); container.remove();

View File

@@ -21,6 +21,7 @@ support-files =
[browser_privatebrowsing_DownloadLastDirWithCPS.js] [browser_privatebrowsing_DownloadLastDirWithCPS.js]
[browser_privatebrowsing_about_nimbus.js] [browser_privatebrowsing_about_nimbus.js]
[browser_privatebrowsing_about_messaging.js]
skip-if = skip-if =
verify # This was already broken on mozilla-central, skipping for now verify # This was already broken on mozilla-central, skipping for now
# to prevent it from causing issues with people who want to use # to prevent it from causing issues with people who want to use

View File

@@ -0,0 +1,244 @@
/* 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/. */
const { ExperimentFakes } = ChromeUtils.import(
"resource://testing-common/NimbusTestUtils.jsm"
);
const { TelemetryTestUtils } = ChromeUtils.import(
"resource://testing-common/TelemetryTestUtils.jsm"
);
const { ASRouter } = ChromeUtils.import(
"resource://activity-stream/lib/ASRouter.jsm"
);
async function openTabAndWaitForRender() {
let { win, tab } = await openAboutPrivateBrowsing();
await SpecialPowers.spawn(tab, [], async function() {
// Wait for render to complete
await ContentTaskUtils.waitForCondition(() =>
content.document.documentElement.hasAttribute(
"PrivateBrowsingRenderComplete"
)
);
});
return { win, tab };
}
async function setupMSExperimentWithMessage(message) {
let doExperimentCleanup = await ExperimentFakes.enrollWithFeatureConfig({
featureId: "pbNewtab",
enabled: true,
value: message,
});
Services.prefs.setStringPref(
"browser.newtabpage.activity-stream.asrouter.providers.messaging-experiments",
'{"id":"messaging-experiments","enabled":true,"type":"remote-experiments","messageGroups":["pbNewtab"],"updateCycleInMs":0}'
);
// Reload the provider
await ASRouter._updateMessageProviders();
// Wait to load the messages from the messaging-experiments provider
await ASRouter.loadMessagesFromAllProviders();
registerCleanupFunction(async () => {
// Reload the provider again at cleanup to remove the experiment message
await ASRouter._updateMessageProviders();
// Wait to load the messages from the messaging-experiments provider
await ASRouter.loadMessagesFromAllProviders();
Services.prefs.clearUserPref(
"browser.newtabpage.activity-stream.asrouter.providers.messaging-experiments"
);
});
Assert.ok(
ASRouter.state.messages.find(m => m.id.includes(message.id)),
"Experiment message found in ASRouter state"
);
return doExperimentCleanup;
}
add_task(async function setup() {
let { win } = await openAboutPrivateBrowsing();
await BrowserTestUtils.closeWindow(win);
});
add_task(async function test_experiment_messaging_system() {
const LOCALE = Services.locale.appLocaleAsBCP47;
let doExperimentCleanup = await setupMSExperimentWithMessage({
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.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(".info a").getAttribute("target"),
"_blank",
"should open info url in new tab"
);
is(
content.document.querySelector(".promo a").getAttribute("target"),
"_blank",
"should open promo url in new tab"
);
});
// There's something buggy here, disabling for now to prevent intermittent failures
// until we fix it in bug 1754536.
//
// TelemetryTestUtils.assertEvents(
// [
// {
//
// method: "expose",
// extra: {
// featureId: "pbNewtab",
// },
// },
// ],
// { category: "normandy" }
// );
await BrowserTestUtils.closeWindow(win);
await doExperimentCleanup();
});
add_task(async function test_experiment_messaging_system_impressions() {
const LOCALE = Services.locale.appLocaleAsBCP47;
let doExperimentCleanup = await setupMSExperimentWithMessage({
id: `PB_NEWTAB_MESSAGING_SYSTEM_${Math.random()}`,
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%",
},
frequency: {
lifetime: 2,
},
// Priority ensures this message is picked over the one in
// OnboardingMessageProvider
priority: 5,
targeting: "true",
});
let { win: win1, tab: tab1 } = await openTabAndWaitForRender();
await SpecialPowers.spawn(tab1, [LOCALE], async function(locale) {
is(
content.document.querySelector(".promo a").getAttribute("href"),
"http://bar.example.com/" + locale,
"should format the promoLinkUrl url"
);
});
let { win: win2, tab: tab2 } = await openTabAndWaitForRender();
await SpecialPowers.spawn(tab2, [LOCALE], async function(locale) {
is(
content.document.querySelector(".promo a").getAttribute("href"),
"http://bar.example.com/" + locale,
"should format the promoLinkUrl url"
);
});
let { win: win3, tab: tab3 } = await openTabAndWaitForRender();
await SpecialPowers.spawn(tab3, [], async function() {
is(
content.document.querySelector(".promo a"),
null,
"should no longer render the experiment message after 2 impressions"
);
});
await BrowserTestUtils.closeWindow(win1);
await BrowserTestUtils.closeWindow(win2);
await BrowserTestUtils.closeWindow(win3);
await doExperimentCleanup();
});
add_task(async function test_experiment_messaging_system_dismiss() {
const LOCALE = Services.locale.appLocaleAsBCP47;
let doExperimentCleanup = await setupMSExperimentWithMessage({
id: `PB_NEWTAB_MESSAGING_SYSTEM_${Math.random()}`,
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",
});
let { win: win1, tab: tab1 } = await openTabAndWaitForRender();
await SpecialPowers.spawn(tab1, [LOCALE], async function(locale) {
is(
content.document.querySelector(".promo a").getAttribute("href"),
"http://bar.example.com/" + locale,
"should format the promoLinkUrl url"
);
content.document.querySelector("#dismiss-btn").click();
});
let { win: win2, tab: tab2 } = await openTabAndWaitForRender();
await SpecialPowers.spawn(tab2, [], async function() {
is(
content.document.querySelector(".promo a"),
null,
"should no longer render the experiment message after dismissing"
);
});
await BrowserTestUtils.closeWindow(win1);
await BrowserTestUtils.closeWindow(win2);
await doExperimentCleanup();
});

View File

@@ -8,9 +8,6 @@ const { ExperimentFakes } = ChromeUtils.import(
const { ExperimentAPI } = ChromeUtils.import( const { ExperimentAPI } = ChromeUtils.import(
"resource://nimbus/ExperimentAPI.jsm" "resource://nimbus/ExperimentAPI.jsm"
); );
const { TelemetryTestUtils } = ChromeUtils.import(
"resource://testing-common/TelemetryTestUtils.jsm"
);
const { PanelTestProvider } = ChromeUtils.import( const { PanelTestProvider } = ChromeUtils.import(
"resource://activity-stream/lib/PanelTestProvider.jsm" "resource://activity-stream/lib/PanelTestProvider.jsm"
); );
@@ -18,16 +15,6 @@ const { ASRouter } = ChromeUtils.import(
"resource://activity-stream/lib/ASRouter.jsm" "resource://activity-stream/lib/ASRouter.jsm"
); );
/**
* These tests ensure that the experiment and remote default capabilities
* for the "privatebrowsing" feature are working as expected.
*/
add_task(async function setup() {
// XXX I believe this is likely to become unnecessary as part of the fix for bug 1749775.
requestLongerTimeout(5);
});
async function openTabAndWaitForRender() { async function openTabAndWaitForRender() {
let { win, tab } = await openAboutPrivateBrowsing(); let { win, tab } = await openAboutPrivateBrowsing();
await SpecialPowers.spawn(tab, [], async function() { await SpecialPowers.spawn(tab, [], async function() {
@@ -59,39 +46,6 @@ function waitForTelemetryEvent(category) {
}, "waiting for telemetry event"); }, "waiting for telemetry event");
} }
async function setupMSExperimentWithMessage(message) {
let doExperimentCleanup = await ExperimentFakes.enrollWithFeatureConfig({
featureId: "pbNewtab",
enabled: true,
value: message,
});
Services.prefs.setStringPref(
"browser.newtabpage.activity-stream.asrouter.providers.messaging-experiments",
'{"id":"messaging-experiments","enabled":true,"type":"remote-experiments","messageGroups":["pbNewtab"],"updateCycleInMs":0}'
);
// Reload the provider
await ASRouter._updateMessageProviders();
// Wait to load the messages from the messaging-experiments provider
await ASRouter.loadMessagesFromAllProviders();
registerCleanupFunction(async () => {
// Reload the provider again at cleanup to remove the experiment message
await ASRouter._updateMessageProviders();
// Wait to load the messages from the messaging-experiments provider
await ASRouter.loadMessagesFromAllProviders();
Services.prefs.clearUserPref(
"browser.newtabpage.activity-stream.asrouter.providers.messaging-experiments"
);
});
Assert.ok(
ASRouter.state.messages.find(m => m.id.includes(message.id)),
"Experiment message found in ASRouter state"
);
return doExperimentCleanup;
}
add_task(async function test_experiment_plain_text() { add_task(async function test_experiment_plain_text() {
const defaultMessageContent = (await PanelTestProvider.getMessages()).find( const defaultMessageContent = (await PanelTestProvider.getMessages()).find(
m => m.template === "pb_newtab" m => m.template === "pb_newtab"
@@ -108,6 +62,7 @@ add_task(async function test_experiment_plain_text() {
infoIcon: "chrome://branding/content/about-logo.png", infoIcon: "chrome://branding/content/about-logo.png",
promoTitle: "Promo title", promoTitle: "Promo title",
promoLinkText: "Promo link", promoLinkText: "Promo link",
promoLinkUrl: "https://test.com",
}, },
}); });
@@ -155,6 +110,7 @@ add_task(async function test_experiment_fluent() {
...defaultMessageContent, ...defaultMessageContent,
infoBody: "fluent:about-private-browsing-info-title", infoBody: "fluent:about-private-browsing-info-title",
promoLinkText: "fluent:about-private-browsing-prominent-cta", promoLinkText: "fluent:about-private-browsing-prominent-cta",
promoLinkUrl: "https://test.com",
}, },
}); });
@@ -331,6 +287,7 @@ add_task(async function test_experiment_bottom_promo() {
...defaultMessageContent, ...defaultMessageContent,
enabled: true, enabled: true,
promoLinkType: "button", promoLinkType: "button",
promoLinkUrl: "http://example.com",
promoSectionStyle: "bottom", promoSectionStyle: "bottom",
promoHeader: "Need more privacy?", promoHeader: "Need more privacy?",
infoTitleEnabled: true, infoTitleEnabled: true,
@@ -388,10 +345,11 @@ add_task(async function test_experiment_below_search_promo() {
...defaultMessageContent, ...defaultMessageContent,
enabled: true, enabled: true,
promoLinkType: "button", promoLinkType: "button",
promoLinkUrl: "http://example.com",
promoSectionStyle: "below-search", promoSectionStyle: "below-search",
promoHeader: "Need more privacy?", promoHeader: "Need more privacy?",
promoTitle: promoTitle:
"Mozilla VPN. Security, reliability and speed — on every device, anywhere you go.", "Mozilla VPN. Security, reliability and speed — on every device, anywhere you go.",
promoImageLarge: "chrome://browser/content/assets/moz-vpn.svg", promoImageLarge: "chrome://browser/content/assets/moz-vpn.svg",
promoImageSmall: "chrome://browser/content/assets/vpn-logo.svg", promoImageSmall: "chrome://browser/content/assets/vpn-logo.svg",
infoTitleEnabled: false, infoTitleEnabled: false,
@@ -447,10 +405,11 @@ add_task(async function test_experiment_top_promo() {
...defaultMessageContent, ...defaultMessageContent,
enabled: true, enabled: true,
promoLinkType: "button", promoLinkType: "button",
promoLinkUrl: "http://example.com",
promoSectionStyle: "top", promoSectionStyle: "top",
promoHeader: "Need more privacy?", promoHeader: "Need more privacy?",
promoTitle: promoTitle:
"Mozilla VPN. Security, reliability and speed — on every device,anywhere you go.", "Mozilla VPN. Security, reliability and speed — on every device, anywhere you go.",
promoImageLarge: "chrome://browser/content/assets/moz-vpn.svg", promoImageLarge: "chrome://browser/content/assets/moz-vpn.svg",
promoImageSmall: "chrome://browser/content/assets/vpn-logo.svg", promoImageSmall: "chrome://browser/content/assets/vpn-logo.svg",
infoTitleEnabled: false, infoTitleEnabled: false,
@@ -492,183 +451,3 @@ add_task(async function test_experiment_top_promo() {
await doExperimentCleanup(); await doExperimentCleanup();
}); });
add_task(async function test_experiment_messaging_system() {
const LOCALE = Services.locale.appLocaleAsBCP47;
let doExperimentCleanup = await setupMSExperimentWithMessage({
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.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(".info a").getAttribute("target"),
"_blank",
"should open info url in new tab"
);
is(
content.document.querySelector(".promo a").getAttribute("target"),
"_blank",
"should open promo url in new tab"
);
});
// There's something buggy here, disabling for now to prevent intermittent failures
// until we fix it in bug 1749775.
//
// TelemetryTestUtils.assertEvents(
// [
// {
//
// method: "expose",
// extra: {
// featureId: "pbNewtab",
// },
// },
// ],
// { category: "normandy" }
// );
await BrowserTestUtils.closeWindow(win);
await doExperimentCleanup();
});
add_task(async function test_experiment_messaging_system_impressions() {
const LOCALE = Services.locale.appLocaleAsBCP47;
let doExperimentCleanup = await setupMSExperimentWithMessage({
id: `PB_NEWTAB_MESSAGING_SYSTEM_${Math.random()}`,
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%",
},
frequency: {
lifetime: 2,
},
// Priority ensures this message is picked over the one in
// OnboardingMessageProvider
priority: 5,
targeting: "true",
});
let { win: win1, tab: tab1 } = await openTabAndWaitForRender();
await SpecialPowers.spawn(tab1, [LOCALE], async function(locale) {
is(
content.document.querySelector(".promo a").getAttribute("href"),
"http://bar.example.com/" + locale,
"should format the promoLinkUrl url"
);
});
let { win: win2, tab: tab2 } = await openTabAndWaitForRender();
await SpecialPowers.spawn(tab2, [LOCALE], async function(locale) {
is(
content.document.querySelector(".promo a").getAttribute("href"),
"http://bar.example.com/" + locale,
"should format the promoLinkUrl url"
);
});
let { win: win3, tab: tab3 } = await openTabAndWaitForRender();
await SpecialPowers.spawn(tab3, [], async function() {
is(
content.document.querySelector(".promo a"),
null,
"should no longer render the experiment message after 2 impressions"
);
});
await BrowserTestUtils.closeWindow(win1);
await BrowserTestUtils.closeWindow(win2);
await BrowserTestUtils.closeWindow(win3);
await doExperimentCleanup();
});
add_task(async function test_experiment_messaging_system_dismiss() {
const LOCALE = Services.locale.appLocaleAsBCP47;
let doExperimentCleanup = await setupMSExperimentWithMessage({
id: `PB_NEWTAB_MESSAGING_SYSTEM_${Math.random()}`,
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",
});
let { win: win1, tab: tab1 } = await openTabAndWaitForRender();
await SpecialPowers.spawn(tab1, [LOCALE], async function(locale) {
is(
content.document.querySelector(".promo a").getAttribute("href"),
"http://bar.example.com/" + locale,
"should format the promoLinkUrl url"
);
content.document.querySelector("#dismiss-btn").click();
});
let { win: win2, tab: tab2 } = await openTabAndWaitForRender();
await SpecialPowers.spawn(tab2, [], async function() {
is(
content.document.querySelector(".promo a"),
null,
"should no longer render the experiment message after dismissing"
);
});
await BrowserTestUtils.closeWindow(win1);
await BrowserTestUtils.closeWindow(win2);
await doExperimentCleanup();
});

View File

@@ -28,6 +28,10 @@ about-private-browsing-get-privacy = Get privacy protections everywhere you brow
about-private-browsing-hide-activity-1 = Hide browsing activity and location with { -mozilla-vpn-brand-name }. One click creates a secure connection, even on public Wi-Fi. about-private-browsing-hide-activity-1 = Hide browsing activity and location with { -mozilla-vpn-brand-name }. One click creates a secure connection, even on public Wi-Fi.
about-private-browsing-prominent-cta = Stay private with { -mozilla-vpn-brand-name } about-private-browsing-prominent-cta = Stay private with { -mozilla-vpn-brand-name }
about-private-browsing-focus-promo-cta = Download { -focus-brand-name }
about-private-browsing-focus-promo-header = { -focus-brand-name }: Private browsing on-the-go
about-private-browsing-focus-promo-text = Our dedicated private browsing mobile app clears your history and cookies every time.
# This string is the title for the banner for search engine selection # This string is the title for the banner for search engine selection
# in a private window. # in a private window.
# Variables: # Variables:

View File

@@ -27,6 +27,7 @@
-translations-brand-name = Firefox Translations -translations-brand-name = Firefox Translations
-rally-brand-name = Mozilla Rally -rally-brand-name = Mozilla Rally
-rally-short-name = Rally -rally-short-name = Rally
-focus-brand-name = Firefox Focus
# “Suggest” can be localized, “Firefox” must be treated as a brand # “Suggest” can be localized, “Firefox” must be treated as a brand
# and kept in English. # and kept in English.

View File

@@ -17,6 +17,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
UITour: "resource:///modules/UITour.jsm", UITour: "resource:///modules/UITour.jsm",
FxAccounts: "resource://gre/modules/FxAccounts.jsm", FxAccounts: "resource://gre/modules/FxAccounts.jsm",
MigrationUtils: "resource:///modules/MigrationUtils.jsm", MigrationUtils: "resource:///modules/MigrationUtils.jsm",
Spotlight: "resource://activity-stream/lib/Spotlight.jsm",
}); });
const SpecialMessageActions = { const SpecialMessageActions = {
@@ -174,7 +175,7 @@ const SpecialMessageActions = {
* Messaging System interactions. * Messaging System interactions.
* *
* @param {{type: string, data?: any}} action User action defined in message JSON. * @param {{type: string, data?: any}} action User action defined in message JSON.
* @param browser {Browser} The browser most relvant to the message. * @param browser {Browser} The browser most relevant to the message.
*/ */
async handleAction(action, browser) { async handleAction(action, browser) {
const window = browser.ownerGlobal; const window = browser.ownerGlobal;
@@ -324,6 +325,9 @@ const SpecialMessageActions = {
true true
); );
break; break;
case "SHOW_SPOTLIGHT":
Spotlight.showSpotlightDialog(browser, action.data);
break;
default: default:
throw new Error( throw new Error(
`Special message action with type ${action.type} is unsupported.` `Special message action with type ${action.type} is unsupported.`

View File

@@ -422,6 +422,29 @@
"required": ["data", "type"], "required": ["data", "type"],
"additionalProperties": false, "additionalProperties": false,
"description": "Resets homepage pref and sections layout" "description": "Resets homepage pref and sections layout"
},
{
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"content": {
"type": "object",
"description": "Object containing content rendered inside spotlight dialog"
}
},
"required": ["content"],
"additionalProperties": false
},
"type": {
"type": "string",
"enum": ["SHOW_SPOTLIGHT"]
}
},
"required": ["data", "type"],
"additionalProperties": false,
"description": "Opens a spotlight dialog"
} }
] ]
} }

View File

@@ -256,3 +256,7 @@ Action for enabling the Total Cookie Protection feature.
Action for disabling the Total Cookie Protection feature and enabling an Action for disabling the Total Cookie Protection feature and enabling an
additional privacy section in about:preferences. additional privacy section in about:preferences.
### `SHOW_SPOTLIGHT`
Action for opening a spotlight tab or window modal using the content passed to the dialog.

View File

@@ -11,6 +11,7 @@ support-files =
[browser_sma_open_protection_panel.js] [browser_sma_open_protection_panel.js]
[browser_sma_open_protection_report.js] [browser_sma_open_protection_report.js]
[browser_sma_open_url.js] [browser_sma_open_url.js]
[browser_sma_open_spotlight_dialog.js]
[browser_sma_pin_current_tab.js] [browser_sma_pin_current_tab.js]
[browser_sma_pin_firefox.js] [browser_sma_pin_firefox.js]
[browser_sma_show_firefox_accounts.js] [browser_sma_show_firefox_accounts.js]

View File

@@ -0,0 +1,36 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const { OnboardingMessageProvider } = ChromeUtils.import(
"resource://activity-stream/lib/OnboardingMessageProvider.jsm"
);
const { Spotlight } = ChromeUtils.import(
"resource://activity-stream/lib/Spotlight.jsm"
);
add_task(async function test_OPEN_SPOTLIGHT_DIALOG() {
let pbNewTabMessage = (
await OnboardingMessageProvider.getUntranslatedMessages()
).filter(m => m.id === "PB_NEWTAB_FOCUS_PROMO");
info(`Testing ${pbNewTabMessage[0].id}`);
let showSpotlightStub = sinon.stub(Spotlight, "showSpotlightDialog");
await SMATestUtils.executeAndValidateAction({
type: "SHOW_SPOTLIGHT",
data: { ...pbNewTabMessage[0].content.promoButton.action.data },
});
Assert.equal(
showSpotlightStub.callCount,
1,
"Should call showSpotlightDialog"
);
Assert.deepEqual(
showSpotlightStub.firstCall.args[1],
pbNewTabMessage[0].content.promoButton.action.data,
"Should be called with action.data"
);
});

View File

@@ -129,6 +129,7 @@ let RemotePageAccessManager = {
"ShouldShowSearch", "ShouldShowSearch",
"ShouldShowSearchBanner", "ShouldShowSearchBanner",
"ShouldShowVPNPromo", "ShouldShowVPNPromo",
"SpecialMessageActionDispatch",
], ],
RPMAddMessageListener: ["*"], RPMAddMessageListener: ["*"],
RPMRemoveMessageListener: ["*"], RPMRemoveMessageListener: ["*"],