summaryrefslogtreecommitdiffstats
path: root/browser/extensions/formautofill/content/ProfileStorage.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'browser/extensions/formautofill/content/ProfileStorage.jsm')
-rw-r--r--browser/extensions/formautofill/content/ProfileStorage.jsm251
1 files changed, 251 insertions, 0 deletions
diff --git a/browser/extensions/formautofill/content/ProfileStorage.jsm b/browser/extensions/formautofill/content/ProfileStorage.jsm
new file mode 100644
index 000000000..843177d4e
--- /dev/null
+++ b/browser/extensions/formautofill/content/ProfileStorage.jsm
@@ -0,0 +1,251 @@
+/* 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.
+ *
+ * The data is stored in JSON format, without indentation, using UTF-8 encoding.
+ * With indentation applied, the file would look like this:
+ *
+ * {
+ * version: 1,
+ * profiles: [
+ * {
+ * guid, // 12 character...
+ *
+ * // profile
+ * organization, // Company
+ * streetAddress, // (Multiline)
+ * addressLevel2, // City/Town
+ * addressLevel1, // Province (Standardized code if possible)
+ * postalCode,
+ * country, // ISO 3166
+ * tel,
+ * email,
+ *
+ * // metadata
+ * timeCreated, // in ms
+ * timeLastUsed, // in ms
+ * timeLastModified, // in ms
+ * timesUsed
+ * },
+ * {
+ * // ...
+ * }
+ * ]
+ * }
+ */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "JSONFile",
+ "resource://gre/modules/JSONFile.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator",
+ "@mozilla.org/uuid-generator;1",
+ "nsIUUIDGenerator");
+
+const SCHEMA_VERSION = 1;
+
+// Name-related fields will be handled in follow-up bugs due to the complexity.
+const VALID_FIELDS = [
+ "organization",
+ "streetAddress",
+ "addressLevel2",
+ "addressLevel1",
+ "postalCode",
+ "country",
+ "tel",
+ "email",
+];
+
+function ProfileStorage(path) {
+ this._path = path;
+}
+
+ProfileStorage.prototype = {
+ /**
+ * Loads the profile data from file to memory.
+ *
+ * @returns {Promise}
+ * @resolves When the operation finished successfully.
+ * @rejects JavaScript exception.
+ */
+ initialize() {
+ this._store = new JSONFile({
+ path: this._path,
+ dataPostProcessor: this._dataPostProcessor.bind(this),
+ });
+ return this._store.load();
+ },
+
+ /**
+ * Adds a new profile.
+ *
+ * @param {Profile} profile
+ * The new profile for saving.
+ */
+ add(profile) {
+ this._store.ensureDataReady();
+
+ let profileToSave = this._normalizeProfile(profile);
+
+ profileToSave.guid = gUUIDGenerator.generateUUID().toString()
+ .replace(/[{}-]/g, "").substring(0, 12);
+
+ // Metadata
+ let now = Date.now();
+ profileToSave.timeCreated = now;
+ profileToSave.timeLastModified = now;
+ profileToSave.timeLastUsed = 0;
+ profileToSave.timesUsed = 0;
+
+ this._store.data.profiles.push(profileToSave);
+
+ this._store.saveSoon();
+ },
+
+ /**
+ * Update the specified profile.
+ *
+ * @param {string} guid
+ * Indicates which profile to update.
+ * @param {Profile} profile
+ * The new profile used to overwrite the old one.
+ */
+ update(guid, profile) {
+ this._store.ensureDataReady();
+
+ let profileFound = this._findByGUID(guid);
+ if (!profileFound) {
+ throw new Error("No matching profile.");
+ }
+
+ let profileToUpdate = this._normalizeProfile(profile);
+ for (let field of VALID_FIELDS) {
+ if (profileToUpdate[field] !== undefined) {
+ profileFound[field] = profileToUpdate[field];
+ } else {
+ delete profileFound[field];
+ }
+ }
+
+ profileFound.timeLastModified = Date.now();
+
+ this._store.saveSoon();
+ },
+
+ /**
+ * Notifies the stroage of the use of the specified profile, so we can update
+ * the metadata accordingly.
+ *
+ * @param {string} guid
+ * Indicates which profile to be notified.
+ */
+ notifyUsed(guid) {
+ this._store.ensureDataReady();
+
+ let profileFound = this._findByGUID(guid);
+ if (!profileFound) {
+ throw new Error("No matching profile.");
+ }
+
+ profileFound.timesUsed++;
+ profileFound.timeLastUsed = Date.now();
+
+ this._store.saveSoon();
+ },
+
+ /**
+ * Removes the specified profile. No error occurs if the profile isn't found.
+ *
+ * @param {string} guid
+ * Indicates which profile to remove.
+ */
+ remove(guid) {
+ this._store.ensureDataReady();
+
+ this._store.data.profiles =
+ this._store.data.profiles.filter(profile => profile.guid != guid);
+ this._store.saveSoon();
+ },
+
+ /**
+ * Returns the profile with the specified GUID.
+ *
+ * @param {string} guid
+ * Indicates which profile to retrieve.
+ * @returns {Profile}
+ * A clone of the profile.
+ */
+ get(guid) {
+ this._store.ensureDataReady();
+
+ let profileFound = this._findByGUID(guid);
+ if (!profileFound) {
+ throw new Error("No matching profile.");
+ }
+
+ // Profile is cloned to avoid accidental modifications from outside.
+ return this._clone(profileFound);
+ },
+
+ /**
+ * Returns all profiles.
+ *
+ * @returns {Array.<Profile>}
+ * An array containing clones of all profiles.
+ */
+ getAll() {
+ this._store.ensureDataReady();
+
+ // Profiles are cloned to avoid accidental modifications from outside.
+ return this._store.data.profiles.map(this._clone);
+ },
+
+ _clone(profile) {
+ return Object.assign({}, profile);
+ },
+
+ _findByGUID(guid) {
+ return this._store.data.profiles.find(profile => profile.guid == guid);
+ },
+
+ _normalizeProfile(profile) {
+ let result = {};
+ for (let key in profile) {
+ if (!VALID_FIELDS.includes(key)) {
+ throw new Error(`"${key}" is not a valid field.`);
+ }
+ if (typeof profile[key] !== "string" &&
+ typeof profile[key] !== "number") {
+ throw new Error(`"${key}" contains invalid data type.`);
+ }
+
+ result[key] = profile[key];
+ }
+ return result;
+ },
+
+ _dataPostProcessor(data) {
+ data.version = SCHEMA_VERSION;
+ if (!data.profiles) {
+ data.profiles = [];
+ }
+ return data;
+ },
+
+ // For test only.
+ _saveImmediately() {
+ return this._store._save();
+ },
+};
+
+this.EXPORTED_SYMBOLS = ["ProfileStorage"];