Bug 1914666: first implementation to show Relay to all desktop browsers r=credential-management-reviewers,fluent-reviewers,flod,dimi,issammani

Differential Revision: https://phabricator.services.mozilla.com/D217620
This commit is contained in:
groovecoder 2024-09-03 17:07:18 +00:00
parent da6bd1bae8
commit 5c93f2f85b
8 changed files with 251 additions and 43 deletions

View File

@ -2503,6 +2503,9 @@ pref("signon.management.page.fileImport.enabled", true);
// "enabled" - user opted in to the feature.
// "disabled" - user opted out of the feature.
pref("signon.firefoxRelay.feature", "available");
// Should Firefox show Relay to all browsers, or only those signed-in to FxA?
// Keep it hidden from about:config for now.
// pref("signon.firefoxRelay.showToAllBrowsers", false);
pref("signon.management.page.breach-alerts.enabled", true);
pref("signon.management.page.vulnerable-passwords.enabled", true);
pref("signon.management.page.sort", "name");

View File

@ -95,6 +95,7 @@
<link rel="localization" href="browser/sidebar.ftl"/>
<link rel="localization" href="preview/profiles.ftl"/>
<link rel="localization" href="preview/onboarding.ftl"/>
<link rel="localization" href="preview/firefoxRelayToAllBrowsers.ftl"/>
<title data-l10n-id="browser-main-window-title"></title>

View File

@ -253,6 +253,19 @@
</popupnotificationcontent>
</popupnotification>
<popupnotification id="fxa-and-relay-integration-offer-notification" hidden="true">
<popupnotificationcontent orient="vertical">
<html:div>
<html:p data-l10n-id="firefox-relay-offer-why-to-use-relay"></html:p>
<html:p id="firefox-relay-offer-what-fxa-and-relay-provides" data-l10n-id="firefox-relay-offer-what-fxa-and-relay-provides"></html:p>
<html:p id="firefox-fxa-and-relay-offer-legal-notice" data-l10n-id="firefox-relay-offer-legal-notice">
<label id="firefox-fxa-and-relay-offer-tos-url" is="text-link" data-l10n-name="tos-url"/>
<label id="firefox-fxa-and-relay-offer-privacy-url" is="text-link" data-l10n-name="privacy-url"/>
</html:p>
</html:div>
</popupnotificationcontent>
</popupnotification>
<popupnotification id="relay-integration-reuse-masks-notification" hidden="true">
<popupnotificationcontent orient="vertical">
<html:div>

View File

@ -0,0 +1,15 @@
# 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/.
firefox-relay-offer-what-fxa-and-relay-provides = An account unlocks access to more privacy-protecting products. All emails sent to your email masks will be forwarded to your real email address (unless you decide to block them).
firefox-relay-and-fxa-opt-in-confirmation-enable-button =
.label = Sign in to { -brand-product-name } and use mask
.accesskey = S
firefox-relay-and-fxa-opt-in-confirmation-disable =
.label = Dont show me this again
.accesskey = D
firefox-relay-and-fxa-opt-in-confirmation-postpone =
.label = Not now
.accesskey = N

View File

@ -19,6 +19,7 @@
browser (%browser/**/*.ftl)
preview/profiles.ftl (../components/profiles/content/profiles.ftl)
preview/backupSettings.ftl (../locales-preview/backupSettings.ftl)
preview/firefoxRelayToAllBrowsers.ftl (../locales-preview/firefoxRelayToAllBrowsers.ftl)
@AB_CD@.jar:
% locale browser @AB_CD@ %locale/browser/

View File

@ -247,6 +247,8 @@ skip-if = ["os == 'android'"]
["browser_relay_telemetry.js"]
["browser_relay_visibility.js"]
["browser_telemetry_SignUpFormRuleset.js"]
["browser_test_changeContentInputValue.js"]

View File

@ -0,0 +1,41 @@
const TEST_URL_PATH = `https://example.org${DIRECTORY_PATH}form_basic_signup.html`;
let gRelayACOptionsTitles;
async function getRelayACItem(browser) {
const popup = document.getElementById("PopupAutoComplete");
await openACPopup(popup, browser, "#form-basic-username");
const popupItemAttributeValue = document
.querySelector("richlistitem")
.getAttribute("ac-value");
const relayItemInPopup = gRelayACOptionsTitles.some(
title => title.value === popupItemAttributeValue
);
return relayItemInPopup;
}
add_setup(async function () {
gRelayACOptionsTitles = await new Localization([
"browser/firefoxRelay.ftl",
"toolkit/branding/brandings.ftl",
]).formatMessages([
"firefox-relay-opt-in-title-1",
"firefox-relay-use-mask-title",
]);
});
add_task(async function test_popup_option_default_not_visible() {
await BrowserTestUtils.withNewTab(TEST_URL_PATH, async browser => {
const relayItemInPopup = await getRelayACItem(browser);
Assert.ok(!relayItemInPopup);
});
});
add_task(async function test_popup_option_visible_when_enabled_for_all() {
await SpecialPowers.pushPrefEnv({
set: [["signon.firefoxRelay.showToAllBrowsers", true]],
});
await BrowserTestUtils.withNewTab(TEST_URL_PATH, async browser => {
const relayItemInPopup = await getRelayACItem(browser);
Assert.ok(relayItemInPopup);
});
});

