summaryrefslogtreecommitdiffstats
path: root/toolkit/components/satchel/megalist/aggregator/datasources
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/satchel/megalist/aggregator/datasources')
-rw-r--r--toolkit/components/satchel/megalist/aggregator/datasources/AddressesDataSource.sys.mjs168
-rw-r--r--toolkit/components/satchel/megalist/aggregator/datasources/BankCardDataSource.sys.mjs302
-rw-r--r--toolkit/components/satchel/megalist/aggregator/datasources/DataSourceBase.sys.mjs41
-rw-r--r--toolkit/components/satchel/megalist/aggregator/datasources/LoginDataSource.sys.mjs522
4 files changed, 522 insertions, 511 deletions
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();