diff options
author | janekptacijarabaci <janekptacijarabaci@seznam.cz> | 2018-03-02 17:52:34 +0100 |
---|---|---|
committer | janekptacijarabaci <janekptacijarabaci@seznam.cz> | 2018-03-02 17:52:34 +0100 |
commit | 166fb9f2893dcfb3375aa3227d116fb0ce2c6d42 (patch) | |
tree | 9bfd8bb159a20f00ff507ab9c27c22434155df23 /devtools/client | |
parent | 26e53627d6922b3b965afd76fc5d72e3cc1d9ba5 (diff) | |
download | UXP-166fb9f2893dcfb3375aa3227d116fb0ce2c6d42.tar UXP-166fb9f2893dcfb3375aa3227d116fb0ce2c6d42.tar.gz UXP-166fb9f2893dcfb3375aa3227d116fb0ce2c6d42.tar.lz UXP-166fb9f2893dcfb3375aa3227d116fb0ce2c6d42.tar.xz UXP-166fb9f2893dcfb3375aa3227d116fb0ce2c6d42.zip |
moebius#339: Make it possible to add cookies, local and session storage entries
Issue #31
https://github.com/MoonchildProductions/moebius/pull/339
Diffstat (limited to 'devtools/client')
-rw-r--r-- | devtools/client/locales/en-US/storage.properties | 6 | ||||
-rw-r--r-- | devtools/client/shared/widgets/TableWidget.js | 27 | ||||
-rw-r--r-- | devtools/client/storage/storage.xul | 4 | ||||
-rw-r--r-- | devtools/client/storage/test/browser.ini | 3 | ||||
-rw-r--r-- | devtools/client/storage/test/browser_storage_cookies_add.js | 20 | ||||
-rw-r--r-- | devtools/client/storage/test/browser_storage_indexeddb_delete_blocked.js | 6 | ||||
-rw-r--r-- | devtools/client/storage/test/browser_storage_localstorage_add.js | 20 | ||||
-rw-r--r-- | devtools/client/storage/test/browser_storage_sessionstorage_add.js | 20 | ||||
-rw-r--r-- | devtools/client/storage/test/head.js | 36 | ||||
-rw-r--r-- | devtools/client/storage/ui.js | 157 | ||||
-rw-r--r-- | devtools/client/themes/storage.css | 14 |
11 files changed, 270 insertions, 43 deletions
diff --git a/devtools/client/locales/en-US/storage.properties b/devtools/client/locales/en-US/storage.properties index 7e7e656ab..4719520bd 100644 --- a/devtools/client/locales/en-US/storage.properties +++ b/devtools/client/locales/en-US/storage.properties @@ -87,7 +87,11 @@ storage.parsedValue.label=Parsed Value # Label of popup menu action to delete storage item. storage.popupMenu.deleteLabel=Delete ā%Sā -# LOCALIZATION NOTE (storage.popupMenu.deleteAllLabel): +# LOCALIZATION NOTE (storage.popupMenu.addItemLabel): +# Label of popup menu action to add an item. +storage.popupMenu.addItemLabel=Add Item + +# LOCALIZATION NOTE (storage.popupMenu.deleteAllFromLabel): # Label of popup menu action to delete all storage items. storage.popupMenu.deleteAllFromLabel=Delete All From ā%Sā diff --git a/devtools/client/shared/widgets/TableWidget.js b/devtools/client/shared/widgets/TableWidget.js index 84645b5b1..c9fa55d77 100644 --- a/devtools/client/shared/widgets/TableWidget.js +++ b/devtools/client/shared/widgets/TableWidget.js @@ -123,6 +123,8 @@ function TableWidget(node, options = {}) { TableWidget.prototype = { items: null, + editBookmark: null, + scrollIntoViewOnUpdate: null, /** * Getter for the headers context menu popup id. @@ -1159,7 +1161,9 @@ Column.prototype = { }, /** - * Called when a row is updated. + * Called when a row is updated e.g. a cell is changed. This means that + * for a new row this method will be called once for each column. If a single + * cell is changed this method will be called just once. * * @param {string} event * The event name of the event. i.e. EVENTS.ROW_UPDATED @@ -1168,7 +1172,23 @@ Column.prototype = { */ onRowUpdated: function (event, id) { this._updateItems(); + if (this.highlightUpdated && this.items[id] != null) { + if (this.table.scrollIntoViewOnUpdate) { + let cell = this.cells[this.items[id]]; + + // When a new row is created this method is called once for each column + // as each cell is updated. We can only scroll to cells if they are + // visible. We check for visibility and once we find the first visible + // cell in a row we scroll it into view and reset the + // scrollIntoViewOnUpdate flag. + if (cell.label.clientHeight > 0) { + cell.scrollIntoView(); + + this.table.scrollIntoViewOnUpdate = null; + } + } + if (this.table.editBookmark) { // A rows position in the table can change as the result of an edit. In // order to ensure that the correct row is highlighted after an edit we @@ -1180,6 +1200,7 @@ Column.prototype = { this.cells[this.items[id]].flash(); } + this.updateZebra(); }, @@ -1594,6 +1615,10 @@ Cell.prototype = { this.label.focus(); }, + scrollIntoView: function () { + this.label.scrollIntoView(false); + }, + destroy: function () { this.label.remove(); this.label = null; diff --git a/devtools/client/storage/storage.xul b/devtools/client/storage/storage.xul index 85425912c..a91900add 100644 --- a/devtools/client/storage/storage.xul +++ b/devtools/client/storage/storage.xul @@ -31,6 +31,7 @@ <menuitem id="storage-tree-popup-delete"/> </menupopup> <menupopup id="storage-table-popup"> + <menuitem id="storage-table-popup-add"/> <menuitem id="storage-table-popup-delete"/> <menuitem id="storage-table-popup-delete-all-from"/> <menuitem id="storage-table-popup-delete-all" @@ -45,6 +46,9 @@ <splitter class="devtools-side-splitter"/> <vbox flex="1"> <hbox id="storage-toolbar" class="devtools-toolbar"> + <button id="add-button" + class="devtools-button add-button"></button> + <spacer flex="1"/> <textbox id="storage-searchbox" class="devtools-filterinput" type="search" diff --git a/devtools/client/storage/test/browser.ini b/devtools/client/storage/test/browser.ini index c13b9d0ec..1250d35d5 100644 --- a/devtools/client/storage/test/browser.ini +++ b/devtools/client/storage/test/browser.ini @@ -22,6 +22,7 @@ support-files = [browser_storage_basic.js] [browser_storage_cache_delete.js] [browser_storage_cache_error.js] +[browser_storage_cookies_add.js] [browser_storage_cookies_delete_all.js] [browser_storage_cookies_domain.js] [browser_storage_cookies_edit.js] @@ -38,11 +39,13 @@ support-files = [browser_storage_indexeddb_delete.js] [browser_storage_indexeddb_delete_blocked.js] [browser_storage_indexeddb_duplicate_names.js] +[browser_storage_localstorage_add.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_add.js] [browser_storage_sessionstorage_edit.js] [browser_storage_sidebar.js] [browser_storage_sidebar_update.js] diff --git a/devtools/client/storage/test/browser_storage_cookies_add.js b/devtools/client/storage/test/browser_storage_cookies_add.js new file mode 100644 index 000000000..ac66eb92c --- /dev/null +++ b/devtools/client/storage/test/browser_storage_cookies_add.js @@ -0,0 +1,20 @@ +/* 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 adding of cookies. + +"use strict"; + +add_task(function* () { + yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-cookies.html"); + showAllColumns(true); + + yield performAdd(["cookies", "http://test1.example.org"]); + yield performAdd(["cookies", "http://test1.example.org"]); + yield performAdd(["cookies", "http://test1.example.org"]); + yield performAdd(["cookies", "http://test1.example.org"]); + yield performAdd(["cookies", "http://test1.example.org"]); + + 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 index f87f15d82..2d77896f3 100644 --- a/devtools/client/storage/test/browser_storage_indexeddb_delete_blocked.js +++ b/devtools/client/storage/test/browser_storage_indexeddb_delete_blocked.js @@ -18,8 +18,8 @@ add_task(function* () { 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 (default)"); + let front = gUI.getCurrentFront(); + let result = yield front.removeDatabase("http://test1.example.org", "idb (default)"); ok(result.blocked, "removeDatabase attempt is blocked"); @@ -47,7 +47,7 @@ add_task(function* () { info("try to delete database from nonexistent host"); let errorThrown = false; try { - result = yield actor.removeDatabase("http://test2.example.org", "idb (default)"); + result = yield front.removeDatabase("http://test2.example.org", "idb (default)"); } catch (ex) { errorThrown = true; } diff --git a/devtools/client/storage/test/browser_storage_localstorage_add.js b/devtools/client/storage/test/browser_storage_localstorage_add.js new file mode 100644 index 000000000..de40957b8 --- /dev/null +++ b/devtools/client/storage/test/browser_storage_localstorage_add.js @@ -0,0 +1,20 @@ +/* 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 adding of localStorage entries. + +"use strict"; + +add_task(function* () { + yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-localstorage.html"); + showAllColumns(true); + + yield performAdd(["localStorage", "http://test1.example.org"]); + yield performAdd(["localStorage", "http://test1.example.org"]); + yield performAdd(["localStorage", "http://test1.example.org"]); + yield performAdd(["localStorage", "http://test1.example.org"]); + yield performAdd(["localStorage", "http://test1.example.org"]); + + yield finishTests(); +}); diff --git a/devtools/client/storage/test/browser_storage_sessionstorage_add.js b/devtools/client/storage/test/browser_storage_sessionstorage_add.js new file mode 100644 index 000000000..8f220bc81 --- /dev/null +++ b/devtools/client/storage/test/browser_storage_sessionstorage_add.js @@ -0,0 +1,20 @@ +/* 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 adding of sessionStorage entries. + +"use strict"; + +add_task(function* () { + yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-sessionstorage.html"); + showAllColumns(true); + + yield performAdd(["sessionStorage", "http://test1.example.org"]); + yield performAdd(["sessionStorage", "http://test1.example.org"]); + yield performAdd(["sessionStorage", "http://test1.example.org"]); + yield performAdd(["sessionStorage", "http://test1.example.org"]); + yield performAdd(["sessionStorage", "http://test1.example.org"]); + + yield finishTests(); +}); diff --git a/devtools/client/storage/test/head.js b/devtools/client/storage/test/head.js index 07a66120e..ec6459b8d 100644 --- a/devtools/client/storage/test/head.js +++ b/devtools/client/storage/test/head.js @@ -897,3 +897,39 @@ function setPermission(url, permission) { .addFromPrincipal(principal, permission, nsIPermissionManager.ALLOW_ACTION); } + +/** + * Add an item. + * @param {Array} store + * An array containing the path to the store to which we wish to add an + * item. + */ +function* performAdd(store) { + let storeName = store.join(" > "); + let toolbar = gPanelWindow.document.getElementById("storage-toolbar"); + let type = store[0]; + + yield selectTreeItem(store); + + let menuAdd = toolbar.querySelector( + "#add-button"); + + if (menuAdd.hidden) { + is(menuAdd.hidden, false, + `performAdd called for ${storeName} but it is not supported`); + return; + } + + let eventEdit = gUI.table.once("row-edit"); + let eventWait = gUI.once("store-objects-updated"); + + menuAdd.click(); + + let rowId = yield eventEdit; + yield eventWait; + + let key = type === "cookies" ? "uniqueKey" : "name"; + let value = getCellValue(rowId, key); + + is(rowId, value, `Row '${rowId}' was successfully added.`); +} diff --git a/devtools/client/storage/ui.js b/devtools/client/storage/ui.js index af0bfa375..27ad307b0 100644 --- a/devtools/client/storage/ui.js +++ b/devtools/client/storage/ui.js @@ -160,12 +160,20 @@ function StorageUI(front, target, panelWin, toolbox) { this._tablePopup = this._panelDoc.getElementById("storage-table-popup"); this._tablePopup.addEventListener("popupshowing", this.onTablePopupShowing); + this.onAddItem = this.onAddItem.bind(this); this.onRemoveItem = this.onRemoveItem.bind(this); this.onRemoveAllFrom = this.onRemoveAllFrom.bind(this); this.onRemoveAll = this.onRemoveAll.bind(this); this.onRemoveAllSessionCookies = this.onRemoveAllSessionCookies.bind(this); this.onRemoveTreeItem = this.onRemoveTreeItem.bind(this); + this._addButton = this._panelDoc.getElementById("add-button"); + this._addButton.addEventListener("command", this.onAddItem); + + this._tablePopupAddItem = this._panelDoc.getElementById( + "storage-table-popup-add"); + this._tablePopupAddItem.addEventListener("command", this.onAddItem); + this._tablePopupDelete = this._panelDoc.getElementById( "storage-table-popup-delete"); this._tablePopupDelete.addEventListener("command", this.onRemoveItem); @@ -221,6 +229,8 @@ StorageUI.prototype = { this.searchBox = null; this._treePopup.removeEventListener("popupshowing", this.onTreePopupShowing); + this._addButton.removeEventListener("command", this.onAddItem); + this._tablePopupAddItem.removeEventListener("command", this.onAddItem); this._treePopupDeleteAll.removeEventListener("command", this.onRemoveAll); this._treePopupDeleteAllSessionCookies.removeEventListener("command", this.onRemoveAllSessionCookies); @@ -243,7 +253,7 @@ StorageUI.prototype = { this.table.clearSelection(); }, - getCurrentActor: function () { + getCurrentFront: function () { let type = this.table.datatype; return this.storageTypes[type]; @@ -264,9 +274,9 @@ StorageUI.prototype = { }, editItem: function (eventType, data) { - let actor = this.getCurrentActor(); + let front = this.getCurrentFront(); - actor.editItem(data); + front.editItem(data); }, /** @@ -520,7 +530,7 @@ StorageUI.prototype = { // The indexedDB type could have sub-type data to fetch. // If having names specified, then it means // we are fetching details of specific database or of object store. - if (type == "indexedDB" && names) { + if (type === "indexedDB" && names) { let [ dbName, objectStoreName ] = JSON.parse(names[0]); if (dbName) { subType = "database"; @@ -529,6 +539,15 @@ StorageUI.prototype = { subType = "object store"; } } + + this.actorSupportsAddItem = yield this._target.actorHasMethod(type, "addItem"); + this.actorSupportsRemoveItem = + yield this._target.actorHasMethod(type, "removeItem"); + this.actorSupportsRemoveAll = + yield this._target.actorHasMethod(type, "removeAll"); + this.actorSupportsRemoveAllSessionCookies = + yield this._target.actorHasMethod(type, "removeAllSessionCookies"); + yield this.resetColumns(type, host, subType); } @@ -536,6 +555,7 @@ StorageUI.prototype = { if (data.length) { this.populateTable(data, reason); } + yield this.updateToolbar(); this.emit("store-objects-updated"); } catch (ex) { console.error(ex); @@ -543,6 +563,27 @@ StorageUI.prototype = { }), /** + * Updates the toolbar hiding and showing buttons as appropriate. + */ + updateToolbar: Task.async(function* () { + let item = this.tree.selectedItem; + let howManyNodesIn = item ? item.length : 0; + + // The first node is just a title e.g. "Cookies" so we need to be at least + // 2 nodes in to show the add button. + let canAdd = this.actorSupportsAddItem && howManyNodesIn > 1; + + if (canAdd) { + this._addButton.hidden = false; + this._addButton.setAttribute("tooltiptext", + L10N.getFormatStr("storage.popupMenu.addItemLabel")); + } else { + this._addButton.hidden = true; + this._addButton.removeAttribute("tooltiptext"); + } + }), + + /** * Populates the storage tree which displays the list of storages present for * the page. * @@ -777,11 +818,19 @@ StorageUI.prototype = { * the storage tree */ onHostSelect: function (event, item) { + if (!item) { + return; + } this.table.clear(); this.hideSidebar(); this.searchBox.value = ""; let [type, host] = item; + this.table.host = host; + this.table.datatype = type; + + this.updateToolbar(); + let names = null; if (!host) { return; @@ -814,7 +863,7 @@ StorageUI.prototype = { let editableFields = []; let hiddenFields = []; let privateFields = []; - let fields = yield this.getCurrentActor().getFields(subtype); + let fields = yield this.getCurrentFront().getFields(subtype); fields.forEach(f => { if (!uniqueKey) { @@ -949,33 +998,47 @@ StorageUI.prototype = { }, /** - * Fires before a cell context menu with the "Delete" action is shown. - * If the currently selected storage object doesn't support removing items, prevent - * showing the menu. + * Fires before a cell context menu with the "Add" or "Delete" action is + * shown. If the currently selected storage object doesn't support adding or + * removing items, prevent showing the menu. */ onTablePopupShowing: function (event) { let selectedItem = this.tree.selectedItem; let type = selectedItem[0]; - let actor = this.getCurrentActor(); // IndexedDB only supports removing items from object stores (level 4 of the tree) - if (!actor.removeItem || (type === "indexedDB" && selectedItem.length !== 4)) { + if ((!this.actorSupportsAddItem && !this.actorSupportsRemoveItem && + type !== "cookies") || + (type === "indexedDB" && selectedItem.length !== 4)) { event.preventDefault(); return; } let rowId = this.table.contextMenuRowId; let data = this.table.items.get(rowId); - let name = data[this.table.uniqueId]; - let separatorRegex = new RegExp(SEPARATOR_GUID, "g"); - let label = addEllipsis((name + "").replace(separatorRegex, "-")); + if (this.actorSupportsRemoveItem) { + let name = data[this.table.uniqueId]; + let separatorRegex = new RegExp(SEPARATOR_GUID, "g"); + let label = addEllipsis((name + "").replace(separatorRegex, "-")); - this._tablePopupDelete.setAttribute("label", - L10N.getFormatStr("storage.popupMenu.deleteLabel", label)); + this._tablePopupDelete.hidden = false; + this._tablePopupDelete.setAttribute("label", + L10N.getFormatStr("storage.popupMenu.deleteLabel", label)); + } else { + this._tablePopupDelete.hidden = true; + } + + if (this.actorSupportsAddItem) { + this._tablePopupAddItem.hidden = false; + this._tablePopupAddItem.setAttribute("label", + L10N.getFormatStr("storage.popupMenu.addItemLabel")); + } else { + this._tablePopupAddItem.hidden = true; + } let showDeleteAllSessionCookies = false; - if (selectedItem && actor.removeAllSessionCookies) { + if (this.actorSupportsRemoveAllSessionCookies) { if (type === "cookies" && selectedItem.length === 2) { showDeleteAllSessionCookies = true; } @@ -1000,13 +1063,12 @@ StorageUI.prototype = { if (selectedItem) { let type = selectedItem[0]; - let actor = this.storageTypes[type]; // The delete all (aka clear) action is displayed for IndexedDB object stores // (level 4 of tree), for Cache objects (level 3) and for the whole host (level 2) // for other storage types (cookies, localStorage, ...). let showDeleteAll = false; - if (actor.removeAll) { + if (this.actorSupportsRemoveAll) { let level; if (type == "indexedDB") { level = 4; @@ -1026,7 +1088,7 @@ StorageUI.prototype = { // The delete all session cookies action is displayed for cookie object stores // (level 2 of tree) let showDeleteAllSessionCookies = false; - if (actor.removeAllSessionCookies) { + if (this.actorSupportsRemoveAllSessionCookies) { if (type === "cookies" && selectedItem.length === 2) { showDeleteAllSessionCookies = true; } @@ -1055,44 +1117,54 @@ StorageUI.prototype = { }, /** + * Handles adding an item from the storage + */ + onAddItem: function () { + if (!this.tree.selectedItem) { + return; + } + let front = this.getCurrentFront(); + let [, host] = this.tree.selectedItem; + + // Prepare to scroll into view. + this.table.scrollIntoViewOnUpdate = true; + this.table.editBookmark = createGUID(); + front.addItem(this.table.editBookmark, host); + }, + + /** * Handles removing an item from the storage */ onRemoveItem: function () { let [, host, ...path] = this.tree.selectedItem; - let actor = this.getCurrentActor(); + let front = this.getCurrentFront(); let rowId = this.table.contextMenuRowId; let data = this.table.items.get(rowId); let name = data[this.table.uniqueId]; if (path.length > 0) { name = JSON.stringify([...path, name]); } - actor.removeItem(host, name); + front.removeItem(host, name); }, /** * Handles removing all items from the storage */ onRemoveAll: function () { - // Cannot use this.currentActor() if the handler is called from the - // tree context menu: it returns the correct value only after the - // table data from server are successfully fetched (and that's async). - let [type, host, ...path] = this.tree.selectedItem; - let actor = this.storageTypes[type]; + let [, host, ...path] = this.tree.selectedItem; + let front = this.getCurrentFront(); let name = path.length > 0 ? JSON.stringify(path) : undefined; - actor.removeAll(host, name); + front.removeAll(host, name); }, /** * Handles removing all session cookies from the storage */ onRemoveAllSessionCookies: function () { - // Cannot use this.currentActor() if the handler is called from the - // tree context menu: it returns the correct value only after the - // table data from server is successfully fetched (and that's async). let [, host, ...path] = this.tree.selectedItem; - let actor = this.getCurrentActor(); + let front = this.getCurrentFront(); let name = path.length > 0 ? JSON.stringify(path) : undefined; - actor.removeAllSessionCookies(host, name); + front.removeAllSessionCookies(host, name); }, /** @@ -1101,11 +1173,11 @@ StorageUI.prototype = { */ onRemoveAllFrom: function () { let [, host] = this.tree.selectedItem; - let actor = this.getCurrentActor(); + let front = this.getCurrentFront(); let rowId = this.table.contextMenuRowId; let data = this.table.items.get(rowId); - actor.removeAll(host, data.host); + front.removeAll(host, data.host); }, onRemoveTreeItem: function () { @@ -1119,9 +1191,9 @@ StorageUI.prototype = { }, removeDatabase: function (host, dbName) { - let actor = this.storageTypes.indexedDB; + let front = this.getCurrentFront(); - actor.removeDatabase(host, dbName).then(result => { + front.removeDatabase(host, dbName).then(result => { if (result.blocked) { let notificationBox = this._toolbox.getNotificationBox(); notificationBox.appendNotification( @@ -1141,8 +1213,17 @@ StorageUI.prototype = { }, removeCache: function (host, cacheName) { - let actor = this.storageTypes.Cache; + let front = this.getCurrentFront(); - actor.removeItem(host, JSON.stringify([ cacheName ])); + front.removeItem(host, JSON.stringify([ cacheName ])); }, }; + +// Helper Functions + +function createGUID() { + return "{cccccccc-cccc-4ccc-yccc-cccccccccccc}".replace(/[cy]/g, c => { + let r = Math.random() * 16 | 0, v = c == "c" ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); +} diff --git a/devtools/client/themes/storage.css b/devtools/client/themes/storage.css index 1e611f842..1d4da9bd6 100644 --- a/devtools/client/themes/storage.css +++ b/devtools/client/themes/storage.css @@ -32,6 +32,20 @@ min-width: 250px; } +#storage-toolbar .add-button::before { + margin: 0; + background-image: url("chrome://devtools/skin/images/add.svg"); + -moz-user-focus: normal; +} + +#storage-toolbar .devtools-button { + min-width: unset; +} + +#storage-toolbar .devtools-button hbox { + display: none; +} + /* Responsive sidebar */ @media (max-width: 700px) { #storage-tree, |