Bug 1060947 - Listing indexedDB databases throws if any of them have 0 objectStores. r=mratcliffe
--- a/devtools/client/storage/test/browser.ini
+++ b/devtools/client/storage/test/browser.ini
@@ -1,15 +1,16 @@
[DEFAULT]
tags = devtools
subsuite = devtools
support-files =
storage-cache-error.html
storage-complex-values.html
storage-cookies.html
+ storage-empty-objectstores.html
storage-listings.html
storage-localstorage.html
storage-overflow.html
storage-search.html
storage-secured-iframe.html
storage-sessionstorage.html
storage-unsecured-iframe.html
storage-updates.html
@@ -17,18 +18,19 @@ support-files =
!/devtools/client/framework/test/shared-head.js
[browser_storage_basic.js]
[browser_storage_cache_error.js]
[browser_storage_cookies_delete_all.js]
[browser_storage_cookies_edit.js]
[browser_storage_cookies_edit_keyboard.js]
[browser_storage_cookies_tab_navigation.js]
-[browser_storage_dynamic_updates.js]
-[browser_storage_localstorage_edit.js]
[browser_storage_delete.js]
[browser_storage_delete_all.js]
[browser_storage_delete_tree.js]
+[browser_storage_dynamic_updates.js]
+[browser_storage_empty_objectstores.js]
+[browser_storage_localstorage_edit.js]
[browser_storage_overflow.js]
[browser_storage_search.js]
[browser_storage_sessionstorage_edit.js]
[browser_storage_sidebar.js]
[browser_storage_values.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/storage/test/browser_storage_empty_objectstores.js
@@ -0,0 +1,77 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+// Basic test to assert that the storage tree and table corresponding to each
+// item in the storage tree is correctly displayed.
+
+"use strict";
+
+// Entries that should be present in the tree for this test
+// Format for each entry in the array:
+// [
+// ["path", "to", "tree", "item"],
+// - The path to the tree item to click formed by id of each item
+// ["key_value1", "key_value2", ...]
+// - The value of the first (unique) column for each row in the table
+// corresponding to the tree item selected.
+// ]
+// These entries are formed by the cookies, local storage, session storage and
+// indexedDB entries created in storage-listings.html,
+// storage-secured-iframe.html and storage-unsecured-iframe.html
+const storeItems = [
+ [["indexedDB", "http://test1.example.org"],
+ ["idb1", "idb2"]],
+ [["indexedDB", "http://test1.example.org", "idb1"],
+ ["obj1", "obj2"]],
+ [["indexedDB", "http://test1.example.org", "idb2"],
+ []],
+ [["indexedDB", "http://test1.example.org", "idb1", "obj1"],
+ [1, 2, 3]],
+ [["indexedDB", "http://test1.example.org", "idb1", "obj2"],
+ [1]]
+];
+
+/**
+ * Test that the desired number of tree items are present
+ */
+function testTree() {
+ let doc = gPanelWindow.document;
+ for (let [item] of storeItems) {
+ ok(doc.querySelector(`[data-id='${JSON.stringify(item)}']`),
+ `Tree item ${item} should be present in the storage tree`);
+ }
+}
+
+/**
+ * Test that correct table entries are shown for each of the tree item
+ */
+let testTables = function* () {
+ let doc = gPanelWindow.document;
+ // Expand all nodes so that the synthesized click event actually works
+ gUI.tree.expandAll();
+
+ // Click the tree items and wait for the table to be updated
+ for (let [item, ids] of storeItems) {
+ yield selectTreeItem(item);
+
+ // Check whether correct number of items are present in the table
+ is(doc.querySelectorAll(
+ ".table-widget-wrapper:first-of-type .table-widget-cell"
+ ).length, ids.length, "Number of items in table is correct");
+
+ // Check if all the desired items are present in the table
+ for (let id of ids) {
+ ok(doc.querySelector(".table-widget-cell[data-id='" + id + "']"),
+ `Table item ${id} should be present`);
+ }
+ }
+};
+
+add_task(function* () {
+ yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-empty-objectstores.html");
+
+ testTree();
+ yield testTables();
+ yield finishTests();
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/storage/test/storage-empty-objectstores.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for proper listing indexedDB databases with no object stores</title>
+</head>
+<body>
+<script type="application/javascript;version=1.7">
+
+window.setup = function* () {
+ let request = indexedDB.open("idb1", 1);
+ let db = yield new Promise((resolve, reject) => {
+ request.onerror = e => reject(Error("error opening db connection"));
+ request.onupgradeneeded = event => {
+ let db = event.target.result;
+ let store1 = db.createObjectStore("obj1", { keyPath: "id" });
+ store1.createIndex("name", "name", { unique: false });
+ store1.createIndex("email", "email", { unique: true });
+ let store2 = db.createObjectStore("obj2", { keyPath: "id2" });
+ store1.transaction.oncomplete = () => resolve(db);
+ };
+ });
+
+ yield new Promise(resolve => request.onsuccess = resolve);
+
+ let transaction = db.transaction(["obj1", "obj2"], "readwrite");
+ let store1 = transaction.objectStore("obj1");
+ let store2 = transaction.objectStore("obj2");
+
+ store1.add({id: 1, name: "foo", email: "foo@bar.com"});
+ store1.add({id: 2, name: "foo2", email: "foo2@bar.com"});
+ store1.add({id: 3, name: "foo2", email: "foo3@bar.com"});
+ store2.add({id2: 1, name: "foo", email: "foo@bar.com", extra: "baz"});
+
+ yield new Promise(resolve => transaction.oncomplete = resolve);
+
+ db.close();
+
+ request = indexedDB.open("idb2", 1);
+ let db2 = yield new Promise((resolve, reject) => {
+ request.onerror = e => reject(Error("error opening db2 connection"));
+ request.onupgradeneeded = event => resolve(event.target.result);
+ });
+
+ yield new Promise(resolve => request.onsuccess = resolve);
+
+ db2.close();
+ dump("added indexedDB items from main page\n");
+};
+
+window.clear = function* () {
+ for (let dbName of ["idb1", "idb2"]) {
+ yield new Promise(resolve => {
+ indexedDB.deleteDatabase(dbName).onsuccess = resolve;
+ });
+ }
+ dump("removed indexedDB items from main page\n");
+};
+
+</script>
+</body>
+</html>
--- a/devtools/server/actors/storage.js
+++ b/devtools/server/actors/storage.js
@@ -1531,19 +1531,23 @@ StorageActors.createActor({
* cannot be performed synchronously. Thus, the preListStores method exists to
* do the same task asynchronously.
*/
populateStoresForHosts: function() {},
getNamesForHost: function(host) {
let names = [];
- for (let [dbName, metaData] of this.hostVsStores.get(host)) {
- for (let objectStore of metaData.objectStores.keys()) {
- names.push(JSON.stringify([dbName, objectStore]));
+ for (let [dbName, {objectStores}] of this.hostVsStores.get(host)) {
+ if (objectStores.size) {
+ for (let objectStore of objectStores.keys()) {
+ names.push(JSON.stringify([dbName, objectStore]));
+ }
+ } else {
+ names.push(JSON.stringify([dbName]));
}
}
return names;
},
/**
* Returns the total number of entries for various types of requests to
* getStoreObjects for Indexed DB actor.
@@ -1629,26 +1633,26 @@ StorageActors.createActor({
/**
* Returns the over-the-wire implementation of the indexed db entity.
*/
toStoreObject: function(item) {
if (!item) {
return null;
}
- if (item.indexes) {
+ if ("indexes" in item) {
// Object store meta data
return {
objectStore: item.name,
keyPath: item.keyPath,
autoIncrement: item.autoIncrement,
indexes: item.indexes
};
}
- if (item.objectStores) {
+ if ("objectStores" in item) {
// DB meta data
return {
db: item.name,
origin: item.origin,
version: item.version,
objectStores: item.objectStores
};
}