Bug 1003860 - Service worker cache for storage actor. r=mratcliffe
☠☠ backed out by 2b9d095eccfd ☠ ☠
authorAlexandre Poirot <poirot.alex@gmail.com>
Wed, 20 Jan 2016 14:09:25 -0800
changeset 303137 8d93e84979b502359d728845813f13224378a201
parent 303136 22d80ccb4626eaf220e7d00abe713893f88fe26c
child 303138 8737105685f1b5feeb592791aa4ea498f491da96
push id8978
push userraliiev@mozilla.com
push dateMon, 25 Jan 2016 14:05:32 +0000
treeherdermozilla-aurora@b9a803752a2c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmratcliffe
bugs1003860
milestone46.0a1
Bug 1003860 - Service worker cache for storage actor. r=mratcliffe
devtools/client/locales/en-US/storage.properties
devtools/client/storage/test/browser_storage_basic.js
devtools/client/storage/test/head.js
devtools/client/storage/test/storage-listings.html
devtools/server/actors/storage.js
--- a/devtools/client/locales/en-US/storage.properties
+++ b/devtools/client/locales/en-US/storage.properties
@@ -49,16 +49,17 @@ table.emptyText=No data present for sele
 
 # LOCALIZATION NOTE (tree.labels.*):
 # These strings are the labels for Storage type groups present in the Storage
 # Tree, like cookies, local storage etc.
 tree.labels.cookies=Cookies
 tree.labels.localStorage=Local Storage
 tree.labels.sessionStorage=Session Storage
 tree.labels.indexedDB=Indexed DB
+tree.labels.Cache=Cache Storage
 
 # LOCALIZATION NOTE (table.headers.*.*):
 # These strings are the header names of the columns in the Storage Table for
 # each type of storage available through the Storage Tree to the side.
 table.headers.cookies.name=Name
 table.headers.cookies.path=Path
 table.headers.cookies.host=Domain
 table.headers.cookies.expires=Expires on
@@ -79,16 +80,19 @@ table.headers.cookies.isSecure=isSecure
 table.headers.cookies.isDomain=isDomain
 
 table.headers.localStorage.name=Key
 table.headers.localStorage.value=Value
 
 table.headers.sessionStorage.name=Key
 table.headers.sessionStorage.value=Value
 
+table.headers.Cache.url=URL
+table.headers.Cache.status=Status
+
 table.headers.indexedDB.name=Key
 table.headers.indexedDB.db=Database Name
 table.headers.indexedDB.objectStore=Object Store Name
 table.headers.indexedDB.value=Value
 table.headers.indexedDB.origin=Origin
 table.headers.indexedDB.version=Version
 table.headers.indexedDB.objectStores=Object Stores
 table.headers.indexedDB.keyPath=Key
