Bug 1254392 - Change getJSON behaviour and move caches to local storage, r=jryans
authorBenoit Chabod <be.chabod@gmail.com>
Wed, 01 Jun 2016 06:15:00 +0200
changeset 340898 e88afebe9226340f67730f2c867a78a33e7fa4e0
parent 340897 22047a4eea784c15026c77911c0bd6ea1b70fa68
child 340899 7520b940afc85c299b6cb500477c8906eecde531
push id1183
push userraliiev@mozilla.com
push dateMon, 05 Sep 2016 20:01:49 +0000
treeherdermozilla-release@3148731bed45 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjryans
bugs1254392
milestone49.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1254392 - Change getJSON behaviour and move caches to local storage, r=jryans
devtools/client/shared/devices.js
devtools/client/shared/getjson.js
devtools/client/webide/content/simulator.js
devtools/client/webide/content/webide.js
devtools/client/webide/modules/addons.js
devtools/client/webide/test/test_simulators.html
--- a/devtools/client/shared/devices.js
+++ b/devtools/client/shared/devices.js
@@ -42,21 +42,21 @@ function AddDevice(device, type = "phone
   if (!list) {
     list = localDevices[type] = [];
   }
   list.push(device);
 }
 exports.AddDevice = AddDevice;
 
 // Get the complete devices catalog.
