diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index 51a3bcc80e04..56ffe45ba719 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1610,6 +1610,9 @@ pref("browser.contentblocking.reportBreakage.url", "https://tracking-protection- // Enable Protections report's Lockwise card by default. pref("browser.contentblocking.report.lockwise.enabled", true); +// Enable Protections report's Monitor card by default. +pref("browser.contentblocking.report.monitor.enabled", true); + // Enables the new Protections Panel. #ifdef NIGHTLY_BUILD pref("browser.protections_panel.enabled", true); diff --git a/browser/base/content/logos/monitor.svg b/browser/base/content/logos/monitor.svg new file mode 100644 index 000000000000..cb7ffd8d93c3 --- /dev/null +++ b/browser/base/content/logos/monitor.svg @@ -0,0 +1 @@ + diff --git a/browser/base/content/test/static/browser_parsable_script.js b/browser/base/content/test/static/browser_parsable_script.js index 1d3ab794baca..2d147edabce8 100644 --- a/browser/base/content/test/static/browser_parsable_script.js +++ b/browser/base/content/test/static/browser_parsable_script.js @@ -20,6 +20,7 @@ const kESModuleList = new Set([ /browser\/aboutlogins\/.*\.js$/, /browser\/protections.js$/, /browser\/lockwise-card.js$/, + /browser\/monitor-card.js$/, /toolkit\/content\/global\/certviewer\/components\/.*\.js$/, /toolkit\/content\/global\/certviewer\/.*\.js$/, ]); diff --git a/browser/base/jar.mn b/browser/base/jar.mn index 12d48a7a39e9..b60f7ec22696 100644 --- a/browser/base/jar.mn +++ b/browser/base/jar.mn @@ -21,6 +21,7 @@ browser.jar: content/browser/illustrations/blue-berror.svg (content/illustrations/blue-berror.svg) content/browser/logos/lockwise.svg (content/logos/lockwise.svg) content/browser/logos/lockwise-mobile-app.svg (content/logos/lockwise-mobile-app.svg) + content/browser/logos/monitor.svg (content/logos/monitor.svg) content/browser/aboutNetError.xhtml (content/aboutNetError.xhtml) content/browser/aboutNetError.js (content/aboutNetError.js) content/browser/aboutRobots-icon.png (content/aboutRobots-icon.png) diff --git a/browser/components/about/AboutProtectionsHandler.jsm b/browser/components/about/AboutProtectionsHandler.jsm index 9aaad3e50c82..b2af1f1e0cc8 100644 --- a/browser/components/about/AboutProtectionsHandler.jsm +++ b/browser/components/about/AboutProtectionsHandler.jsm @@ -41,12 +41,15 @@ var AboutProtectionsHandler = { "OpenSyncPreferences", // Fetching data "FetchContentBlockingEvents", + "FetchMonitorData", "FetchUserLoginsData", // Getting prefs - "GetEnabledLockwiseCard", + "GetEnabledPrefs", ], - - PREF_LOCKWISE_CARD_ENABLED: "browser.contentblocking.report.lockwise.enabled", + _prefs: { + LockwiseCard: "browser.contentblocking.report.lockwise.enabled", + MonitorCard: "browser.contentblocking.report.monitor.enabled", + }, init() { this.receiveMessage = this.receiveMessage.bind(this); @@ -70,24 +73,43 @@ var AboutProtectionsHandler = { /** * Retrieves login data for the user. * - * @return {{ isLoggedIn: Boolean, - * numberOfLogins: Number, - * numberOfSyncedDevices: Number }} + * @return {{ hasFxa: Boolean, + * numLogins: Number, + * numSyncedDevices: Number }} * The login data. */ async getLoginData() { - const loginCount = Services.logins.countLogins("", "", ""); let syncedDevices = []; - const isLoggedWithFxa = await fxAccounts.accountStatus(); + const hasFxa = await fxAccounts.accountStatus(); - if (isLoggedWithFxa) { + if (hasFxa) { syncedDevices = await fxAccounts.getDeviceList(); } return { - isLoggedIn: loginCount > 0 || syncedDevices.length > 0, - numberOfLogins: loginCount, - numberOfSyncedDevices: syncedDevices.length, + hasFxa, + numLogins: Services.logins.countLogins("", "", ""), + numSyncedDevices: syncedDevices.length, + }; + }, + + /** + * Retrieves monitor data for the user. + * + * @return {{ monitoredEmails: Number, + * numBreaches: Number, + * passwords: Number, + * error: Boolean }} + * Monitor data. + */ + async getMonitorData() { + // TODO: Fetch real data for endpoints in Bug 1559424. + return { + monitoredEmails: 1, + numBreaches: 11, + passwords: 8, + lockwisePasswords: 2, + error: false, }; }, @@ -154,6 +176,13 @@ var AboutProtectionsHandler = { dataToSend ); break; + case "FetchMonitorData": + this.sendMessage( + aMessage.target, + "SendMonitorData", + await this.getMonitorData() + ); + break; case "FetchUserLoginsData": this.sendMessage( aMessage.target, @@ -161,13 +190,17 @@ var AboutProtectionsHandler = { await this.getLoginData() ); break; - case "GetEnabledLockwiseCard": - const enabled = Services.prefs.getBoolPref( - this.PREF_LOCKWISE_CARD_ENABLED - ); - this.sendMessage(aMessage.target, "SendEnabledLockWiseCardPref", { - isEnabled: enabled, - }); + case "GetEnabledPrefs": + const prefs = Object.keys(this._prefs); + + // Get all the enabled prefs and send separate messages depending on their names. + for (let name of prefs) { + const message = `SendEnabled${name}Pref`; + const isEnabled = Services.prefs.getBoolPref(this._prefs[name]); + this.sendMessage(aMessage.target, message, { + isEnabled, + }); + } break; } }, diff --git a/browser/components/protections/content/lockwise-card.js b/browser/components/protections/content/lockwise-card.js index ba2ee69b6a5a..9f841f77ff63 100644 --- a/browser/components/protections/content/lockwise-card.js +++ b/browser/components/protections/content/lockwise-card.js @@ -36,13 +36,11 @@ export default class LockwiseCard { ); lockwiseCard.classList.remove("hidden"); }); - - // Dispatch messages to retrieve data for the Lockwise card. - RPMSendAsyncMessage("FetchUserLoginsData"); } buildContent(data) { - const { isLoggedIn, numberOfLogins, numberOfSyncedDevices } = data; + const { hasFxa, numLogins, numSyncedDevices } = data; + const isLoggedIn = numLogins > 0 || hasFxa; const title = this.doc.getElementById("lockwise-title"); const headerContent = this.doc.getElementById("lockwise-header-content"); const lockwiseBodyContent = this.doc.getElementById( @@ -60,11 +58,7 @@ export default class LockwiseCard { title.textContent = "Firefox Lockwise"; headerContent.textContent = "Securely store and sync your passwords to all your devices."; - this.renderContentForLoggedInUser( - container, - numberOfLogins, - numberOfSyncedDevices - ); + this.renderContentForLoggedInUser(container, numLogins, numSyncedDevices); } else { title.textContent = "Never forget a password again"; headerContent.textContent = diff --git a/browser/components/protections/content/monitor-card.js b/browser/components/protections/content/monitor-card.js new file mode 100644 index 000000000000..9554e80dbdf0 --- /dev/null +++ b/browser/components/protections/content/monitor-card.js @@ -0,0 +1,91 @@ +/* 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/. */ + +/* eslint-env mozilla/frame-script */ + +export default class MonitorClass { + constructor(document) { + this.doc = document; + } + + init() { + const signUpForMonitorButton = this.doc.getElementById( + "sign-up-for-monitor-button" + ); + signUpForMonitorButton.addEventListener("click", () => { + console.log("TODO: Where is this link supposed to go."); + }); + + RPMAddMessageListener("SendUserLoginsData", ({ data }) => { + // Wait for monitor data and display the card. + this.getMonitorData(data); + RPMSendAsyncMessage("FetchMonitorData"); + }); + } + + /** + * Adds a listener for receiving the monitor data. Once received then display this data + * in the card. + * + * @param {Object} loginData + * Login data received from the Logins service. + */ + getMonitorData(loginData) { + RPMAddMessageListener("SendMonitorData", ({ data: monitorData }) => { + // Once data for the user is retrieved, display the monitor card. + this.buildContent(loginData, monitorData); + + // Show the Monitor card. + const monitorCard = this.doc.querySelector( + ".report-card.monitor-card.hidden" + ); + monitorCard.classList.remove("hidden"); + }); + } + + buildContent(loginData, monitorData) { + const { hasFxa, numLogins } = loginData; + const isLoggedIn = numLogins > 0 || hasFxa; + const headerContent = this.doc.querySelector( + "#monitor-header-content span" + ); + const monitorCard = this.doc.querySelector(".report-card.monitor-card"); + if (isLoggedIn && !monitorData.error) { + monitorCard.classList.add("has-logins"); + headerContent.textContent = + "Firefox Monitor warns you if your info has appeared in a known data breach"; + this.renderContentForUserWithLogins(monitorData); + } else { + monitorCard.classList.add("no-logins"); + const signUpForMonitorButton = this.doc.getElementById( + "sign-up-for-monitor-button" + ); + signUpForMonitorButton.textContent = hasFxa + ? "Turn on Monitor" + : "Sign up for Monitor"; + headerContent.textContent = + "Check Firefox Monitor to see if you've been part of a data breach and get alerts about new breaches."; + } + } + + renderContentForUserWithLogins(monitorData) { + const storedEmail = this.doc.querySelector( + "span[data-type='stored-emails']" + ); + const knownBreaches = this.doc.querySelector( + "span[data-type='known-breaches']" + ); + const exposedPasswords = this.doc.querySelector( + "span[data-type='exposed-passwords']" + ); + const exposedLockwisePasswords = this.doc.querySelector( + ".number-of-breaches.block" + ); + + storedEmail.textContent = monitorData.monitoredEmails; + knownBreaches.textContent = monitorData.numBreaches; + exposedPasswords.textContent = monitorData.passwords; + exposedLockwisePasswords.textContent = monitorData.lockwisePasswords; + } +} diff --git a/browser/components/protections/content/protections.css b/browser/components/protections/content/protections.css index 2961967d9902..307967a898e0 100644 --- a/browser/components/protections/content/protections.css +++ b/browser/components/protections/content/protections.css @@ -97,10 +97,26 @@ body[focuseddatatype=cryptominer] { background-color: var(--blue-80); } -.report-card.lockwise-card .card-header { +.report-card.lockwise-card .card-header, +.report-card.monitor-card.no-logins .card-header { grid-template-columns: 2fr 6fr 7fr; } + +/* We want to hide certain components depending on its state. */ +a.hidden, +.card-body.hidden, +.lockwise-card.hidden, +#lockwise-body-content .has-logins.hidden, +#lockwise-body-content .no-logins.hidden, +.monitor-card.hidden, +.monitor-card.no-logins .card-body, +.monitor-card.no-logins #monitor-header-content a, +.monitor-card.no-logins .inline-text-icon.monitor-scanned-text, +.monitor-card.has-logins #sign-up-for-monitor-button { + display: none; +} + .icon { width: 60px; height: 60px; @@ -116,6 +132,10 @@ body[focuseddatatype=cryptominer] { background: url("chrome://browser/content/logos/lockwise.svg") no-repeat center/cover; } +.monitor-card .icon { + background: url("chrome://browser/content/logos/monitor.svg") no-repeat center/cover; +} + .report-card { display: grid; grid-template-columns: 100%; @@ -389,13 +409,6 @@ label:hover { grid-gap: 10px; } -a.hidden, -.lockwise-card.hidden, -#lockwise-body-content .has-logins.hidden, -#lockwise-body-content .no-logins.hidden { - display: none; -} - .number-of-logins { background-color: var(--dark-grey); } @@ -404,7 +417,7 @@ a.hidden, background-color: var(--orange); } -.lockwise-text-icon { +.inline-text-icon { background-size: 16px 16px; background-repeat: no-repeat; background-position-x: 3px; @@ -427,10 +440,6 @@ a.hidden, background-image: url("chrome://browser/skin/sync.svg"); } -.non-logged-in-user-content { - grid-column: 2; -} - .block { border-radius: 4px; text-align: center; @@ -443,3 +452,108 @@ a.hidden, #lockwise-body-content .has-logins a { margin-inline-start: 10px; } + +/* Monitor card */ + +#monitor-body-content .monitor-breached-passwords { + grid: 1fr / minmax(70px, auto) 1fr; + grid-row: 3; + grid-column: span 3; + display: grid; + align-items: center; + font-size: 0.85rem; + border-top: var(--card-divider); + padding-top: 20px; + line-height: 18px; + grid-column-gap: 10px; +} + +.monitor-scanned-text { + background-image: url("chrome://browser/skin/reload.svg"); + font-size: 0.85rem; +} + +.monitor-card #monitor-header-content > a { + display: block; + margin-block-start: 5px; +} + +.monitor-card.has-logins #monitor-body-content { + display: grid; + grid: 2fr 1fr auto / repeat(3, 160px); + grid-column-gap: 12px; + align-items: center; +} + +.monitor-block { + display: flex; + flex-direction: column; + border-radius: 4px; + color: #FFFFFF; + text-align: center; + padding: 25px 5px 25px 5px; +} + +.email { + background: linear-gradient(162.33deg, #AB71FF 0%, #9059FF 100%); +} + +.email .monitor-icon { + background-image: url(chrome://browser/skin/mail.svg); +} + +.breaches { + background: linear-gradient(162.33deg, #9059FF 0%, #7542E5 100%); +} + +.breaches .monitor-icon { + background-image: url(chrome://browser/skin/fxa/avatar.svg); +} + +.passwords { + background: linear-gradient(162.33deg, #7542E5 0%, #592ACB 100%); +} + +.passwords .monitor-icon { + background-image: url(chrome://browser/skin/login.svg); +} + +.monitor-view-full-report { + grid-row: 2; + grid-column: span 2; + font-size: 0.85rem; +} + +.monitor-stat { + display: flex; + font-size: 1.75rem; + font-weight: bold; + margin-block-end: 5px; + word-break: break-all; + justify-content: center; + flex-wrap: wrap; +} + +.monitor-stat .monitor-icon { + background-size: 24px 24px; + background-repeat: no-repeat; + -moz-context-properties: fill,fill-opacity; + fill: white; + fill-opacity: 0.65; + width: 24px; + height: 24px; + display: block; + padding: 5px; + background-position-y: center; +} + +.info-text { + font-size: .69rem; + line-height: 13px; + margin-block-start: 5px; +} + +.number-of-breaches.block { + background-color: var(--orange); + padding: 15px; +} diff --git a/browser/components/protections/content/protections.html b/browser/components/protections/content/protections.html index 4d443bf2aa92..6e5c4bef7d8a 100644 --- a/browser/components/protections/content/protections.html +++ b/browser/components/protections/content/protections.html @@ -14,6 +14,7 @@ + Protection Report @@ -82,6 +83,75 @@ + +