Source code

Revision control

Copy as Markdown

Other Tools

/* 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/. */
/*
* Implements an interface of the storage of Form Autofill for GeckoView.
*/
import {
FormAutofillStorageBase,
CreditCardsBase,
AddressesBase,
} from "resource://autofill/FormAutofillStorageBase.sys.mjs";
import { JSONFile } from "resource://gre/modules/JSONFile.sys.mjs";
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
Address: "resource://gre/modules/GeckoViewAutocomplete.sys.mjs",
CreditCard: "resource://gre/modules/GeckoViewAutocomplete.sys.mjs",
GeckoViewAutocomplete: "resource://gre/modules/GeckoViewAutocomplete.sys.mjs",
});
class GeckoViewStorage extends JSONFile {
constructor() {
super({ path: null, sanitizedBasename: "GeckoViewStorage" });
}
async updateCreditCards() {
const creditCards =
await lazy.GeckoViewAutocomplete.fetchCreditCards().then(
results => results?.map(r => lazy.CreditCard.parse(r).toGecko()) ?? [],
_ => []
);
super.data.creditCards = creditCards;
}
async updateAddresses() {
const addresses = await lazy.GeckoViewAutocomplete.fetchAddresses().then(
results => results?.map(r => lazy.Address.parse(r).toGecko()) ?? [],
_ => []
);
super.data.addresses = addresses;
}
async load() {
super.data = { creditCards: {}, addresses: {} };
await this.updateCreditCards();
await this.updateAddresses();
}
ensureDataReady() {
if (this.dataReady) {
return;
}
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
}
async _save() {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
}
}
class Addresses extends AddressesBase {
// Override AutofillRecords methods.
_initialize() {
this._initializePromise = Promise.resolve();
}
async _saveRecord(record) {
lazy.GeckoViewAutocomplete.onAddressSave(lazy.Address.fromGecko(record));
}
/**
* Returns the record with the specified GUID.
*
* @param {string} guid
* Indicates which record to retrieve.
* @param {object} options
* @param {boolean} [options.rawData = false]
* Returns a raw record without modifications and the computed fields
* (this includes private fields)
* @returns {Promise<object>}
* A clone of the record.
*/
async get(guid, { rawData = false } = {}) {
await this._store.updateAddresses();
return super.get(guid, { rawData });
}
/**
* Returns all records.
*
* @param {object} options
* @param {boolean} [options.rawData = false]
* Returns raw records without modifications and the computed fields.
* @param {boolean} [options.includeDeleted = false]
* Also return any tombstone records.
* @returns {Promise<Array.<object>>}
* An array containing clones of all records.
*/
async getAll({ rawData = false, includeDeleted = false } = {}) {
await this._store.updateAddresses();
return super.getAll({ rawData, includeDeleted });
}
/**
* Return all saved field names in the collection.
*
* @returns {Set} Set containing saved field names.
*/
async getSavedFieldNames() {
await this._store.updateAddresses();
return super.getSavedFieldNames();
}
async reconcile(_remoteRecord) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
}
async findDuplicateGUID(_remoteRecord) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
}
}
class CreditCards extends CreditCardsBase {
async _encryptNumber(_creditCard) {
// Don't encrypt or obfuscate for GV, since we don't store or show
// the number. The API has to always provide the original number.
}
// Override AutofillRecords methods.
_initialize() {
this._initializePromise = Promise.resolve();
}
async _saveRecord(record) {
lazy.GeckoViewAutocomplete.onCreditCardSave(
lazy.CreditCard.fromGecko(record)
);
}
/**
* Returns the record with the specified GUID.
*
* @param {string} guid
* Indicates which record to retrieve.
* @param {object} options
* @param {boolean} [options.rawData = false]
* Returns a raw record without modifications and the computed fields
* (this includes private fields)
* @returns {Promise<object>}
* A clone of the record.
*/
async get(guid, { rawData = false } = {}) {
await this._store.updateCreditCards();
return super.get(guid, { rawData });
}
/**
* Returns all records.
*
* @param {object} options
* @param {boolean} [options.rawData = false]
* Returns raw records without modifications and the computed fields.
* @param {boolean} [options.includeDeleted = false]
* Also return any tombstone records.
* @returns {Promise<Array.<object>>}
* An array containing clones of all records.
*/
async getAll({ rawData = false, includeDeleted = false } = {}) {
await this._store.updateCreditCards();
return super.getAll({ rawData, includeDeleted });
}
/**
* Return all saved field names in the collection.
*
* @returns {Set} Set containing saved field names.
*/
async getSavedFieldNames() {
await this._store.updateCreditCards();
return super.getSavedFieldNames();
}
/**
* Find a duplicate credit card record in the storage.
*
* A record is considered as a duplicate of another record when two records
* are the "same". This might be true even when some of their fields are
* different. For example, one record has the same credit card number but has
* different expiration date as the other record are still considered as
* "duplicate".
* This is different from `getMatchRecord`, which ensures all the fields with
* value in the the record is equal to the returned record.
*
* @param {object} record
* The credit card for duplication checking. please make sure the
* record is normalized.
* @returns {object}
* Return the first duplicated record found in storage, null otherwise.
*/
async *getDuplicateRecords(record) {
if (!record["cc-number"]) {
return null;
}
await this._store.updateCreditCards();
for (const recordInStorage of this._data) {
if (recordInStorage.deleted) {
continue;
}
if (recordInStorage["cc-number"] == record["cc-number"]) {
yield recordInStorage;
}
}
return null;
}
async reconcile(_remoteRecord) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
}
async findDuplicateGUID(_remoteRecord) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
}
}
export class FormAutofillStorage extends FormAutofillStorageBase {
constructor() {
super(null);
}
getAddresses() {
if (!this._addresses) {
this._store.ensureDataReady();
this._addresses = new Addresses(this._store);
}
return this._addresses;
}
getCreditCards() {
if (!this._creditCards) {
this._store.ensureDataReady();
this._creditCards = new CreditCards(this._store);
}
return this._creditCards;
}
/**
* Initializes the in-memory async store API.
*
* @returns {JSONFile}
* The JSONFile store.
*/
_initializeStore() {
return new GeckoViewStorage();
}
}
// The singleton exposed by this module.
export const formAutofillStorage = new FormAutofillStorage();