-function GetDevices(bypassCache = false) {
+function GetDevices() {
   let deferred = promise.defer();
 
   // Fetch common devices from Mozilla's CDN.
-  getJSON(DEVICES_URL, bypassCache).then(devices => {
+  getJSON(DEVICES_URL).then(devices => {
     for (let type in localDevices) {
       if (!devices[type]) {
         devices.TYPES.push(type);
         devices[type] = [];
       }
       devices[type] = localDevices[type].concat(devices[type]);
     }
     deferred.resolve(devices);
--- a/devtools/client/shared/getjson.js
+++ b/devtools/client/shared/getjson.js
@@ -3,45 +3,70 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {CC} = require("chrome");
 const promise = require("promise");
 const Services = require("Services");
 
+loader.lazyRequireGetter(this, "asyncStorage", "devtools/shared/async-storage");
+
 const XMLHttpRequest = CC("@mozilla.org/xmlextras/xmlhttprequest;1");
 
-// Downloads and caches a JSON file from a URL given by the pref.
-exports.getJSON = function (prefName, bypassCache) {
-  if (!bypassCache) {
-    try {
-      let str = Services.prefs.getCharPref(prefName + "_cache");
-      let json = JSON.parse(str);
-      return promise.resolve(json);
-    } catch (e) {
-      // no pref or invalid json. Let's continue
-    }
-  }
-
+/**
+ * Downloads and caches a JSON file from an URL given by a pref.
+ *
+ * @param {String} prefName
+ *        The preference for the target URL
+ *
+ * @return {Promise}
+ *         - Resolved with the JSON object in case of successful request
+ *           or cache hit
+ *         - Rejected with an error message in case of failure
+ */
+exports.getJSON = function (prefName) {
   let deferred = promise.defer();
   let xhr = new XMLHttpRequest();
 
+  // We used to store cached data in preferences, but now we use asyncStorage
+  // Migration step: if it still exists, move this now useless preference in its
+  // new location and clear it
+  if (Services.prefs.prefHasUserValue(prefName + "_cache")) {
+    let json = Services.prefs.getCharPref(prefName + "_cache");
+    asyncStorage.setItem(prefName + "_cache", json).catch(function (e) {
+      // Could not move the cache, let's log the error but continue
+      console.error(e);
+    });
+    Services.prefs.clearUserPref(prefName + "_cache");
+  }
+
+  function readFromStorage(networkError) {
+    asyncStorage.getItem(prefName + "_cache").then(function (json) {
+      deferred.resolve(json);
+    }).catch(function (e) {
+      deferred.reject("JSON not available, CDN error: " + networkError +
+                      ", storage error: " + e);
+    });
+  }
+
   xhr.onload = () => {
-    let json;
     try {
-      json = JSON.parse(xhr.responseText);
+      let json = JSON.parse(xhr.responseText);
+      asyncStorage.setItem(prefName + "_cache", json).catch(function (e) {
+        // Could not update cache, let's log the error but continue
+        console.error(e);
+      });
+      deferred.resolve(json);
     } catch (e) {
-      return deferred.reject("Invalid JSON");
+      readFromStorage(e);
     }
-    Services.prefs.setCharPref(prefName + "_cache", xhr.responseText);
-    return deferred.resolve(json);
   };
 
   xhr.onerror = (e) => {
-    deferred.reject("Network error");
+    readFromStorage(e);
   };
 
   xhr.open("get", Services.prefs.getCharPref(prefName));
   xhr.send();
 
   return deferred.promise;
 };
--- a/devtools/client/webide/content/simulator.js
+++ b/devtools/client/webide/content/simulator.js
@@ -109,25 +109,31 @@ var SimulatorEditor = {
 
     this._simulator = null;
 
     return this.init().then(() => {
       this._simulator = simulator;
 
       // Update the form fields.
       this._form.name.value = simulator.name;
+
       this.updateVersionSelector();
       this.updateProfileSelector();
       this.updateDeviceSelector();
       this.updateDeviceFields();
 
       // Change visibility of 'TV Simulator Menu'.
       let tvSimMenu = document.querySelector("#tv_simulator_menu");
       tvSimMenu.style.visibility = (this._simulator.type === "television") ?
                                    "visible" : "hidden";
+
+      // Trigger any listener waiting for this update
+      let change = document.createEvent("HTMLEvents");
+      change.initEvent("change", true, true);
+      this._form.dispatchEvent(change);
     });
   },
 
   // Open the directory of TV Simulator config.
   showTVConfigDirectory() {
     let profD = Services.dirsvc.get("ProfD", Ci.nsIFile);
     profD.append("extensions");
     profD.append(this._simulator.addon.id);
--- a/devtools/client/webide/content/webide.js
+++ b/devtools/client/webide/content/webide.js
@@ -42,19 +42,19 @@ const MS_PER_DAY = 86400000;
    Object.defineProperty(this, key, {
      value: value,
      enumerable: true,
      writable: false
    });
  });
 
 // Download remote resources early
-getJSON("devtools.webide.addonsURL", true);
-getJSON("devtools.webide.templatesURL", true);
-getJSON("devtools.devices.url", true);
+getJSON("devtools.webide.addonsURL");
+getJSON("devtools.webide.templatesURL");
+getJSON("devtools.devices.url");
 
 // See bug 989619
 console.log = console.log.bind(console);
 console.warn = console.warn.bind(console);
 console.error = console.error.bind(console);
 
 window.addEventListener("load", function onLoad() {
   window.removeEventListener("load", onLoad);
--- a/devtools/client/webide/modules/addons.js
+++ b/devtools/client/webide/modules/addons.js
@@ -51,17 +51,17 @@ var GetAvailableAddons_promise = null;
 var GetAvailableAddons = exports.GetAvailableAddons = function () {
   if (!GetAvailableAddons_promise) {
     let deferred = promise.defer();
     GetAvailableAddons_promise = deferred.promise;
     let addons = {
       simulators: [],
       adb: null
     };
-    getJSON(ADDONS_URL, true).then(json => {
+    getJSON(ADDONS_URL).then(json => {
       for (let stability in json) {
         for (let version of json[stability]) {
           addons.simulators.push(new SimulatorAddon(stability, version));
         }
       }
       addons.adb = new ADBAddon();
       addons.adapters = new AdaptersAddon();
       deferred.resolve(addons);
--- a/devtools/client/webide/test/test_simulators.html
+++ b/devtools/client/webide/test/test_simulators.html
@@ -143,28 +143,37 @@
 
           simulatorList.querySelectorAll(".configure-button")[0].click();
           is(win.document.querySelector("#deck").selectedPanel, simulatorPanel, "Simulator deck panel is selected");
 
           yield lazyIframeIsLoaded(simulatorPanel);
 
           let doc = simulatorPanel.contentWindow.document;
           let form = doc.querySelector("#simulator-editor");
+
+          let formReady = new Promise((resolve, reject) => {
+            form.addEventListener("change", () => {
+              resolve();
+            });
+          });
+
           let change = doc.createEvent("HTMLEvents");
           change.initEvent("change", true, true);
 
           function set(input, value) {
             input.value = value;
             input.dispatchEvent(change);
             return nextTick();
           }
 
           let MockFilePicker = SpecialPowers.MockFilePicker;
           MockFilePicker.init(simulatorPanel.contentWindow);
 
+          yield formReady;
+
           // Test `name`.
 
           is(form.name.value, find(".runtime-panel-item-simulator").textContent, "Original simulator name");
 
           let customName = "CustomFox ";
           yield set(form.name, customName + "1.0");
 
           is(find(".runtime-panel-item-simulator").textContent, form.name.value, "Updated simulator name");