diff options
Diffstat (limited to 'browser/components/aboutlogins/content/aboutLogins.mjs')
-rw-r--r-- | browser/components/aboutlogins/content/aboutLogins.mjs | 288 |
1 files changed, 288 insertions, 0 deletions
diff --git a/browser/components/aboutlogins/content/aboutLogins.mjs b/browser/components/aboutlogins/content/aboutLogins.mjs new file mode 100644 index 0000000000..f0402fedc1 --- /dev/null +++ b/browser/components/aboutlogins/content/aboutLogins.mjs @@ -0,0 +1,288 @@ +/* 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/. */ + +import { + recordTelemetryEvent, + setKeyboardAccessForNonDialogElements, +} from "./aboutLoginsUtils.mjs"; + +// The init code isn't wrapped in a DOMContentLoaded/load event listener so the +// page works properly when restored from session restore. +const gElements = { + fxAccountsButton: document.querySelector("fxaccounts-button"), + loginList: document.querySelector("login-list"), + loginIntro: document.querySelector("login-intro"), + loginItem: document.querySelector("login-item"), + loginFilter: document + .querySelector("login-list") + .shadowRoot.querySelector("login-filter"), + menuButton: document.querySelector("menu-button"), + // removeAllLogins button is nested inside of menuButton + get removeAllButton() { + return this.menuButton.shadowRoot.querySelector( + ".menuitem-remove-all-logins" + ); + }, +}; + +let numberOfLogins = 0; + +function updateNoLogins() { + document.documentElement.classList.toggle("no-logins", numberOfLogins == 0); + gElements.loginList.classList.toggle("no-logins", numberOfLogins == 0); + gElements.loginItem.classList.toggle("no-logins", numberOfLogins == 0); + gElements.removeAllButton.disabled = numberOfLogins == 0; +} + +function handleAllLogins(logins) { + gElements.loginList.setLogins(logins); + numberOfLogins = logins.length; + updateNoLogins(); +} + +let fxaLoggedIn = null; +let passwordSyncEnabled = null; + +function handleSyncState(syncState) { + gElements.fxAccountsButton.updateState(syncState); + gElements.loginIntro.updateState(syncState); + fxaLoggedIn = syncState.loggedIn; + passwordSyncEnabled = syncState.passwordSyncEnabled; +} + +window.addEventListener("AboutLoginsChromeToContent", event => { + switch (event.detail.messageType) { + case "AllLogins": { + document.documentElement.classList.remove( + "primary-password-auth-required" + ); + setKeyboardAccessForNonDialogElements(true); + handleAllLogins(event.detail.value); + break; + } + case "ImportPasswordsDialog": { + let dialog = document.querySelector("import-summary-dialog"); + let options = { + logins: event.detail.value, + }; + dialog.show(options); + break; + } + case "ImportPasswordsErrorDialog": { + let dialog = document.querySelector("import-error-dialog"); + dialog.show(event.detail.value); + break; + } + case "LoginAdded": { + gElements.loginList.loginAdded(event.detail.value); + gElements.loginItem.loginAdded(event.detail.value); + numberOfLogins++; + updateNoLogins(); + break; + } + case "LoginModified": { + gElements.loginList.loginModified(event.detail.value); + gElements.loginItem.loginModified(event.detail.value); + break; + } + case "LoginRemoved": { + // The loginRemoved function of loginItem needs to be called before + // the one in loginList since it will remove the editing. So that the + // discard dialog won't show up if we delete a login after edit it. + gElements.loginItem.loginRemoved(event.detail.value); + gElements.loginList.loginRemoved(event.detail.value); + numberOfLogins--; + updateNoLogins(); + break; + } + case "PrimaryPasswordAuthRequired": { + document.documentElement.classList.add("primary-password-auth-required"); + setKeyboardAccessForNonDialogElements(false); + break; + } + case "RemaskPassword": { + window.dispatchEvent(new CustomEvent("AboutLoginsRemaskPassword")); + break; + } + case "RemoveAllLogins": { + handleAllLogins(event.detail.value); + document.documentElement.classList.remove("login-selected"); + break; + } + case "SetBreaches": { + gElements.loginList.setBreaches(event.detail.value); + gElements.loginItem.setBreaches(event.detail.value); + break; + } + case "SetVulnerableLogins": { + gElements.loginList.setVulnerableLogins(event.detail.value); + gElements.loginItem.setVulnerableLogins(event.detail.value); + break; + } + case "Setup": { + handleAllLogins(event.detail.value.logins); + handleSyncState(event.detail.value.syncState); + gElements.loginList.setSortDirection(event.detail.value.selectedSort); + document.documentElement.classList.add("initialized"); + gElements.loginList.classList.add("initialized"); + break; + } + case "ShowLoginItemError": { + gElements.loginItem.showLoginItemError(event.detail.value); + break; + } + case "SyncState": { + handleSyncState(event.detail.value); + break; + } + case "UpdateBreaches": { + gElements.loginList.updateBreaches(event.detail.value); + gElements.loginItem.updateBreaches(event.detail.value); + break; + } + case "UpdateVulnerableLogins": { + gElements.loginList.updateVulnerableLogins(event.detail.value); + gElements.loginItem.updateVulnerableLogins(event.detail.value); + break; + } + } +}); + +window.addEventListener("AboutLoginsRemoveAllLoginsDialog", () => { + let loginItem = document.querySelector("login-item"); + let options = {}; + if (fxaLoggedIn && passwordSyncEnabled) { + options.title = "about-logins-confirm-remove-all-sync-dialog-title"; + options.message = "about-logins-confirm-remove-all-sync-dialog-message"; + } else { + options.title = "about-logins-confirm-remove-all-dialog-title"; + options.message = "about-logins-confirm-remove-all-dialog-message"; + } + options.confirmCheckboxLabel = + "about-logins-confirm-remove-all-dialog-checkbox-label"; + options.confirmButtonLabel = + "about-logins-confirm-remove-all-dialog-confirm-button-label"; + options.count = numberOfLogins; + + let dialog = document.querySelector("remove-logins-dialog"); + let dialogPromise = dialog.show(options); + try { + dialogPromise.then( + () => { + if (loginItem.dataset.isNewLogin) { + // Bug 1681042 - Resetting the form prevents a double confirmation dialog since there + // may be pending changes in the new login. + loginItem.resetForm(); + window.dispatchEvent(new CustomEvent("AboutLoginsClearSelection")); + } else if (loginItem.dataset.editing) { + loginItem._toggleEditing(); + } + window.document.documentElement.classList.remove("login-selected"); + let removeAllEvt = new CustomEvent("AboutLoginsRemoveAllLogins", { + bubbles: true, + }); + window.dispatchEvent(removeAllEvt); + }, + () => {} + ); + } catch (e) { + if (e != undefined) { + throw e; + } + } +}); + +window.addEventListener("AboutLoginsExportPasswordsDialog", async () => { + recordTelemetryEvent({ + object: "export", + method: "mgmt_menu_item_used", + }); + let dialog = document.querySelector("confirmation-dialog"); + let options = { + title: "about-logins-confirm-export-dialog-title", + message: "about-logins-confirm-export-dialog-message", + confirmButtonLabel: "about-logins-confirm-export-dialog-confirm-button", + }; + try { + await dialog.show(options); + document.dispatchEvent( + new CustomEvent("AboutLoginsExportPasswords", { bubbles: true }) + ); + } catch (ex) { + // The user cancelled the dialog. + } +}); + +async function interceptFocusKey() { + // Intercept Ctrl+F on the page to focus login filter box + const [findKey] = await document.l10n.formatMessages([ + { id: "about-logins-login-filter" }, + ]); + const focusKey = findKey.attributes + .find(a => a.name == "key") + .value.toLowerCase(); + document.addEventListener("keydown", event => { + if (event.key == focusKey && event.getModifierState("Accel")) { + event.preventDefault(); + document + .querySelector("login-list") + .shadowRoot.querySelector("login-filter") + .shadowRoot.querySelector("input") + .focus(); + } + }); +} + +await interceptFocusKey(); + +// Begin code that executes on page load. + +let searchParamsChanged = false; +let { protocol, pathname, searchParams } = new URL(document.location); + +recordTelemetryEvent({ + method: "open_management", + object: searchParams.get("entryPoint") || "direct", +}); + +if (searchParams.has("entryPoint")) { + // Remove this parameter from the URL (after recording above) to make it + // cleaner for bookmarking and switch-to-tab and so that bookmarked values + // don't skew telemetry. + searchParams.delete("entryPoint"); + searchParamsChanged = true; +} + +if (searchParams.has("filter")) { + let filter = searchParams.get("filter"); + if (!filter) { + // Remove empty `filter` params to give a cleaner URL for bookmarking and + // switch-to-tab + searchParams.delete("filter"); + searchParamsChanged = true; + } +} + +if (searchParamsChanged) { + const paramsPart = searchParams.toString() ? `?${searchParams}` : ""; + const newURL = protocol + pathname + paramsPart + document.location.hash; + // This redirect doesn't stop this script from running so ensure you guard + // later code if it shouldn't run before and after the redirect. + window.location.replace(newURL); +} else if (searchParams.has("filter")) { + // This must be after the `location.replace` so it doesn't cause telemetry to + // record a filter event before the navigation to clean the URL. + gElements.loginFilter.value = searchParams.get("filter"); +} + +if (!searchParamsChanged) { + if (document.location.hash) { + const loginDomainOrGuid = decodeURIComponent( + document.location.hash.slice(1) + ); + gElements.loginList.selectLoginByDomainOrGuid(loginDomainOrGuid); + } + gElements.loginFilter.focus(); + document.dispatchEvent(new CustomEvent("AboutLoginsInit", { bubbles: true })); +} |