зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1569846: Add breach alert dismissal. r=MattN,fluent-reviewers,flod
Differential Revision: https://phabricator.services.mozilla.com/D41034 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
bb4f8fad6d
Коммит
7fa8371641
|
@ -135,6 +135,7 @@ let LEGACY_ACTORS = {
|
||||||
AboutLoginsCopyLoginDetail: { wantUntrusted: true },
|
AboutLoginsCopyLoginDetail: { wantUntrusted: true },
|
||||||
AboutLoginsCreateLogin: { wantUntrusted: true },
|
AboutLoginsCreateLogin: { wantUntrusted: true },
|
||||||
AboutLoginsDeleteLogin: { wantUntrusted: true },
|
AboutLoginsDeleteLogin: { wantUntrusted: true },
|
||||||
|
AboutLoginsDismissBreachAlert: { wantUntrusted: true },
|
||||||
AboutLoginsImport: { wantUntrusted: true },
|
AboutLoginsImport: { wantUntrusted: true },
|
||||||
AboutLoginsInit: { wantUntrusted: true },
|
AboutLoginsInit: { wantUntrusted: true },
|
||||||
AboutLoginsOpenFAQ: { wantUntrusted: true },
|
AboutLoginsOpenFAQ: { wantUntrusted: true },
|
||||||
|
@ -636,6 +637,7 @@ const listeners = {
|
||||||
mm: {
|
mm: {
|
||||||
"AboutLogins:CreateLogin": ["AboutLoginsParent"],
|
"AboutLogins:CreateLogin": ["AboutLoginsParent"],
|
||||||
"AboutLogins:DeleteLogin": ["AboutLoginsParent"],
|
"AboutLogins:DeleteLogin": ["AboutLoginsParent"],
|
||||||
|
"AboutLogins:DismissBreachAlert": ["AboutLoginsParent"],
|
||||||
"AboutLogins:Import": ["AboutLoginsParent"],
|
"AboutLogins:Import": ["AboutLoginsParent"],
|
||||||
"AboutLogins:MasterPasswordRequest": ["AboutLoginsParent"],
|
"AboutLogins:MasterPasswordRequest": ["AboutLoginsParent"],
|
||||||
"AboutLogins:OpenFAQ": ["AboutLoginsParent"],
|
"AboutLogins:OpenFAQ": ["AboutLoginsParent"],
|
||||||
|
|
|
@ -90,6 +90,12 @@ class AboutLoginsChild extends ActorChild {
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "AboutLoginsDismissBreachAlert": {
|
||||||
|
this.mm.sendAsyncMessage("AboutLogins:DismissBreachAlert", {
|
||||||
|
login: event.detail,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "AboutLoginsImport": {
|
case "AboutLoginsImport": {
|
||||||
this.mm.sendAsyncMessage("AboutLogins:Import");
|
this.mm.sendAsyncMessage("AboutLogins:Import");
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -123,6 +123,21 @@ var AboutLoginsParent = {
|
||||||
Services.logins.removeLogin(login);
|
Services.logins.removeLogin(login);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "AboutLogins:DismissBreachAlert": {
|
||||||
|
const login = message.data.login;
|
||||||
|
|
||||||
|
await LoginHelper.recordBreachAlertDismissal(login.guid);
|
||||||
|
const logins = await this.getAllLogins();
|
||||||
|
const breachesByLoginGUID = await LoginHelper.getBreachesForLogins(
|
||||||
|
logins
|
||||||
|
);
|
||||||
|
const messageManager = message.target.messageManager;
|
||||||
|
messageManager.sendAsyncMessage(
|
||||||
|
"AboutLogins:UpdateBreaches",
|
||||||
|
breachesByLoginGUID
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "AboutLogins:SyncEnable": {
|
case "AboutLogins:SyncEnable": {
|
||||||
message.target.ownerGlobal.gSync.openFxAEmailFirstPage(
|
message.target.ownerGlobal.gSync.openFxAEmailFirstPage(
|
||||||
"password-manager"
|
"password-manager"
|
||||||
|
|
|
@ -137,6 +137,7 @@
|
||||||
<div class="breach-alert">
|
<div class="breach-alert">
|
||||||
<span class="breach-alert-text" data-l10n-id="breach-alert-text"></span>
|
<span class="breach-alert-text" data-l10n-id="breach-alert-text"></span>
|
||||||
<a class="breach-alert-link" data-l10n-id="breach-alert-link" href="#" rel="noopener noreferer" target="_blank"></a>
|
<a class="breach-alert-link" data-l10n-id="breach-alert-link" href="#" rel="noopener noreferer" target="_blank"></a>
|
||||||
|
<button class="dismiss-breach-alert"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="login-item-favicon-wrapper">
|
<div class="login-item-favicon-wrapper">
|
||||||
|
|
|
@ -228,6 +228,7 @@ input[type="url"][readOnly]:hover:active {
|
||||||
padding-inline-start: 36px;
|
padding-inline-start: 36px;
|
||||||
padding-inline-end: 92px;
|
padding-inline-end: 92px;
|
||||||
margin-block-end: 40px;
|
margin-block-end: 40px;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.breach-alert:dir(rtl) {
|
.breach-alert:dir(rtl) {
|
||||||
|
@ -240,6 +241,28 @@ a.breach-alert-link {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dismiss-breach-alert {
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
position: absolute;
|
||||||
|
background-image: url("chrome://global/skin/icons/close.svg");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
min-height: 16px;
|
||||||
|
min-width: 16px;
|
||||||
|
-moz-context-properties: fill, fill-opacity;
|
||||||
|
fill-opacity: 0;
|
||||||
|
fill: var(--grey-90);
|
||||||
|
inset-inline-end: 12px;
|
||||||
|
inset-block-start: 12px
|
||||||
|
}
|
||||||
|
|
||||||
|
.dismiss-breach-alert,
|
||||||
|
.dismiss-breach-alert:hover {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
@supports -moz-bool-pref("browser.in-content.dark-mode") {
|
@supports -moz-bool-pref("browser.in-content.dark-mode") {
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
:host {
|
:host {
|
||||||
|
|
|
@ -65,6 +65,9 @@ export default class LoginItem extends HTMLElement {
|
||||||
this._timeChanged = this.shadowRoot.querySelector(".time-changed");
|
this._timeChanged = this.shadowRoot.querySelector(".time-changed");
|
||||||
this._timeUsed = this.shadowRoot.querySelector(".time-used");
|
this._timeUsed = this.shadowRoot.querySelector(".time-used");
|
||||||
this._breachAlert = this.shadowRoot.querySelector(".breach-alert");
|
this._breachAlert = this.shadowRoot.querySelector(".breach-alert");
|
||||||
|
this._dismissBreachAlert = this.shadowRoot.querySelector(
|
||||||
|
".dismiss-breach-alert"
|
||||||
|
);
|
||||||
|
|
||||||
this.render();
|
this.render();
|
||||||
|
|
||||||
|
@ -73,6 +76,7 @@ export default class LoginItem extends HTMLElement {
|
||||||
this._copyPasswordButton.addEventListener("click", this);
|
this._copyPasswordButton.addEventListener("click", this);
|
||||||
this._copyUsernameButton.addEventListener("click", this);
|
this._copyUsernameButton.addEventListener("click", this);
|
||||||
this._deleteButton.addEventListener("click", this);
|
this._deleteButton.addEventListener("click", this);
|
||||||
|
this._dismissBreachAlert.addEventListener("click", this);
|
||||||
this._editButton.addEventListener("click", this);
|
this._editButton.addEventListener("click", this);
|
||||||
this._form.addEventListener("submit", this);
|
this._form.addEventListener("submit", this);
|
||||||
this._openSiteButton.addEventListener("click", this);
|
this._openSiteButton.addEventListener("click", this);
|
||||||
|
@ -92,6 +96,10 @@ export default class LoginItem extends HTMLElement {
|
||||||
".breach-alert-link"
|
".breach-alert-link"
|
||||||
);
|
);
|
||||||
breachAlertLink.href = breachDetails.breachAlertURL;
|
breachAlertLink.href = breachDetails.breachAlertURL;
|
||||||
|
document.l10n.setAttributes(
|
||||||
|
this._dismissBreachAlert,
|
||||||
|
"breach-alert-dismiss"
|
||||||
|
);
|
||||||
this._breachAlert.hidden = false;
|
this._breachAlert.hidden = false;
|
||||||
}
|
}
|
||||||
document.l10n.setAttributes(this._timeCreated, "login-item-time-created", {
|
document.l10n.setAttributes(this._timeCreated, "login-item-time-created", {
|
||||||
|
@ -135,6 +143,15 @@ export default class LoginItem extends HTMLElement {
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dismissBreachAlert() {
|
||||||
|
document.dispatchEvent(
|
||||||
|
new CustomEvent("AboutLoginsDismissBreachAlert", {
|
||||||
|
bubbles: true,
|
||||||
|
detail: this._login,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async handleEvent(event) {
|
async handleEvent(event) {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case "AboutLoginsInitialLoginSelected": {
|
case "AboutLoginsInitialLoginSelected": {
|
||||||
|
@ -251,6 +268,10 @@ export default class LoginItem extends HTMLElement {
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (classList.contains("dismiss-breach-alert")) {
|
||||||
|
this.dismissBreachAlert();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (classList.contains("edit-button")) {
|
if (classList.contains("edit-button")) {
|
||||||
this._toggleEditing();
|
this._toggleEditing();
|
||||||
|
|
||||||
|
|
|
@ -90,16 +90,15 @@ export default class LoginList extends HTMLElement {
|
||||||
// Show, hide, and update state of the list items per the applied search filter.
|
// Show, hide, and update state of the list items per the applied search filter.
|
||||||
for (let guid of this._loginGuidsSortedOrder) {
|
for (let guid of this._loginGuidsSortedOrder) {
|
||||||
let { listItem } = this._logins[guid];
|
let { listItem } = this._logins[guid];
|
||||||
|
|
||||||
if (guid == this._selectedGuid) {
|
if (guid == this._selectedGuid) {
|
||||||
this._setListItemAsSelected(listItem);
|
this._setListItemAsSelected(listItem);
|
||||||
}
|
}
|
||||||
if (
|
listItem.classList.toggle(
|
||||||
|
"breached",
|
||||||
this._breachesByLoginGUID &&
|
this._breachesByLoginGUID &&
|
||||||
this._breachesByLoginGUID.has(listItem.dataset.guid)
|
this._breachesByLoginGUID.has(listItem.dataset.guid)
|
||||||
) {
|
);
|
||||||
listItem.classList.add("breached");
|
|
||||||
}
|
|
||||||
|
|
||||||
listItem.hidden = !visibleLoginGuids.has(listItem.dataset.guid);
|
listItem.hidden = !visibleLoginGuids.has(listItem.dataset.guid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ support-files =
|
||||||
# Skip ASAN and debug since waiting for content events is already slow.
|
# Skip ASAN and debug since waiting for content events is already slow.
|
||||||
[browser_aaa_eventTelemetry_run_first.js]
|
[browser_aaa_eventTelemetry_run_first.js]
|
||||||
skip-if = asan || debug
|
skip-if = asan || debug
|
||||||
|
[browser_breachAlertDismissals.js]
|
||||||
[browser_confirmDeleteDialog.js]
|
[browser_confirmDeleteDialog.js]
|
||||||
[browser_copyToClipboardButton.js]
|
[browser_copyToClipboardButton.js]
|
||||||
[browser_createLogin.js]
|
[browser_createLogin.js]
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
const TEST_BREACHES = [
|
||||||
|
{
|
||||||
|
AddedDate: "2019-12-20T23:56:26Z",
|
||||||
|
BreachDate: "2018-12-16",
|
||||||
|
Domain: "breached.com",
|
||||||
|
Name: "Breached",
|
||||||
|
PwnCount: 1643100,
|
||||||
|
DataClasses: ["Email addresses", "Usernames", "Passwords", "IP addresses"],
|
||||||
|
_status: "synced",
|
||||||
|
id: "047940fe-d2fd-4314-b636-b4a952ee0043",
|
||||||
|
last_modified: "1541615610052",
|
||||||
|
schema: "1541615609018",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
add_task(async function setup() {
|
||||||
|
TEST_LOGIN3 = await addLogin(TEST_LOGIN3);
|
||||||
|
await BrowserTestUtils.openNewForegroundTab({
|
||||||
|
gBrowser,
|
||||||
|
url: "about:logins",
|
||||||
|
});
|
||||||
|
registerCleanupFunction(() => {
|
||||||
|
BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||||
|
Services.logins.removeAllLogins();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function test_show_login() {
|
||||||
|
let browser = gBrowser.selectedBrowser;
|
||||||
|
TEST_LOGIN3.timePasswordChanged = 12345;
|
||||||
|
let testBreaches = await LoginHelper.getBreachesForLogins(
|
||||||
|
[TEST_LOGIN3],
|
||||||
|
TEST_BREACHES
|
||||||
|
);
|
||||||
|
browser.messageManager.sendAsyncMessage(
|
||||||
|
"AboutLogins:UpdateBreaches",
|
||||||
|
testBreaches
|
||||||
|
);
|
||||||
|
await ContentTask.spawn(browser, TEST_LOGIN3, async () => {
|
||||||
|
let loginItem = Cu.waiveXrays(content.document.querySelector("login-item"));
|
||||||
|
let breachAlert = loginItem.shadowRoot.querySelector(".breach-alert");
|
||||||
|
let breachAlertVisible = await ContentTaskUtils.waitForCondition(() => {
|
||||||
|
return !breachAlert.hidden;
|
||||||
|
}, "Waiting for breach alert to be visible");
|
||||||
|
ok(
|
||||||
|
breachAlertVisible,
|
||||||
|
"Breach alert should be visible for a breached login."
|
||||||
|
);
|
||||||
|
|
||||||
|
let breachAlertDismissalButton = breachAlert.querySelector(
|
||||||
|
".dismiss-breach-alert"
|
||||||
|
);
|
||||||
|
breachAlertDismissalButton.click();
|
||||||
|
|
||||||
|
let breachAlertDismissed = await ContentTaskUtils.waitForCondition(() => {
|
||||||
|
return breachAlert.hidden;
|
||||||
|
}, "Waiting for breach alert to be dismissed");
|
||||||
|
ok(
|
||||||
|
breachAlertDismissed,
|
||||||
|
"Breach alert should not be visible after alert dismissal."
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
|
@ -6,6 +6,7 @@ let nsLoginInfo = new Components.Constructor(
|
||||||
Ci.nsILoginInfo,
|
Ci.nsILoginInfo,
|
||||||
"init"
|
"init"
|
||||||
);
|
);
|
||||||
|
|
||||||
let TEST_LOGIN1 = new nsLoginInfo(
|
let TEST_LOGIN1 = new nsLoginInfo(
|
||||||
"https://example.com/",
|
"https://example.com/",
|
||||||
"https://example.com/",
|
"https://example.com/",
|
||||||
|
@ -25,6 +26,16 @@ let TEST_LOGIN2 = new nsLoginInfo(
|
||||||
"password"
|
"password"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let TEST_LOGIN3 = new nsLoginInfo(
|
||||||
|
"https://breached.com/",
|
||||||
|
"https://breached.com/",
|
||||||
|
null,
|
||||||
|
"breachedLogin1",
|
||||||
|
"pass3",
|
||||||
|
"breachedLogin",
|
||||||
|
"password"
|
||||||
|
);
|
||||||
|
|
||||||
async function addLogin(login) {
|
async function addLogin(login) {
|
||||||
let storageChangedPromised = TestUtils.topicObserved(
|
let storageChangedPromised = TestUtils.topicObserved(
|
||||||
"passwordmgr-storage-changed",
|
"passwordmgr-storage-changed",
|
||||||
|
|
|
@ -101,7 +101,7 @@ add_task(async function test_update_breaches() {
|
||||||
|
|
||||||
let correspondingBreach = TEST_BREACHES_MAP.get(gLoginItem._login.guid);
|
let correspondingBreach = TEST_BREACHES_MAP.get(gLoginItem._login.guid);
|
||||||
let breachAlert = gLoginItem.shadowRoot.querySelector(".breach-alert");
|
let breachAlert = gLoginItem.shadowRoot.querySelector(".breach-alert");
|
||||||
ok(!breachAlert.hidden, "Breach alert should be visible");
|
ok(!isHidden(breachAlert), "Breach alert should be visible");
|
||||||
is(breachAlert.querySelector(".breach-alert-link").href, correspondingBreach.breachAlertURL, "Breach alert link should be equal to the correspondingBreach.breachAlertURL.");
|
is(breachAlert.querySelector(".breach-alert-link").href, correspondingBreach.breachAlertURL, "Breach alert link should be equal to the correspondingBreach.breachAlertURL.");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -111,7 +111,7 @@ add_task(async function test_breach_alert_is_correctly_hidden() {
|
||||||
await asyncElementRendered();
|
await asyncElementRendered();
|
||||||
|
|
||||||
let breachAlert = gLoginItem.shadowRoot.querySelector(".breach-alert");
|
let breachAlert = gLoginItem.shadowRoot.querySelector(".breach-alert");
|
||||||
ok(breachAlert.hidden, "Breach alert should not be visible on login without an associated breach.");
|
ok(isHidden(breachAlert), "Breach alert should not be visible on login without an associated breach.");
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(async function test_edit_login() {
|
add_task(async function test_edit_login() {
|
||||||
|
|
|
@ -163,3 +163,41 @@ add_task(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
add_task(
|
||||||
|
async function test_getBreachesForLogins_breachAlertHiddenAfterDismissal() {
|
||||||
|
BREACHED_LOGIN.guid = "{d2de5ac1-4de6-e544-a7af-1f75abcba92b}";
|
||||||
|
|
||||||
|
await Services.logins.initializationPromise;
|
||||||
|
const storageJSON =
|
||||||
|
Services.logins.wrappedJSObject._storage.wrappedJSObject;
|
||||||
|
|
||||||
|
storageJSON.recordBreachAlertDismissal(BREACHED_LOGIN.guid);
|
||||||
|
|
||||||
|
const breachesByLoginGUID = await LoginHelper.getBreachesForLogins(
|
||||||
|
[BREACHED_LOGIN, NOT_BREACHED_LOGIN],
|
||||||
|
TEST_BREACHES
|
||||||
|
);
|
||||||
|
Assert.strictEqual(
|
||||||
|
breachesByLoginGUID.size,
|
||||||
|
0,
|
||||||
|
"Should be 0 breached logins after dismissal: " + BREACHED_LOGIN.origin
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
add_task(async function test_getBreachesForLogins_newBreachAfterDismissal() {
|
||||||
|
TEST_BREACHES[0].AddedDate = new Date().toISOString();
|
||||||
|
|
||||||
|
const breachesByLoginGUID = await LoginHelper.getBreachesForLogins(
|
||||||
|
[BREACHED_LOGIN, NOT_BREACHED_LOGIN],
|
||||||
|
TEST_BREACHES
|
||||||
|
);
|
||||||
|
|
||||||
|
Assert.strictEqual(
|
||||||
|
breachesByLoginGUID.size,
|
||||||
|
1,
|
||||||
|
"Should be 1 breached login after new breach following the dismissal of a previous breach: " +
|
||||||
|
BREACHED_LOGIN.origin
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
@ -130,3 +130,5 @@ confirm-discard-changes-dialog-confirm-button = Discard
|
||||||
|
|
||||||
breach-alert-text = Passwords were leaked or stolen from this website since you last updated your login details. Change your password to protect your account.
|
breach-alert-text = Passwords were leaked or stolen from this website since you last updated your login details. Change your password to protect your account.
|
||||||
breach-alert-link = Learn more about this breach.
|
breach-alert-link = Learn more about this breach.
|
||||||
|
breach-alert-dismiss =
|
||||||
|
.title = Close this alert
|
||||||
|
|
|
@ -1113,6 +1113,14 @@ this.LoginHelper = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async recordBreachAlertDismissal(loginGuid) {
|
||||||
|
await Services.logins.initializationPromise;
|
||||||
|
const storageJSON =
|
||||||
|
Services.logins.wrappedJSObject._storage.wrappedJSObject;
|
||||||
|
|
||||||
|
return storageJSON.recordBreachAlertDismissal(loginGuid);
|
||||||
|
},
|
||||||
|
|
||||||
async getBreachesForLogins(logins, breaches = null) {
|
async getBreachesForLogins(logins, breaches = null) {
|
||||||
const breachesByLoginGUID = new Map();
|
const breachesByLoginGUID = new Map();
|
||||||
if (!breaches) {
|
if (!breaches) {
|
||||||
|
@ -1137,6 +1145,11 @@ this.LoginHelper = {
|
||||||
);
|
);
|
||||||
const baseBreachAlertURL = new URL(BREACH_ALERT_URL);
|
const baseBreachAlertURL = new URL(BREACH_ALERT_URL);
|
||||||
|
|
||||||
|
await Services.logins.initializationPromise;
|
||||||
|
const storageJSON =
|
||||||
|
Services.logins.wrappedJSObject._storage.wrappedJSObject;
|
||||||
|
const dismissedBreachAlertsByLoginGUID = storageJSON.getBreachAlertDismissalsByLoginGUID();
|
||||||
|
|
||||||
// Determine potentially breached logins by checking their origin and the last time
|
// Determine potentially breached logins by checking their origin and the last time
|
||||||
// they were changed. It's important to note here that we are NOT considering the
|
// they were changed. It's important to note here that we are NOT considering the
|
||||||
// username and password of that login.
|
// username and password of that login.
|
||||||
|
@ -1146,11 +1159,16 @@ this.LoginHelper = {
|
||||||
if (!breach.Domain) {
|
if (!breach.Domain) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
const breachDate = new Date(breach.BreachDate).getTime();
|
||||||
|
const breachAddedDate = new Date(breach.AddedDate).getTime();
|
||||||
if (
|
if (
|
||||||
Services.eTLD.hasRootDomain(loginURI.host, breach.Domain) &&
|
Services.eTLD.hasRootDomain(loginURI.host, breach.Domain) &&
|
||||||
breach.hasOwnProperty("DataClasses") &&
|
breach.hasOwnProperty("DataClasses") &&
|
||||||
breach.DataClasses.includes("Passwords") &&
|
breach.DataClasses.includes("Passwords") &&
|
||||||
login.timePasswordChanged < new Date(breach.BreachDate).getTime()
|
login.timePasswordChanged < breachDate &&
|
||||||
|
(!dismissedBreachAlertsByLoginGUID[login.guid] ||
|
||||||
|
dismissedBreachAlertsByLoginGUID[login.guid]
|
||||||
|
.timeBreachAlertDismissed < breachAddedDate)
|
||||||
) {
|
) {
|
||||||
let breachAlertURL = new URL(breach.Name, baseBreachAlertURL);
|
let breachAlertURL = new URL(breach.Name, baseBreachAlertURL);
|
||||||
breach.breachAlertURL = breachAlertURL.href;
|
breach.breachAlertURL = breachAlertURL.href;
|
||||||
|
|
|
@ -98,6 +98,10 @@ LoginStore.prototype._dataPostProcessor = function(data) {
|
||||||
data.logins = [];
|
data.logins = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!data.dismissedBreachAlertsByLoginGUID) {
|
||||||
|
data.dismissedBreachAlertsByLoginGUID = {};
|
||||||
|
}
|
||||||
|
|
||||||
// Indicate that the current version of the code has touched the file.
|
// Indicate that the current version of the code has touched the file.
|
||||||
data.version = kDataVersion;
|
data.version = kDataVersion;
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ interface nsILoginManager : nsISupports {
|
||||||
nsILoginInfo addLogin(in nsILoginInfo aLogin);
|
nsILoginInfo addLogin(in nsILoginInfo aLogin);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Like addLogin, but asynchronous and for many logins.
|
* Like addLogin, but asynchronous and for many logins.
|
||||||
*
|
*
|
||||||
|
|
|
@ -283,6 +283,23 @@ this.LoginManagerStorage_json.prototype = {
|
||||||
LoginHelper.notifyStorageChanged("modifyLogin", [oldStoredLogin, newLogin]);
|
LoginHelper.notifyStorageChanged("modifyLogin", [oldStoredLogin, newLogin]);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async recordBreachAlertDismissal(loginGUID) {
|
||||||
|
this._store.ensureDataReady();
|
||||||
|
const dismissedBreachAlertsByLoginGUID = this._store._data
|
||||||
|
.dismissedBreachAlertsByLoginGUID;
|
||||||
|
|
||||||
|
dismissedBreachAlertsByLoginGUID[loginGUID] = {
|
||||||
|
timeBreachAlertDismissed: new Date().getTime(),
|
||||||
|
};
|
||||||
|
|
||||||
|
return this._store.saveSoon();
|
||||||
|
},
|
||||||
|
|
||||||
|
getBreachAlertDismissalsByLoginGUID() {
|
||||||
|
this._store.ensureDataReady();
|
||||||
|
return this._store._data.dismissedBreachAlertsByLoginGUID;
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {nsILoginInfo[]}
|
* @return {nsILoginInfo[]}
|
||||||
*/
|
*/
|
||||||
|
|
Загрузка…
Ссылка в новой задаче