summaryrefslogtreecommitdiffstats
path: root/devtools/server
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/server')
-rw-r--r--devtools/server/actors/storage.js470
-rw-r--r--devtools/server/tests/browser/browser.ini2
-rw-r--r--devtools/server/tests/browser/browser_storage_cookies-duplicate-names.js105
-rw-r--r--devtools/server/tests/browser/browser_storage_dynamic_windows.js52
-rw-r--r--devtools/server/tests/browser/browser_storage_listings.js58
-rw-r--r--devtools/server/tests/browser/browser_storage_updates.js32
-rw-r--r--devtools/server/tests/browser/head.js36
-rw-r--r--devtools/server/tests/browser/storage-cookies-same-name.html28
8 files changed, 570 insertions, 213 deletions
diff --git a/devtools/server/actors/storage.js b/devtools/server/actors/storage.js
index 572cd6b68..015d849ee 100644
--- a/devtools/server/actors/storage.js
+++ b/devtools/server/actors/storage.js
@@ -15,6 +15,17 @@ const {isWindowIncluded} = require("devtools/shared/layout/utils");
const specs = require("devtools/shared/specs/storage");
const { Task } = require("devtools/shared/task");
+const DEFAULT_VALUE = "value";
+
+loader.lazyRequireGetter(this, "naturalSortCaseInsensitive",
+ "devtools/client/shared/natural-sort", true);
+
+// GUID to be used as a separator in compound keys. This must match the same
+// constant in devtools/client/storage/ui.js,
+// devtools/client/storage/test/head.js and
+// devtools/server/tests/browser/head.js
+const SEPARATOR_GUID = "{9d414cc5-8319-0a04-0586-c0a6ae01670a}";
+
loader.lazyImporter(this, "OS", "resource://gre/modules/osfile.jsm");
loader.lazyImporter(this, "Sqlite", "resource://gre/modules/Sqlite.jsm");
@@ -87,7 +98,7 @@ var StorageActors = {};
* - observe : Method which gets triggered on the notificaiton of the watched
* topic.
* - getNamesForHost : Given a host, get list of all known store names.
- * - getValuesForHost : Given a host (and optianally a name) get all known
+ * - getValuesForHost : Given a host (and optionally a name) get all known
* store objects.
* - toStoreObject : Given a store object, convert it to the required format
* so that it can be transferred over wire.
@@ -135,6 +146,9 @@ StorageActors.defaults = function (typeName, observationTopic) {
* Converts the window.location object into host.
*/
getHostName(location) {
+ if (location.protocol === "chrome:") {
+ return location.href;
+ }
return location.hostname || location.href;
},
@@ -315,15 +329,20 @@ StorageActors.defaults = function (typeName, observationTopic) {
toReturn.data.push(...values);
}
}
+
toReturn.total = this.getObjectsSize(host, names, options);
+
if (offset > toReturn.total) {
// In this case, toReturn.data is an empty array.
toReturn.offset = toReturn.total;
toReturn.data = [];
} else {
- toReturn.data = toReturn.data.sort((a, b) => {
- return a[sortOn] - b[sortOn];
- }).slice(offset, offset + size).map(a => this.toStoreObject(a));
+ // We need to use natural sort before slicing.
+ let sorted = toReturn.data.sort((a, b) => {
+ return naturalSortCaseInsensitive(a[sortOn], b[sortOn]);
+ });
+ let sliced = sorted.slice(offset, offset + size);
+ toReturn.data = sliced.map(a => this.toStoreObject(a));
}
} else {
let obj = yield this.getValuesForHost(host, undefined, undefined,
@@ -333,15 +352,18 @@ StorageActors.defaults = function (typeName, observationTopic) {
}
toReturn.total = obj.length;
+
if (offset > toReturn.total) {
// In this case, toReturn.data is an empty array.
toReturn.offset = offset = toReturn.total;
toReturn.data = [];
} else {
- toReturn.data = obj.sort((a, b) => {
- return a[sortOn] - b[sortOn];
- }).slice(offset, offset + size)
- .map(object => this.toStoreObject(object));
+ // We need to use natural sort before slicing.
+ let sorted = obj.sort((a, b) => {
+ return naturalSortCaseInsensitive(a[sortOn], b[sortOn]);
+ });
+ let sliced = sorted.slice(offset, offset + size);
+ toReturn.data = sliced.map(object => this.toStoreObject(object));
}
}
@@ -460,11 +482,13 @@ StorageActors.createActor({
}
return {
+ uniqueKey: `${cookie.name}${SEPARATOR_GUID}${cookie.host}` +
+ `${SEPARATOR_GUID}${cookie.path}`,
name: cookie.name,
- path: cookie.path || "",
host: cookie.host || "",
+ path: cookie.path || "",
- // because expires is in seconds
+ // because creationTime is in micro seconds
expires: (cookie.expires || 0) * 1000,
// because it is in micro seconds
@@ -488,7 +512,10 @@ StorageActors.createActor({
for (let cookie of cookies) {
if (this.isCookieAtHost(cookie, host)) {
- this.hostVsStores.get(host).set(cookie.name, cookie);
+ let uniqueKey = `${cookie.name}${SEPARATOR_GUID}${cookie.host}` +
+ `${SEPARATOR_GUID}${cookie.path}`;
+
+ this.hostVsStores.get(host).set(uniqueKey, cookie);
}
}
},
@@ -521,8 +548,11 @@ StorageActors.createActor({
case "changed":
if (hosts.length) {
for (let host of hosts) {
- this.hostVsStores.get(host).set(subject.name, subject);
- data[host] = [subject.name];
+ let uniqueKey = `${subject.name}${SEPARATOR_GUID}${subject.host}` +
+ `${SEPARATOR_GUID}${subject.path}`;
+
+ this.hostVsStores.get(host).set(uniqueKey, subject);
+ data[host] = [uniqueKey];
}
this.storageActor.update(action, "cookies", data);
}
@@ -531,8 +561,11 @@ StorageActors.createActor({
case "deleted":
if (hosts.length) {
for (let host of hosts) {
- this.hostVsStores.get(host).delete(subject.name);
- data[host] = [subject.name];
+ let uniqueKey = `${subject.name}${SEPARATOR_GUID}${subject.host}` +
+ `${SEPARATOR_GUID}${subject.path}`;
+
+ this.hostVsStores.get(host).delete(uniqueKey);
+ data[host] = [uniqueKey];
}
this.storageActor.update("deleted", "cookies", data);
}
@@ -543,8 +576,11 @@ StorageActors.createActor({
for (let host of hosts) {
let stores = [];
for (let cookie of subject) {
- this.hostVsStores.get(host).delete(cookie.name);
- stores.push(cookie.name);
+ let uniqueKey = `${cookie.name}${SEPARATOR_GUID}${cookie.host}` +
+ `${SEPARATOR_GUID}${cookie.path}`;
+
+ this.hostVsStores.get(host).delete(uniqueKey);
+ stores.push(uniqueKey);
}
data[host] = stores;
}
@@ -566,15 +602,17 @@ StorageActors.createActor({
getFields: Task.async(function* () {
return [
- { name: "name", editable: 1},
- { name: "path", editable: 1},
- { name: "host", editable: 1},
- { name: "expires", editable: 1},
- { name: "lastAccessed", editable: 0},
- { name: "value", editable: 1},
- { name: "isDomain", editable: 0},
- { name: "isSecure", editable: 1},
- { name: "isHttpOnly", editable: 1}
+ { name: "uniqueKey", editable: false, private: true },
+ { name: "name", editable: true, hidden: false },
+ { name: "host", editable: true, hidden: false },
+ { name: "path", editable: true, hidden: false },
+ { name: "expires", editable: true, hidden: false },
+ { name: "lastAccessed", editable: false, hidden: false },
+ { name: "creationTime", editable: false, hidden: true },
+ { name: "value", editable: true, hidden: false },
+ { name: "isDomain", editable: false, hidden: true },
+ { name: "isSecure", editable: true, hidden: true },
+ { name: "isHttpOnly", editable: true, hidden: false }
];
}),
@@ -591,6 +629,14 @@ StorageActors.createActor({
this.editCookie(data);
}),
+ addItem: Task.async(function* (guid) {
+ let doc = this.storageActor.document;
+ let time = new Date().getTime();
+ let expiry = new Date(time + 3600 * 24 * 1000).toGMTString();
+
+ doc.cookie = `${guid}=${DEFAULT_VALUE};expires=${expiry}`;
+ }),
+
removeItem: Task.async(function* (host, name) {
let doc = this.storageActor.document;
this.removeCookie(host, name, doc.nodePrincipal
@@ -603,6 +649,12 @@ StorageActors.createActor({
.originAttributes);
}),
+ removeAllSessionCookies: Task.async(function* (host, domain) {
+ let doc = this.storageActor.document;
+ this.removeAllSessionCookies(host, domain, doc.nodePrincipal
+ .originAttributes);
+ }),
+
maybeSetupChildProcess() {
cookieHelpers.onCookieChanged = this.onCookieChanged.bind(this);
@@ -619,6 +671,8 @@ StorageActors.createActor({
cookieHelpers.removeCookie.bind(cookieHelpers);
this.removeAllCookies =
cookieHelpers.removeAllCookies.bind(cookieHelpers);
+ this.removeAllSessionCookies =
+ cookieHelpers.removeAllSessionCookies.bind(cookieHelpers);
return;
}
@@ -642,6 +696,8 @@ StorageActors.createActor({
callParentProcess.bind(null, "removeCookie");
this.removeAllCookies =
callParentProcess.bind(null, "removeAllCookies");
+ this.removeAllSessionCookies =
+ callParentProcess.bind(null, "removeAllSessionCookies");
addMessageListener("debug:storage-cookie-request-child",
cookieHelpers.handleParentRequest);
@@ -696,7 +752,7 @@ var cookieHelpers = {
* {
* host: "http://www.mozilla.org",
* field: "value",
- * key: "name",
+ * editCookie: "name",
* oldValue: "%7BHello%7D",
* newValue: "%7BHelloo%7D",
* items: {
@@ -720,10 +776,14 @@ var cookieHelpers = {
let origPath = field === "path" ? oldValue : data.items.path;
let cookie = null;
- let enumerator = Services.cookies.getCookiesFromHost(origHost, data.originAttributes || {});
+ let enumerator =
+ Services.cookies.getCookiesFromHost(origHost, data.originAttributes || {});
+
while (enumerator.hasMoreElements()) {
let nsiCookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
- if (nsiCookie.name === origName && nsiCookie.host === origHost) {
+ if (nsiCookie.name === origName &&
+ nsiCookie.host === origHost &&
+ nsiCookie.path === origPath) {
cookie = {
host: nsiCookie.host,
path: nsiCookie.path,
@@ -743,7 +803,7 @@ var cookieHelpers = {
return;
}
- // If the date is expired set it for 1 minute in the future.
+ // If the date is expired set it for 10 seconds in the future.
let now = new Date();
if (!cookie.isSession && (cookie.expires * 1000) <= now) {
let tenSecondsFromNow = (now.getTime() + 10 * 1000) / 1000;
@@ -797,6 +857,15 @@ var cookieHelpers = {
},
_removeCookies(host, opts = {}) {
+ // We use a uniqueId to emulate compound keys for cookies. We need to
+ // extract the cookie name to remove the correct cookie.
+ if (opts.name) {
+ let split = opts.name.split(SEPARATOR_GUID);
+
+ opts.name = split[0];
+ opts.path = split[2];
+ }
+
function hostMatches(cookieHost, matchHost) {
if (cookieHost == null) {
return matchHost == null;
@@ -807,12 +876,16 @@ var cookieHelpers = {
return cookieHost == host;
}
- let enumerator = Services.cookies.getCookiesFromHost(host, opts.originAttributes || {});
+ let enumerator =
+ Services.cookies.getCookiesFromHost(host, opts.originAttributes || {});
+
while (enumerator.hasMoreElements()) {
let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
if (hostMatches(cookie.host, host) &&
(!opts.name || cookie.name === opts.name) &&
- (!opts.domain || cookie.host === opts.domain)) {
+ (!opts.domain || cookie.host === opts.domain) &&
+ (!opts.path || cookie.path === opts.path) &&
+ (!opts.session || (!cookie.expires && !cookie.maxAge))) {
Services.cookies.remove(
cookie.host,
cookie.name,
@@ -834,6 +907,10 @@ var cookieHelpers = {
this._removeCookies(host, { domain, originAttributes });
},
+ removeAllSessionCookies(host, domain, originAttributes) {
+ this._removeCookies(host, { domain, originAttributes, session: true });
+ },
+
addCookieObservers() {
Services.obs.addObserver(cookieHelpers, "cookie-changed", false);
return null;
@@ -898,6 +975,12 @@ var cookieHelpers = {
let rowdata = msg.data.args[0];
return cookieHelpers.editCookie(rowdata);
}
+ case "createNewCookie": {
+ let host = msg.data.args[0];
+ let guid = msg.data.args[1];
+ let originAttributes = msg.data.args[2];
+ return cookieHelpers.createNewCookie(host, guid, originAttributes);
+ }
case "removeCookie": {
let host = msg.data.args[0];
let name = msg.data.args[1];
@@ -910,6 +993,12 @@ var cookieHelpers = {
let originAttributes = msg.data.args[2];
return cookieHelpers.removeAllCookies(host, domain, originAttributes);
}
+ case "removeAllSessionCookies": {
+ let host = msg.data.args[0];
+ let domain = msg.data.args[1];
+ let originAttributes = msg.data.args[2];
+ return cookieHelpers.removeAllSessionCookies(host, domain, originAttributes);
+ }
default:
console.error("ERR_DIRECTOR_PARENT_UNKNOWN_METHOD", msg.json.method);
throw new Error("ERR_DIRECTOR_PARENT_UNKNOWN_METHOD");
@@ -1004,6 +1093,9 @@ function getObjectForLocalOrSessionStorage(type) {
if (!location.host) {
return location.href;
}
+ if (location.protocol === "chrome:") {
+ return location.href;
+ }
return location.protocol + "//" + location.host;
},
@@ -1024,11 +1116,19 @@ function getObjectForLocalOrSessionStorage(type) {
getFields: Task.async(function* () {
return [
- { name: "name", editable: 1},
- { name: "value", editable: 1}
+ { name: "name", editable: true },
+ { name: "value", editable: true }
];
}),
+ addItem: Task.async(function* (guid, host) {
+ let storage = this.hostVsStores.get(host);
+ if (!storage) {
+ return;
+ }
+ storage.setItem(guid, DEFAULT_VALUE);
+ }),
+
/**
* Edit localStorage or sessionStorage fields.
*
@@ -1143,6 +1243,11 @@ StorageActors.createActor({
// The |chrome| cache is the cache implicitely cached by the platform,
// hosting the source file of the service worker.
let { CacheStorage } = this.storageActor.window;
+
+ if (!CacheStorage) {
+ return [];
+ }
+
let cache = new CacheStorage("content", principal);
return cache;
}),
@@ -1205,8 +1310,8 @@ StorageActors.createActor({
getFields: Task.async(function* () {
return [
- { name: "url", editable: 0 },
- { name: "status", editable: 0 }
+ { name: "url", editable: false },
+ { name: "status", editable: false }
];
}),
@@ -1214,6 +1319,9 @@ StorageActors.createActor({
if (!location.host) {
return location.href;
}
+ if (location.protocol === "chrome:") {
+ return location.href;
+ }
return location.protocol + "//" + location.host;
},
@@ -1386,12 +1494,15 @@ ObjectStoreMetadata.prototype = {
* The host associated with this indexed db.
* @param {IDBDatabase} db
* The particular indexed db.
+ * @param {String} storage
+ * Storage type, either "temporary", "default" or "persistent".
*/
-function DatabaseMetadata(origin, db) {
+function DatabaseMetadata(origin, db, storage) {
this._origin = origin;
this._name = db.name;
this._version = db.version;
this._objectStores = [];
+ this.storage = storage;
if (db.objectStoreNames.length) {
let transaction = db.transaction(db.objectStoreNames, "readonly");
@@ -1411,7 +1522,9 @@ DatabaseMetadata.prototype = {
toObject() {
return {
+ uniqueKey: `${this._name}${SEPARATOR_GUID}${this.storage}`,
name: this._name,
+ storage: this.storage,
origin: this._origin,
version: this._version,
objectStores: this._objectStores.size
@@ -1487,6 +1600,9 @@ StorageActors.createActor({
if (!location.host) {
return location.href;
}
+ if (location.protocol === "chrome:") {
+ return location.href;
+ }
return location.protocol + "//" + location.host;
},
@@ -1579,15 +1695,17 @@ StorageActors.createActor({
populateStoresForHost: Task.async(function* (host) {
let storeMap = new Map();
let {names} = yield this.getDBNamesForHost(host);
+
let win = this.storageActor.getWindowFromHost(host);
if (win) {
let principal = win.document.nodePrincipal;
- for (let name of names) {
- let metadata = yield this.getDBMetaData(host, principal, name);
+ for (let {name, storage} of names) {
+ let metadata = yield this.getDBMetaData(host, principal, name, storage);
metadata = indexedDBHelpers.patchMetadataMapsAndProtos(metadata);
- storeMap.set(name, metadata);
+
+ storeMap.set(`${name} (${storage})`, metadata);
}
}
@@ -1614,16 +1732,30 @@ StorageActors.createActor({
if ("objectStores" in item) {
// DB meta data
return {
+ uniqueKey: `${item.name} (${item.storage})`,
db: item.name,
+ storage: item.storage,
origin: item.origin,
version: item.version,
objectStores: item.objectStores
};
}
+
+ let value = JSON.stringify(item.value);
+
+ // FIXME: Bug 1318029 - Due to a bug that is thrown whenever a
+ // LongStringActor string reaches DebuggerServer.LONG_STRING_LENGTH we need
+ // to trim the value. When the bug is fixed we should stop trimming the
+ // string here.
+ let maxLength = DebuggerServer.LONG_STRING_LENGTH - 1;
+ if (value.length > maxLength) {
+ value = value.substr(0, maxLength);
+ }
+
// Indexed db entry
return {
name: item.name,
- value: new LongStringActor(this.conn, JSON.stringify(item.value))
+ value: new LongStringActor(this.conn, value)
};
},
@@ -1659,16 +1791,20 @@ StorageActors.createActor({
maybeSetupChildProcess() {
if (!DebuggerServer.isInChildProcess) {
this.backToChild = (func, rv) => rv;
+ this.clearDBStore = indexedDBHelpers.clearDBStore;
+ this.findIDBPathsForHost = indexedDBHelpers.findIDBPathsForHost;
+ this.findSqlitePathsForHost = indexedDBHelpers.findSqlitePathsForHost;
+ this.findStorageTypePaths = indexedDBHelpers.findStorageTypePaths;
this.getDBMetaData = indexedDBHelpers.getDBMetaData;
- this.openWithPrincipal = indexedDBHelpers.openWithPrincipal;
this.getDBNamesForHost = indexedDBHelpers.getDBNamesForHost;
- this.getSanitizedHost = indexedDBHelpers.getSanitizedHost;
this.getNameFromDatabaseFile = indexedDBHelpers.getNameFromDatabaseFile;
- this.getValuesForHost = indexedDBHelpers.getValuesForHost;
this.getObjectStoreData = indexedDBHelpers.getObjectStoreData;
+ this.getSanitizedHost = indexedDBHelpers.getSanitizedHost;
+ this.getValuesForHost = indexedDBHelpers.getValuesForHost;
+ this.openWithPrincipal = indexedDBHelpers.openWithPrincipal;
this.removeDB = indexedDBHelpers.removeDB;
this.removeDBRecord = indexedDBHelpers.removeDBRecord;
- this.clearDBStore = indexedDBHelpers.clearDBStore;
+ this.splitNameAndStorage = indexedDBHelpers.splitNameAndStorage;
return;
}
@@ -1681,6 +1817,7 @@ StorageActors.createActor({
});
this.getDBMetaData = callParentProcessAsync.bind(null, "getDBMetaData");
+ this.splitNameAndStorage = callParentProcessAsync.bind(null, "splitNameAndStorage");
this.getDBNamesForHost = callParentProcessAsync.bind(null, "getDBNamesForHost");
this.getValuesForHost = callParentProcessAsync.bind(null, "getValuesForHost");
this.removeDB = callParentProcessAsync.bind(null, "removeDB");
@@ -1725,26 +1862,28 @@ StorageActors.createActor({
// Detail of database
case "database":
return [
- { name: "objectStore", editable: 0 },
- { name: "keyPath", editable: 0 },
- { name: "autoIncrement", editable: 0 },
- { name: "indexes", editable: 0 },
+ { name: "objectStore", editable: false },
+ { name: "keyPath", editable: false },
+ { name: "autoIncrement", editable: false },
+ { name: "indexes", editable: false },
];
// Detail of object store
case "object store":
return [
- { name: "name", editable: 0 },
- { name: "value", editable: 0 }
+ { name: "name", editable: false },
+ { name: "value", editable: false }
];
// Detail of indexedDB for one origin
default:
return [
- { name: "db", editable: 0 },
- { name: "origin", editable: 0 },
- { name: "version", editable: 0 },
- { name: "objectStores", editable: 0 },
+ { name: "uniqueKey", editable: false, private: true },
+ { name: "db", editable: false },
+ { name: "storage", editable: false },
+ { name: "origin", editable: false },
+ { name: "version", editable: false },
+ { name: "objectStores", editable: false },
];
}
})
@@ -1776,14 +1915,14 @@ var indexedDBHelpers = {
* `name` for the given `host` with its `principal`. The stored metadata
* information is of `DatabaseMetadata` type.
*/
- getDBMetaData: Task.async(function* (host, principal, name) {
- let request = this.openWithPrincipal(principal, name);
+ getDBMetaData: Task.async(function* (host, principal, name, storage) {
+ let request = this.openWithPrincipal(principal, name, storage);
let success = promise.defer();
request.onsuccess = event => {
let db = event.target.result;
- let dbData = new DatabaseMetadata(host, db);
+ let dbData = new DatabaseMetadata(host, db, storage);
db.close();
success.resolve(this.backToChild("getDBMetaData", dbData));
@@ -1796,21 +1935,37 @@ var indexedDBHelpers = {
return success.promise;
}),
+ splitNameAndStorage: function (name) {
+ let lastOpenBracketIndex = name.lastIndexOf("(");
+ let lastCloseBracketIndex = name.lastIndexOf(")");
+ let delta = lastCloseBracketIndex - lastOpenBracketIndex - 1;
+
+ let storage = name.substr(lastOpenBracketIndex + 1, delta);
+
+ name = name.substr(0, lastOpenBracketIndex - 1);
+
+ return { storage, name };
+ },
+
/**
* Opens an indexed db connection for the given `principal` and
* database `name`.
*/
- openWithPrincipal(principal, name) {
- return indexedDBForStorage.openForPrincipal(principal, name);
+ openWithPrincipal: function (principal, name, storage) {
+ return indexedDBForStorage.openForPrincipal(principal, name,
+ { storage: storage });
},
- removeDB: Task.async(function* (host, principal, name) {
+ removeDB: Task.async(function* (host, principal, dbName) {
let result = new promise(resolve => {
- let request = indexedDBForStorage.deleteForPrincipal(principal, name);
+ let {name, storage} = this.splitNameAndStorage(dbName);
+ let request =
+ indexedDBForStorage.deleteForPrincipal(principal, name,
+ { storage: storage });
request.onsuccess = () => {
resolve({});
- this.onItemUpdated("deleted", host, [name]);
+ this.onItemUpdated("deleted", host, [dbName]);
};
request.onblocked = () => {
@@ -1836,10 +1991,11 @@ var indexedDBHelpers = {
removeDBRecord: Task.async(function* (host, principal, dbName, storeName, id) {
let db;
+ let {name, storage} = this.splitNameAndStorage(dbName);
try {
db = yield new promise((resolve, reject) => {
- let request = this.openWithPrincipal(principal, dbName);
+ let request = this.openWithPrincipal(principal, name, storage);
request.onsuccess = ev => resolve(ev.target.result);
request.onerror = ev => reject(ev.target.error);
});
@@ -1868,10 +2024,11 @@ var indexedDBHelpers = {
clearDBStore: Task.async(function* (host, principal, dbName, storeName) {
let db;
+ let {name, storage} = this.splitNameAndStorage(dbName);
try {
db = yield new promise((resolve, reject) => {
- let request = this.openWithPrincipal(principal, dbName);
+ let request = this.openWithPrincipal(principal, name, storage);
request.onsuccess = ev => resolve(ev.target.result);
request.onerror = ev => reject(ev.target.error);
});
@@ -1903,46 +2060,101 @@ var indexedDBHelpers = {
*/
getDBNamesForHost: Task.async(function* (host) {
let sanitizedHost = this.getSanitizedHost(host);
- let directory = OS.Path.join(OS.Constants.Path.profileDir, "storage",
- "default", sanitizedHost, "idb");
-
- let exists = yield OS.File.exists(directory);
- if (!exists && host.startsWith("about:")) {
- // try for moz-safe-about directory
- sanitizedHost = this.getSanitizedHost("moz-safe-" + host);
- directory = OS.Path.join(OS.Constants.Path.profileDir, "storage",
- "permanent", sanitizedHost, "idb");
- exists = yield OS.File.exists(directory);
- }
- if (!exists) {
- return this.backToChild("getDBNamesForHost", {names: []});
+ let profileDir = OS.Constants.Path.profileDir;
+ let files = [];
+ let names = [];
+ let storagePath = OS.Path.join(profileDir, "storage");
+
+ // We expect sqlite DB paths to look something like this:
+ // - PathToProfileDir/storage/default/http+++www.example.com/
+ // idb/1556056096MeysDaabta.sqlite
+ // - PathToProfileDir/storage/permanent/http+++www.example.com/
+ // idb/1556056096MeysDaabta.sqlite
+ // - PathToProfileDir/storage/temporary/http+++www.example.com/
+ // idb/1556056096MeysDaabta.sqlite
+ // The subdirectory inside the storage folder is determined by the storage
+ // type:
+ // - default: { storage: "default" } or not specified.
+ // - permanent: { storage: "persistent" }.
+ // - temporary: { storage: "temporary" }.
+ let sqliteFiles = yield this.findSqlitePathsForHost(storagePath, sanitizedHost);
+
+ for (let file of sqliteFiles) {
+ let splitPath = OS.Path.split(file).components;
+ let idbIndex = splitPath.indexOf("idb");
+ let storage = splitPath[idbIndex - 2];
+ let relative = file.substr(profileDir.length + 1);
+
+ files.push({
+ file: relative,
+ storage: storage === "permanent" ? "persistent" : storage
+ });
}
- let names = [];
- let dirIterator = new OS.File.DirectoryIterator(directory);
- try {
- yield dirIterator.forEach(file => {
- // Skip directories.
- if (file.isDir) {
- return null;
+ if (files.length > 0) {
+ for (let {file, storage} of files) {
+ let name = yield this.getNameFromDatabaseFile(file);
+ if (name) {
+ names.push({
+ name,
+ storage
+ });
}
+ }
+ }
+ return this.backToChild("getDBNamesForHost", {names});
+ }),
- // Skip any non-sqlite files.
- if (!file.name.endsWith(".sqlite")) {
- return null;
+ /**
+ * Find all SQLite files that hold IndexedDB data for a host, such as:
+ * storage/temporary/http+++www.example.com/idb/1556056096MeysDaabta.sqlite
+ */
+ findSqlitePathsForHost: Task.async(function* (storagePath, sanitizedHost) {
+ let sqlitePaths = [];
+ let idbPaths = yield this.findIDBPathsForHost(storagePath, sanitizedHost);
+ for (let idbPath of idbPaths) {
+ let iterator = new OS.File.DirectoryIterator(idbPath);
+ yield iterator.forEach(entry => {
+ if (!entry.isDir && entry.path.endsWith(".sqlite")) {
+ sqlitePaths.push(entry.path);
}
-
- return this.getNameFromDatabaseFile(file.path).then(name => {
- if (name) {
- names.push(name);
- }
- return null;
- });
});
- } finally {
- dirIterator.close();
+ iterator.close();
}
- return this.backToChild("getDBNamesForHost", {names: names});
+ return sqlitePaths;
+ }),
+
+ /**
+ * Find all paths that hold IndexedDB data for a host, such as:
+ * storage/temporary/http+++www.example.com/idb
+ */
+ findIDBPathsForHost: Task.async(function* (storagePath, sanitizedHost) {
+ let idbPaths = [];
+ let typePaths = yield this.findStorageTypePaths(storagePath);
+ for (let typePath of typePaths) {
+ let idbPath = OS.Path.join(typePath, sanitizedHost, "idb");
+ if (yield OS.File.exists(idbPath)) {
+ idbPaths.push(idbPath);
+ }
+ }
+ return idbPaths;
+ }),
+
+ /**
+ * Find all the storage types, such as "default", "permanent", or "temporary".
+ * These names have changed over time, so it seems simpler to look through all types
+ * that currently exist in the profile.
+ */
+ findStorageTypePaths: Task.async(function* (storagePath) {
+ let iterator = new OS.File.DirectoryIterator(storagePath);
+ let typePaths = [];
+ yield iterator.forEach(entry => {
+ if (entry.isDir) {
+ typePaths.push(entry.path);
+ }
+ });
+ iterator.close();
+ return typePaths;
}),
/**
@@ -1950,6 +2162,9 @@ var indexedDBHelpers = {
* name.
*/
getSanitizedHost(host) {
+ if (host.startsWith("about:")) {
+ host = "moz-safe-" + host;
+ }
return host.replace(ILLEGAL_CHAR_REGEX, "+");
},
@@ -1963,7 +2178,7 @@ var indexedDBHelpers = {
// Content pages might be having an open transaction for the same indexed db
// which this sqlite file belongs to. In that case, sqlite.openConnection
- // will throw. Thus we retey for some time to see if lock is removed.
+ // will throw. Thus we retry for some time to see if lock is removed.
while (!connection && retryCount++ < 25) {
try {
connection = yield Sqlite.openConnection({ path: path });
@@ -2024,8 +2239,14 @@ var indexedDBHelpers = {
return this.backToChild("getValuesForHost", {objectStores: objectStores});
}
// Get either all entries from the object store, or a particular id
- let result = yield this.getObjectStoreData(host, principal, db2,
- objectStore, id, options.index, options.size);
+ let storage = hostVsStores.get(host).get(db2).storage;
+ let result = yield this.getObjectStoreData(host, principal, db2, storage, {
+ objectStore: objectStore,
+ id: id,
+ index: options.index,
+ offset: 0,
+ size: options.size
+ });
return this.backToChild("getValuesForHost", {result: result});
}),
@@ -2039,23 +2260,27 @@ var indexedDBHelpers = {
* The principal of the given document.
* @param {string} dbName
* The name of the indexed db from the above host.
- * @param {string} objectStore
- * The name of the object store from the above db.
- * @param {string} id
- * id of the requested entry from the above object store.
- * null if all entries from the above object store are requested.
- * @param {string} index
- * name of the IDBIndex to be iterated on while fetching entries.
- * null or "name" if no index is to be iterated.
- * @param {number} offset
- * ofsset of the entries to be fetched.
- * @param {number} size
- * The intended size of the entries to be fetched.
+ * @param {String} storage
+ * Storage type, either "temporary", "default" or "persistent".
+ * @param {Object} requestOptions
+ * An object in the following format:
+ * {
+ * objectStore: The name of the object store from the above db,
+ * id: Id of the requested entry from the above object
+ * store. null if all entries from the above object
+ * store are requested,
+ * index: Name of the IDBIndex to be iterated on while fetching
+ * entries. null or "name" if no index is to be
+ * iterated,
+ * offset: offset of the entries to be fetched,
+ * size: The intended size of the entries to be fetched
+ * }
*/
- getObjectStoreData(host, principal, dbName, objectStore, id, index,
- offset, size) {
- let request = this.openWithPrincipal(principal, dbName);
+ getObjectStoreData(host, principal, dbName, storage, requestOptions) {
+ let {name} = this.splitNameAndStorage(dbName);
+ let request = this.openWithPrincipal(principal, name, storage);
let success = promise.defer();
+ let {objectStore, id, index, offset, size} = requestOptions;
let data = [];
let db;
@@ -2157,8 +2382,12 @@ var indexedDBHelpers = {
switch (msg.json.method) {
case "getDBMetaData": {
- let [host, principal, name] = args;
- return indexedDBHelpers.getDBMetaData(host, principal, name);
+ let [host, principal, name, storage] = args;
+ return indexedDBHelpers.getDBMetaData(host, principal, name, storage);
+ }
+ case "splitNameAndStorage": {
+ let [name] = args;
+ return indexedDBHelpers.splitNameAndStorage(name);
}
case "getDBNamesForHost": {
let [host] = args;
@@ -2170,8 +2399,8 @@ var indexedDBHelpers = {
hostVsStores, principal);
}
case "removeDB": {
- let [host, principal, name] = args;
- return indexedDBHelpers.removeDB(host, principal, name);
+ let [host, principal, dbName] = args;
+ return indexedDBHelpers.removeDB(host, principal, dbName);
}
case "removeDBRecord": {
let [host, principal, db, store, id] = args;
@@ -2480,6 +2709,7 @@ let StorageActor = protocol.ActorClassWithSpec(specs.storageSpec, {
// added or changed update
this.removeNamesFromUpdateList("added", storeType, data);
this.removeNamesFromUpdateList("changed", storeType, data);
+
for (let host in data) {
if (data[host].length == 0 && this.boundUpdate.added &&
this.boundUpdate.added[storeType] &&
diff --git a/devtools/server/tests/browser/browser.ini b/devtools/server/tests/browser/browser.ini
index c05933230..b7929e2b0 100644
--- a/devtools/server/tests/browser/browser.ini
+++ b/devtools/server/tests/browser/browser.ini
@@ -11,6 +11,7 @@ support-files =
doc_perf.html
navigate-first.html
navigate-second.html
+ storage-cookies-same-name.html
storage-dynamic-windows.html
storage-listings.html
storage-unsecured-iframe.html
@@ -80,6 +81,7 @@ skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still di
#[browser_perf-front-profiler-01.js] bug 1077464
#[browser_perf-front-profiler-05.js] bug 1077464
#[browser_perf-front-profiler-06.js]
+[browser_storage_cookies-duplicate-names.js]
[browser_storage_dynamic_windows.js]
[browser_storage_listings.js]
[browser_storage_updates.js]
diff --git a/devtools/server/tests/browser/browser_storage_cookies-duplicate-names.js b/devtools/server/tests/browser/browser_storage_cookies-duplicate-names.js
new file mode 100644
index 000000000..c1cf0aa72
--- /dev/null
+++ b/devtools/server/tests/browser/browser_storage_cookies-duplicate-names.js
@@ -0,0 +1,105 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the storage panel is able to display multiple cookies with the same
+// name (and different paths).
+
+const {StorageFront} = require("devtools/shared/fronts/storage");
+Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/server/tests/browser/storage-helpers.js", this);
+
+const TESTDATA = {
+ "test1.example.org": [
+ {
+ name: "name",
+ value: "value1",
+ expires: 0,
+ path: "/",
+ host: "test1.example.org",
+ isDomain: false,
+ isSecure: false,
+ },
+ {
+ name: "name",
+ value: "value2",
+ expires: 0,
+ path: "/path2/",
+ host: "test1.example.org",
+ isDomain: false,
+ isSecure: false,
+ },
+ {
+ name: "name",
+ value: "value3",
+ expires: 0,
+ path: "/path3/",
+ host: "test1.example.org",
+ isDomain: false,
+ isSecure: false,
+ }
+ ]
+};
+
+add_task(function* () {
+ yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-cookies-same-name.html");
+
+ initDebuggerServer();
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ let form = yield connectDebuggerClient(client);
+ let front = StorageFront(client, form);
+ let data = yield front.listStores();
+
+ ok(data.cookies, "Cookies storage actor is present");
+
+ yield testCookies(data.cookies);
+ yield clearStorage();
+
+ // Forcing GC/CC to get rid of docshells and windows created by this test.
+ forceCollections();
+ yield client.close();
+ forceCollections();
+ DebuggerServer.destroy();
+ forceCollections();
+});
+
+function testCookies(cookiesActor) {
+ let numHosts = Object.keys(cookiesActor.hosts).length;
+ is(numHosts, 1, "Correct number of host entries for cookies");
+ return testCookiesObjects(0, cookiesActor.hosts, cookiesActor);
+}
+
+var testCookiesObjects = Task.async(function* (index, hosts, cookiesActor) {
+ let host = Object.keys(hosts)[index];
+ let matchItems = data => {
+ is(data.total, TESTDATA[host].length,
+ "Number of cookies in host " + host + " matches");
+ for (let item of data.data) {
+ let found = false;
+ for (let toMatch of TESTDATA[host]) {
+ if (item.name === toMatch.name &&
+ item.host === toMatch.host &&
+ item.path === toMatch.path) {
+ found = true;
+ ok(true, "Found cookie " + item.name + " in response");
+ is(item.value.str, toMatch.value, "The value matches.");
+ is(item.expires, toMatch.expires, "The expiry time matches.");
+ is(item.path, toMatch.path, "The path matches.");
+ is(item.host, toMatch.host, "The host matches.");
+ is(item.isSecure, toMatch.isSecure, "The isSecure value matches.");
+ is(item.isDomain, toMatch.isDomain, "The isDomain value matches.");
+ break;
+ }
+ }
+ ok(found, "cookie " + item.name + " should exist in response");
+ }
+ };
+
+ ok(!!TESTDATA[host], "Host is present in the list : " + host);
+ matchItems(yield cookiesActor.getStoreObjects(host));
+ if (index == Object.keys(hosts).length - 1) {
+ return;
+ }
+ yield testCookiesObjects(++index, hosts, cookiesActor);
+});
diff --git a/devtools/server/tests/browser/browser_storage_dynamic_windows.js b/devtools/server/tests/browser/browser_storage_dynamic_windows.js
index 440c91222..91b4155d9 100644
--- a/devtools/server/tests/browser/browser_storage_dynamic_windows.js
+++ b/devtools/server/tests/browser/browser_storage_dynamic_windows.js
@@ -66,6 +66,7 @@ function markOutMatched(toBeEmptied, data, deleted) {
info("Testing for " + storageType);
for (let host in data[storageType]) {
ok(toBeEmptied[storageType][host], "Host " + host + " found");
+
if (!deleted) {
for (let item of data[storageType][host]) {
let index = toBeEmptied[storageType][host].indexOf(item);
@@ -87,50 +88,6 @@ function markOutMatched(toBeEmptied, data, deleted) {
}
}
-// function testReload(front) {
-// info("Testing if reload works properly");
-
-// let shouldBeEmptyFirst = Cu.cloneInto(beforeReload, {});
-// let shouldBeEmptyLast = Cu.cloneInto(beforeReload, {});
-// return new Promise(resolve => {
-
-// let onStoresUpdate = data => {
-// info("in stores update of testReload");
-// // This might be second time stores update is happening, in which case,
-// // data.deleted will be null.
-// // OR.. This might be the first time on a super slow machine where both
-// // data.deleted and data.added is missing in the first update.
-// if (data.deleted) {
-// markOutMatched(shouldBeEmptyFirst, data.deleted, true);
-// }
-
-// if (!Object.keys(shouldBeEmptyFirst).length) {
-// info("shouldBeEmptyFirst is empty now");
-// }
-
-// // stores-update call might not have data.added for the first time on
-// // slow machines, in which case, data.added will be null
-// if (data.added) {
-// markOutMatched(shouldBeEmptyLast, data.added);
-// }
-
-// if (!Object.keys(shouldBeEmptyLast).length) {
-// info("Everything to be received is received.");
-// endTestReloaded();
-// }
-// };
-
-// let endTestReloaded = () => {
-// front.off("stores-update", onStoresUpdate);
-// resolve();
-// };
-
-// front.on("stores-update", onStoresUpdate);
-
-// content.location.reload();
-// });
-// }
-
function testAddIframe(front) {
info("Testing if new iframe addition works properly");
return new Promise(resolve => {
@@ -142,7 +99,10 @@ function testAddIframe(front) {
"https://sectest1.example.org": ["iframe-s-ss1"]
},
cookies: {
- "sectest1.example.org": ["sc1"]
+ "sectest1.example.org": [
+ getCookieId("sc1", "sectest1.example.org",
+ "/browser/devtools/server/tests/browser/")
+ ]
},
indexedDB: {
// empty because indexed db creation happens after the page load, so at
@@ -150,7 +110,7 @@ function testAddIframe(front) {
"https://sectest1.example.org": []
},
Cache: {
- "https://sectest1.example.org":[]
+ "https://sectest1.example.org": []
}
};
diff --git a/devtools/server/tests/browser/browser_storage_listings.js b/devtools/server/tests/browser/browser_storage_listings.js
index 4ff3c3fc1..e47a320b8 100644
--- a/devtools/server/tests/browser/browser_storage_listings.js
+++ b/devtools/server/tests/browser/browser_storage_listings.js
@@ -20,15 +20,6 @@ const storeMap = {
isSecure: false,
},
{
- name: "cs2",
- value: "sessionCookie",
- path: "/",
- host: ".example.org",
- expires: 0,
- isDomain: true,
- isSecure: false,
- },
- {
name: "c3",
value: "foobar-2",
expires: 2000000001000,
@@ -130,24 +121,24 @@ const storeMap = {
const IDBValues = {
listStoresResponse: {
"http://test1.example.org": [
- ["idb1", "obj1"], ["idb1", "obj2"], ["idb2", "obj3"]
+ ["idb1 (default)", "obj1"], ["idb1 (default)", "obj2"], ["idb2 (default)", "obj3"]
],
"http://sectest1.example.org": [
],
"https://sectest1.example.org": [
- ["idb-s1", "obj-s1"], ["idb-s2", "obj-s2"]
+ ["idb-s1 (default)", "obj-s1"], ["idb-s2 (default)", "obj-s2"]
]
},
- dbDetails : {
+ dbDetails: {
"http://test1.example.org": [
{
- db: "idb1",
+ db: "idb1 (default)",
origin: "http://test1.example.org",
version: 1,
objectStores: 2
},
{
- db: "idb2",
+ db: "idb2 (default)",
origin: "http://test1.example.org",
version: 1,
objectStores: 1
@@ -157,13 +148,13 @@ const IDBValues = {
],
"https://sectest1.example.org": [
{
- db: "idb-s1",
+ db: "idb-s1 (default)",
origin: "https://sectest1.example.org",
version: 1,
objectStores: 1
},
{
- db: "idb-s2",
+ db: "idb-s2 (default)",
origin: "https://sectest1.example.org",
version: 1,
objectStores: 1
@@ -172,7 +163,7 @@ const IDBValues = {
},
objectStoreDetails: {
"http://test1.example.org": {
- idb1: [
+ "idb1 (default)": [
{
objectStore: "obj1",
keyPath: "id",
@@ -199,7 +190,7 @@ const IDBValues = {
indexes: []
}
],
- idb2: [
+ "idb2 (default)": [
{
objectStore: "obj3",
keyPath: "id3",
@@ -217,7 +208,7 @@ const IDBValues = {
},
"http://sectest1.example.org" : {},
"https://sectest1.example.org": {
- "idb-s1": [
+ "idb-s1 (default)": [
{
objectStore: "obj-s1",
keyPath: "id",
@@ -225,7 +216,7 @@ const IDBValues = {
indexes: []
},
],
- "idb-s2": [
+ "idb-s2 (default)": [
{
objectStore: "obj-s2",
keyPath: "id3",
@@ -245,7 +236,7 @@ const IDBValues = {
},
entries: {
"http://test1.example.org": {
- "idb1#obj1": [
+ "idb1 (default)#obj1": [
{
name: 1,
value: {
@@ -271,7 +262,7 @@ const IDBValues = {
}
}
],
- "idb1#obj2": [
+ "idb1 (default)#obj2": [
{
name: 1,
value: {
@@ -282,11 +273,11 @@ const IDBValues = {
}
}
],
- "idb2#obj3": []
+ "idb2 (default)#obj3": []
},
"http://sectest1.example.org" : {},
"https://sectest1.example.org": {
- "idb-s1#obj-s1": [
+ "idb-s1 (default)#obj-s1": [
{
name: 6,
value: {
@@ -304,7 +295,7 @@ const IDBValues = {
}
}
],
- "idb-s2#obj-s2": [
+ "idb-s2 (default)#obj-s2": [
{
name: 13,
value: {
@@ -337,7 +328,8 @@ function* testStores(data) {
}
function testCookies(cookiesActor) {
- is(Object.keys(cookiesActor.hosts).length, 2, "Correct number of host entries for cookies");
+ is(Object.keys(cookiesActor.hosts).length, 2,
+ "Correct number of host entries for cookies");
return testCookiesObjects(0, cookiesActor.hosts, cookiesActor);
}
@@ -346,9 +338,9 @@ var testCookiesObjects = Task.async(function* (index, hosts, cookiesActor) {
let matchItems = data => {
let cookiesLength = 0;
for (let secureCookie of storeMap.cookies[host]) {
- if (secureCookie.isSecure) {
- ++cookiesLength;
- }
+ if (secureCookie.isSecure) {
+ ++cookiesLength;
+ }
}
// Any secure cookies did not get stored in the database.
is(data.total, storeMap.cookies[host].length - cookiesLength,
@@ -478,17 +470,17 @@ var testIndexedDBs = Task.async(function* (index, hosts, indexedDBActor) {
for (let item of data.data) {
let found = false;
for (let toMatch of IDBValues.dbDetails[host]) {
- if (item.db == toMatch.db) {
+ if (item.uniqueKey == toMatch.db) {
found = true;
- ok(true, "Found indexed db " + item.db + " in response");
+ ok(true, "Found indexed db " + item.uniqueKey + " in response");
is(item.origin, toMatch.origin, "The origin matches.");
is(item.version, toMatch.version, "The version matches.");
is(item.objectStores, toMatch.objectStores,
- "The numebr of object stores matches.");
+ "The number of object stores matches.");
break;
}
}
- ok(found, "indexed db " + item.name + " should exist in response");
+ ok(found, "indexed db " + item.uniqueKey + " should exist in response");
}
};
diff --git a/devtools/server/tests/browser/browser_storage_updates.js b/devtools/server/tests/browser/browser_storage_updates.js
index 28b2e509f..01a35cefc 100644
--- a/devtools/server/tests/browser/browser_storage_updates.js
+++ b/devtools/server/tests/browser/browser_storage_updates.js
@@ -27,7 +27,12 @@ const TESTS = [
expected: {
added: {
cookies: {
- "test1.example.org": ["c1", "c2"]
+ "test1.example.org": [
+ getCookieId("c1", "test1.example.org",
+ "/browser/devtools/server/tests/browser/"),
+ getCookieId("c2", "test1.example.org",
+ "/browser/devtools/server/tests/browser/")
+ ]
},
localStorage: {
"http://test1.example.org": ["l1"]
@@ -48,7 +53,10 @@ const TESTS = [
expected: {
changed: {
cookies: {
- "test1.example.org": ["c1"]
+ "test1.example.org": [
+ getCookieId("c1", "test1.example.org",
+ "/browser/devtools/server/tests/browser/"),
+ ]
}
},
added: {
@@ -74,7 +82,10 @@ const TESTS = [
expected: {
deleted: {
cookies: {
- "test1.example.org": ["c2"]
+ "test1.example.org": [
+ getCookieId("c2", "test1.example.org",
+ "/browser/devtools/server/tests/browser/"),
+ ]
},
localStorage: {
"http://test1.example.org": ["l1"]
@@ -112,7 +123,10 @@ const TESTS = [
expected: {
added: {
cookies: {
- "test1.example.org": ["c3"]
+ "test1.example.org": [
+ getCookieId("c3", "test1.example.org",
+ "/browser/devtools/server/tests/browser/"),
+ ]
},
sessionStorage: {
"http://test1.example.org": ["s1", "s2"]
@@ -125,7 +139,10 @@ const TESTS = [
},
deleted: {
cookies: {
- "test1.example.org": ["c1"]
+ "test1.example.org": [
+ getCookieId("c1", "test1.example.org",
+ "/browser/devtools/server/tests/browser/"),
+ ]
},
localStorage: {
"http://test1.example.org": ["l2"]
@@ -158,7 +175,10 @@ const TESTS = [
expected: {
deleted: {
cookies: {
- "test1.example.org": ["c3"]
+ "test1.example.org": [
+ getCookieId("c3", "test1.example.org",
+ "/browser/devtools/server/tests/browser/"),
+ ]
}
}
}
diff --git a/devtools/server/tests/browser/head.js b/devtools/server/tests/browser/head.js
index 1e7f09d95..5cf98c2b0 100644
--- a/devtools/server/tests/browser/head.js
+++ b/devtools/server/tests/browser/head.js
@@ -2,6 +2,10 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+/* eslint no-unused-vars: [2, {"vars": "local"}] */
+
var Cc = Components.classes;
var Ci = Components.interfaces;
var Cu = Components.utils;
@@ -19,6 +23,11 @@ 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;
+// GUID to be used as a separator in compound keys. This must match the same
+// constant in devtools/server/actors/storage.js,
+// devtools/client/storage/ui.js and devtools/client/storage/test/head.js
+const SEPARATOR_GUID = "{9d414cc5-8319-0a04-0586-c0a6ae01670a}";
+
// All tests are asynchronous.
waitForExplicitFinish();
@@ -94,7 +103,6 @@ function once(target, eventName, useCapture = false) {
info("Waiting for event: '" + eventName + "' on " + target + ".");
return new Promise(resolve => {
-
for (let [add, remove] of [
["addEventListener", "removeEventListener"],
["addListener", "removeListener"],
@@ -137,6 +145,8 @@ function getMockTabActor(win) {
}
registerCleanupFunction(function tearDown() {
+ Services.cookies.removeAll();
+
while (gBrowser.tabs.length > 1) {
gBrowser.removeCurrentTab();
}
@@ -148,8 +158,11 @@ function idleWait(time) {
function busyWait(time) {
let start = Date.now();
+ // eslint-disable-next-line
let stack;
- while (Date.now() - start < time) { stack = Components.stack; }
+ while (Date.now() - start < time) {
+ stack = Components.stack;
+ }
}
/**
@@ -172,11 +185,12 @@ function waitUntil(predicate, interval = 10) {
}
function waitForMarkerType(front, types, predicate,
- unpackFun = (name, data) => data.markers,
- eventName = "timeline-data")
-{
+ unpackFun = (name, data) => data.markers,
+ eventName = "timeline-data") {
types = [].concat(types);
- predicate = predicate || function () { return true; };
+ predicate = predicate || function () {
+ return true;
+ };
let filteredMarkers = [];
let { promise, resolve } = defer();
@@ -190,9 +204,11 @@ function waitForMarkerType(front, types, predicate,
let markers = unpackFun(name, data);
info("Got markers: " + JSON.stringify(markers, null, 2));
- filteredMarkers = filteredMarkers.concat(markers.filter(m => types.indexOf(m.name) !== -1));
+ filteredMarkers = filteredMarkers.concat(
+ markers.filter(m => types.indexOf(m.name) !== -1));
- if (types.every(t => filteredMarkers.some(m => m.name === t)) && predicate(filteredMarkers)) {
+ if (types.every(t => filteredMarkers.some(m => m.name === t)) &&
+ predicate(filteredMarkers)) {
front.off(eventName, handler);
resolve(filteredMarkers);
}
@@ -201,3 +217,7 @@ function waitForMarkerType(front, types, predicate,
return promise;
}
+
+function getCookieId(name, domain, path) {
+ return `${name}${SEPARATOR_GUID}${domain}${SEPARATOR_GUID}${path}`;
+}
diff --git a/devtools/server/tests/browser/storage-cookies-same-name.html b/devtools/server/tests/browser/storage-cookies-same-name.html
new file mode 100644
index 000000000..e3e092ec3
--- /dev/null
+++ b/devtools/server/tests/browser/storage-cookies-same-name.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Storage inspector cookies with duplicate names</title>
+</head>
+<body onload="createCookies()">
+<script type="application/javascript;version=1.7">
+"use strict";
+function createCookies() {
+ document.cookie = "name=value1;path=/;";
+ document.cookie = "name=value2;path=/path2/;";
+ document.cookie = "name=value3;path=/path3/;";
+}
+
+window.removeCookie = function (name) {
+ document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT";
+};
+
+window.clearCookies = function () {
+ let cookies = document.cookie;
+ for (let cookie of cookies.split(";")) {
+ removeCookie(cookie.split("=")[0]);
+ }
+};
+</script>
+</body>
+</html>