Bug 1381297 - Store the installDate of an extension as a number in the extension-settings.json file, r?aswan draft
authorBob Silverberg <bsilverberg@mozilla.com>
Thu, 20 Jul 2017 09:02:36 -0400
changeset 615034 d9b38ba51309d51aae9fa9c87da3debc32909506
parent 614627 986b85f2306929ee2168338f84c51969c612825f
child 639061 fb8c1b16884d93bc5601d62261974391f8e9f8da
push id70224
push userbmo:bob.silverberg@gmail.com
push dateTue, 25 Jul 2017 10:19:43 +0000
reviewersaswan
bugs1381297
milestone56.0a1
Bug 1381297 - Store the installDate of an extension as a number in the extension-settings.json file, r?aswan MozReview-Commit-ID: 1P7hy23Yyk6
toolkit/components/extensions/ExtensionSettingsStore.jsm
toolkit/components/extensions/test/xpcshell/test_ext_extensionSettingsStore.js
--- a/toolkit/components/extensions/ExtensionSettingsStore.jsm
+++ b/toolkit/components/extensions/ExtensionSettingsStore.jsm
@@ -18,17 +18,17 @@
  *
  * {
  *   type: { // The type of settings being stored in this object, i.e., prefs.
  *     key: { // The unique key for the setting.
  *       initialValue, // The initial value of the setting.
  *       precedenceList: [
  *         {
  *           id, // The id of the extension requesting the setting.
- *           installDate, // The install date of the extension.
+ *           installDate, // The install date of the extension, stored as a number.
  *           value, // The value of the setting requested by the extension.
  *           enabled // Whether the setting is currently enabled.
  *         }
  *       ],
  *     },
  *     key: {
  *       // ...
  *     }
@@ -48,25 +48,52 @@ Cu.import("resource://gre/modules/Servic
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                   "resource://gre/modules/AddonManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "JSONFile",
                                   "resource://gre/modules/JSONFile.jsm");
 
 const JSON_FILE_NAME = "extension-settings.json";
+const JSON_FILE_VERSION = 2;
 const STORE_PATH = OS.Path.join(Services.dirsvc.get("ProfD", Ci.nsIFile).path, JSON_FILE_NAME);
 
 let _store;
 
+// Test-only method to force reloading of the JSON file, by removing the
+// cached file from memory.
+function clearFileFromCache() {
+  let finalizePromise = _store.finalize();
+  _store = null;
+  return finalizePromise;
+}
+
+// Processes the JSON data when read from disk to convert string dates into numbers.
+function dataPostProcessor(json) {
+  if (json.version !== JSON_FILE_VERSION) {
+    for (let storeType in json) {
+      for (let setting in json[storeType]) {
+        for (let extData of json[storeType][setting].precedenceList) {
+          if (typeof extData.installDate != "number") {
+            extData.installDate = new Date(extData.installDate).valueOf();
+          }
+        }
+      }
+    }
+    json.version = JSON_FILE_VERSION;
+  }
+  return json;
+}
+
 // Get the internal settings store, which is persisted in a JSON file.
 function getStore(type) {
   if (!_store) {
     let initStore = new JSONFile({
       path: STORE_PATH,
+      dataPostProcessor,
     });
     initStore.ensureDataReady();
     _store = initStore;
   }
 
   // Ensure a property exists for the given type.
   if (!_store.data[type]) {
     _store.data[type] = {};
@@ -227,17 +254,18 @@ this.ExtensionSettingsStore = {
       };
     }
     let keyInfo = store.data[type][key];
     // Check for this item in the precedenceList.
     let foundIndex = keyInfo.precedenceList.findIndex(item => item.id == id);
     if (foundIndex === -1) {
       // No item for this extension, so add a new one.
       let addon = await AddonManager.getAddonByID(id);
-      keyInfo.precedenceList.push({id, installDate: addon.installDate, value, enabled: true});
+      keyInfo.precedenceList.push(
+        {id, installDate: addon.installDate.valueOf(), value, enabled: true});
     } else {
       // Item already exists or this extension, so update it.
       keyInfo.precedenceList[foundIndex].value = value;
     }
 
     // Sort the list.
     keyInfo.precedenceList.sort(precedenceComparator);
 
@@ -396,13 +424,26 @@ this.ExtensionSettingsStore = {
     }
 
     let topItem = enabledItems[0];
     if (topItem.id == id) {
       return "controlled_by_this_extension";
     }
 
     let addon = await AddonManager.getAddonByID(id);
-    return topItem.installDate > addon.installDate ?
+    return topItem.installDate > addon.installDate.valueOf() ?
       "controlled_by_other_extensions" :
       "controllable_by_this_extension";
   },
+
+  /**
+   * Test-only method to force reloading of the JSON file.
+   *
+   * Note that this method simply clears the local variable that stores the
+   * file, so the next time the file is accessed it will be reloaded.
+   *
+   * @returns {Promise}
+   *          A promise that resolves once the settings store has been cleared.
+   */
+  _reloadFile() {
+    return clearFileFromCache();
+  },
 };
--- a/toolkit/components/extensions/test/xpcshell/test_ext_extensionSettingsStore.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_extensionSettingsStore.js
@@ -118,16 +118,19 @@ add_task(async function test_settings_st
       "getSetting returns correct item with more than one item in the list.");
     let levelOfControl = await ExtensionSettingsStore.getLevelOfControl(extensions[extensionIndex], TEST_TYPE, key);
     equal(
       levelOfControl,
       "controlled_by_other_extensions",
       "getLevelOfControl returns correct levelOfControl when another extension is in control.");
   }
 
+  // Reload the settings store to emulate a browser restart.
+  await ExtensionSettingsStore._reloadFile();
+
   // Add a setting for the newest extension.
   for (let key of KEY_LIST) {
     let extensionIndex = 2;
     let itemToAdd = ITEMS[key][extensionIndex];
     let levelOfControl = await ExtensionSettingsStore.getLevelOfControl(extensions[extensionIndex], TEST_TYPE, key);
     equal(
       levelOfControl,
       "controllable_by_this_extension",