From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- devtools/client/storage/test/.eslintrc.js | 6 + devtools/client/storage/test/browser.ini | 44 ++ .../client/storage/test/browser_storage_basic.js | 118 +++ .../storage/test/browser_storage_cache_delete.js | 46 ++ .../storage/test/browser_storage_cache_error.js | 19 + .../test/browser_storage_cookies_delete_all.js | 74 ++ .../storage/test/browser_storage_cookies_domain.js | 21 + .../storage/test/browser_storage_cookies_edit.js | 22 + .../test/browser_storage_cookies_edit_keyboard.js | 23 + .../test/browser_storage_cookies_tab_navigation.js | 24 + .../client/storage/test/browser_storage_delete.js | 56 ++ .../storage/test/browser_storage_delete_all.js | 90 +++ .../storage/test/browser_storage_delete_tree.js | 67 ++ .../test/browser_storage_dynamic_updates.js | 213 ++++++ .../test/browser_storage_empty_objectstores.js | 77 ++ .../test/browser_storage_indexeddb_delete.js | 47 ++ .../browser_storage_indexeddb_delete_blocked.js | 58 ++ .../test/browser_storage_localstorage_edit.js | 24 + .../test/browser_storage_localstorage_error.js | 24 + .../storage/test/browser_storage_overflow.js | 41 + .../client/storage/test/browser_storage_search.js | 87 +++ .../test/browser_storage_search_keyboard_trap.js | 15 + .../test/browser_storage_sessionstorage_edit.js | 24 + .../client/storage/test/browser_storage_sidebar.js | 125 +++ .../storage/test/browser_storage_sidebar_update.js | 41 + .../client/storage/test/browser_storage_values.js | 165 ++++ devtools/client/storage/test/head.js | 840 +++++++++++++++++++++ .../client/storage/test/storage-cache-error.html | 20 + .../storage/test/storage-complex-values.html | 123 +++ devtools/client/storage/test/storage-cookies.html | 24 + .../storage/test/storage-empty-objectstores.html | 62 ++ .../storage/test/storage-idb-delete-blocked.html | 52 ++ devtools/client/storage/test/storage-listings.html | 126 ++++ .../client/storage/test/storage-localstorage.html | 23 + devtools/client/storage/test/storage-overflow.html | 19 + devtools/client/storage/test/storage-search.html | 23 + .../storage/test/storage-secured-iframe.html | 91 +++ .../storage/test/storage-sessionstorage.html | 23 + .../storage/test/storage-unsecured-iframe.html | 19 + devtools/client/storage/test/storage-updates.html | 64 ++ 40 files changed, 3060 insertions(+) create mode 100644 devtools/client/storage/test/.eslintrc.js create mode 100644 devtools/client/storage/test/browser.ini create mode 100644 devtools/client/storage/test/browser_storage_basic.js create mode 100644 devtools/client/storage/test/browser_storage_cache_delete.js create mode 100644 devtools/client/storage/test/browser_storage_cache_error.js create mode 100644 devtools/client/storage/test/browser_storage_cookies_delete_all.js create mode 100644 devtools/client/storage/test/browser_storage_cookies_domain.js create mode 100644 devtools/client/storage/test/browser_storage_cookies_edit.js create mode 100644 devtools/client/storage/test/browser_storage_cookies_edit_keyboard.js create mode 100644 devtools/client/storage/test/browser_storage_cookies_tab_navigation.js create mode 100644 devtools/client/storage/test/browser_storage_delete.js create mode 100644 devtools/client/storage/test/browser_storage_delete_all.js create mode 100644 devtools/client/storage/test/browser_storage_delete_tree.js create mode 100644 devtools/client/storage/test/browser_storage_dynamic_updates.js create mode 100644 devtools/client/storage/test/browser_storage_empty_objectstores.js create mode 100644 devtools/client/storage/test/browser_storage_indexeddb_delete.js create mode 100644 devtools/client/storage/test/browser_storage_indexeddb_delete_blocked.js create mode 100644 devtools/client/storage/test/browser_storage_localstorage_edit.js create mode 100644 devtools/client/storage/test/browser_storage_localstorage_error.js create mode 100644 devtools/client/storage/test/browser_storage_overflow.js create mode 100644 devtools/client/storage/test/browser_storage_search.js create mode 100644 devtools/client/storage/test/browser_storage_search_keyboard_trap.js create mode 100644 devtools/client/storage/test/browser_storage_sessionstorage_edit.js create mode 100644 devtools/client/storage/test/browser_storage_sidebar.js create mode 100644 devtools/client/storage/test/browser_storage_sidebar_update.js create mode 100644 devtools/client/storage/test/browser_storage_values.js create mode 100644 devtools/client/storage/test/head.js create mode 100644 devtools/client/storage/test/storage-cache-error.html create mode 100644 devtools/client/storage/test/storage-complex-values.html create mode 100644 devtools/client/storage/test/storage-cookies.html create mode 100644 devtools/client/storage/test/storage-empty-objectstores.html create mode 100644 devtools/client/storage/test/storage-idb-delete-blocked.html create mode 100644 devtools/client/storage/test/storage-listings.html create mode 100644 devtools/client/storage/test/storage-localstorage.html create mode 100644 devtools/client/storage/test/storage-overflow.html create mode 100644 devtools/client/storage/test/storage-search.html create mode 100644 devtools/client/storage/test/storage-secured-iframe.html create mode 100644 devtools/client/storage/test/storage-sessionstorage.html create mode 100644 devtools/client/storage/test/storage-unsecured-iframe.html create mode 100644 devtools/client/storage/test/storage-updates.html (limited to 'devtools/client/storage/test') diff --git a/devtools/client/storage/test/.eslintrc.js b/devtools/client/storage/test/.eslintrc.js new file mode 100644 index 000000000..8d15a76d9 --- /dev/null +++ b/devtools/client/storage/test/.eslintrc.js @@ -0,0 +1,6 @@ +"use strict"; + +module.exports = { + // Extend from the shared list of defined globals for mochitests. + "extends": "../../../.eslintrc.mochitests.js" +}; diff --git a/devtools/client/storage/test/browser.ini b/devtools/client/storage/test/browser.ini new file mode 100644 index 000000000..dd7f48bd7 --- /dev/null +++ b/devtools/client/storage/test/browser.ini @@ -0,0 +1,44 @@ +[DEFAULT] +tags = devtools +subsuite = devtools +support-files = + storage-cache-error.html + storage-complex-values.html + storage-cookies.html + storage-empty-objectstores.html + storage-idb-delete-blocked.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 + head.js + !/devtools/client/framework/test/shared-head.js + +[browser_storage_basic.js] +[browser_storage_cache_delete.js] +[browser_storage_cache_error.js] +[browser_storage_cookies_delete_all.js] +[browser_storage_cookies_domain.js] +[browser_storage_cookies_edit.js] +[browser_storage_cookies_edit_keyboard.js] +[browser_storage_cookies_tab_navigation.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_indexeddb_delete.js] +[browser_storage_indexeddb_delete_blocked.js] +[browser_storage_localstorage_edit.js] +[browser_storage_localstorage_error.js] +[browser_storage_overflow.js] +[browser_storage_search.js] +[browser_storage_search_keyboard_trap.js] +[browser_storage_sessionstorage_edit.js] +[browser_storage_sidebar.js] +[browser_storage_sidebar_update.js] +[browser_storage_values.js] diff --git a/devtools/client/storage/test/browser_storage_basic.js b/devtools/client/storage/test/browser_storage_basic.js new file mode 100644 index 000000000..343d46170 --- /dev/null +++ b/devtools/client/storage/test/browser_storage_basic.js @@ -0,0 +1,118 @@ +/* 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 + +// 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 + +"use strict"; + +const testCases = [ + [["cookies", "test1.example.org"], + ["c1", "cs2", "c3", "uc1"]], + [["cookies", "sectest1.example.org"], + ["uc1", "cs2", "sc1"]], + [["localStorage", "http://test1.example.org"], + ["ls1", "ls2"]], + [["localStorage", "http://sectest1.example.org"], + ["iframe-u-ls1"]], + [["localStorage", "https://sectest1.example.org"], + ["iframe-s-ls1"]], + [["sessionStorage", "http://test1.example.org"], + ["ss1"]], + [["sessionStorage", "http://sectest1.example.org"], + ["iframe-u-ss1", "iframe-u-ss2"]], + [["sessionStorage", "https://sectest1.example.org"], + ["iframe-s-ss1"]], + [["indexedDB", "http://test1.example.org"], + ["idb1", "idb2"]], + [["indexedDB", "http://test1.example.org", "idb1"], + ["obj1", "obj2"]], + [["indexedDB", "http://test1.example.org", "idb2"], + ["obj3"]], + [["indexedDB", "http://test1.example.org", "idb1", "obj1"], + [1, 2, 3]], + [["indexedDB", "http://test1.example.org", "idb1", "obj2"], + [1]], + [["indexedDB", "http://test1.example.org", "idb2", "obj3"], + []], + [["indexedDB", "http://sectest1.example.org"], + []], + [["indexedDB", "https://sectest1.example.org"], + ["idb-s1", "idb-s2"]], + [["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) { + ok(doc.querySelector("[data-id='" + JSON.stringify(item[0]) + "']"), + "Tree item " + item[0] + " should be present in the storage tree"); + } +} + +/** + * Test that correct table entries are shown for each of the tree item + */ +function* testTables() { + let doc = gPanelWindow.document; + // Expand all nodes so that the synthesized click event actually works + gUI.tree.expandAll(); + + // First tree item is already selected so no clicking and waiting for update + for (let id of testCases[0][1]) { + ok(doc.querySelector(".table-widget-cell[data-id='" + id + "']"), + "Table item " + id + " should be present"); + } + + // Click rest of the tree items and wait for the table to be updated + for (let item of testCases.slice(1)) { + yield selectTreeItem(item[0]); + + // Check whether correct number of items are present in the table + is(doc.querySelectorAll( + ".table-widget-wrapper:first-of-type .table-widget-cell" + ).length, item[1].length, "Number of items in table is correct"); + + // Check if all the desired items are present in the table + for (let id of item[1]) { + ok(doc.querySelector(".table-widget-cell[data-id='" + id + "']"), + "Table item " + id + " should be present"); + } + } +} + +add_task(function* () { + yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-listings.html"); + + testTree(); + yield testTables(); + + yield finishTests(); +}); diff --git a/devtools/client/storage/test/browser_storage_cache_delete.js b/devtools/client/storage/test/browser_storage_cache_delete.js new file mode 100644 index 000000000..f87aa66e8 --- /dev/null +++ b/devtools/client/storage/test/browser_storage_cache_delete.js @@ -0,0 +1,46 @@ +/* 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/. */ + +/* import-globals-from ../../framework/test/shared-head.js */ + +"use strict"; + +// Test deleting a Cache object from the tree using context menu + +add_task(function* () { + yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-listings.html"); + + let contextMenu = gPanelWindow.document.getElementById("storage-tree-popup"); + let menuDeleteItem = contextMenu.querySelector("#storage-tree-popup-delete"); + + let cacheToDelete = ["Cache", "http://test1.example.org", "plop"]; + + info("test state before delete"); + yield selectTreeItem(cacheToDelete); + ok(gUI.tree.isSelected(cacheToDelete), "Cache item is present in the tree"); + + info("do the delete"); + let eventWait = gUI.once("store-objects-updated"); + + let selector = `[data-id='${JSON.stringify(cacheToDelete)}'] > .tree-widget-item`; + let target = gPanelWindow.document.querySelector(selector); + ok(target, "Cache item's tree element is present"); + + yield waitForContextMenu(contextMenu, target, () => { + info("Opened tree context menu"); + menuDeleteItem.click(); + + let cacheName = cacheToDelete[2]; + ok(menuDeleteItem.getAttribute("label").includes(cacheName), + `Context menu item label contains '${cacheName}')`); + }); + + yield eventWait; + + info("test state after delete"); + yield selectTreeItem(cacheToDelete); + ok(!gUI.tree.isSelected(cacheToDelete), "Cache item is no longer present in the tree"); + + yield finishTests(); +}); diff --git a/devtools/client/storage/test/browser_storage_cache_error.js b/devtools/client/storage/test/browser_storage_cache_error.js new file mode 100644 index 000000000..dfc6056a7 --- /dev/null +++ b/devtools/client/storage/test/browser_storage_cache_error.js @@ -0,0 +1,19 @@ +/* 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/. */ + +"use strict"; + +// Test handling errors in CacheStorage + +add_task(function* () { + yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-cache-error.html"); + + const cacheItemId = ["Cache", "javascript:parent.frameContent"]; + + yield selectTreeItem(cacheItemId); + ok(gUI.tree.isSelected(cacheItemId), + `The item ${cacheItemId.join(" > ")} is present in the tree`); + + yield finishTests(); +}); diff --git a/devtools/client/storage/test/browser_storage_cookies_delete_all.js b/devtools/client/storage/test/browser_storage_cookies_delete_all.js new file mode 100644 index 000000000..6e6008e66 --- /dev/null +++ b/devtools/client/storage/test/browser_storage_cookies_delete_all.js @@ -0,0 +1,74 @@ +/* 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/. */ + +/* import-globals-from ../../framework/test/shared-head.js */ + +"use strict"; + +// Test deleting all cookies + +function* performDelete(store, rowName, deleteAll) { + let contextMenu = gPanelWindow.document.getElementById( + "storage-table-popup"); + let menuDeleteAllItem = contextMenu.querySelector( + "#storage-table-popup-delete-all"); + let menuDeleteAllFromItem = contextMenu.querySelector( + "#storage-table-popup-delete-all-from"); + + let storeName = store.join(" > "); + + yield selectTreeItem(store); + + let eventWait = gUI.once("store-objects-updated"); + + let cells = getRowCells(rowName); + yield waitForContextMenu(contextMenu, cells.name, () => { + info(`Opened context menu in ${storeName}, row '${rowName}'`); + if (deleteAll) { + menuDeleteAllItem.click(); + } else { + menuDeleteAllFromItem.click(); + let hostName = cells.host.value; + ok(menuDeleteAllFromItem.getAttribute("label").includes(hostName), + `Context menu item label contains '${hostName}'`); + } + }); + + yield eventWait; +} + +add_task(function* () { + yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-listings.html"); + + info("test state before delete"); + yield checkState([ + [["cookies", "test1.example.org"], ["c1", "c3", "cs2", "uc1"]], + [["cookies", "sectest1.example.org"], ["cs2", "sc1", "uc1"]], + ]); + + info("delete all from domain"); + // delete only cookies that match the host exactly + yield performDelete(["cookies", "test1.example.org"], "c1", false); + + info("test state after delete all from domain"); + yield checkState([ + // Domain cookies (.example.org) must not be deleted. + [["cookies", "test1.example.org"], ["cs2", "uc1"]], + [["cookies", "sectest1.example.org"], ["cs2", "sc1", "uc1"]], + ]); + + info("delete all"); + // delete all cookies for host, including domain cookies + yield performDelete(["cookies", "sectest1.example.org"], "uc1", true); + + info("test state after delete all"); + yield checkState([ + // Domain cookies (.example.org) are deleted too, so deleting in sectest1 + // also removes stuff from test1. + [["cookies", "test1.example.org"], []], + [["cookies", "sectest1.example.org"], []], + ]); + + yield finishTests(); +}); diff --git a/devtools/client/storage/test/browser_storage_cookies_domain.js b/devtools/client/storage/test/browser_storage_cookies_domain.js new file mode 100644 index 000000000..dc93d6e67 --- /dev/null +++ b/devtools/client/storage/test/browser_storage_cookies_domain.js @@ -0,0 +1,21 @@ +/* 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/. */ + +/* import-globals-from ../../framework/test/shared-head.js */ + +"use strict"; + +// Test that cookies with domain equal to full host name are listed. +// E.g., ".example.org" vs. example.org). Bug 1149497. + +add_task(function* () { + yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-cookies.html"); + + yield checkState([ + [["cookies", "test1.example.org"], + ["test1", "test2", "test3", "test4", "test5"]], + ]); + + yield finishTests(); +}); diff --git a/devtools/client/storage/test/browser_storage_cookies_edit.js b/devtools/client/storage/test/browser_storage_cookies_edit.js new file mode 100644 index 000000000..5818e4864 --- /dev/null +++ b/devtools/client/storage/test/browser_storage_cookies_edit.js @@ -0,0 +1,22 @@ +/* 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 check the editing of cookies. + +"use strict"; + +add_task(function* () { + yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-cookies.html"); + showAllColumns(true); + + yield editCell("test3", "name", "newTest3"); + yield editCell("newTest3", "path", "/"); + yield editCell("newTest3", "host", "test1.example.org"); + yield editCell("newTest3", "expires", "Tue, 14 Feb 2040 17:41:14 GMT"); + yield editCell("newTest3", "value", "newValue3"); + yield editCell("newTest3", "isSecure", "true"); + yield editCell("newTest3", "isHttpOnly", "true"); + + yield finishTests(); +}); diff --git a/devtools/client/storage/test/browser_storage_cookies_edit_keyboard.js b/devtools/client/storage/test/browser_storage_cookies_edit_keyboard.js new file mode 100644 index 000000000..1208c4376 --- /dev/null +++ b/devtools/client/storage/test/browser_storage_cookies_edit_keyboard.js @@ -0,0 +1,23 @@ +/* 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 check the editing of cookies with the keyboard. + +"use strict"; + +add_task(function* () { + yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-cookies.html"); + showAllColumns(true); + + yield startCellEdit("test4", "name"); + yield typeWithTerminator("test6", "VK_TAB"); + yield typeWithTerminator("/", "VK_TAB"); + yield typeWithTerminator(".example.org", "VK_TAB"); + yield typeWithTerminator("Tue, 25 Dec 2040 12:00:00 GMT", "VK_TAB"); + yield typeWithTerminator("test6value", "VK_TAB"); + yield typeWithTerminator("false", "VK_TAB"); + yield typeWithTerminator("false", "VK_TAB"); + + yield finishTests(); +}); diff --git a/devtools/client/storage/test/browser_storage_cookies_tab_navigation.js b/devtools/client/storage/test/browser_storage_cookies_tab_navigation.js new file mode 100644 index 000000000..783a0c844 --- /dev/null +++ b/devtools/client/storage/test/browser_storage_cookies_tab_navigation.js @@ -0,0 +1,24 @@ +/* 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 check cookie table tab navigation. + +"use strict"; + +add_task(function* () { + yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-cookies.html"); + showAllColumns(true); + + yield startCellEdit("test1", "name"); + + PressKeyXTimes("VK_TAB", 18); + is(getCurrentEditorValue(), "value3", + "We have tabbed to the correct cell."); + + PressKeyXTimes("VK_TAB", 18, {shiftKey: true}); + is(getCurrentEditorValue(), "test1", + "We have shift-tabbed to the correct cell."); + + yield finishTests(); +}); diff --git a/devtools/client/storage/test/browser_storage_delete.js b/devtools/client/storage/test/browser_storage_delete.js new file mode 100644 index 000000000..c0e2b0ad7 --- /dev/null +++ b/devtools/client/storage/test/browser_storage_delete.js @@ -0,0 +1,56 @@ +/* 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/. */ + +/* import-globals-from ../../framework/test/shared-head.js */ + +"use strict"; + +// Test deleting storage items + +const TEST_CASES = [ + [["localStorage", "http://test1.example.org"], + "ls1", "name"], + [["sessionStorage", "http://test1.example.org"], + "ss1", "name"], + [["cookies", "test1.example.org"], + "c1", "name"], + [["indexedDB", "http://test1.example.org", "idb1", "obj1"], + 1, "name"], + [["Cache", "http://test1.example.org", "plop"], + MAIN_DOMAIN + "404_cached_file.js", "url"], +]; + +add_task(function* () { + yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-listings.html"); + + let contextMenu = gPanelWindow.document.getElementById("storage-table-popup"); + let menuDeleteItem = contextMenu.querySelector("#storage-table-popup-delete"); + + for (let [ treeItem, rowName, cellToClick] of TEST_CASES) { + let treeItemName = treeItem.join(" > "); + + info(`Selecting tree item ${treeItemName}`); + yield selectTreeItem(treeItem); + + let row = getRowCells(rowName); + ok(gUI.table.items.has(rowName), `There is a row '${rowName}' in ${treeItemName}`); + + let eventWait = gUI.once("store-objects-updated"); + + yield waitForContextMenu(contextMenu, row[cellToClick], () => { + info(`Opened context menu in ${treeItemName}, row '${rowName}'`); + menuDeleteItem.click(); + let truncatedRowName = String(rowName).substr(0, 16); + ok(menuDeleteItem.getAttribute("label").includes(truncatedRowName), + `Context menu item label contains '${rowName}' (maybe truncated)`); + }); + + yield eventWait; + + ok(!gUI.table.items.has(rowName), + `There is no row '${rowName}' in ${treeItemName} after deletion`); + } + + yield finishTests(); +}); diff --git a/devtools/client/storage/test/browser_storage_delete_all.js b/devtools/client/storage/test/browser_storage_delete_all.js new file mode 100644 index 000000000..c4b6048fb --- /dev/null +++ b/devtools/client/storage/test/browser_storage_delete_all.js @@ -0,0 +1,90 @@ +/* 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/. */ + +/* import-globals-from ../../framework/test/shared-head.js */ + +"use strict"; + +// Test deleting all storage items + +add_task(function* () { + yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-listings.html"); + + let contextMenu = gPanelWindow.document.getElementById("storage-table-popup"); + let menuDeleteAllItem = contextMenu.querySelector( + "#storage-table-popup-delete-all"); + + info("test state before delete"); + const beforeState = [ + [["localStorage", "http://test1.example.org"], + ["ls1", "ls2"]], + [["localStorage", "http://sectest1.example.org"], + ["iframe-u-ls1"]], + [["localStorage", "https://sectest1.example.org"], + ["iframe-s-ls1"]], + [["sessionStorage", "http://test1.example.org"], + ["ss1"]], + [["sessionStorage", "http://sectest1.example.org"], + ["iframe-u-ss1", "iframe-u-ss2"]], + [["sessionStorage", "https://sectest1.example.org"], + ["iframe-s-ss1"]], + [["indexedDB", "http://test1.example.org", "idb1", "obj1"], + [1, 2, 3]], + [["Cache", "http://test1.example.org", "plop"], + [MAIN_DOMAIN + "404_cached_file.js", MAIN_DOMAIN + "browser_storage_basic.js"]], + ]; + + yield checkState(beforeState); + + info("do the delete"); + const deleteHosts = [ + [["localStorage", "https://sectest1.example.org"], "iframe-s-ls1", "name"], + [["sessionStorage", "https://sectest1.example.org"], "iframe-s-ss1", "name"], + [["indexedDB", "http://test1.example.org", "idb1", "obj1"], 1, "name"], + [["Cache", "http://test1.example.org", "plop"], + MAIN_DOMAIN + "404_cached_file.js", "url"], + ]; + + for (let [store, rowName, cellToClick] of deleteHosts) { + let storeName = store.join(" > "); + + yield selectTreeItem(store); + + let eventWait = gUI.once("store-objects-cleared"); + + let cell = getRowCells(rowName)[cellToClick]; + yield waitForContextMenu(contextMenu, cell, () => { + info(`Opened context menu in ${storeName}, row '${rowName}'`); + menuDeleteAllItem.click(); + }); + + yield eventWait; + } + + info("test state after delete"); + const afterState = [ + // iframes from the same host, one secure, one unsecure, are independent + // from each other. Delete all in one doesn't touch the other one. + [["localStorage", "http://test1.example.org"], + ["ls1", "ls2"]], + [["localStorage", "http://sectest1.example.org"], + ["iframe-u-ls1"]], + [["localStorage", "https://sectest1.example.org"], + []], + [["sessionStorage", "http://test1.example.org"], + ["ss1"]], + [["sessionStorage", "http://sectest1.example.org"], + ["iframe-u-ss1", "iframe-u-ss2"]], + [["sessionStorage", "https://sectest1.example.org"], + []], + [["indexedDB", "http://test1.example.org", "idb1", "obj1"], + []], + [["Cache", "http://test1.example.org", "plop"], + []], + ]; + + yield checkState(afterState); + + yield finishTests(); +}); diff --git a/devtools/client/storage/test/browser_storage_delete_tree.js b/devtools/client/storage/test/browser_storage_delete_tree.js new file mode 100644 index 000000000..867a1c8b6 --- /dev/null +++ b/devtools/client/storage/test/browser_storage_delete_tree.js @@ -0,0 +1,67 @@ +/* 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/. */ + +/* import-globals-from ../../framework/test/shared-head.js */ + +"use strict"; + +// Test deleting all storage items from the tree. + +add_task(function* () { + yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-listings.html"); + + let contextMenu = gPanelWindow.document.getElementById("storage-tree-popup"); + let menuDeleteAllItem = contextMenu.querySelector( + "#storage-tree-popup-delete-all"); + + info("test state before delete"); + yield checkState([ + [["cookies", "test1.example.org"], ["c1", "c3", "cs2", "uc1"]], + [["localStorage", "http://test1.example.org"], ["ls1", "ls2"]], + [["sessionStorage", "http://test1.example.org"], ["ss1"]], + [["indexedDB", "http://test1.example.org", "idb1", "obj1"], [1, 2, 3]], + [["Cache", "http://test1.example.org", "plop"], + [MAIN_DOMAIN + "404_cached_file.js", MAIN_DOMAIN + "browser_storage_basic.js"]], + ]); + + info("do the delete"); + const deleteHosts = [ + ["cookies", "test1.example.org"], + ["localStorage", "http://test1.example.org"], + ["sessionStorage", "http://test1.example.org"], + ["indexedDB", "http://test1.example.org", "idb1", "obj1"], + ["Cache", "http://test1.example.org", "plop"], + ]; + + for (let store of deleteHosts) { + let storeName = store.join(" > "); + + yield selectTreeItem(store); + + let eventName = "store-objects-" + + (store[0] == "cookies" ? "updated" : "cleared"); + let eventWait = gUI.once(eventName); + + let selector = `[data-id='${JSON.stringify(store)}'] > .tree-widget-item`; + let target = gPanelWindow.document.querySelector(selector); + ok(target, `tree item found in ${storeName}`); + yield waitForContextMenu(contextMenu, target, () => { + info(`Opened tree context menu in ${storeName}`); + menuDeleteAllItem.click(); + }); + + yield eventWait; + } + + info("test state after delete"); + yield checkState([ + [["cookies", "test1.example.org"], []], + [["localStorage", "http://test1.example.org"], []], + [["sessionStorage", "http://test1.example.org"], []], + [["indexedDB", "http://test1.example.org", "idb1", "obj1"], []], + [["Cache", "http://test1.example.org", "plop"], []], + ]); + + yield finishTests(); +}); diff --git a/devtools/client/storage/test/browser_storage_dynamic_updates.js b/devtools/client/storage/test/browser_storage_dynamic_updates.js new file mode 100644 index 000000000..f881146d2 --- /dev/null +++ b/devtools/client/storage/test/browser_storage_dynamic_updates.js @@ -0,0 +1,213 @@ +/* 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/. */ + +"use strict"; + +add_task(function* () { + yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-updates.html"); + + let $ = id => gPanelWindow.document.querySelector(id); + let $$ = sel => gPanelWindow.document.querySelectorAll(sel); + + gUI.tree.expandAll(); + + ok(gUI.sidebar.hidden, "Sidebar is initially hidden"); + yield selectTableItem("c1"); + + // test that value is something initially + let initialValue = [[ + {name: "c1", value: "1.2.3.4.5.6.7"}, + {name: "c1.Path", value: "/browser"} + ], [ + {name: "c1", value: "Array"}, + {name: "c1.0", value: "1"}, + {name: "c1.6", value: "7"} + ]]; + + // test that value is something initially + let finalValue = [[ + {name: "c1", value: '{"foo": 4,"bar":6}'}, + {name: "c1.Path", value: "/browser"} + ], [ + {name: "c1", value: "Object"}, + {name: "c1.foo", value: "4"}, + {name: "c1.bar", value: "6"} + ]]; + // Check that sidebar shows correct initial value + yield findVariableViewProperties(initialValue[0], false); + yield findVariableViewProperties(initialValue[1], true); + // Check if table shows correct initial value + ok($("#value [data-id='c1'].table-widget-cell"), "cell is present"); + is($("#value [data-id='c1'].table-widget-cell").value, "1.2.3.4.5.6.7", + "correct initial value in table"); + gWindow.addCookie("c1", '{"foo": 4,"bar":6}', "/browser"); + yield gUI.once("sidebar-updated"); + + yield findVariableViewProperties(finalValue[0], false); + yield findVariableViewProperties(finalValue[1], true); + ok($("#value [data-id='c1'].table-widget-cell"), + "cell is present after update"); + is($("#value [data-id='c1'].table-widget-cell").value, '{"foo": 4,"bar":6}', + "correct final value in table"); + + // Add a new entry + is($$("#value .table-widget-cell").length, 2, + "Correct number of rows before update 0"); + + gWindow.addCookie("c3", "booyeah"); + + // Wait once for update and another time for value fetching + yield gUI.once("store-objects-updated"); + yield gUI.once("store-objects-updated"); + + is($$("#value .table-widget-cell").length, 3, + "Correct number of rows after update 1"); + + // Add another + gWindow.addCookie("c4", "booyeah"); + + // Wait once for update and another time for value fetching + yield gUI.once("store-objects-updated"); + yield gUI.once("store-objects-updated"); + + is($$("#value .table-widget-cell").length, 4, + "Correct number of rows after update 2"); + + // Removing cookies + gWindow.removeCookie("c1", "/browser"); + + yield gUI.once("sidebar-updated"); + + is($$("#value .table-widget-cell").length, 3, + "Correct number of rows after delete update 3"); + + ok(!$("#c1"), "Correct row got deleted"); + + ok(!gUI.sidebar.hidden, "Sidebar still visible for next row"); + + // Check if next element's value is visible in sidebar + yield findVariableViewProperties([{name: "c2", value: "foobar"}]); + + // Keep deleting till no rows + + gWindow.removeCookie("c3"); + + yield gUI.once("store-objects-updated"); + + is($$("#value .table-widget-cell").length, 2, + "Correct number of rows after delete update 4"); + + // Check if next element's value is visible in sidebar + yield findVariableViewProperties([{name: "c2", value: "foobar"}]); + + gWindow.removeCookie("c2", "/browser"); + + yield gUI.once("sidebar-updated"); + + yield findVariableViewProperties([{name: "c4", value: "booyeah"}]); + + is($$("#value .table-widget-cell").length, 1, + "Correct number of rows after delete update 5"); + + gWindow.removeCookie("c4"); + + yield gUI.once("store-objects-updated"); + + is($$("#value .table-widget-cell").length, 0, + "Correct number of rows after delete update 6"); + ok(gUI.sidebar.hidden, "Sidebar is hidden when no rows"); + + // Testing in local storage + yield selectTreeItem(["localStorage", "http://test1.example.org"]); + + is($$("#value .table-widget-cell").length, 7, + "Correct number of rows after delete update 7"); + + ok($(".table-widget-cell[data-id='ls4']"), "ls4 exists before deleting"); + + gWindow.localStorage.removeItem("ls4"); + + yield gUI.once("store-objects-updated"); + + is($$("#value .table-widget-cell").length, 6, + "Correct number of rows after delete update 8"); + ok(!$(".table-widget-cell[data-id='ls4']"), + "ls4 does not exists after deleting"); + + gWindow.localStorage.setItem("ls4", "again"); + + yield gUI.once("store-objects-updated"); + yield gUI.once("store-objects-updated"); + + is($$("#value .table-widget-cell").length, 7, + "Correct number of rows after delete update 9"); + ok($(".table-widget-cell[data-id='ls4']"), + "ls4 came back after adding it again"); + + // Updating a row + gWindow.localStorage.setItem("ls2", "ls2-changed"); + + yield gUI.once("store-objects-updated"); + yield gUI.once("store-objects-updated"); + + is($("#value [data-id='ls2']").value, "ls2-changed", + "Value got updated for local storage"); + + // Testing in session storage + yield selectTreeItem(["sessionStorage", "http://test1.example.org"]); + + is($$("#value .table-widget-cell").length, 3, + "Correct number of rows for session storage"); + + gWindow.sessionStorage.setItem("ss4", "new-item"); + + yield gUI.once("store-objects-updated"); + yield gUI.once("store-objects-updated"); + + is($$("#value .table-widget-cell").length, 4, + "Correct number of rows after session storage update"); + + // deleting item + + gWindow.sessionStorage.removeItem("ss3"); + + yield gUI.once("store-objects-updated"); + + gWindow.sessionStorage.removeItem("ss1"); + + yield gUI.once("store-objects-updated"); + + is($$("#value .table-widget-cell").length, 2, + "Correct number of rows after removing items from session storage"); + + yield selectTableItem("ss2"); + + ok(!gUI.sidebar.hidden, "sidebar is visible"); + + // Checking for correct value in sidebar before update + yield findVariableViewProperties([{name: "ss2", value: "foobar"}]); + + gWindow.sessionStorage.setItem("ss2", "changed=ss2"); + + yield gUI.once("sidebar-updated"); + + is($("#value [data-id='ss2']").value, "changed=ss2", + "Value got updated for session storage in the table"); + + yield findVariableViewProperties([{name: "ss2", value: "changed=ss2"}]); + + // Clearing items. Bug 1233497 makes it so that we can no longer yield + // CPOWs from Tasks. We work around this by calling clear via a ContentTask + // instead. + yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () { + return Task.spawn(content.wrappedJSObject.clear); + }); + + yield gUI.once("store-objects-cleared"); + + is($$("#value .table-widget-cell").length, 0, + "Table should be cleared"); + + yield finishTests(); +}); diff --git a/devtools/client/storage/test/browser_storage_empty_objectstores.js b/devtools/client/storage/test/browser_storage_empty_objectstores.js new file mode 100644 index 000000000..1749c91b8 --- /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(); +}); diff --git a/devtools/client/storage/test/browser_storage_indexeddb_delete.js b/devtools/client/storage/test/browser_storage_indexeddb_delete.js new file mode 100644 index 000000000..18a0daf69 --- /dev/null +++ b/devtools/client/storage/test/browser_storage_indexeddb_delete.js @@ -0,0 +1,47 @@ +/* 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/. */ + +/* import-globals-from ../../framework/test/shared-head.js */ + +"use strict"; + +// Test deleting indexedDB database from the tree using context menu + +add_task(function* () { + yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-empty-objectstores.html"); + + let contextMenu = gPanelWindow.document.getElementById("storage-tree-popup"); + let menuDeleteDb = contextMenu.querySelector("#storage-tree-popup-delete"); + + info("test state before delete"); + yield checkState([ + [["indexedDB", "http://test1.example.org"], ["idb1", "idb2"]], + ]); + + info("do the delete"); + const deletedDb = ["indexedDB", "http://test1.example.org", "idb1"]; + + yield selectTreeItem(deletedDb); + + // Wait once for update and another time for value fetching + let eventWait = gUI.once("store-objects-updated").then( + () => gUI.once("store-objects-updated")); + + let selector = `[data-id='${JSON.stringify(deletedDb)}'] > .tree-widget-item`; + let target = gPanelWindow.document.querySelector(selector); + ok(target, `tree item found in ${deletedDb.join(" > ")}`); + yield waitForContextMenu(contextMenu, target, () => { + info(`Opened tree context menu in ${deletedDb.join(" > ")}`); + menuDeleteDb.click(); + }); + + yield eventWait; + + info("test state after delete"); + yield checkState([ + [["indexedDB", "http://test1.example.org"], ["idb2"]], + ]); + + yield finishTests(); +}); diff --git a/devtools/client/storage/test/browser_storage_indexeddb_delete_blocked.js b/devtools/client/storage/test/browser_storage_indexeddb_delete_blocked.js new file mode 100644 index 000000000..6e89c4f28 --- /dev/null +++ b/devtools/client/storage/test/browser_storage_indexeddb_delete_blocked.js @@ -0,0 +1,58 @@ +/* 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/. */ + +/* import-globals-from ../../framework/test/shared-head.js */ + +"use strict"; + +// Test what happens when deleting indexedDB database is blocked + +add_task(function* () { + yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-idb-delete-blocked.html"); + + info("test state before delete"); + yield checkState([ + [["indexedDB", "http://test1.example.org"], ["idb"]] + ]); + + info("do the delete"); + yield selectTreeItem(["indexedDB", "http://test1.example.org"]); + let actor = gUI.getCurrentActor(); + let result = yield actor.removeDatabase("http://test1.example.org", "idb"); + + ok(result.blocked, "removeDatabase attempt is blocked"); + + info("test state after blocked delete"); + yield checkState([ + [["indexedDB", "http://test1.example.org"], ["idb"]] + ]); + + let eventWait = gUI.once("store-objects-updated"); + + info("telling content to close the db"); + yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () { + let win = content.wrappedJSObject; + yield win.closeDb(); + }); + + info("waiting for store update events"); + yield eventWait; + + info("test state after real delete"); + yield checkState([ + [["indexedDB", "http://test1.example.org"], []] + ]); + + info("try to delete database from nonexistent host"); + let errorThrown = false; + try { + result = yield actor.removeDatabase("http://test2.example.org", "idb"); + } catch (ex) { + errorThrown = true; + } + + ok(errorThrown, "error was reported when trying to delete"); + + yield finishTests(); +}); diff --git a/devtools/client/storage/test/browser_storage_localstorage_edit.js b/devtools/client/storage/test/browser_storage_localstorage_edit.js new file mode 100644 index 000000000..86409e0ac --- /dev/null +++ b/devtools/client/storage/test/browser_storage_localstorage_edit.js @@ -0,0 +1,24 @@ +/* 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 check the editing of localStorage. + +"use strict"; + +add_task(function* () { + yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-localstorage.html"); + + yield selectTreeItem(["localStorage", "http://test1.example.org"]); + + yield editCell("TestLS1", "name", "newTestLS1"); + yield editCell("newTestLS1", "value", "newValueLS1"); + + yield editCell("TestLS3", "name", "newTestLS3"); + yield editCell("newTestLS3", "value", "newValueLS3"); + + yield editCell("TestLS5", "name", "newTestLS5"); + yield editCell("newTestLS5", "value", "newValueLS5"); + + yield finishTests(); +}); diff --git a/devtools/client/storage/test/browser_storage_localstorage_error.js b/devtools/client/storage/test/browser_storage_localstorage_error.js new file mode 100644 index 000000000..923ca6ca9 --- /dev/null +++ b/devtools/client/storage/test/browser_storage_localstorage_error.js @@ -0,0 +1,24 @@ +/* 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/. */ + +"use strict"; + +// Test that for pages where local/sessionStorage is not available (like about:home), +// the host still appears in the storage tree and no unhandled exception is thrown. + +add_task(function* () { + yield openTabAndSetupStorage("about:home"); + + let itemsToOpen = [ + ["localStorage", "about:home"], + ["sessionStorage", "about:home"] + ]; + + for (let item of itemsToOpen) { + yield selectTreeItem(item); + ok(gUI.tree.isSelected(item), `Item ${item.join(" > ")} is present in the tree`); + } + + yield finishTests(); +}); diff --git a/devtools/client/storage/test/browser_storage_overflow.js b/devtools/client/storage/test/browser_storage_overflow.js new file mode 100644 index 000000000..88181ca05 --- /dev/null +++ b/devtools/client/storage/test/browser_storage_overflow.js @@ -0,0 +1,41 @@ +// Test endless scrolling when a lot of items are present in the storage +// inspector table. +"use strict"; + +add_task(function* () { + yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-overflow.html"); + + let $ = id => gPanelWindow.document.querySelector(id); + let $$ = sel => gPanelWindow.document.querySelectorAll(sel); + + gUI.tree.expandAll(); + yield selectTreeItem(["localStorage", "http://test1.example.org"]); + + let table = $("#storage-table .table-widget-body"); + let cellHeight = $(".table-widget-cell").getBoundingClientRect().height; + + is($$("#value .table-widget-cell").length, 50, + "Table should initially display 50 items"); + + let onStoresUpdate = gUI.once("store-objects-updated"); + table.scrollTop += cellHeight * 50; + yield onStoresUpdate; + + is($$("#value .table-widget-cell").length, 100, + "Table should display 100 items after scrolling"); + + onStoresUpdate = gUI.once("store-objects-updated"); + table.scrollTop += cellHeight * 50; + yield onStoresUpdate; + + is($$("#value .table-widget-cell").length, 150, + "Table should display 150 items after scrolling"); + + onStoresUpdate = gUI.once("store-objects-updated"); + table.scrollTop += cellHeight * 50; + yield onStoresUpdate; + + is($$("#value .table-widget-cell").length, 160, + "Table should display all 160 items after scrolling"); + yield finishTests(); +}); diff --git a/devtools/client/storage/test/browser_storage_search.js b/devtools/client/storage/test/browser_storage_search.js new file mode 100644 index 000000000..bbe0947b9 --- /dev/null +++ b/devtools/client/storage/test/browser_storage_search.js @@ -0,0 +1,87 @@ +// Tests the filter search box in the storage inspector +"use strict"; + +add_task(function* () { + yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-search.html"); + + let $$ = sel => gPanelWindow.document.querySelectorAll(sel); + gUI.tree.expandAll(); + yield selectTreeItem(["localStorage", "http://test1.example.org"]); + + // Results: 0=hidden, 1=visible + let testcases = [ + // Test that search isn't case-sensitive + { + value: "FoO", + results: [0, 0, 1, 1, 0, 1, 0] + }, + { + value: "OR", + results: [0, 1, 0, 0, 0, 1, 0] + }, + { + value: "aNImAl", + results: [0, 1, 0, 0, 0, 0, 0] + }, + // Test numbers + { + value: "01", + results: [1, 0, 0, 0, 0, 0, 1] + }, + { + value: "2016", + results: [0, 0, 0, 0, 0, 0, 1] + }, + { + value: "56789", + results: [1, 0, 0, 0, 0, 0, 0] + }, + // Test filtering by value + { + value: "horse", + results: [0, 1, 0, 0, 0, 0, 0] + }, + { + value: "$$$", + results: [0, 0, 0, 0, 1, 0, 0] + }, + { + value: "bar", + results: [0, 0, 1, 1, 0, 0, 0] + }, + // Test input with whitespace + { + value: "energy b", + results: [0, 0, 0, 1, 0, 0, 0] + }, + // Test no input at all + { + value: "", + results: [1, 1, 1, 1, 1, 1, 1] + }, + // Test input that matches nothing + { + value: "input that matches nothing", + results: [0, 0, 0, 0, 0, 0, 0] + } + ]; + + let names = $$("#name .table-widget-cell"); + let rows = $$("#value .table-widget-cell"); + for (let testcase of testcases) { + info(`Testing input: ${testcase.value}`); + + gUI.searchBox.value = testcase.value; + gUI.filterItems(); + + for (let i = 0; i < rows.length; i++) { + info(`Testing row ${i}`); + info(`key: ${names[i].value}, value: ${rows[i].value}`); + let state = testcase.results[i] ? "visible" : "hidden"; + is(rows[i].hasAttribute("hidden"), !testcase.results[i], + `Row ${i} should be ${state}`); + } + } + + yield finishTests(); +}); diff --git a/devtools/client/storage/test/browser_storage_search_keyboard_trap.js b/devtools/client/storage/test/browser_storage_search_keyboard_trap.js new file mode 100644 index 000000000..71dfd32c0 --- /dev/null +++ b/devtools/client/storage/test/browser_storage_search_keyboard_trap.js @@ -0,0 +1,15 @@ +// Test ability to focus search field by using keyboard +"use strict"; + +add_task(function* () { + yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-search.html"); + + gUI.tree.expandAll(); + yield selectTreeItem(["localStorage", "http://test1.example.org"]); + + yield focusSearchBoxUsingShortcut(gPanelWindow); + ok(containsFocus(gPanelWindow.document, gUI.searchBox), + "Focus is in a searchbox"); + + yield finishTests(); +}); diff --git a/devtools/client/storage/test/browser_storage_sessionstorage_edit.js b/devtools/client/storage/test/browser_storage_sessionstorage_edit.js new file mode 100644 index 000000000..9629eec0b --- /dev/null +++ b/devtools/client/storage/test/browser_storage_sessionstorage_edit.js @@ -0,0 +1,24 @@ +/* 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 check the editing of localStorage. + +"use strict"; + +add_task(function* () { + yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-sessionstorage.html"); + + yield selectTreeItem(["sessionStorage", "http://test1.example.org"]); + + yield editCell("TestSS1", "name", "newTestSS1"); + yield editCell("newTestSS1", "value", "newValueSS1"); + + yield editCell("TestSS3", "name", "newTestSS3"); + yield editCell("newTestSS3", "value", "newValueSS3"); + + yield editCell("TestSS5", "name", "newTestSS5"); + yield editCell("newTestSS5", "value", "newValueSS5"); + + yield finishTests(); +}); diff --git a/devtools/client/storage/test/browser_storage_sidebar.js b/devtools/client/storage/test/browser_storage_sidebar.js new file mode 100644 index 000000000..9b60026a0 --- /dev/null +++ b/devtools/client/storage/test/browser_storage_sidebar.js @@ -0,0 +1,125 @@ +/* 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/. */ + +// Test to verify that the sidebar opens, closes and updates +// This test is not testing the values in the sidebar, being tested in _values + +// Format: [ +// or or +// null to press Escape, +// , +// +// ] + +"use strict"; + +const testCases = [ + { + location: ["cookies", "sectest1.example.org"], + sidebarHidden: true + }, + { + location: "cs2", + sidebarHidden: false + }, + { + sendEscape: true + }, + { + location: "cs2", + sidebarHidden: false + }, + { + location: "uc1", + sidebarHidden: false + }, + { + location: "uc1", + sidebarHidden: false + }, + + { + location: ["localStorage", "http://sectest1.example.org"], + sidebarHidden: true + }, + { + location: "iframe-u-ls1", + sidebarHidden: false + }, + { + location: "iframe-u-ls1", + sidebarHidden: false + }, + { + sendEscape: true + }, + + { + location: ["sessionStorage", "http://test1.example.org"], + sidebarHidden: true + }, + { + location: "ss1", + sidebarHidden: false + }, + { + sendEscape: true + }, + + { + location: ["indexedDB", "http://test1.example.org"], + sidebarHidden: true + }, + { + location: "idb2", + sidebarHidden: false + }, + + { + location: ["indexedDB", "http://test1.example.org", "idb2", "obj3"], + sidebarHidden: true + }, + + { + location: ["indexedDB", "https://sectest1.example.org", "idb-s2"], + sidebarHidden: true + }, + { + location: "obj-s2", + sidebarHidden: false + }, + { + sendEscape: true + }, { + location: "obj-s2", + sidebarHidden: false + } +]; + +add_task(function* () { + yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-listings.html"); + + for (let test of testCases) { + let { location, sidebarHidden, sendEscape } = test; + + info("running " + JSON.stringify(test)); + + if (Array.isArray(location)) { + yield selectTreeItem(location); + } else if (location) { + yield selectTableItem(location); + } + + if (sendEscape) { + EventUtils.sendKey("ESCAPE", gPanelWindow); + } else { + is(gUI.sidebar.hidden, sidebarHidden, + "correct visibility state of sidebar."); + } + + info("-".repeat(80)); + } + + yield finishTests(); +}); diff --git a/devtools/client/storage/test/browser_storage_sidebar_update.js b/devtools/client/storage/test/browser_storage_sidebar_update.js new file mode 100644 index 000000000..419d63020 --- /dev/null +++ b/devtools/client/storage/test/browser_storage_sidebar_update.js @@ -0,0 +1,41 @@ +/* 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/. */ + +// Test to verify that the sidebar is not broken when several updates +// come in quick succession. See bug 1260380 - it could happen that the +// "Parsed Value" section gets duplicated. + +"use strict"; + +add_task(function* () { + const ITEM_NAME = "ls1"; + const UPDATE_COUNT = 3; + + yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-complex-values.html"); + + let updated = gUI.once("sidebar-updated"); + yield selectTreeItem(["localStorage", "http://test1.example.org"]); + yield selectTableItem(ITEM_NAME); + yield updated; + + is(gUI.sidebar.hidden, false, "sidebar is visible"); + + // do several updates in a row and wait for them to finish + let updates = []; + for (let i = 0; i < UPDATE_COUNT; i++) { + info(`Performing update #${i}`); + updates.push(gUI.once("sidebar-updated")); + gUI.displayObjectSidebar(); + } + yield promise.all(updates); + + info("Updates performed, going to verify result"); + let parsedScope = gUI.view.getScopeAtIndex(1); + let elements = parsedScope.target.querySelectorAll( + `.name[value="${ITEM_NAME}"]`); + is(elements.length, 1, + `There is only one displayed variable named '${ITEM_NAME}'`); + + yield finishTests(); +}); diff --git a/devtools/client/storage/test/browser_storage_values.js b/devtools/client/storage/test/browser_storage_values.js new file mode 100644 index 000000000..920ce350e --- /dev/null +++ b/devtools/client/storage/test/browser_storage_values.js @@ -0,0 +1,165 @@ +/* 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/. */ + +// Test to verify that the values shown in sidebar are correct + +// Format: [ +// or or +// null do click nothing, +// null to skip checking value in variables view or a key value pair object +// which will be asserted to exist in the storage sidebar, +// true if the check is to be made in the parsed value section +// ] + +"use strict"; + +const LONG_WORD = "a".repeat(1000); + +const testCases = [ + ["cs2", [ + {name: "cs2", value: "sessionCookie"}, + {name: "cs2.Path", value: "/"}, + {name: "cs2.HostOnly", value: "false"}, + {name: "cs2.HttpOnly", value: "false"}, + {name: "cs2.Domain", value: ".example.org"}, + {name: "cs2.Expires", value: "Session"}, + {name: "cs2.Secure", value: "false"}, + ]], + ["c1", [ + {name: "c1", value: JSON.stringify(["foo", "Bar", {foo: "Bar"}])}, + {name: "c1.Path", value: "/browser"}, + {name: "c1.HostOnly", value: "true"}, + {name: "c1.HttpOnly", value: "false"}, + {name: "c1.Domain", value: "test1.example.org"}, + {name: "c1.Expires", value: new Date(2000000000000).toUTCString()}, + {name: "c1.Secure", value: "false"}, + ]], + [null, [ + {name: "c1", value: "Array"}, + {name: "c1.0", value: "foo"}, + {name: "c1.1", value: "Bar"}, + {name: "c1.2", value: "Object"}, + {name: "c1.2.foo", value: "Bar"}, + ], true], + ["c_encoded", [ + {name: "c_encoded", value: encodeURIComponent(JSON.stringify({foo: {foo1: "bar"}}))} + ]], + [null, [ + {name: "c_encoded", value: "Object"}, + {name: "c_encoded.foo", value: "Object"}, + {name: "c_encoded.foo.foo1", value: "bar"} + ], true], + [["localStorage", "http://test1.example.org"]], + ["ls2", [ + {name: "ls2", value: "foobar-2"} + ]], + ["ls1", [ + {name: "ls1", value: JSON.stringify({ + es6: "for", the: "win", baz: [0, 2, 3, { + deep: "down", + nobody: "cares" + }]})} + ]], + [null, [ + {name: "ls1", value: "Object"}, + {name: "ls1.es6", value: "for"}, + {name: "ls1.the", value: "win"}, + {name: "ls1.baz", value: "Array"}, + {name: "ls1.baz.0", value: "0"}, + {name: "ls1.baz.1", value: "2"}, + {name: "ls1.baz.2", value: "3"}, + {name: "ls1.baz.3", value: "Object"}, + {name: "ls1.baz.3.deep", value: "down"}, + {name: "ls1.baz.3.nobody", value: "cares"}, + ], true], + ["ls3", [ + {name: "ls3", "value": "http://foobar.com/baz.php"} + ]], + [null, [ + {name: "ls3", "value": "http://foobar.com/baz.php", dontMatch: true} + ], true], + [["sessionStorage", "http://test1.example.org"]], + ["ss1", [ + {name: "ss1", value: "This#is#an#array"} + ]], + [null, [ + {name: "ss1", value: "Array"}, + {name: "ss1.0", value: "This"}, + {name: "ss1.1", value: "is"}, + {name: "ss1.2", value: "an"}, + {name: "ss1.3", value: "array"}, + ], true], + ["ss2", [ + {name: "ss2", value: "Array"}, + {name: "ss2.0", value: "This"}, + {name: "ss2.1", value: "is"}, + {name: "ss2.2", value: "another"}, + {name: "ss2.3", value: "array"}, + ], true], + ["ss3", [ + {name: "ss3", value: "Object"}, + {name: "ss3.this", value: "is"}, + {name: "ss3.an", value: "object"}, + {name: "ss3.foo", value: "bar"}, + ], true], + ["ss4", [ + {name: "ss4", value: "Array"}, + {name: "ss4.0", value: ""}, + {name: "ss4.1", value: "array"}, + {name: "ss4.2", value: ""}, + {name: "ss4.3", value: "with"}, + {name: "ss4.4", value: "empty"}, + {name: "ss4.5", value: "items"}, + ], true], + ["ss5", [ + {name: "ss5", value: "Array"}, + {name: "ss5.0", value: LONG_WORD}, + {name: "ss5.1", value: LONG_WORD}, + {name: "ss5.2", value: LONG_WORD}, + {name: "ss5.3", value: `${LONG_WORD}&${LONG_WORD}`}, + {name: "ss5.4", value: `${LONG_WORD}&${LONG_WORD}`}, + ], true], + [["indexedDB", "http://test1.example.org", "idb1", "obj1"]], + [1, [ + {name: 1, value: JSON.stringify({id: 1, name: "foo", email: "foo@bar.com"})} + ]], + [null, [ + {name: "1.id", value: "1"}, + {name: "1.name", value: "foo"}, + {name: "1.email", value: "foo@bar.com"}, + ], true], + [["indexedDB", "http://test1.example.org", "idb1", "obj2"]], + [1, [ + {name: 1, value: JSON.stringify({ + id2: 1, name: "foo", email: "foo@bar.com", extra: "baz" + })} + ]], + [null, [ + {name: "1.id2", value: "1"}, + {name: "1.name", value: "foo"}, + {name: "1.email", value: "foo@bar.com"}, + {name: "1.extra", value: "baz"}, + ], true] +]; + +add_task(function* () { + yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-complex-values.html"); + + gUI.tree.expandAll(); + + for (let item of testCases) { + info("clicking for item " + item); + + if (Array.isArray(item[0])) { + yield selectTreeItem(item[0]); + continue; + } else if (item[0]) { + yield selectTableItem(item[0]); + } + + yield findVariableViewProperties(item[1], item[2]); + } + + yield finishTests(); +}); diff --git a/devtools/client/storage/test/head.js b/devtools/client/storage/test/head.js new file mode 100644 index 000000000..9662393cf --- /dev/null +++ b/devtools/client/storage/test/head.js @@ -0,0 +1,840 @@ +/* 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/. */ + +"use strict"; + +/* eslint no-unused-vars: [2, {"vars": "local"}] */ +/* import-globals-from ../../framework/test/shared-head.js */ + +// shared-head.js handles imports, constants, and utility functions +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js", + this); + +const {TableWidget} = require("devtools/client/shared/widgets/TableWidget"); +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; + +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); +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); +}); + +/** + * This generator function opens the given url in a new tab, then sets up the + * page by waiting for all cookies, indexedDB items etc. to be created; Then + * opens the storage inspector and waits for the storage tree and table to be + * populated. + * + * @param url {String} The url to be opened in the new tab + * + * @return {Promise} A promise that resolves after storage inspector is ready + */ +function* openTabAndSetupStorage(url) { + let tab = yield addTab(url); + let content = tab.linkedBrowser.contentWindow; + + gWindow = content.wrappedJSObject; + + // Setup the async storages in main window and for all its iframes + yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () { + /** + * Get all windows including frames recursively. + * + * @param {Window} [baseWindow] + * The base window at which to start looking for child windows + * (optional). + * @return {Set} + * A set of windows. + */ + function getAllWindows(baseWindow) { + let windows = new Set(); + + let _getAllWindows = function (win) { + windows.add(win.wrappedJSObject); + + for (let i = 0; i < win.length; i++) { + _getAllWindows(win[i]); + } + }; + _getAllWindows(baseWindow); + + return windows; + } + + let windows = getAllWindows(content); + for (let win of windows) { + let readyState = win.document.readyState; + info(`Found a window: ${readyState}`); + if (readyState != "complete") { + yield new Promise(resolve => { + let onLoad = () => { + win.removeEventListener("load", onLoad); + resolve(); + }; + win.addEventListener("load", onLoad); + }); + } + if (win.setup) { + yield win.setup(); + } + } + }); + + // open storage inspector + return yield openStoragePanel(); +} + +/** + * Open the toolbox, with the storage tool visible. + * + * @param cb {Function} Optional callback, if you don't want to use the returned + * promise + * + * @return {Promise} a promise that resolves when the storage inspector is ready + */ +var openStoragePanel = Task.async(function* (cb) { + info("Opening the storage inspector"); + let target = TargetFactory.forTab(gBrowser.selectedTab); + + let storage, toolbox; + + // Checking if the toolbox and the storage are already loaded + // The storage-updated event should only be waited for if the storage + // isn't loaded yet + toolbox = gDevTools.getToolbox(target); + if (toolbox) { + storage = toolbox.getPanel("storage"); + if (storage) { + gPanelWindow = storage.panelWindow; + gUI = storage.UI; + gToolbox = toolbox; + info("Toolbox and storage already open"); + if (cb) { + return cb(storage, toolbox); + } + + return { + toolbox: toolbox, + storage: storage + }; + } + } + + info("Opening the toolbox"); + toolbox = yield gDevTools.showToolbox(target, "storage"); + storage = toolbox.getPanel("storage"); + gPanelWindow = storage.panelWindow; + gUI = storage.UI; + gToolbox = toolbox; + + // The table animation flash causes some timeouts on Linux debug tests, + // so we disable it + gUI.animationsEnabled = false; + + info("Waiting for the stores to update"); + yield gUI.once("store-objects-updated"); + + yield waitForToolboxFrameFocus(toolbox); + + if (cb) { + return cb(storage, toolbox); + } + + return { + toolbox: toolbox, + storage: storage + }; +}); + +/** + * Wait for the toolbox frame to receive focus after it loads + * + * @param toolbox {Toolbox} + * + * @return a promise that resolves when focus has been received + */ +function waitForToolboxFrameFocus(toolbox) { + info("Making sure that the toolbox's frame is focused"); + let def = promise.defer(); + waitForFocus(def.resolve, toolbox.win); + return def.promise; +} + +/** + * Forces GC, CC and Shrinking GC to get rid of disconnected docshells and + * windows. + */ +function forceCollections() { + Cu.forceGC(); + Cu.forceCC(); + Cu.forceShrinkingGC(); +} + +/** + * Cleans up and finishes the test + */ +function* finishTests() { + // Bug 1233497 makes it so that we can no longer yield CPOWs from Tasks. + // We work around this by calling clear() via a ContentTask instead. + yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () { + /** + * Get all windows including frames recursively. + * + * @param {Window} [baseWindow] + * The base window at which to start looking for child windows + * (optional). + * @return {Set} + * A set of windows. + */ + function getAllWindows(baseWindow) { + let windows = new Set(); + + let _getAllWindows = function (win) { + windows.add(win.wrappedJSObject); + + for (let i = 0; i < win.length; i++) { + _getAllWindows(win[i]); + } + }; + _getAllWindows(baseWindow); + + return windows; + } + + let windows = getAllWindows(content); + for (let win of windows) { + // Some windows (e.g., about: URLs) don't have storage available + try { + win.localStorage.clear(); + win.sessionStorage.clear(); + } catch (ex) { + // ignore + } + + if (win.clear) { + yield win.clear(); + } + } + }); + + Services.cookies.removeAll(); + forceCollections(); + finish(); +} + +// Sends a click event on the passed DOM node in an async manner +function* click(node) { + let def = promise.defer(); + + node.scrollIntoView(); + + // We need setTimeout here to allow any scrolling to complete before clicking + // the node. + setTimeout(() => { + node.click(); + def.resolve(); + }, 200); + + return def; +} + +/** + * Recursively expand the variables view up to a given property. + * + * @param options + * Options for view expansion: + * - rootVariable: start from the given scope/variable/property. + * - expandTo: string made up of property names you want to expand. + * For example: "body.firstChild.nextSibling" given |rootVariable: + * document|. + * @return object + * A promise that is resolved only when the last property in |expandTo| + * is found, and rejected otherwise. Resolution reason is always the + * last property - |nextSibling| in the example above. Rejection is + * always the last property that was found. + */ +function variablesViewExpandTo(options) { + let root = options.rootVariable; + let expandTo = options.expandTo.split("."); + let lastDeferred = promise.defer(); + + function getNext(prop) { + let name = expandTo.shift(); + let newProp = prop.get(name); + + if (expandTo.length > 0) { + ok(newProp, "found property " + name); + if (newProp && newProp.expand) { + newProp.expand(); + getNext(newProp); + } else { + lastDeferred.reject(prop); + } + } else if (newProp) { + lastDeferred.resolve(newProp); + } else { + lastDeferred.reject(prop); + } + } + + if (root && root.expand) { + root.expand(); + getNext(root); + } else { + lastDeferred.resolve(root); + } + + return lastDeferred.promise; +} + +/** + * Find variables or properties in a VariablesView instance. + * + * @param array ruleArray + * The array of rules you want to match. Each rule is an object with: + * - name (string|regexp): property name to match. + * - value (string|regexp): property value to match. + * - dontMatch (boolean): make sure the rule doesn't match any property. + * @param boolean parsed + * true if we want to test the rules in the parse value section of the + * storage sidebar + * @return object + * A promise object that is resolved when all the rules complete + * matching. The resolved callback is given an array of all the rules + * you wanted to check. Each rule has a new property: |matchedProp| + * which holds a reference to the Property object instance from the + * VariablesView. If the rule did not match, then |matchedProp| is + * undefined. + */ +function findVariableViewProperties(ruleArray, parsed) { + // Initialize the search. + function init() { + // If parsed is true, we are checking rules in the parsed value section of + // the storage sidebar. That scope uses a blank variable as a placeholder + // Thus, adding a blank parent to each name + if (parsed) { + ruleArray = ruleArray.map(({name, value, dontMatch}) => { + return {name: "." + name, value, dontMatch}; + }); + } + // Separate out the rules that require expanding properties throughout the + // view. + let expandRules = []; + let rules = ruleArray.filter(rule => { + if (typeof rule.name == "string" && rule.name.indexOf(".") > -1) { + expandRules.push(rule); + return false; + } + return true; + }); + + // Search through the view those rules that do not require any properties to + // be expanded. Build the array of matchers, outstanding promises to be + // resolved. + let outstanding = []; + + finder(rules, gUI.view, outstanding); + + // Process the rules that need to expand properties. + let lastStep = processExpandRules.bind(null, expandRules); + + // Return the results - a promise resolved to hold the updated ruleArray. + let returnResults = onAllRulesMatched.bind(null, ruleArray); + + return promise.all(outstanding).then(lastStep).then(returnResults); + } + + function onMatch(prop, rule, matched) { + if (matched && !rule.matchedProp) { + rule.matchedProp = prop; + } + } + + function finder(rules, view, promises) { + for (let scope of view) { + for (let [, prop] of scope) { + for (let rule of rules) { + let matcher = matchVariablesViewProperty(prop, rule); + promises.push(matcher.then(onMatch.bind(null, prop, rule))); + } + } + } + } + + function processExpandRules(rules) { + let rule = rules.shift(); + if (!rule) { + return promise.resolve(null); + } + + let deferred = promise.defer(); + let expandOptions = { + rootVariable: gUI.view.getScopeAtIndex(parsed ? 1 : 0), + expandTo: rule.name + }; + + variablesViewExpandTo(expandOptions).then(function onSuccess(prop) { + let name = rule.name; + let lastName = name.split(".").pop(); + rule.name = lastName; + + let matched = matchVariablesViewProperty(prop, rule); + return matched.then(onMatch.bind(null, prop, rule)).then(function () { + rule.name = name; + }); + }, function onFailure() { + return promise.resolve(null); + }).then(processExpandRules.bind(null, rules)).then(function () { + deferred.resolve(null); + }); + + return deferred.promise; + } + + function onAllRulesMatched(rules) { + for (let rule of rules) { + let matched = rule.matchedProp; + if (matched && !rule.dontMatch) { + ok(true, "rule " + rule.name + " matched for property " + matched.name); + } else if (matched && rule.dontMatch) { + ok(false, "rule " + rule.name + " should not match property " + + matched.name); + } else { + ok(rule.dontMatch, "rule " + rule.name + " did not match any property"); + } + } + return rules; + } + + return init(); +} + +/** + * Check if a given Property object from the variables view matches the given + * rule. + * + * @param object prop + * The variable's view Property instance. + * @param object rule + * Rules for matching the property. See findVariableViewProperties() for + * details. + * @return object + * A promise that is resolved when all the checks complete. Resolution + * result is a boolean that tells your promise callback the match + * result: true or false. + */ +function matchVariablesViewProperty(prop, rule) { + function resolve(result) { + return promise.resolve(result); + } + + if (!prop) { + return resolve(false); + } + + if (rule.name) { + let match = rule.name instanceof RegExp ? + rule.name.test(prop.name) : + prop.name == rule.name; + if (!match) { + return resolve(false); + } + } + + if ("value" in rule) { + let displayValue = prop.displayValue; + if (prop.displayValueClassName == "token-string") { + displayValue = displayValue.substring(1, displayValue.length - 1); + } + + let match = rule.value instanceof RegExp ? + rule.value.test(displayValue) : + displayValue == rule.value; + if (!match) { + info("rule " + rule.name + " did not match value, expected '" + + rule.value + "', found '" + displayValue + "'"); + return resolve(false); + } + } + + return resolve(true); +} + +/** + * Click selects a row in the table. + * + * @param {[String]} ids + * The array id of the item in the tree + */ +function* selectTreeItem(ids) { + /* If this item is already selected, return */ + if (gUI.tree.isSelected(ids)) { + return; + } + + let updated = gUI.once("store-objects-updated"); + gUI.tree.selectedItem = ids; + yield updated; +} + +/** + * Click selects a row in the table. + * + * @param {String} id + * The id of the row in the table widget + */ +function* selectTableItem(id) { + let selector = ".table-widget-cell[data-id='" + id + "']"; + let target = gPanelWindow.document.querySelector(selector); + ok(target, "table item found with ids " + id); + + yield click(target); + yield gUI.once("sidebar-updated"); +} + +/** + * Wait for eventName on target. + * @param {Object} target An observable object that either supports on/off or + * addEventListener/removeEventListener + * @param {String} eventName + * @param {Boolean} [useCapture] for addEventListener/removeEventListener + * @return A promise that resolves when the event has been handled + */ +function once(target, eventName, useCapture = false) { + info("Waiting for event: '" + eventName + "' on " + target + "."); + + let deferred = promise.defer(); + + for (let [add, remove] of [ + ["addEventListener", "removeEventListener"], + ["addListener", "removeListener"], + ["on", "off"] + ]) { + if ((add in target) && (remove in target)) { + target[add](eventName, function onEvent(...aArgs) { + target[remove](eventName, onEvent, useCapture); + deferred.resolve.apply(deferred, aArgs); + }, useCapture); + break; + } + } + + return deferred.promise; +} + +/** + * Get values for a row. + * + * @param {String} id + * The uniqueId of the given row. + * @param {Boolean} includeHidden + * Include hidden columns. + * + * @return {Object} + * An object of column names to values for the given row. + */ +function getRowValues(id, includeHidden = false) { + let cells = getRowCells(id, includeHidden); + let values = {}; + + for (let name in cells) { + let cell = cells[name]; + + values[name] = cell.value; + } + + return values; +} + +/** + * Get cells for a row. + * + * @param {String} id + * The uniqueId of the given row. + * @param {Boolean} includeHidden + * Include hidden columns. + * + * @return {Object} + * An object of column names to cells for the given row. + */ +function getRowCells(id, includeHidden = false) { + let doc = gPanelWindow.document; + let table = gUI.table; + let item = doc.querySelector(".table-widget-column#" + table.uniqueId + + " .table-widget-cell[value='" + id + "']"); + + if (!item) { + ok(false, "Row id '" + id + "' exists"); + } + + let index = table.columns.get(table.uniqueId).visibleCellNodes.indexOf(item); + let cells = {}; + + for (let [name, column] of [...table.columns]) { + if (!includeHidden && column.column.parentNode.hidden) { + continue; + } + cells[name] = column.visibleCellNodes[index]; + } + + return cells; +} + +/** + * Get a cell value. + * + * @param {String} id + * The uniqueId of the row. + * @param {String} column + * The id of the column + * + * @yield {String} + * The cell value. + */ +function getCellValue(id, column) { + let row = getRowValues(id, true); + + return row[column]; +} + +/** + * Edit a cell value. The cell is assumed to be in edit mode, see startCellEdit. + * + * @param {String} id + * The uniqueId of the row. + * @param {String} column + * The id of the column + * @param {String} newValue + * Replacement value. + * @param {Boolean} validate + * Validate result? Default true. + * + * @yield {String} + * The uniqueId of the changed row. + */ +function* editCell(id, column, newValue, validate = true) { + let row = getRowCells(id, true); + let editableFieldsEngine = gUI.table._editableFieldsEngine; + + editableFieldsEngine.edit(row[column]); + + yield typeWithTerminator(newValue, "VK_RETURN", validate); +} + +/** + * Begin edit mode for a cell. + * + * @param {String} id + * The uniqueId of the row. + * @param {String} column + * The id of the column + * @param {Boolean} selectText + * Select text? Default true. + */ +function* startCellEdit(id, column, selectText = true) { + let row = getRowCells(id, true); + let editableFieldsEngine = gUI.table._editableFieldsEngine; + let cell = row[column]; + + info("Selecting row " + id); + gUI.table.selectedRow = id; + + info("Starting cell edit (" + id + ", " + column + ")"); + editableFieldsEngine.edit(cell); + + if (!selectText) { + let textbox = gUI.table._editableFieldsEngine.textbox; + textbox.selectionEnd = textbox.selectionStart; + } +} + +/** + * Check a cell value. + * + * @param {String} id + * The uniqueId of the row. + * @param {String} column + * The id of the column + * @param {String} expected + * Expected value. + */ +function checkCell(id, column, expected) { + is(getCellValue(id, column), expected, + column + " column has the right value for " + id); +} + +/** + * Show or hide a column. + * + * @param {String} id + * The uniqueId of the given column. + * @param {Boolean} state + * true = show, false = hide + */ +function showColumn(id, state) { + let columns = gUI.table.columns; + let column = columns.get(id); + + if (state) { + column.wrapper.removeAttribute("hidden"); + } else { + column.wrapper.setAttribute("hidden", true); + } +} + +/** + * Show or hide all columns. + * + * @param {Boolean} state + * true = show, false = hide + */ +function showAllColumns(state) { + let columns = gUI.table.columns; + + for (let [id] of columns) { + showColumn(id, state); + } +} + +/** + * Type a string in the currently selected editor and then wait for the row to + * be updated. + * + * @param {String} str + * The string to type. + * @param {String} terminator + * The terminating key e.g. VK_RETURN or VK_TAB + * @param {Boolean} validate + * Validate result? Default true. + */ +function* typeWithTerminator(str, terminator, validate = true) { + let editableFieldsEngine = gUI.table._editableFieldsEngine; + let textbox = editableFieldsEngine.textbox; + let colName = textbox.closest(".table-widget-column").id; + + let changeExpected = str !== textbox.value; + + if (!changeExpected) { + return editableFieldsEngine.currentTarget.getAttribute("data-id"); + } + + info("Typing " + str); + EventUtils.sendString(str); + + info("Pressing " + terminator); + EventUtils.synthesizeKey(terminator, {}); + + if (validate) { + info("Validating results... waiting for ROW_EDIT event."); + let uniqueId = yield gUI.table.once(TableWidget.EVENTS.ROW_EDIT); + + checkCell(uniqueId, colName, str); + return uniqueId; + } + + return yield gUI.table.once(TableWidget.EVENTS.ROW_EDIT); +} + +function getCurrentEditorValue() { + let editableFieldsEngine = gUI.table._editableFieldsEngine; + let textbox = editableFieldsEngine.textbox; + + return textbox.value; +} + +/** + * Press a key x times. + * + * @param {String} key + * The key to press e.g. VK_RETURN or VK_TAB + * @param {Number} x + * The number of times to press the key. + * @param {Object} modifiers + * The event modifier e.g. {shiftKey: true} + */ +function PressKeyXTimes(key, x, modifiers = {}) { + for (let i = 0; i < x; i++) { + EventUtils.synthesizeKey(key, modifiers); + } +} + +/** + * Verify the storage inspector state: check that given type/host exists + * in the tree, and that the table contains rows with specified names. + * + * @param {Array} state Array of state specifications. For example, + * [["cookies", "example.com"], ["c1", "c2"]] means to select the + * "example.com" host in cookies and then verify there are "c1" and "c2" + * cookies (and no other ones). + */ +function* checkState(state) { + for (let [store, names] of state) { + let storeName = store.join(" > "); + info(`Selecting tree item ${storeName}`); + yield selectTreeItem(store); + + let items = gUI.table.items; + + is(items.size, names.length, + `There is correct number of rows in ${storeName}`); + for (let name of names) { + ok(items.has(name), + `There is item with name '${name}' in ${storeName}`); + } + } +} + +/** + * Checks if document's active element is within the given element. + * @param {HTMLDocument} doc document with active element in question + * @param {DOMNode} container element tested on focus containment + * @return {Boolean} + */ +function containsFocus(doc, container) { + let elm = doc.activeElement; + while (elm) { + if (elm === container) { + return true; + } + elm = elm.parentNode; + } + return false; +} + +var focusSearchBoxUsingShortcut = Task.async(function* (panelWin, callback) { + info("Focusing search box"); + let searchBox = panelWin.document.getElementById("storage-searchbox"); + let focused = once(searchBox, "focus"); + + panelWin.focus(); + let strings = Services.strings.createBundle( + "chrome://devtools/locale/storage.properties"); + synthesizeKeyShortcut(strings.GetStringFromName("storage.filter.key")); + + yield focused; + + if (callback) { + callback(); + } +}); diff --git a/devtools/client/storage/test/storage-cache-error.html b/devtools/client/storage/test/storage-cache-error.html new file mode 100644 index 000000000..80b14e287 --- /dev/null +++ b/devtools/client/storage/test/storage-cache-error.html @@ -0,0 +1,20 @@ + + + + + Storage inspector test for handling errors in CacheStorage + + + + + diff --git a/devtools/client/storage/test/storage-complex-values.html b/devtools/client/storage/test/storage-complex-values.html new file mode 100644 index 000000000..d96da1932 --- /dev/null +++ b/devtools/client/storage/test/storage-complex-values.html @@ -0,0 +1,123 @@ + + + + + + Storage inspector test for correct values in the sidebar + + + + + diff --git a/devtools/client/storage/test/storage-cookies.html b/devtools/client/storage/test/storage-cookies.html new file mode 100644 index 000000000..97c15abaa --- /dev/null +++ b/devtools/client/storage/test/storage-cookies.html @@ -0,0 +1,24 @@ + + + + + + Storage inspector cookie test + + + + + diff --git a/devtools/client/storage/test/storage-empty-objectstores.html b/devtools/client/storage/test/storage-empty-objectstores.html new file mode 100644 index 000000000..096e90a32 --- /dev/null +++ b/devtools/client/storage/test/storage-empty-objectstores.html @@ -0,0 +1,62 @@ + + + + + Test for proper listing indexedDB databases with no object stores + + + + + diff --git a/devtools/client/storage/test/storage-idb-delete-blocked.html b/devtools/client/storage/test/storage-idb-delete-blocked.html new file mode 100644 index 000000000..ef7017f08 --- /dev/null +++ b/devtools/client/storage/test/storage-idb-delete-blocked.html @@ -0,0 +1,52 @@ + + + + + Test for proper listing indexedDB databases with no object stores + + + + + diff --git a/devtools/client/storage/test/storage-listings.html b/devtools/client/storage/test/storage-listings.html new file mode 100644 index 000000000..de3054d3a --- /dev/null +++ b/devtools/client/storage/test/storage-listings.html @@ -0,0 +1,126 @@ + + + + + + Storage inspector test for listing hosts and storages + + + + + + + diff --git a/devtools/client/storage/test/storage-localstorage.html b/devtools/client/storage/test/storage-localstorage.html new file mode 100644 index 000000000..3a560b096 --- /dev/null +++ b/devtools/client/storage/test/storage-localstorage.html @@ -0,0 +1,23 @@ + + + + + + Storage inspector localStorage test + + + + + diff --git a/devtools/client/storage/test/storage-overflow.html b/devtools/client/storage/test/storage-overflow.html new file mode 100644 index 000000000..ee8db36e6 --- /dev/null +++ b/devtools/client/storage/test/storage-overflow.html @@ -0,0 +1,19 @@ + + + + + + Storage inspector endless scrolling test + + + + + diff --git a/devtools/client/storage/test/storage-search.html b/devtools/client/storage/test/storage-search.html new file mode 100644 index 000000000..1a84ff622 --- /dev/null +++ b/devtools/client/storage/test/storage-search.html @@ -0,0 +1,23 @@ + + + + + + Storage inspector table filtering test + + + + + diff --git a/devtools/client/storage/test/storage-secured-iframe.html b/devtools/client/storage/test/storage-secured-iframe.html new file mode 100644 index 000000000..8424fd4cd --- /dev/null +++ b/devtools/client/storage/test/storage-secured-iframe.html @@ -0,0 +1,91 @@ + + + + + + + + + + diff --git a/devtools/client/storage/test/storage-sessionstorage.html b/devtools/client/storage/test/storage-sessionstorage.html new file mode 100644 index 000000000..12de07e13 --- /dev/null +++ b/devtools/client/storage/test/storage-sessionstorage.html @@ -0,0 +1,23 @@ + + + + + + Storage inspector sessionStorage test + + + + + diff --git a/devtools/client/storage/test/storage-unsecured-iframe.html b/devtools/client/storage/test/storage-unsecured-iframe.html new file mode 100644 index 000000000..a69ffdfd1 --- /dev/null +++ b/devtools/client/storage/test/storage-unsecured-iframe.html @@ -0,0 +1,19 @@ + + + + + + + + + + diff --git a/devtools/client/storage/test/storage-updates.html b/devtools/client/storage/test/storage-updates.html new file mode 100644 index 000000000..a009814b2 --- /dev/null +++ b/devtools/client/storage/test/storage-updates.html @@ -0,0 +1,64 @@ + + + + + + Storage inspector blank html for tests + + + + + -- cgit v1.2.3