/* 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 })); }