Bug 1627337 - Update lockwise card display and copy. r=ewright,fluent-reviewers,flod

Depends on D72693

Differential Revision: https://phabricator.services.mozilla.com/D72694
This commit is contained in:
prathiksha 2020-05-07 09:23:41 +00:00
parent 9ed228ef9c
commit a4ed2b48f2
12 changed files with 551 additions and 190 deletions

View File

@ -139,20 +139,17 @@ class AboutProtectionsParent extends JSWindowActorParent {
/**
* Retrieves login data for the user.
*
* @return {{ hasFxa: Boolean,
* @return {{
* numLogins: Number,
* mobileDeviceConnected: Boolean }}
* The login data.
*/
async getLoginData() {
if (gTestOverride && "getLoginData" in gTestOverride) {
return gTestOverride.getLoginData();
}
let hasFxa = false;
try {
if ((hasFxa = !!(await fxAccounts.getSignedInUser()))) {
if (await fxAccounts.getSignedInUser()) {
await fxAccounts.device.refreshDeviceList();
}
} catch (e) {
@ -170,7 +167,6 @@ class AboutProtectionsParent extends JSWindowActorParent {
).length;
return {
hasFxa,
numLogins: userFacingLogins,
mobileDeviceConnected,
};
@ -360,7 +356,9 @@ class AboutProtectionsParent extends JSWindowActorParent {
return this.getMonitorData();
case "FetchUserLoginsData":
return this.getLoginData();
let { potentiallyBreachedLogins } = await this.getMonitorData();
let loginsData = await this.getLoginData();
return { ...loginsData, potentiallyBreachedLogins };
case "ClearMonitorCache":
this.monitorResponse = null;

View File

@ -23,15 +23,22 @@ export default class LockwiseCard {
* Initializes message listeners/senders.
*/
init() {
const openAboutLoginsButton = this.doc.getElementById(
"open-about-logins-button"
const savePasswordsButton = this.doc.getElementById(
"save-passwords-button"
);
savePasswordsButton.addEventListener(
"click",
this.openAboutLogins.bind(this)
);
const managePasswordsButton = this.doc.getElementById(
"manage-passwords-button"
);
managePasswordsButton.addEventListener(
"click",
this.openAboutLogins.bind(this)
);
openAboutLoginsButton.addEventListener("click", () => {
this.doc.sendTelemetryEvent("click", "lw_open_button");
RPMSendAsyncMessage("OpenAboutLogins");
});
// Attach link to Firefox Lockwise ios mobile app.
const androidLockwiseAppLink = this.doc.getElementById(
"lockwise-android-inline-link"
);
@ -56,15 +63,40 @@ export default class LockwiseCard {
});
}
openAboutLogins() {
const lockwiseCard = this.doc.querySelector(".lockwise-card");
if (lockwiseCard.classList.contains("has-logins")) {
if (lockwiseCard.classList.contains("breached-logins")) {
this.doc.sendTelemetryEvent(
"click",
"lw_open_button",
"manage_breached_passwords"
);
} else if (lockwiseCard.classList.contains("no-breached-logins")) {
this.doc.sendTelemetryEvent(
"click",
"lw_open_button",
"manage_passwords"
);
}
} else if (lockwiseCard.classList.contains("no-logins")) {
this.doc.sendTelemetryEvent("click", "lw_open_button", "save_passwords");
}
RPMSendAsyncMessage("OpenAboutLogins");
}
buildContent(data) {
const { hasFxa, numLogins } = data;
const isLoggedIn = numLogins > 0 || hasFxa;
const { numLogins, potentiallyBreachedLogins } = data;
const hasLogins = numLogins > 0;
const title = this.doc.getElementById("lockwise-title");
const headerContent = this.doc.getElementById("lockwise-header-content");
const headerContent = this.doc.querySelector(
"#lockwise-header-content span"
);
const lockwiseBodyContent = this.doc.getElementById(
"lockwise-body-content"
);
const cardBody = this.doc.querySelector(".lockwise-card .card-body");
const lockwiseCard = this.doc.querySelector(".card.lockwise-card");
const exitIcon = lockwiseBodyContent.querySelector(".exit-icon");
// User has closed the lockwise promotion, hide it and don't show again.
@ -74,16 +106,18 @@ export default class LockwiseCard {
cardBody.classList.add("hidden");
});
if (isLoggedIn) {
let container = lockwiseBodyContent.querySelector(".has-logins");
container.classList.remove("hidden");
title.setAttribute("data-l10n-id", "lockwise-title-logged-in");
if (hasLogins) {
lockwiseCard.classList.remove("no-logins");
lockwiseCard.classList.add("has-logins");
title.setAttribute("data-l10n-id", "lockwise-title-logged-in2");
headerContent.setAttribute(
"data-l10n-id",
"lockwise-header-content-logged-in"
);
this.renderContentForLoggedInUser(container, numLogins);
this.renderContentForLoggedInUser(numLogins, potentiallyBreachedLogins);
} else {
lockwiseCard.classList.remove("has-logins");
lockwiseCard.classList.add("no-logins");
if (
!RPMGetBoolPref(
"browser.contentblocking.report.hide_lockwise_app",
@ -104,36 +138,49 @@ export default class LockwiseCard {
}
/**
* Displays the number of stored logins for a user.
* Displays strings indicating stored logins for a user.
*
* @param {Element} container
* The containing element for the content.
* @param {Number} storedLogins
* The number of browser-stored logins.
* @param {Number} potentiallyBreachedLogins
* The number of potentially breached logins.
*/
renderContentForLoggedInUser(container, storedLogins) {
const lockwiseCardBody = this.doc.querySelector(
".card.lockwise-card .card-body"
renderContentForLoggedInUser(storedLogins, potentiallyBreachedLogins) {
const lockwiseScannedText = this.doc.getElementById(
"lockwise-scanned-text"
);
lockwiseCardBody.classList.remove("hidden");
const lockwiseScannedIcon = this.doc.getElementById(
"lockwise-scanned-icon"
);
const lockwiseCard = this.doc.querySelector(".card.lockwise-card");
// Set the text for number of stored logins.
const numberOfLoginsBlock = container.querySelector(
".number-of-logins.block"
);
numberOfLoginsBlock.textContent = storedLogins;
const lockwisePasswordsStored = this.doc.getElementById(
"lockwise-passwords-stored"
);
lockwisePasswordsStored.setAttribute(
"data-l10n-args",
JSON.stringify({ count: storedLogins })
);
lockwisePasswordsStored.setAttribute(
"data-l10n-id",
"lockwise-passwords-stored"
);
if (potentiallyBreachedLogins) {
document.l10n.setAttributes(
lockwiseScannedText,
"lockwise-scanned-text-breached-logins",
{
count: potentiallyBreachedLogins,
}
);
lockwiseScannedIcon.setAttribute(
"src",
"chrome://browser/skin/protections/breached-password.svg"
);
lockwiseCard.classList.add("breached-logins");
} else {
document.l10n.setAttributes(
lockwiseScannedText,
"lockwise-scanned-text-no-breached-logins",
{
count: storedLogins,
}
);
lockwiseScannedIcon.setAttribute(
"src",
"chrome://browser/skin/protections/resolved-breach.svg"
);
lockwiseCard.classList.add("no-breached-logins");
}
const howItWorksLink = this.doc.getElementById("lockwise-how-it-works");
howItWorksLink.href = HOW_IT_WORKS_URL_PREF;

View File

@ -81,8 +81,9 @@ h2 {
#manage-protections,
.card-header > button,
#save-passwords-button,
#get-proxy-extension-link,
#open-about-logins-button,
#manage-passwords-button,
#sign-up-for-monitor-link {
grid-area: 1 / 5 / 1 / -1;
margin: 0;
@ -93,6 +94,10 @@ h2 {
line-height: initial;
}
.lockwise-card.has-logins .wrapper div:nth-child(1) {
grid-area: 1 / 1 / 1 / 6;
}
.card:not(.has-logins) .wrapper div:nth-child(1),
.etp-card.custom-not-blocking .wrapper div:nth-child(1) {
grid-area: 1 / 1 / 1 / 5;
@ -118,6 +123,10 @@ a.hidden,
.lockwise-card.hidden,
#lockwise-body-content .has-logins.hidden,
#lockwise-body-content .no-logins.hidden,
.lockwise-card.no-logins #lockwise-how-it-works,
.lockwise-card.no-logins .lockwise-scanned-wrapper,
.lockwise-card.no-logins #manage-passwords-button,
.lockwise-card.has-logins #save-passwords-button,
.monitor-card.hidden,
.monitor-card.no-logins .card-body,
.monitor-card.no-logins #monitor-header-content a,
@ -605,11 +614,6 @@ label[for="tab-cryptominer"]:hover ~ #highlight-hover {
display: block;
}
.passwords-stored-text {
width: max-content;
padding-inline-start: 4px;
}
.block {
background-color: var(--grey-60);
border-radius: 4px;
@ -625,6 +629,27 @@ label[for="tab-cryptominer"]:hover ~ #highlight-hover {
margin-inline-start: 10px;
}
.lockwise-scanned-wrapper {
display: grid;
grid-template-columns: 7% auto;
margin-block-start: 24px;
grid-area: 2 / 1 / 2 / 5;
padding-bottom: 1.7em;
}
#lockwise-scanned-text {
margin-inline-end: 15px;
}
#lockwise-scanned-icon {
margin-top: 7px;
}
#manage-passwords-button {
grid-area: 2 / 5 / 2 / 7;
margin-inline-end: 15px;
}
/* Monitor card */
#monitor-body-content .monitor-breached-passwords {
@ -653,6 +678,7 @@ label[for="tab-cryptominer"]:hover ~ #highlight-hover {
display: block;
}
.lockwise-card #lockwise-header-content > a,
.monitor-card #monitor-header-content > a {
display: block;
margin-block-start: 5px;

View File

@ -213,10 +213,20 @@
<!-- Insert Lockwise card title here. -->
</h2>
<p id="lockwise-header-content" class="content">
<!-- Insert Lockwise header content here. -->
<span>
<!-- Insert Lockwise header content here. -->
</span>
<a target="_blank" id="lockwise-how-it-works" data-l10n-id="lockwise-how-it-works-link" href=""></a>
</p>
</div>
<button id="open-about-logins-button" class="primary" data-l10n-id="protection-report-view-logins-button"></button>
<button id="save-passwords-button" class="primary" data-l10n-id="protection-report-save-passwords-button"></button>
<div class="lockwise-scanned-wrapper">
<img class="icon-small" id="lockwise-scanned-icon" />
<span id="lockwise-scanned-text" class="content">
<!-- Display message for stored logins here. -->
</span>
</div>
<button id="manage-passwords-button" class="primary" data-l10n-id="protection-report-manage-passwords-button"></button>
</div>
</div>
<div class="card-body hidden">
@ -235,18 +245,6 @@
</p>
</span>
</div>
<div class="has-logins hidden">
<span class="number-of-logins block">
<!-- Display number of stored logins here. -->
</span>
<span class="passwords-stored-text">
<img class="icon-small" src= "chrome://browser/skin/login.svg"/>
<span id="lockwise-passwords-stored">
<!-- Display message for stored logins here. -->
<a target="_blank" id="lockwise-how-it-works" data-l10n-name="lockwise-how-it-works" href=""></a>
</span>
</span>
</div>
</div>
</div>
</section>

View File

@ -4,114 +4,277 @@
"use strict";
const nsLoginInfo = new Components.Constructor(
"@mozilla.org/login-manager/loginInfo;1",
Ci.nsILoginInfo,
"init"
const { AboutProtectionsParent } = ChromeUtils.import(
"resource:///actors/AboutProtectionsParent.jsm"
);
const ABOUT_LOGINS_URL = "about:logins";
const TEST_LOGIN1 = new nsLoginInfo(
"https://example.com/",
"https://example.com/",
null,
"user1",
"pass1",
"username",
"password"
);
const TEST_LOGIN2 = new nsLoginInfo(
"https://2.example.com/",
"https://2.example.com/",
null,
"user2",
"pass2",
"username",
"password"
);
add_task(async function() {
add_task(async function testNoLoginsLockwiseCardUI() {
let tab = await BrowserTestUtils.openNewForegroundTab({
url: "about:protections",
gBrowser,
});
let aboutLoginsPromise = BrowserTestUtils.waitForNewTab(
gBrowser,
ABOUT_LOGINS_URL
);
info("Check that the correct content is displayed for non-logged in users.");
info(
"Check that the correct lockwise card content is displayed for non-logged in users."
);
await SpecialPowers.spawn(tab.linkedBrowser, [], async function() {
await ContentTaskUtils.waitForCondition(() => {
const noLogins = content.document.querySelector(
"#lockwise-body-content .no-logins"
);
return ContentTaskUtils.is_visible(noLogins);
}, "Lockwise card for user with no logins is shown.");
const lockwiseCard = content.document.querySelector(".lockwise-card");
return ContentTaskUtils.is_visible(lockwiseCard);
}, "Lockwise card for user with no logins is visible.");
const noLoginsContent = content.document.querySelector(
const lockwiseTitle = content.document.querySelector("#lockwise-title");
is(
lockwiseTitle.textContent,
"Never forget a password again",
"Correct lockwise title is shown"
);
const lockwiseHowItWorks = content.document.querySelector(
"#lockwise-how-it-works"
);
ok(
ContentTaskUtils.is_hidden(lockwiseHowItWorks),
"How it works link is hidden"
);
const lockwiseHeaderString = content.document.querySelector(
"#lockwise-header-content span"
).textContent;
ok(
lockwiseHeaderString.includes(
"Firefox Lockwise securely stores your passwords in your browser"
),
"Correct lockwise header string is shown"
);
const lockwiseScannedWrapper = content.document.querySelector(
".lockwise-scanned-wrapper"
);
ok(
ContentTaskUtils.is_hidden(lockwiseScannedWrapper),
"Lockwise scanned wrapper is hidden"
);
const lockwiseBodyContent = content.document.querySelector(
"#lockwise-body-content .no-logins"
);
const hasLoginsContent = content.document.querySelector(
"#lockwise-body-content .has-logins"
ok(
ContentTaskUtils.is_visible(lockwiseBodyContent),
"Lockwise app content is visible"
);
ok(
ContentTaskUtils.is_visible(noLoginsContent),
"Content for user with no logins is shown."
const managePasswordsButton = content.document.querySelector(
"#manage-passwords-button"
);
ok(
ContentTaskUtils.is_hidden(hasLoginsContent),
"Content for user with logins is hidden."
ContentTaskUtils.is_hidden(managePasswordsButton),
"Manage passwords button is hidden"
);
const savePasswordsButton = content.document.querySelector(
"#save-passwords-button"
);
ok(
ContentTaskUtils.is_visible(savePasswordsButton),
"Save passwords button is visible in the header"
);
info(
"Click on the save passwords button and check that it opens about:logins in a new tab"
);
savePasswordsButton.click();
});
let loginsTab = await aboutLoginsPromise;
info("about:logins was successfully opened in a new tab");
gBrowser.removeTab(loginsTab);
gBrowser.removeTab(tab);
});
info("Add a login and check that content for a logged in user is displayed.");
add_task(async function testLockwiseCardUIWithLogins() {
let tab = await BrowserTestUtils.openNewForegroundTab({
url: "about:protections",
gBrowser,
});
let aboutLoginsPromise = BrowserTestUtils.waitForNewTab(
gBrowser,
ABOUT_LOGINS_URL
);
info(
"Add a login and check that lockwise card content for a logged in user is displayed correctly"
);
Services.logins.addLogin(TEST_LOGIN1);
await reloadTab(tab);
await SpecialPowers.spawn(tab.linkedBrowser, [], async function() {
await ContentTaskUtils.waitForCondition(() => {
const hasLogins = content.document.querySelector(
"#lockwise-body-content .has-logins"
);
const hasLogins = content.document.querySelector(".lockwise-card");
return ContentTaskUtils.is_visible(hasLogins);
}, "Lockwise card for user with logins is shown.");
}, "Lockwise card for user with logins is visible");
const noLoginsContent = content.document.querySelector(
const lockwiseTitle = content.document.querySelector("#lockwise-title");
is(
lockwiseTitle.textContent,
"Password Management",
"Correct lockwise title is shown"
);
const lockwiseHowItWorks = content.document.querySelector(
"#lockwise-how-it-works"
);
ok(
ContentTaskUtils.is_visible(lockwiseHowItWorks),
"How it works link is visible"
);
const lockwiseHeaderString = content.document.querySelector(
"#lockwise-header-content span"
).textContent;
ok(
lockwiseHeaderString.includes(
"Securely store and sync your passwords to all your devices"
),
"Correct lockwise header string is shown"
);
const lockwiseScannedWrapper = content.document.querySelector(
".lockwise-scanned-wrapper"
);
ok(
ContentTaskUtils.is_visible(lockwiseScannedWrapper),
"Lockwise scanned wrapper is visible"
);
const lockwiseScannedText = content.document.querySelector(
"#lockwise-scanned-text"
).textContent;
is(
lockwiseScannedText,
"1 password stored securely.",
"Correct lockwise scanned text is shown"
);
const lockwiseBodyContent = content.document.querySelector(
"#lockwise-body-content .no-logins"
);
const hasLoginsContent = content.document.querySelector(
"#lockwise-body-content .has-logins"
);
const numberOfLogins = hasLoginsContent.querySelector(
".number-of-logins.block"
ok(
ContentTaskUtils.is_hidden(lockwiseBodyContent),
"Lockwise app content is hidden"
);
ok(
ContentTaskUtils.is_hidden(noLoginsContent),
"Content for user with no logins is hidden."
const savePasswordsButton = content.document.querySelector(
"#save-passwords-button"
);
ok(
ContentTaskUtils.is_visible(hasLoginsContent),
"Content for user with logins is shown."
ContentTaskUtils.is_hidden(savePasswordsButton),
"Save passwords button is hidden"
);
is(numberOfLogins.textContent, 1, "One stored login should be displayed");
const managePasswordsButton = content.document.querySelector(
"#manage-passwords-button"
);
ok(
ContentTaskUtils.is_visible(managePasswordsButton),
"Manage passwords button is visible"
);
info(
"Click on the manage passwords button and check that it opens about:logins in a new tab"
);
managePasswordsButton.click();
});
let loginsTab = await aboutLoginsPromise;
info("about:logins was successfully opened in a new tab");
gBrowser.removeTab(loginsTab);
info(
"Add another login and check the number of stored logins is updated after reload."
"Add another login and check that the scanned text about stored logins is updated after reload."
);
Services.logins.addLogin(TEST_LOGIN2);
await reloadTab(tab);
await SpecialPowers.spawn(tab.linkedBrowser, [], async function() {
await ContentTaskUtils.waitForCondition(() => {
const hasLogins = content.document.querySelector(".has-logins");
return ContentTaskUtils.is_visible(hasLogins);
}, "Lockwise card for user with logins is shown.");
const numberOfLogins = content.document.querySelector(
"#lockwise-body-content .has-logins .number-of-logins.block"
const lockwiseScannedText = content.document.querySelector(
"#lockwise-scanned-text"
).textContent;
ContentTaskUtils.waitForCondition(
() =>
lockwiseScannedText.textContent ==
"Your passwords are being stored securely.",
"Correct lockwise scanned text is shown"
);
});
is(numberOfLogins.textContent, 2, "Two stored logins should be displayed");
Services.logins.removeLogin(TEST_LOGIN1);
Services.logins.removeLogin(TEST_LOGIN2);
gBrowser.removeTab(tab);
});
add_task(async function testLockwiseCardUIWithBreachedLogins() {
info(
"Add a breached login and test that the lockwise scanned text is displayed correctly"
);
let tab = await BrowserTestUtils.openNewForegroundTab({
url: "about:protections",
gBrowser,
});
Services.logins.addLogin(TEST_LOGIN1);
info("Mock monitor data with a breached login to test the Lockwise UI");
AboutProtectionsParent.setTestOverride(mockGetMonitorDataForLockwiseCard(1));
await reloadTab(tab);
await SpecialPowers.spawn(tab.linkedBrowser, [], async function() {
const lockwiseScannedText = content.document.querySelector(
"#lockwise-scanned-text"
);
ok(
ContentTaskUtils.is_visible(lockwiseScannedText),
"Lockwise scanned text is visible"
);
await ContentTaskUtils.waitForCondition(
() =>
lockwiseScannedText.textContent ==
"1 password may have been exposed in a data breach."
);
info("Correct lockwise scanned text is shown");
});
info(
"Mock monitor data with more than one breached logins to test the Lockwise UI"
);
AboutProtectionsParent.setTestOverride(mockGetMonitorDataForLockwiseCard(2));
await reloadTab(tab);
await SpecialPowers.spawn(tab.linkedBrowser, [], async function() {
const lockwiseScannedText = content.document.querySelector(
"#lockwise-scanned-text"
);
ok(
ContentTaskUtils.is_visible(lockwiseScannedText),
"Lockwise scanned text is visible"
);
await ContentTaskUtils.waitForCondition(
() =>
lockwiseScannedText.textContent ==
"2 passwords may have been exposed in a data breach."
);
info("Correct lockwise scanned text is shown");
});
AboutProtectionsParent.setTestOverride(null);
Services.logins.removeLogin(TEST_LOGIN1);
gBrowser.removeTab(tab);
});
add_task(async function testLockwiseCardPref() {
let tab = await BrowserTestUtils.openNewForegroundTab({
url: "about:protections",
gBrowser,
});
info("Disable showing the Lockwise card.");
@ -121,24 +284,18 @@ add_task(async function() {
);
await reloadTab(tab);
await SpecialPowers.spawn(tab.linkedBrowser, [], async function() {
const lockwiseCard = content.document.querySelector(".lockwise-card");
await ContentTaskUtils.waitForCondition(() => {
const lockwiseCard = content.document.querySelector(".lockwise-card");
return !lockwiseCard["data-enabled"];
}, "Lockwise card is not enabled.");
const lockwiseCard = content.document.querySelector(".lockwise-card");
ok(ContentTaskUtils.is_hidden(lockwiseCard), "Lockwise card is hidden.");
});
// set the pref back to displaying the card.
// Set the pref back to displaying the card.
Services.prefs.setBoolPref(
"browser.contentblocking.report.lockwise.enabled",
true
);
// remove logins
Services.logins.removeLogin(TEST_LOGIN1);
Services.logins.removeLogin(TEST_LOGIN2);
await BrowserTestUtils.removeTab(tab);
gBrowser.removeTab(tab);
});

View File

@ -7,21 +7,6 @@
const { AboutProtectionsParent } = ChromeUtils.import(
"resource:///actors/AboutProtectionsParent.jsm"
);
const nsLoginInfo = new Components.Constructor(
"@mozilla.org/login-manager/loginInfo;1",
Ci.nsILoginInfo,
"init"
);
const TEST_LOGIN1 = new nsLoginInfo(
"https://example.com/",
"https://example.com/",
null,
"user1",
"pass1",
"username",
"password"
);
let fakeDataWithNoError = {
monitoredEmails: 1,
@ -55,7 +40,6 @@ const mockGetMonitorAndLoginData = data => {
getMonitorData: async () => data,
getLoginData: () => {
return {
hasFxa: true,
numLogins: Services.logins.countLogins("", "", ""),
};
},

View File

@ -10,6 +10,10 @@ XPCOMUtils.defineLazyServiceGetter(
"nsITrackingDBService"
);
const { AboutProtectionsParent } = ChromeUtils.import(
"resource:///actors/AboutProtectionsParent.jsm"
);
const LOG = {
"https://1.example.com": [
[Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT, true, 1],
@ -177,6 +181,73 @@ add_task(async function checkTelemetryClickEvents() {
gBrowser,
});
// Add user logins.
Services.logins.addLogin(TEST_LOGIN1);
await reloadTab(tab);
await SpecialPowers.spawn(tab.linkedBrowser, [], async function() {
const managePasswordsButton = await ContentTaskUtils.waitForCondition(
() => {
return content.document.getElementById("manage-passwords-button");
},
"Manage passwords button exists"
);
ContentTaskUtils.waitForCondition(
ContentTaskUtils.is_visible(managePasswordsButton),
"manage passwords button is visible"
);
managePasswordsButton.click();
});
let events = await waitForTelemetryEventCount(4);
events = events.filter(
e =>
e[1] == "security.ui.protections" &&
e[2] == "click" &&
e[3] == "lw_open_button" &&
e[4] == "manage_passwords"
);
is(
events.length,
1,
`recorded telemetry for lw_open_button when there are no breached passwords`
);
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
// Add breached logins.
AboutProtectionsParent.setTestOverride(mockGetMonitorDataForLockwiseCard(4));
await reloadTab(tab);
await SpecialPowers.spawn(tab.linkedBrowser, [], async function() {
const managePasswordsButton = await ContentTaskUtils.waitForCondition(
() => {
return content.document.getElementById("manage-passwords-button");
},
"Manage passwords button exists"
);
ContentTaskUtils.waitForCondition(
ContentTaskUtils.is_visible(managePasswordsButton),
"manage passwords button is visible"
);
managePasswordsButton.click();
});
events = await waitForTelemetryEventCount(7);
events = events.filter(
e =>
e[1] == "security.ui.protections" &&
e[2] == "click" &&
e[3] == "lw_open_button" &&
e[4] == "manage_breached_passwords"
);
is(
events.length,
1,
`recorded telemetry for lw_open_button when there are breached passwords`
);
AboutProtectionsParent.setTestOverride(null);
Services.logins.removeLogin(TEST_LOGIN1);
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
await reloadTab(tab);
await SpecialPowers.spawn(tab.linkedBrowser, [], async function() {
// Show all elements, so we can click on them, even though our user is not logged in.
let hidden_elements = content.document.querySelectorAll(".hidden");
@ -184,23 +255,28 @@ add_task(async function checkTelemetryClickEvents() {
el.style.display = "block ";
}
const openAboutLogins = await ContentTaskUtils.waitForCondition(() => {
const savePasswordsButton = await ContentTaskUtils.waitForCondition(() => {
// Opens an extra tab
return content.document.getElementById("open-about-logins-button");
}, "openAboutLogins exists");
return content.document.getElementById("save-passwords-button");
}, "Save Passwords button exists");
openAboutLogins.click();
savePasswordsButton.click();
});
let events = await waitForTelemetryEventCount(2);
events = await waitForTelemetryEventCount(10);
events = events.filter(
e =>
e[1] == "security.ui.protections" &&
e[2] == "click" &&
e[3] == "lw_open_button"
e[3] == "lw_open_button" &&
e[4] == "save_passwords"
);
is(events.length, 1, `recorded telemetry for lw_open_button`);
is(
events.length,
1,
`recorded telemetry for lw_open_button when there are no stored passwords`
);
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
await SpecialPowers.spawn(tab.linkedBrowser, [], async function() {
const lockwiseAndroidAppLink = await ContentTaskUtils.waitForCondition(
@ -213,7 +289,7 @@ add_task(async function checkTelemetryClickEvents() {
lockwiseAndroidAppLink.click();
});
events = await waitForTelemetryEventCount(3);
events = await waitForTelemetryEventCount(11);
events = events.filter(
e =>
@ -232,7 +308,7 @@ add_task(async function checkTelemetryClickEvents() {
lockwiseReportLink.click();
});
events = await waitForTelemetryEventCount(4);
events = await waitForTelemetryEventCount(12);
events = events.filter(
e =>
@ -251,7 +327,7 @@ add_task(async function checkTelemetryClickEvents() {
openLockwise.click();
});
events = await waitForTelemetryEventCount(5);
events = await waitForTelemetryEventCount(13);
events = events.filter(
e =>
@ -269,7 +345,7 @@ add_task(async function checkTelemetryClickEvents() {
monitorReportLink.click();
});
events = await waitForTelemetryEventCount(6);
events = await waitForTelemetryEventCount(14);
events = events.filter(
e =>
@ -287,7 +363,7 @@ add_task(async function checkTelemetryClickEvents() {
monitorAboutLink.click();
});
events = await waitForTelemetryEventCount(7);
events = await waitForTelemetryEventCount(15);
events = events.filter(
e =>
@ -305,7 +381,7 @@ add_task(async function checkTelemetryClickEvents() {
signUpForMonitorLink.click();
});
events = await waitForTelemetryEventCount(8);
events = await waitForTelemetryEventCount(16);
events = events.filter(
e =>
@ -323,7 +399,7 @@ add_task(async function checkTelemetryClickEvents() {
socialLearnMoreLink.click();
});
events = await waitForTelemetryEventCount(9);
events = await waitForTelemetryEventCount(17);
events = events.filter(
e =>
@ -342,7 +418,7 @@ add_task(async function checkTelemetryClickEvents() {
cookieLearnMoreLink.click();
});
events = await waitForTelemetryEventCount(10);
events = await waitForTelemetryEventCount(18);
events = events.filter(
e =>
@ -361,7 +437,7 @@ add_task(async function checkTelemetryClickEvents() {
trackerLearnMoreLink.click();
});
events = await waitForTelemetryEventCount(11);
events = await waitForTelemetryEventCount(19);
events = events.filter(
e =>
@ -387,7 +463,7 @@ add_task(async function checkTelemetryClickEvents() {
fingerprinterLearnMoreLink.click();
});
events = await waitForTelemetryEventCount(12);
events = await waitForTelemetryEventCount(20);
events = events.filter(
e =>
@ -413,7 +489,7 @@ add_task(async function checkTelemetryClickEvents() {
cryptominerLearnMoreLink.click();
});
events = await waitForTelemetryEventCount(13);
events = await waitForTelemetryEventCount(21);
events = events.filter(
e =>
@ -436,7 +512,7 @@ add_task(async function checkTelemetryClickEvents() {
lockwiseIOSAppLink.click();
});
events = await waitForTelemetryEventCount(14);
events = await waitForTelemetryEventCount(22);
events = events.filter(
e =>
@ -461,7 +537,7 @@ add_task(async function checkTelemetryClickEvents() {
mobileAppLink.click();
});
events = await waitForTelemetryEventCount(15);
events = await waitForTelemetryEventCount(23);
events = events.filter(
e =>
e[1] == "security.ui.protections" &&
@ -471,10 +547,8 @@ add_task(async function checkTelemetryClickEvents() {
is(events.length, 1, `recorded telemetry for mobile_app_link`);
await BrowserTestUtils.removeTab(tab);
// We open two extra tabs with the click events.
// We open one extra tabs with the click event.
// 1. Monitor's "Viewed Saved Logins" link (goes to about:logins)
// 2. Lockwise's "Open Nightly" button (goes to about:logins)
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
});

View File

@ -6,6 +6,32 @@
"use strict";
const nsLoginInfo = new Components.Constructor(
"@mozilla.org/login-manager/loginInfo;1",
Ci.nsILoginInfo,
"init"
);
const TEST_LOGIN1 = new nsLoginInfo(
"https://example.com/",
"https://example.com/",
null,
"user1",
"pass1",
"username",
"password"
);
const TEST_LOGIN2 = new nsLoginInfo(
"https://2.example.com/",
"https://2.example.com/",
null,
"user2",
"pass2",
"username",
"password"
);
async function reloadTab(tab) {
const tabReloaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
gBrowser.reloadTab(tab);
@ -17,10 +43,31 @@ const mockGetLoginDataWithSyncedDevices = (mobileDeviceConnected = false) => {
return {
getLoginData: () => {
return {
hasFxa: true,
numLogins: Services.logins.countLogins("", "", ""),
mobileDeviceConnected,
};
},
};
};
// Used to replace AboutProtectionsHandler.getMonitorData in front-end tests.
const mockGetMonitorDataForLockwiseCard = (
potentiallyBreachedLogins = 0,
error = false
) => {
return {
getMonitorData: () => {
if (error) {
return { error };
}
return {
monitoredEmails: 1,
numBreaches: 3,
passwords: 8,
potentiallyBreachedLogins,
error,
};
},
};
};

View File

@ -73,24 +73,36 @@ mobile-app-card-content = Use the mobile browser with built-in protection agains
mobile-app-links = { -brand-product-name } Browser for <a data-l10n-name="android-mobile-inline-link">Android</a> and <a data-l10n-name="ios-mobile-inline-link">iOS</a>
lockwise-title = Never forget a password again
lockwise-title-logged-in = { -lockwise-brand-name }
lockwise-title-logged-in2 = Password Management
lockwise-header-content = { -lockwise-brand-name } securely stores your passwords in your browser.
lockwise-header-content-logged-in = Securely store and sync your passwords to all your devices.
protection-report-view-logins-button = View Logins
.title = Go to Saved Logins
protection-report-save-passwords-button = Save Passwords
.title = Save Passwords on { -lockwise-brand-short-name }
protection-report-manage-passwords-button = Manage Passwords
.title = Manage Passwords on { -lockwise-brand-short-name }
lockwise-mobile-app-title = Take your passwords everywhere
lockwise-no-logins-card-content = Use passwords saved in { -brand-short-name } on any device.
lockwise-app-links = { -lockwise-brand-name } for <a data-l10n-name="lockwise-android-inline-link">Android</a> and <a data-l10n-name="lockwise-ios-inline-link">iOS</a>
# This string is displayed after a large numeral that indicates the total number
# of email addresses being monitored. Dont add $count to
# your localization, because it would result in the number showing twice.
lockwise-passwords-stored =
# Variables:
# $count (Number) - Number of passwords exposed in data breaches.
lockwise-scanned-text-breached-logins =
{ $count ->
[one] Password stored securely <a data-l10n-name="lockwise-how-it-works">How it works</a>
*[other] Passwords stored securely <a data-l10n-name="lockwise-how-it-works">How it works</a>
[one] 1 password may have been exposed in a data breach.
*[other] { $count } passwords may have been exposed in a data breach.
}
# While English doesn't use the number in the plural form, you can add $count to your language
# if needed for grammatical reasons.
# Variables:
# $count (Number) - Number of passwords stored in Lockwise.
lockwise-scanned-text-no-breached-logins =
{ $count ->
[one] 1 password stored securely.
*[other] Your passwords are being stored securely.
}
lockwise-how-it-works-link = How it works
turn-on-sync = Turn on { -sync-brand-short-name }…
.title = Go to sync preferences

View File

@ -112,6 +112,9 @@
skin/classic/browser/panel-icon-retry.svg (../shared/panel-icon-retry.svg)
skin/classic/browser/toolbar-drag-indicator.svg (../shared/toolbar-drag-indicator.svg)
skin/classic/browser/protections/resolved-breach.svg (../shared/protections/resolved-breach.svg)
skin/classic/browser/protections/breached-password.svg (../shared/protections/breached-password.svg)
skin/classic/browser/preferences/bookmark.svg (../shared/preferences/bookmark.svg)
skin/classic/browser/preferences/critters-postcard.jpg (../shared/preferences/critters-postcard.jpg)
skin/classic/browser/preferences/extensions.svg (../shared/preferences/extensions.svg)

View File

@ -0,0 +1,6 @@
<!-- 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/. -->
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg">
<path d="M10 1.755A1.755 1.755 0 0112.279.081l6.55 2.046A1.67 1.67 0 0120 3.72v12.546c0 .73-.475 1.375-1.171 1.592l-6.551 2.047A1.754 1.754 0 0110 18.232zM7 2a1 1 0 010 2H2.491A.491.491 0 002 4.491V15.51c0 .271.22.491.491.491h4.51a1 1 0 010 2H2.49A2.494 2.494 0 010 15.51V4.49a2.494 2.494 0 012.491-2.49zm8 11.993c-.552 0-1 .45-1 1.004S14.448 16 15 16s1-.449 1-1.003c0-.555-.448-1.004-1-1.004zM15 4a1 1 0 00-1 1v6a1 1 0 002 0V5a1 1 0 00-1-1z" fill="#A4000F" fill-rule="evenodd"/>
</svg>

After

Width:  |  Height:  |  Size: 765 B

View File

@ -0,0 +1,9 @@
<!-- 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/. -->
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg">
<g fill="none" fill-rule="evenodd">
<path d="M10 20a10 10 0 1110-10c-.006 5.52-4.48 9.994-10 10z" fill="#2AC3A2"/>
<path d="M8.263 15.296a.881.881 0 01-.613-.252l-2.623-2.622a.875.875 0 011.243-1.233l1.878 1.878 5.492-7.869a.868.868 0 011.422.997l-6.085 8.697a.86.86 0 01-.635.367l-.079.037z" fill="#FFF"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 607 B