Bug 1003860 - Service worker cache for storage actor. r=mratcliffe
--- 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