View File

@ -31,6 +31,7 @@ const gConfig = (function () {
"signon.firefoxRelay.manage_url"
),
relayFeaturePref: "signon.firefoxRelay.feature",
showToAllBrowsersPref: "signon.firefoxRelay.showToAllBrowsers",
termsOfServiceUrl: Services.urlFormatter.formatURLPref(
"signon.firefoxRelay.terms_of_service_url"
),
@ -48,10 +49,14 @@ ChromeUtils.defineLazyGetter(lazy, "fxAccounts", () =>
"resource://gre/modules/FxAccounts.sys.mjs"
).getFxAccountsSingleton()
);
ChromeUtils.defineLazyGetter(lazy, "fxAccountsCommon", () =>
ChromeUtils.importESModule("resource://gre/modules/FxAccountsCommon.sys.mjs")
);
ChromeUtils.defineLazyGetter(lazy, "strings", function () {
return new Localization([
"branding/brand.ftl",
"browser/firefoxRelay.ftl",
"preview/firefoxRelayToAllBrowsers.ftl",
"toolkit/branding/brandings.ftl",
]);
});
@ -200,6 +205,33 @@ async function formatMessages(...ids) {
});
}
function getPostpone(postponeStrings, feature) {
return {
label: postponeStrings.label,
accessKey: postponeStrings.accesskey,
dismiss: true,
callback() {
lazy.log.info(
"user decided not to decide about Firefox Relay integration"
);
feature.markAsOffered();
FirefoxRelayTelemetry.recordRelayOptInPanelEvent("postponed", gFlowId);
},
};
}
function getDisableIntegration(disableStrings, feature) {
return {
label: disableStrings.label,
accessKey: disableStrings.accesskey,
dismiss: true,
callback() {
lazy.log.info("user opted out from Firefox Relay integration");
feature.markAsDisabled();
FirefoxRelayTelemetry.recordRelayOptInPanelEvent("disabled", gFlowId);
},
};
}
async function showReusableMasksAsync(browser, origin, error) {
const [reusableMasks, status] = await getReusableMasksAsync(browser, origin);
if (!reusableMasks) {
@ -393,8 +425,9 @@ class RelayOffered {
if (
!hasInput &&
isSignup(scenarioName) &&
(await hasFirefoxAccountAsync()) &&
!Services.prefs.prefIsLocked("signon.firefoxRelay.feature")
!Services.prefs.prefIsLocked(gConfig.relayFeaturePref) &&
((await hasFirefoxAccountAsync()) ||
Services.prefs.getBoolPref(gConfig.showToAllBrowsersPref, false))
) {
const [title, subtitle] = await formatMessages(
"firefox-relay-opt-in-title-1",
@ -450,10 +483,132 @@ class RelayOffered {
async offerRelayIntegration(feature, browser, origin) {
const fxaUser = await lazy.fxAccounts.getSignedInUser();
if (!fxaUser) {
return null;
return this.offerRelayIntegrationToSignedOutUser(
feature,
browser,
origin
);
}
return this.offerRelayIntegrationToFxAUser(
feature,
browser,
origin,
fxaUser
);
}
async offerRelayIntegrationToSignedOutUser(feature, browser, origin) {
const { PopupNotifications } = browser.ownerGlobal.wrappedJSObject;
let fillUsername;
const fillUsernamePromise = new Promise(
resolve => (fillUsername = resolve)
);
const [enableStrings, disableStrings, postponeStrings] =
await formatMessages(
"firefox-relay-and-fxa-opt-in-confirmation-enable-button",
"firefox-relay-and-fxa-opt-in-confirmation-disable",
"firefox-relay-and-fxa-opt-in-confirmation-postpone"
);
const enableIntegration = {
label: enableStrings.label,
accessKey: enableStrings.accesskey,
dismiss: true,
callback: async () => {
lazy.log.info(
"user opted in to Mozilla account and Firefox Relay integration"
);
// Capture the flowId here since async operations might take some time to resolve
// and by then gFlowId might have another value
const flowId = gFlowId;
// Capture the selected tab panel ID so we can come back to it after the user finishes FXA sign-in
const tabPanelId = browser.ownerGlobal.gBrowser.selectedTab.linkedPanel;
// TODO: add some visual treatment to the tab and/or the form field to indicate to the user
// that they need to complete sign-in to receive a mask
// Add an observer for ONVERIFIED_NOTIFICATION
// to detect when user verifies their email during FXA sign-in
// TODO: handle existing FXA users who don't have to verify email, but simply have to complete the login
const obs = async (_subject, _topic) => {
// Remove the observer to prevent it from running again
Services.obs.removeObserver(
obs,
lazy.fxAccountsCommon.ONVERIFIED_NOTIFICATION
);
// Go back to the tab with the form that started the FXA sign-in flow
const tabToFocus = Array.from(browser.ownerGlobal.gBrowser.tabs).find(
tab => tab.linkedPanel === tabPanelId
);
if (!tabToFocus) {
// If the tab has been closed, return
// TODO: figure out the real UX here?
return;
}
browser.ownerGlobal.gBrowser.selectedTab = tabToFocus;
// Create the relay user, mark feature enabled, fill in the username field with a mask
if (await this.notifyServerTermsAcceptedAsync(browser)) {
feature.markAsEnabled();
FirefoxRelayTelemetry.recordRelayOptInPanelEvent("enabled", flowId);
fillUsername(await generateUsernameAsync(browser, origin));
}
};
Services.obs.addObserver(
obs,
lazy.fxAccountsCommon.ONVERIFIED_NOTIFICATION
);
// Open tab to sign up for FxA and Relay
const fxaUrl =
await lazy.fxAccounts.constructor.config.promiseConnectAccountURI(
"relay_integration",
{
service: "relay",
}
);
browser.ownerGlobal.openWebLinkIn(fxaUrl, "tab");
},
};
const postpone = getPostpone(postponeStrings, feature);
const disableIntegration = getDisableIntegration(disableStrings, feature);
let notification;
feature.markAsOffered();
notification = PopupNotifications.show(
browser,
"fxa-and-relay-integration-offer",
"", // content is provided after popup shown
"password-notification-icon",
enableIntegration,
[postpone, disableIntegration],
{
autofocus: true,
removeOnDismissal: true,
learnMoreURL: gConfig.learnMoreURL,
eventCallback: event => {
const document = notification.owner.panel.ownerDocument;
switch (event) {
case "shown":
customizeNotificationHeader(notification);
document.getElementById("firefox-relay-offer-tos-url").href =
gConfig.termsOfServiceUrl;
document.getElementById("firefox-relay-offer-privacy-url").href =
gConfig.privacyPolicyUrl;
FirefoxRelayTelemetry.recordRelayOptInPanelEvent(
"shown",
gFlowId
);
break;
}
},
}
);
return fillUsernamePromise;
}
async offerRelayIntegrationToFxAUser(feature, browser, origin, fxaUser) {
const { PopupNotifications } = browser.ownerGlobal.wrappedJSObject;
let fillUsername;
const fillUsernamePromise = new Promise(
@ -481,28 +636,8 @@ class RelayOffered {
}
},
};
const postpone = {
label: postponeStrings.label,
accessKey: postponeStrings.accesskey,
dismiss: true,
callback() {
lazy.log.info(
"user decided not to decide about Firefox Relay integration"
);
feature.markAsOffered();
FirefoxRelayTelemetry.recordRelayOptInPanelEvent("postponed", gFlowId);
},
};
const disableIntegration = {
label: disableStrings.label,
accessKey: disableStrings.accesskey,
dismiss: true,
callback() {
lazy.log.info("user opted out from Firefox Relay integration");
feature.markAsDisabled();
FirefoxRelayTelemetry.recordRelayOptInPanelEvent("disabled", gFlowId);
},
};
const postpone = getPostpone(postponeStrings, feature);
const disableIntegration = getDisableIntegration(disableStrings, feature);
let notification;
feature.markAsOffered();
notification = PopupNotifications.show(
@ -517,26 +652,22 @@ class RelayOffered {
removeOnDismissal: true,
learnMoreURL: gConfig.learnMoreURL,
eventCallback: event => {
const document = notification.owner.panel.ownerDocument;
switch (event) {
case "shown":
customizeNotificationHeader(notification);
const document = notification.owner.panel.ownerDocument;
const tosLink = document.getElementById(
"firefox-relay-offer-tos-url"
);
tosLink.href = gConfig.termsOfServiceUrl;
const privacyLink = document.getElementById(
"firefox-relay-offer-privacy-url"
);
privacyLink.href = gConfig.privacyPolicyUrl;
const content = document.querySelector(
`popupnotification[id=${notification.id}-notification] popupnotificationcontent`
);
const line3 = content.querySelector(
"[id=firefox-relay-offer-what-relay-provides]"
);
document.getElementById("firefox-relay-offer-tos-url").href =
gConfig.termsOfServiceUrl;
document.getElementById("firefox-relay-offer-privacy-url").href =
gConfig.privacyPolicyUrl;
document.l10n.setAttributes(
line3,
document
.querySelector(
`popupnotification[id=${notification.id}-notification] popupnotificationcontent`
)
.querySelector(
"[id=firefox-relay-offer-what-relay-provides]"
),
"firefox-relay-offer-what-relay-provides",
{
useremail: fxaUser.email,
@ -561,7 +692,8 @@ class RelayEnabled {
if (
!hasInput &&
isSignup(scenarioName) &&
(await hasFirefoxAccountAsync())
((await hasFirefoxAccountAsync()) ||
Services.prefs.getBoolPref(gConfig.showToAllBrowsersPref, false))
) {
const [title] = await formatMessages("firefox-relay-use-mask-title");
yield new ParentAutocompleteOption(