summaryrefslogtreecommitdiffstats
path: root/browser/components/aboutlogins/content/aboutLogins.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/aboutlogins/content/aboutLogins.mjs')
-rw-r--r--browser/components/aboutlogins/content/aboutLogins.mjs288
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 }));
+}