diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 05:43:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 05:43:14 +0000 |
commit | 8dd16259287f58f9273002717ec4d27e97127719 (patch) | |
tree | 3863e62a53829a84037444beab3abd4ed9dfc7d0 /toolkit/components/satchel/megalist/aggregator | |
parent | Releasing progress-linux version 126.0.1-1~progress7.99u1. (diff) | |
download | firefox-8dd16259287f58f9273002717ec4d27e97127719.tar.xz firefox-8dd16259287f58f9273002717ec4d27e97127719.zip |
Merging upstream version 127.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
5 files changed, 536 insertions, 511 deletions
diff --git a/toolkit/components/satchel/megalist/aggregator/Aggregator.sys.mjs b/toolkit/components/satchel/megalist/aggregator/Aggregator.sys.mjs index e101fadd16..f3e39ade28 100644 --- a/toolkit/components/satchel/megalist/aggregator/Aggregator.sys.mjs +++ b/toolkit/components/satchel/megalist/aggregator/Aggregator.sys.mjs @@ -60,6 +60,16 @@ export class Aggregator { this.#sources.push(source); } + callFunction(dataSource, functionName) { + const source = this.#sources.find( + source => source.constructor.name === dataSource + ); + + if (source && source[functionName]) { + source[functionName](); + } + } + /** * Exposes interface for a datasource to communicate with Aggregator. */ @@ -73,6 +83,10 @@ export class Aggregator { refreshAllLinesOnScreen() { aggregator.forEachViewModel(vm => vm.refreshAllLinesOnScreen()); }, + + setLayout(layout) { + aggregator.forEachViewModel(vm => vm.setLayout(layout)); + }, }; } } diff --git a/toolkit/components/satchel/megalist/aggregator/datasources/AddressesDataSource.sys.mjs b/toolkit/components/satchel/megalist/aggregator/datasources/AddressesDataSource.sys.mjs index f00df0b40b..f38d89f88f 100644 --- a/toolkit/components/satchel/megalist/aggregator/datasources/AddressesDataSource.sys.mjs +++ b/toolkit/components/satchel/megalist/aggregator/datasources/AddressesDataSource.sys.mjs @@ -56,103 +56,87 @@ export class AddressesDataSource extends DataSourceBase { constructor(...args) { super(...args); - this.formatMessages( - "addresses-section-label", - "address-name-label", - "address-phone-label", - "address-email-label", - "command-copy", - "addresses-disabled", - "command-delete", - "command-edit", - "addresses-command-create" - ).then( - ([ - headerLabel, - nameLabel, - phoneLabel, - emailLabel, - copyLabel, - addressesDisabled, - deleteLabel, - editLabel, - createLabel, - ]) => { - const copyCommand = { id: "Copy", label: copyLabel }; - const editCommand = { id: "Edit", label: editLabel }; - const deleteCommand = { id: "Delete", label: deleteLabel }; - this.#addressesDisabledMessage = addressesDisabled; - this.#header = this.createHeaderLine(headerLabel); - this.#header.commands.push({ id: "Create", label: createLabel }); - - let self = this; - - function prototypeLine(label, key, options = {}) { - return self.prototypeDataLine({ - label: { value: label }, - value: { - get() { - return this.editingValue ?? this.record[key]; - }, + this.localizeStrings({ + headerLabel: "addresses-section-label", + nameLabel: "address-name-label", + phoneLabel: "address-phone-label", + emailLabel: "address-email-label", + addressesDisabled: "addresses-disabled", + }).then(strings => { + const copyCommand = { id: "Copy", label: "command-copy" }; + const editCommand = { id: "Edit", label: "command-edit" }; + const deleteCommand = { id: "Delete", label: "command-delete" }; + this.#addressesDisabledMessage = strings.addressesDisabled; + this.#header = this.createHeaderLine(strings.headerLabel); + this.#header.commands.push({ + id: "Create", + label: "addresses-command-create", + }); + + let self = this; + + function prototypeLine(label, key, options = {}) { + return self.prototypeDataLine({ + label: { value: label }, + value: { + get() { + return this.editingValue ?? this.record[key]; }, - commands: { - value: [copyCommand, editCommand, "-", deleteCommand], + }, + commands: { + value: [copyCommand, editCommand, "-", deleteCommand], + }, + executeEdit: { + value() { + this.editingValue = this.record[key] ?? ""; + this.refreshOnScreen(); }, - executeEdit: { - value() { - this.editingValue = this.record[key] ?? ""; - this.refreshOnScreen(); - }, + }, + executeSave: { + async value(value) { + if (await updateAddress(this.record, key, value)) { + this.executeCancel(); + } }, - executeSave: { - async value(value) { - if (await updateAddress(this.record, key, value)) { - this.executeCancel(); - } - }, - }, - ...options, - }); - } - - this.#namePrototype = prototypeLine(nameLabel, "name", { - start: { value: true }, + }, + ...options, }); - this.#organizationPrototype = prototypeLine( - "Organization", - "organization" - ); - this.#streetAddressPrototype = prototypeLine( - "Street Address", - "street-address" - ); - this.#addressLevelThreePrototype = prototypeLine( - "Neighbourhood", - "address-level3" - ); - this.#addressLevelTwoPrototype = prototypeLine( - "City", - "address-level2" - ); - this.#addressLevelOnePrototype = prototypeLine( - "Province", - "address-level1" - ); - this.#postalCodePrototype = prototypeLine("Postal Code", "postal-code"); - this.#countryPrototype = prototypeLine("Country", "country"); - this.#phonePrototype = prototypeLine(phoneLabel, "tel"); - this.#emailPrototype = prototypeLine(emailLabel, "email", { - end: { value: true }, - }); - - Services.obs.addObserver(this, "formautofill-storage-changed"); - Services.prefs.addObserver( - "extensions.formautofill.addresses.enabled", - this - ); - this.#reloadDataSource(); } - ); + + this.#namePrototype = prototypeLine(strings.nameLabel, "name", { + start: { value: true }, + }); + this.#organizationPrototype = prototypeLine( + "Organization", + "organization" + ); + this.#streetAddressPrototype = prototypeLine( + "Street Address", + "street-address" + ); + this.#addressLevelThreePrototype = prototypeLine( + "Neighbourhood", + "address-level3" + ); + this.#addressLevelTwoPrototype = prototypeLine("City", "address-level2"); + this.#addressLevelOnePrototype = prototypeLine( + "Province", + "address-level1" + ); + this.#postalCodePrototype = prototypeLine("Postal Code", "postal-code"); + this.#countryPrototype = prototypeLine("Country", "country"); + this.#phonePrototype = prototypeLine(strings.phoneLabel, "tel"); + this.#emailPrototype = prototypeLine(strings.emailLabel, "email", { + end: { value: true }, + }); + + Services.obs.addObserver(this, "formautofill-storage-changed"); + Services.prefs.addObserver( + "extensions.formautofill.addresses.enabled", + this + ); + this.#reloadDataSource(); + }); } async #reloadDataSource() { diff --git a/toolkit/components/satchel/megalist/aggregator/datasources/BankCardDataSource.sys.mjs b/toolkit/components/satchel/megalist/aggregator/datasources/BankCardDataSource.sys.mjs index 06266a7979..9fc1a4e429 100644 --- a/toolkit/components/satchel/megalist/aggregator/datasources/BankCardDataSource.sys.mjs +++ b/toolkit/components/satchel/megalist/aggregator/datasources/BankCardDataSource.sys.mjs @@ -68,187 +68,157 @@ export class BankCardDataSource extends DataSourceBase { constructor(...args) { super(...args); // Wait for Fluent to provide strings before loading data - this.formatMessages( - "payments-section-label", - "card-number-label", - "card-expiration-label", - "card-holder-label", - "command-copy", - "command-reveal", - "command-conceal", - "payments-disabled", - "command-delete", - "command-edit", - "payments-command-create" - ).then( - ([ - headerLabel, - numberLabel, - expirationLabel, - holderLabel, - copyCommandLabel, - revealCommandLabel, - concealCommandLabel, - cardsDisabled, - deleteCommandLabel, - editCommandLabel, - cardsCreateCommandLabel, - ]) => { - const copyCommand = { id: "Copy", label: copyCommandLabel }; - const editCommand = { - id: "Edit", - label: editCommandLabel, - verify: true, - }; - const deleteCommand = { - id: "Delete", - label: deleteCommandLabel, - verify: true, - }; - this.#cardsDisabledMessage = cardsDisabled; - this.#header = this.createHeaderLine(headerLabel); - this.#header.commands.push({ - id: "Create", - label: cardsCreateCommandLabel, - }); - this.#cardNumberPrototype = this.prototypeDataLine({ - label: { value: numberLabel }, - concealed: { value: true, writable: true }, - start: { value: true }, - value: { - async get() { - if (this.editingValue !== undefined) { - return this.editingValue; - } + this.localizeStrings({ + headerLabel: "payments-section-label", + numberLabel: "card-number-label", + expirationLabel: "card-expiration-label", + holderLabel: "card-holder-label", + cardsDisabled: "payments-disabled", + }).then(strings => { + const copyCommand = { id: "Copy", label: "command-copy" }; + const editCommand = { id: "Edit", label: "command-edit", verify: true }; + const deleteCommand = { + id: "Delete", + label: "command-delete", + verify: true, + }; + this.#cardsDisabledMessage = strings.cardsDisabled; + this.#header = this.createHeaderLine(strings.headerLabel); + this.#header.commands.push({ + id: "Create", + label: "payments-command-create", + }); + this.#cardNumberPrototype = this.prototypeDataLine({ + label: { value: strings.numberLabel }, + concealed: { value: true, writable: true }, + start: { value: true }, + value: { + async get() { + if (this.isEditing()) { + return this.editingValue; + } - if (this.concealed) { - return ( - "••••••••" + - this.record["cc-number"].replaceAll("*", "").substr(-4) - ); - } - - await decryptCard(this.record); - return this.record["cc-number-decrypted"]; - }, - }, - valueIcon: { - get() { - const typeToImage = { - amex: "third-party/cc-logo-amex.png", - cartebancaire: "third-party/cc-logo-cartebancaire.png", - diners: "third-party/cc-logo-diners.svg", - discover: "third-party/cc-logo-discover.png", - jcb: "third-party/cc-logo-jcb.svg", - mastercard: "third-party/cc-logo-mastercard.svg", - mir: "third-party/cc-logo-mir.svg", - unionpay: "third-party/cc-logo-unionpay.svg", - visa: "third-party/cc-logo-visa.svg", - }; + if (this.concealed) { return ( - "chrome://formautofill/content/" + - (typeToImage[this.record["cc-type"]] ?? - "icon-credit-card-generic.svg") + "••••••••" + + this.record["cc-number"].replaceAll("*", "").substr(-4) ); - }, - }, - commands: { - get() { - const commands = [ - { id: "Conceal", label: concealCommandLabel }, - { ...copyCommand, verify: true }, - editCommand, - "-", - deleteCommand, - ]; - if (this.concealed) { - commands[0] = { - id: "Reveal", - label: revealCommandLabel, - verify: true, - }; - } - return commands; - }, + } + + await decryptCard(this.record); + return this.record["cc-number-decrypted"]; }, - executeReveal: { - value() { - this.concealed = false; - this.refreshOnScreen(); - }, + }, + valueIcon: { + get() { + const typeToImage = { + amex: "third-party/cc-logo-amex.png", + cartebancaire: "third-party/cc-logo-cartebancaire.png", + diners: "third-party/cc-logo-diners.svg", + discover: "third-party/cc-logo-discover.png", + jcb: "third-party/cc-logo-jcb.svg", + mastercard: "third-party/cc-logo-mastercard.svg", + mir: "third-party/cc-logo-mir.svg", + unionpay: "third-party/cc-logo-unionpay.svg", + visa: "third-party/cc-logo-visa.svg", + }; + return ( + "chrome://formautofill/content/" + + (typeToImage[this.record["cc-type"]] ?? + "icon-credit-card-generic.svg") + ); }, - executeConceal: { - value() { - this.concealed = true; - this.refreshOnScreen(); - }, + }, + commands: { + *value() { + if (this.concealed) { + yield { id: "Reveal", label: "command-reveal", verify: true }; + } else { + yield { id: "Conceal", label: "command-conceal" }; + } + yield { ...copyCommand, verify: true }; + yield editCommand; + yield "-"; + yield deleteCommand; }, - executeCopy: { - async value() { - await decryptCard(this.record); - this.copyToClipboard(this.record["cc-number-decrypted"]); - }, + }, + executeReveal: { + value() { + this.concealed = false; + this.refreshOnScreen(); }, - executeEdit: { - async value() { - await decryptCard(this.record); - this.editingValue = this.record["cc-number-decrypted"] ?? ""; - this.refreshOnScreen(); - }, + }, + executeConceal: { + value() { + this.concealed = true; + this.refreshOnScreen(); }, - executeSave: { - async value(value) { - if (updateCard(this.record, "cc-number", value)) { - this.executeCancel(); - } - }, + }, + executeCopy: { + async value() { + await decryptCard(this.record); + this.copyToClipboard(this.record["cc-number-decrypted"]); }, - }); - this.#expirationPrototype = this.prototypeDataLine({ - label: { value: expirationLabel }, - value: { - get() { - return `${this.record["cc-exp-month"]}/${this.record["cc-exp-year"]}`; - }, + }, + executeEdit: { + async value() { + await decryptCard(this.record); + this.editingValue = this.record["cc-number-decrypted"] ?? ""; + this.refreshOnScreen(); }, - commands: { - value: [copyCommand, editCommand, "-", deleteCommand], + }, + executeSave: { + async value(value) { + if (updateCard(this.record, "cc-number", value)) { + this.executeCancel(); + } }, - }); - this.#holderNamePrototype = this.prototypeDataLine({ - label: { value: holderLabel }, - end: { value: true }, - value: { - get() { - return this.editingValue ?? this.record["cc-name"]; - }, + }, + }); + this.#expirationPrototype = this.prototypeDataLine({ + label: { value: strings.expirationLabel }, + value: { + get() { + return `${this.record["cc-exp-month"]}/${this.record["cc-exp-year"]}`; }, - commands: { - value: [copyCommand, editCommand, "-", deleteCommand], + }, + commands: { + value: [copyCommand, editCommand, "-", deleteCommand], + }, + }); + this.#holderNamePrototype = this.prototypeDataLine({ + label: { value: strings.holderLabel }, + end: { value: true }, + value: { + get() { + return this.editingValue ?? this.record["cc-name"]; }, - executeEdit: { - value() { - this.editingValue = this.record["cc-name"] ?? ""; - this.refreshOnScreen(); - }, + }, + commands: { + value: [copyCommand, editCommand, "-", deleteCommand], + }, + executeEdit: { + value() { + this.editingValue = this.record["cc-name"] ?? ""; + this.refreshOnScreen(); }, - executeSave: { - async value(value) { - if (updateCard(this.record, "cc-name", value)) { - this.executeCancel(); - } - }, + }, + executeSave: { + async value(value) { + if (updateCard(this.record, "cc-name", value)) { + this.executeCancel(); + } }, - }); + }, + }); - Services.obs.addObserver(this, "formautofill-storage-changed"); - Services.prefs.addObserver( - "extensions.formautofill.creditCards.enabled", - this - ); - this.#reloadDataSource(); - } - ); + Services.obs.addObserver(this, "formautofill-storage-changed"); + Services.prefs.addObserver( + "extensions.formautofill.creditCards.enabled", + this + ); + this.#reloadDataSource(); + }); } /** @@ -280,7 +250,7 @@ export class BankCardDataSource extends DataSourceBase { `${card["cc-exp-month"]}/${card["cc-exp-year"]}` .toUpperCase() .includes(searchText) || - card["cc-name"].toUpperCase().includes(searchText) + card["cc-name"]?.toUpperCase().includes(searchText) ); this.formatMessages({ diff --git a/toolkit/components/satchel/megalist/aggregator/datasources/DataSourceBase.sys.mjs b/toolkit/components/satchel/megalist/aggregator/datasources/DataSourceBase.sys.mjs index 49be733aef..ee7dfed5eb 100644 --- a/toolkit/components/satchel/megalist/aggregator/datasources/DataSourceBase.sys.mjs +++ b/toolkit/components/satchel/megalist/aggregator/datasources/DataSourceBase.sys.mjs @@ -63,7 +63,30 @@ export class DataSourceBase { this.#aggregatorApi.refreshAllLinesOnScreen(); } + setLayout(layout) { + this.#aggregatorApi.setLayout(layout); + } + formatMessages = createFormatMessages("preview/megalist.ftl"); + static ftl = new Localization(["preview/megalist.ftl"]); + + async localizeStrings(strings) { + const keys = Object.keys(strings); + const localisationIds = Object.values(strings).map(id => ({ id })); + const messages = await DataSourceBase.ftl.formatMessages(localisationIds); + + for (let i = 0; i < messages.length; i++) { + let { attributes, value } = messages[i]; + if (attributes) { + value = attributes.reduce( + (result, { name, value }) => ({ ...result, [name]: value }), + {} + ); + } + strings[keys[i]] = value; + } + return strings; + } /** * Prototype for the each line. @@ -94,6 +117,10 @@ export class DataSourceBase { return true; }, + isEditing() { + return this.editingValue !== undefined; + }, + copyToClipboard(text) { lazy.ClipboardHelper.copyString(text, lazy.ClipboardHelper.Sensitive); }, @@ -135,6 +162,9 @@ export class DataSourceBase { refreshOnScreen() { this.source.refreshSingleLineOnScreen(this); }, + setLayout(data) { + this.source.setLayout(data); + }, }; /** @@ -144,7 +174,6 @@ export class DataSourceBase { * @returns {object} section header line */ createHeaderLine(label) { - const toggleCommand = { id: "Toggle", label: "" }; const result = { label, value: "", @@ -164,7 +193,7 @@ export class DataSourceBase { lineIsReady: () => true, - commands: [toggleCommand], + commands: [{ id: "Toggle", label: "command-toggle" }], executeToggle() { this.collapsed = !this.collapsed; @@ -172,10 +201,6 @@ export class DataSourceBase { }, }; - this.formatMessages("command-toggle").then(([toggleLabel]) => { - toggleCommand.label = toggleLabel; - }); - return result; } @@ -244,6 +269,10 @@ export class DataSourceBase { return this.lines[index]; } + cancelDialog() { + this.setLayout(null); + } + *enumerateLinesForMatchingRecords(searchText, stats, match) { stats.total = 0; stats.count = 0; diff --git a/toolkit/components/satchel/megalist/aggregator/datasources/LoginDataSource.sys.mjs b/toolkit/components/satchel/megalist/aggregator/datasources/LoginDataSource.sys.mjs index 324bc4d141..7e74ce2488 100644 --- a/toolkit/components/satchel/megalist/aggregator/datasources/LoginDataSource.sys.mjs +++ b/toolkit/components/satchel/megalist/aggregator/datasources/LoginDataSource.sys.mjs @@ -19,13 +19,6 @@ XPCOMUtils.defineLazyPreferenceGetter( false ); -XPCOMUtils.defineLazyPreferenceGetter( - lazy, - "VULNERABLE_PASSWORDS_ENABLED", - "signon.management.page.vulnerable-passwords.enabled", - false -); - /** * Data source for Logins. * @@ -45,235 +38,239 @@ export class LoginDataSource extends DataSourceBase { constructor(...args) { super(...args); // Wait for Fluent to provide strings before loading data - this.formatMessages( - "passwords-section-label", - "passwords-origin-label", - "passwords-username-label", - "passwords-password-label", - "command-open", - "command-copy", - "command-reveal", - "command-conceal", - "passwords-disabled", - "command-delete", - "command-edit", - "passwords-command-create", - "passwords-command-import", - "passwords-command-export", - "passwords-command-remove-all", - "passwords-command-settings", - "passwords-command-help", - "passwords-import-file-picker-title", - "passwords-import-file-picker-import-button", - "passwords-import-file-picker-csv-filter-title", - "passwords-import-file-picker-tsv-filter-title" - ).then( - ([ - headerLabel, - originLabel, - usernameLabel, - passwordLabel, - openCommandLabel, - copyCommandLabel, - revealCommandLabel, - concealCommandLabel, - passwordsDisabled, - deleteCommandLabel, - editCommandLabel, - passwordsCreateCommandLabel, - passwordsImportCommandLabel, - passwordsExportCommandLabel, - passwordsRemoveAllCommandLabel, - passwordsSettingsCommandLabel, - passwordsHelpCommandLabel, - passwordsImportFilePickerTitle, - passwordsImportFilePickerImportButton, - passwordsImportFilePickerCsvFilterTitle, - passwordsImportFilePickerTsvFilterTitle, - ]) => { - const copyCommand = { id: "Copy", label: copyCommandLabel }; - const editCommand = { id: "Edit", label: editCommandLabel }; - const deleteCommand = { id: "Delete", label: deleteCommandLabel }; - this.breachedSticker = { type: "warning", label: "BREACH" }; - this.vulnerableSticker = { type: "risk", label: "🤮 Vulnerable" }; - this.#loginsDisabledMessage = passwordsDisabled; - this.#header = this.createHeaderLine(headerLabel); - this.#header.commands.push( - { id: "Create", label: passwordsCreateCommandLabel }, - { id: "Import", label: passwordsImportCommandLabel }, - { id: "Export", label: passwordsExportCommandLabel }, - { id: "RemoveAll", label: passwordsRemoveAllCommandLabel }, - { id: "Settings", label: passwordsSettingsCommandLabel }, - { id: "Help", label: passwordsHelpCommandLabel } + this.localizeStrings({ + headerLabel: "passwords-section-label", + originLabel: "passwords-origin-label", + usernameLabel: "passwords-username-label", + passwordLabel: "passwords-password-label", + passwordsDisabled: "passwords-disabled", + passwordsImportFilePickerTitle: "passwords-import-file-picker-title", + passwordsImportFilePickerImportButton: + "passwords-import-file-picker-import-button", + passwordsImportFilePickerCsvFilterTitle: + "passwords-import-file-picker-csv-filter-title", + passwordsImportFilePickerTsvFilterTitle: + "passwords-import-file-picker-tsv-filter-title", + dismissBreachCommandLabel: "passwords-dismiss-breach-alert-command", + }).then(strings => { + const copyCommand = { id: "Copy", label: "command-copy" }; + const editCommand = { id: "Edit", label: "command-edit" }; + const deleteCommand = { id: "Delete", label: "command-delete" }; + const dismissBreachCommand = { + id: "DismissBreach", + label: strings.dismissBreachCommandLabel, + }; + const noOriginSticker = { type: "error", label: "😾 Missing origin" }; + const noPasswordSticker = { type: "error", label: "😾 Missing password" }; + const breachedSticker = { type: "warning", label: "BREACH" }; + const vulnerableSticker = { type: "risk", label: "🤮 Vulnerable" }; + this.#loginsDisabledMessage = strings.passwordsDisabled; + this.#header = this.createHeaderLine(strings.headerLabel); + this.#header.commands.push( + { id: "Create", label: "passwords-command-create" }, + { id: "Import", label: "passwords-command-import" }, + { id: "Export", label: "passwords-command-export" }, + { id: "RemoveAll", label: "passwords-command-remove-all" }, + { id: "Settings", label: "passwords-command-settings" }, + { id: "Help", label: "passwords-command-help" } + ); + this.#header.executeImport = async () => + this.#importFromFile( + strings.passwordsImportFilePickerTitle, + strings.passwordsImportFilePickerImportButton, + strings.passwordsImportFilePickerCsvFilterTitle, + strings.passwordsImportFilePickerTsvFilterTitle ); - this.#header.executeImport = async () => { - await this.#importFromFile( - passwordsImportFilePickerTitle, - passwordsImportFilePickerImportButton, - passwordsImportFilePickerCsvFilterTitle, - passwordsImportFilePickerTsvFilterTitle - ); - }; - this.#header.executeSettings = () => { - this.#openPreferences(); - }; - this.#header.executeHelp = () => { - this.#getHelp(); - }; - - this.#originPrototype = this.prototypeDataLine({ - label: { value: originLabel }, - start: { value: true }, - value: { - get() { - return this.record.displayOrigin; - }, + + this.#header.executeRemoveAll = () => this.#removeAllPasswords(); + this.#header.executeSettings = () => this.#openPreferences(); + this.#header.executeHelp = () => this.#getHelp(); + this.#header.executeExport = () => this.#exportAllPasswords(); + + this.#originPrototype = this.prototypeDataLine({ + label: { value: strings.originLabel }, + start: { value: true }, + value: { + get() { + return this.record.displayOrigin; + }, + }, + valueIcon: { + get() { + return `page-icon:${this.record.origin}`; }, - valueIcon: { - get() { - return `page-icon:${this.record.origin}`; - }, + }, + href: { + get() { + return this.record.origin; }, - href: { - get() { - return this.record.origin; - }, + }, + commands: { + *value() { + yield { id: "Open", label: "command-open" }; + yield copyCommand; + yield "-"; + yield deleteCommand; + + if (this.breached) { + yield dismissBreachCommand; + } }, - commands: { - value: [ - { id: "Open", label: openCommandLabel }, - copyCommand, - "-", - deleteCommand, - ], + }, + executeDismissBreach: { + value() { + lazy.LoginBreaches.recordBreachAlertDismissal(this.record.guid); + delete this.breached; + this.refreshOnScreen(); }, - executeCopy: { - value() { - this.copyToClipboard(this.record.origin); - }, + }, + executeCopy: { + value() { + this.copyToClipboard(this.record.origin); }, - }); - this.#usernamePrototype = this.prototypeDataLine({ - label: { value: usernameLabel }, - value: { - get() { - return this.editingValue ?? this.record.username; - }, + }, + executeDelete: { + value() { + this.setLayout({ id: "remove-login" }); + }, + }, + stickers: { + *value() { + if (this.isEditing() && !this.editingValue.length) { + yield noOriginSticker; + } + + if (this.breached) { + yield breachedSticker; + } }, - commands: { value: [copyCommand, editCommand, "-", deleteCommand] }, - executeEdit: { - value() { - this.editingValue = this.record.username ?? ""; - this.refreshOnScreen(); - }, + }, + }); + this.#usernamePrototype = this.prototypeDataLine({ + label: { value: strings.usernameLabel }, + value: { + get() { + return this.editingValue ?? this.record.username; }, - executeSave: { - value(value) { - try { - const modifiedLogin = this.record.clone(); - modifiedLogin.username = value; - Services.logins.modifyLogin(this.record, modifiedLogin); - } catch (error) { - //todo - console.error("failed to modify login", error); - } - this.executeCancel(); - }, + }, + commands: { value: [copyCommand, editCommand, "-", deleteCommand] }, + executeEdit: { + value() { + this.editingValue = this.record.username ?? ""; + this.refreshOnScreen(); }, - }); - this.#passwordPrototype = this.prototypeDataLine({ - label: { value: passwordLabel }, - concealed: { value: true, writable: true }, - end: { value: true }, - value: { - get() { - return ( - this.editingValue ?? - (this.concealed ? "••••••••" : this.record.password) - ); - }, + }, + executeSave: { + value(value) { + try { + const modifiedLogin = this.record.clone(); + modifiedLogin.username = value; + Services.logins.modifyLogin(this.record, modifiedLogin); + } catch (error) { + //todo + console.error("failed to modify login", error); + } + this.executeCancel(); + }, + }, + }); + this.#passwordPrototype = this.prototypeDataLine({ + label: { value: strings.passwordLabel }, + concealed: { value: true, writable: true }, + end: { value: true }, + value: { + get() { + return ( + this.editingValue ?? + (this.concealed ? "••••••••" : this.record.password) + ); }, - commands: { - get() { - const commands = [ - { id: "Conceal", label: concealCommandLabel }, - { - id: "Copy", - label: copyCommandLabel, - verify: true, - }, - editCommand, - "-", - deleteCommand, - ]; - if (this.concealed) { - commands[0] = { - id: "Reveal", - label: revealCommandLabel, - verify: true, - }; - } - return commands; - }, + }, + stickers: { + *value() { + if (this.isEditing() && !this.editingValue.length) { + yield noPasswordSticker; + } + + if (this.vulnerable) { + yield vulnerableSticker; + } }, - executeReveal: { - value() { - this.concealed = false; - this.refreshOnScreen(); - }, + }, + commands: { + *value() { + if (this.concealed) { + yield { id: "Reveal", label: "command-reveal", verify: true }; + } else { + yield { id: "Conceal", label: "command-conceal" }; + } + yield { ...copyCommand, verify: true }; + yield editCommand; + yield "-"; + yield deleteCommand; }, - executeConceal: { - value() { - this.concealed = true; - this.refreshOnScreen(); - }, + }, + executeReveal: { + value() { + this.concealed = false; + this.refreshOnScreen(); }, - executeCopy: { - value() { - this.copyToClipboard(this.record.password); - }, + }, + executeConceal: { + value() { + this.concealed = true; + this.refreshOnScreen(); }, - executeEdit: { - value() { - this.editingValue = this.record.password ?? ""; - this.refreshOnScreen(); - }, + }, + executeCopy: { + value() { + this.copyToClipboard(this.record.password); }, - executeSave: { - value(value) { - try { - const modifiedLogin = this.record.clone(); - modifiedLogin.password = value; - Services.logins.modifyLogin(this.record, modifiedLogin); - } catch (error) { - //todo - console.error("failed to modify login", error); - } - this.executeCancel(); - }, + }, + executeEdit: { + value() { + this.editingValue = this.record.password ?? ""; + this.refreshOnScreen(); }, - }); + }, + executeSave: { + value(value) { + if (!value) { + return; + } - Services.obs.addObserver(this, "passwordmgr-storage-changed"); - Services.prefs.addObserver("signon.rememberSignons", this); - Services.prefs.addObserver( - "signon.management.page.breach-alerts.enabled", - this - ); - Services.prefs.addObserver( - "signon.management.page.vulnerable-passwords.enabled", - this - ); - this.#reloadDataSource(); - } - ); + try { + const modifiedLogin = this.record.clone(); + modifiedLogin.password = value; + Services.logins.modifyLogin(this.record, modifiedLogin); + } catch (error) { + //todo + console.error("failed to modify login", error); + } + this.executeCancel(); + }, + }, + }); + + Services.obs.addObserver(this, "passwordmgr-storage-changed"); + Services.prefs.addObserver("signon.rememberSignons", this); + Services.prefs.addObserver( + "signon.management.page.breach-alerts.enabled", + this + ); + Services.prefs.addObserver( + "signon.management.page.vulnerable-passwords.enabled", + this + ); + this.#reloadDataSource(); + }); } async #importFromFile(title, buttonLabel, csvTitle, tsvTitle) { const { BrowserWindowTracker } = ChromeUtils.importESModule( "resource:///modules/BrowserWindowTracker.sys.mjs" ); - const browser = BrowserWindowTracker.getTopWindow().gBrowser; + const browsingContext = BrowserWindowTracker.getTopWindow().browsingContext; let { result, path } = await this.openFilePickerDialog( title, buttonLabel, @@ -287,26 +284,38 @@ export class LoginDataSource extends DataSourceBase { extensionPattern: "*.tsv", }, ], - browser.ownerGlobal + browsingContext ); if (result != Ci.nsIFilePicker.returnCancel) { - let summary; try { - summary = await LoginCSVImport.importFromCSV(path); + const summary = await LoginCSVImport.importFromCSV(path); + const counts = { added: 0, modified: 0, no_change: 0, error: 0 }; + + for (const item of summary) { + counts[item.result] += 1; + } + const l10nArgs = Object.values(counts).map(count => ({ count })); + + this.setLayout({ + id: "import-logins", + l10nArgs, + }); } catch (e) { - // TODO: Display error for import - } - if (summary) { - // TODO: Display successful import summary + this.setLayout({ id: "import-error" }); } } } - async openFilePickerDialog(title, okButtonLabel, appendFilters, ownerGlobal) { + async openFilePickerDialog( + title, + okButtonLabel, + appendFilters, + browsingContext + ) { return new Promise(resolve => { let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker); - fp.init(ownerGlobal, title, Ci.nsIFilePicker.modeOpen); + fp.init(browsingContext, title, Ci.nsIFilePicker.modeOpen); for (const appendFilter of appendFilters) { fp.appendFilter(appendFilter.title, appendFilter.extensionPattern); } @@ -318,6 +327,48 @@ export class LoginDataSource extends DataSourceBase { }); } + #removeAllPasswords() { + let count = 0; + let currentRecord; + for (const line of this.lines) { + if (line.record != currentRecord) { + count += 1; + currentRecord = line.record; + } + } + + this.setLayout({ id: "remove-logins", l10nArgs: [{ count }] }); + } + + #exportAllPasswords() { + this.setLayout({ id: "export-logins" }); + } + + confirmRemoveAll() { + Services.logins.removeAllLogins(); + this.cancelDialog(); + } + + confirmExportLogins() { + // TODO: Implement this. + // We need to simplify this function first + // https://searchfox.org/mozilla-central/source/browser/components/aboutlogins/AboutLoginsParent.sys.mjs#377 + // It's too messy right now. + this.cancelDialog(); + } + + confirmRemoveLogin() { + // TODO: Simplify getting record directly. + const login = this.lines?.[0]?.record; + Services.logins.removeLogin(login); + this.cancelDialog(); + } + + confirmRetryImport() { + // TODO: Implement this. + this.cancelDialog(); + } + #openPreferences() { const { BrowserWindowTracker } = ChromeUtils.importESModule( "resource:///modules/BrowserWindowTracker.sys.mjs" @@ -422,31 +473,8 @@ export class LoginDataSource extends DataSourceBase { this.#passwordPrototype ); - let breachIndex = - originLine.stickers?.findIndex(s => s === this.breachedSticker) ?? -1; - let breach = breachesMap.get(login.guid); - if (breach && breachIndex < 0) { - originLine.stickers ??= []; - originLine.stickers.push(this.breachedSticker); - } else if (!breach && breachIndex >= 0) { - originLine.stickers.splice(breachIndex, 1); - } - - const vulnerable = lazy.VULNERABLE_PASSWORDS_ENABLED - ? lazy.LoginBreaches.getPotentiallyVulnerablePasswordsByLoginGUID([ - login, - ]).size - : 0; - - let vulnerableIndex = - passwordLine.stickers?.findIndex(s => s === this.vulnerableSticker) ?? - -1; - if (vulnerable && vulnerableIndex < 0) { - passwordLine.stickers ??= []; - passwordLine.stickers.push(this.vulnerableSticker); - } else if (!vulnerable && vulnerableIndex >= 0) { - passwordLine.stickers.splice(vulnerableIndex, 1); - } + originLine.breached = breachesMap.has(login.guid); + passwordLine.vulnerable = lazy.LoginBreaches.isVulnerablePassword(login); }); this.afterReloadingDataSource(); |