--- a/devtools/client/storage/test/browser_storage_basic.js
+++ b/devtools/client/storage/test/browser_storage_basic.js
@@ -56,16 +56,19 @@ const testCases = [
   [["indexedDB", "https://sectest1.example.org", "idb-s1"],
    ["obj-s1"]],
   [["indexedDB", "https://sectest1.example.org", "idb-s2"],
    ["obj-s2"]],
   [["indexedDB", "https://sectest1.example.org", "idb-s1", "obj-s1"],
    [6, 7]],
   [["indexedDB", "https://sectest1.example.org", "idb-s2", "obj-s2"],
    [16]],
+  [["Cache", "http://test1.example.org", "plop"],
+   [MAIN_DOMAIN + "404_cached_file.js", MAIN_DOMAIN + "browser_storage_basic.js"]],
+
 ];
 
 /**
  * Test that the desired number of tree items are present
  */
 function testTree() {
   let doc = gPanelWindow.document;
   for (let item of testCases) {
--- a/devtools/client/storage/test/head.js
+++ b/devtools/client/storage/test/head.js
@@ -9,36 +9,40 @@ var { require } = Cu.import("resource://
 var { TargetFactory } = require("devtools/client/framework/target");
 var promise = require("promise");
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
 
 const SPLIT_CONSOLE_PREF = "devtools.toolbox.splitconsoleEnabled";
 const STORAGE_PREF = "devtools.storage.enabled";
 const DUMPEMIT_PREF = "devtools.dump.emit";
 const DEBUGGERLOG_PREF = "devtools.debugger.log";
+// Allows Cache API to be working on usage `http` test page
+const CACHES_ON_HTTP_PREF = "dom.caches.testing.enabled";
 const PATH = "browser/devtools/client/storage/test/";
 const MAIN_DOMAIN = "http://test1.example.org/" + PATH;
 const ALT_DOMAIN = "http://sectest1.example.org/" + PATH;
 const ALT_DOMAIN_SECURED = "https://sectest1.example.org:443/" + PATH;
 
 waitForExplicitFinish();
 
 var gToolbox, gPanelWindow, gWindow, gUI;
 
 // Services.prefs.setBoolPref(DUMPEMIT_PREF, true);
 // Services.prefs.setBoolPref(DEBUGGERLOG_PREF, true);
 
 Services.prefs.setBoolPref(STORAGE_PREF, true);
+Services.prefs.setBoolPref(CACHES_ON_HTTP_PREF, true);
 DevToolsUtils.testing = true;
 registerCleanupFunction(() => {
   gToolbox = gPanelWindow = gWindow = gUI = null;
   Services.prefs.clearUserPref(STORAGE_PREF);
   Services.prefs.clearUserPref(SPLIT_CONSOLE_PREF);
   Services.prefs.clearUserPref(DUMPEMIT_PREF);
   Services.prefs.clearUserPref(DEBUGGERLOG_PREF);
+  Services.prefs.clearUserPref(CACHES_ON_HTTP_PREF);
   DevToolsUtils.testing = false;
   while (gBrowser.tabs.length > 1) {
     gBrowser.removeCurrentTab();
   }
 });
 
 /**
  * Add a new test tab in the browser and load the given url.
--- a/devtools/client/storage/test/storage-listings.html
+++ b/devtools/client/storage/test/storage-listings.html
@@ -92,32 +92,41 @@ let idbGenerator = function*() {
 
 function deleteDB(dbName) {
   return new Promise(resolve => {
     dump("removing database " + dbName + " from " + document.location + "\n");
     indexedDB.deleteDatabase(dbName).onsuccess = resolve;
   });
 }
 
+let cacheGenerator = function*() {
+  let cache = yield caches.open("plop");
+  yield cache.add("404_cached_file.js");
+  yield cache.add("browser_storage_basic.js");
+};
+
 window.setup = function*() {
   yield idbGenerator();
+  yield cacheGenerator();
 };
 
 window.clear = function*() {
   document.cookie = "c1=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/browser";
   document.cookie =
     "c3=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; secure=true";
   document.cookie =
     "cs2=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=" +
     partialHostname;
 
   localStorage.clear();
   sessionStorage.clear();
 
   yield deleteDB("idb1");
   yield deleteDB("idb2");
 
+  yield caches.delete("plop");
+
   dump("removed cookies, localStorage, sessionStorage and indexedDB data " +
        "from " + document.location + "\n");
 };
 </script>
 </body>
 </html>
--- a/devtools/server/actors/storage.js
+++ b/devtools/server/actors/storage.js
@@ -917,16 +917,135 @@ StorageActors.createActor({
  * The Session Storage actor and front.
  */
 StorageActors.createActor({
   typeName: "sessionStorage",
   observationTopic: "dom-storage2-changed",
   storeObjectType: "storagestoreobject"
 }, getObjectForLocalOrSessionStorage("sessionStorage"));
 
+
+let CacheAttributes = [
+  "url",
+  "status",
+];
+types.addDictType("cacheobject", {
+  "url": "string",
+  "status": "string"
+});
+
+// Array of Cache store objects
+types.addDictType("cachestoreobject", {
+  total: "number",
+  offset: "number",
+  data: "array:nullable:cacheobject"
+});
+
+StorageActors.createActor({
+  typeName: "Cache",
+  storeObjectType: "cachestoreobject"
+}, {
+  getCachesForHost: Task.async(function*(host) {
+    let uri = Services.io.newURI(host, null, null);
+    let principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(uri);
+
+    // The first argument tells if you want to get |content| cache or |chrome| cache.
+    // The |content| cache is the cache explicitely named by the web content
+    // (service worker or web page).
+    // The |chrome| cache is the cache implicitely cached by the platform, hosting the
+    // source file of the service worker.
+    let { CacheStorage } = this.storageActor.window;
+    let cache = new CacheStorage("content", principal);
+    return cache;
+  }),
+
+  preListStores: Task.async(function*() {
+    this.hostVsStores = new Map();
+    for (let host of this.hosts) {
+      yield this.populateStoresForHost(host);
+    }
+  }),
+
+  form: function(form, detail) {
+    if (detail === "actorid") {
+      return this.actorID;
+    }
+
+    let hosts = {};
+    for (let host of this.hosts) {
+      hosts[host] = this.getNamesForHost(host);
+    }
+
+    return {
+      actor: this.actorID,
+      hosts: hosts
+    };
+  },
+
+  getNamesForHost: function(host) {
+    // UI code expect each name to be a JSON string of an array :/
+    return [...this.hostVsStores.get(host).keys()].map(a => JSON.stringify([a]));
+  },
+
+  getValuesForHost: Task.async(function*(host, name) {
+    if (!name) return [];
+    // UI is weird and expect a JSON stringified array... and pass it back :/
+    name = JSON.parse(name)[0];
+
+    let cache = this.hostVsStores.get(host).get(name);
+    let requests = yield cache.keys();
+    let results = [];
+    for(let request of requests) {
+      let response = yield cache.match(request);
+      // Unwrap the response to get access to all its properties if the
+      // response happen to be 'opaque', when it is a Cross Origin Request.
+      response = response.cloneUnfiltered();
+      results.push(yield this.processEntry(request, response));
+    }
+    return results;
+  }),
+
+  processEntry: Task.async(function*(request, response) {
+    return {
+      url: String(request.url),
+      status: String(response.statusText),
+    };
+  }),
+
+  getHostName: function(location) {
+    if (!location.host) {
+      return location.href;
+    }
+    return location.protocol + "//" + location.host;
+  },
+
+  populateStoresForHost: Task.async(function*(host, window) {
+    let storeMap = new Map();
+    let caches = yield this.getCachesForHost(host);
+    for (let name of (yield caches.keys())) {
+      storeMap.set(name, (yield caches.open(name)));
+    }
+    this.hostVsStores.set(host, storeMap);
+  }),
+
+  populateStoresForHosts: function() {},
+
+  /**
+   * Given a url, correctly determine its protocol + hostname part.
+   */
+  getSchemaAndHost: function(url) {
+    let uri = Services.io.newURI(url, null, null);
+    return uri.scheme + "://" + uri.hostPort;
+  },
+
+  toStoreObject: function(item) {
+    return item;
+  },
+});
+
 /**
  * Code related to the Indexed DB actor and front
  */
 
 // Metadata holder objects for various components of Indexed DB
 
 /**
  * Meta data object for a particular index in an object store