Bug 1252871 - Add a utility for managing extension preferences r?aswan draft
authorMatthew Wein <mwein@mozilla.com>
Wed, 26 Oct 2016 11:08:11 +0100
changeset 429727 a2d9dcc8cb229fb955f23a9ac75b83863b3730b2
parent 429726 0fc47ea07ece85441eb1f6187124b510837d7978
child 429728 274e9d524817483707164938810e911c8528c44f
push id33647
push usermwein@mozilla.com
push dateWed, 26 Oct 2016 11:31:01 +0000
reviewersaswan
bugs1252871
milestone52.0a1
Bug 1252871 - Add a utility for managing extension preferences r?aswan MozReview-Commit-ID: FZT2R3SadA1
toolkit/components/extensions/Extension.jsm
toolkit/components/extensions/ExtensionUtils.jsm
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -47,18 +47,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "MessageChannel",
                                   "resource://gre/modules/MessageChannel.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                   "resource://gre/modules/osfile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
-                                  "resource://gre/modules/Preferences.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "require",
                                   "resource://devtools/shared/Loader.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Schemas",
                                   "resource://gre/modules/Schemas.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
 
 Cu.import("resource://gre/modules/ExtensionContent.jsm");
@@ -77,16 +75,17 @@ let schemaURLs = new Set();
 if (!AppConstants.RELEASE_OR_BETA) {
   schemaURLs.add("chrome://extensions/content/schemas/experiments.json");
 }
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 var {
   BaseContext,
   EventEmitter,
+  ExtensionPreferences,
   SchemaAPIManager,
   LocaleData,
   instanceOf,
   LocalAPIImplementation,
   flushJarCache,
 } = ExtensionUtils;
 
 XPCOMUtils.defineLazyGetter(this, "console", ExtensionUtils.getConsole);
@@ -562,52 +561,32 @@ ParentAPIManager.init();
 
 // All moz-extension URIs use a machine-specific UUID rather than the
 // extension's own ID in the host component. This makes it more
 // difficult for web pages to detect whether a user has a given add-on
 // installed (by trying to load a moz-extension URI referring to a
 // web_accessible_resource from the extension). UUIDMap.get()
 // returns the UUID for a given add-on ID.
 var UUIDMap = {
-  _read() {
-    let pref = Preferences.get(UUID_MAP_PREF, "{}");
-    try {
-      return JSON.parse(pref);
-    } catch (e) {
-      Cu.reportError(`Error parsing ${UUID_MAP_PREF}.`);
-      return {};
+  get(id, create = true) {
+    if (ExtensionPreferences.has(id, UUID_MAP_PREF)) {
+      return ExtensionPreferences.get(id, UUID_MAP_PREF);
+    } else if (create) {
+      // Create a new UUID and strip off the outer curly brackets.
+      let uuid = uuidGen.generateUUID().number.slice(1, -1);
+      ExtensionPreferences.set(id, UUID_MAP_PREF, uuid);
+      return uuid;
     }
-  },
-
-  _write(map) {
-    Preferences.set(UUID_MAP_PREF, JSON.stringify(map));
-  },
-
-  get(id, create = true) {
-    let map = this._read();
-
-    if (id in map) {
-      return map[id];
-    }
-
-    let uuid = null;
-    if (create) {
-      uuid = uuidGen.generateUUID().number;
-      uuid = uuid.slice(1, -1); // Strip { and } off the UUID.
-
-      map[id] = uuid;
-      this._write(map);
-    }
-    return uuid;
+    return null;
   },
 
   remove(id) {
-    let map = this._read();
-    delete map[id];
-    this._write(map);
+    if (ExtensionPreferences.has(id, UUID_MAP_PREF)) {
+      ExtensionPreferences.delete(id, UUID_MAP_PREF);
+    }
   },
 };
 
 // This is the old interface that UUIDMap replaced, to be removed when
 // the references listed in bug 1291399 are updated.
 /* exported getExtensionUUID */
 function getExtensionUUID(id) {
   return UUIDMap.get(id, true);
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -894,16 +894,75 @@ LocaleData.prototype = {
 
   get uiLocale() {
     // Return the browser locale, but convert it to a Chrome-style
     // locale code.
     return Locale.getLocale().replace(/-/g, "_");
   },
 };
 
+/**
+ * Utility for managing extension preferences.
+ *
+ * For each preference, a map is stored associating
+ * extension IDs to preferences.
+ */
+class ExtensionPreferences {
+  /**
+   * Retrieves the preferences for the specified extension id.
+   *
+   * @param {string} id The ID of the extension.
+   * @param {string} pref The preference to look up.
+   * @returns {*} the JSON serializable preferences stored for the extension.
+   */
+  static get(id, pref) {
+    let value = Preferences.get(pref, "[]");
+    let map = new Map(JSON.parse(value));
+    return map.get(id);
+  }
+
+  /**
+   * Checks if preference exists for the specified extension id.
+   *
+   * @param {string} id The ID of the extension.
+   * @param {string} pref The preference to check.
+   * @returns {boolean} true if the extension has preferences stored.
+   */
+  static has(id, pref) {
+    let value = Preferences.get(pref, "[]");
+    let map = new Map(JSON.parse(value));
+    return map.has(id);
+  }
+
+  /**
+   * Stores JSON serializable preferences for the specified extension id.
+   *
+   * @param {string} id The ID of the extension.
+   * @param {string} pref The preference to update.
+   * @param {*} prefs The JSON serializable preferences to store.
+   */
+  static set(id, pref, prefs) {
+    let map = new Map(JSON.parse(Preferences.get(pref, "[]")));
+    map.set(id, prefs);
+    Preferences.set(pref, JSON.stringify([...map]));
+  }
+
+  /**
+   * Deletes the preferences for the specified extension id.
+   *
+   * @param {string} id The ID of the extension.
+   * @param {string} pref The preference to delete.
+   */
+  static delete(id, pref) {
+    let map = new Map(JSON.parse(Preferences.get(pref, "[]")));
+    map.delete(id);
+    Preferences.set(pref, JSON.stringify([...map]));
+  }
+}
+
 // This is a generic class for managing event listeners. Example usage:
 //
 // new EventManager(context, "api.subAPI", fire => {
 //   let listener = (...) => {
 //     // Fire any listeners registered with addListener.
 //     fire(arg1, arg2);
 //   };
 //   // Register the listener.
@@ -2113,16 +2172,17 @@ this.ExtensionUtils = {
   runSafeSync,
   runSafeSyncWithoutClone,
   runSafeWithoutClone,
   stylesheetMap,
   BaseContext,
   DefaultWeakMap,
   EventEmitter,
   EventManager,
+  ExtensionPreferences,
   IconDetails,
   LocalAPIImplementation,
   LocaleData,
   Messenger,
   PlatformInfo,
   SchemaAPIInterface,
   SingletonEventManager,
   SpreadArgs,