moebius#339: Make it possible to add cookies, local and session storage entries

Issue #31
https://github.com/MoonchildProductions/moebius/pull/339
This commit is contained in:
janekptacijarabaci 2018-03-02 17:52:34 +01:00 • committed by Roy Tam
commit 6d865bfda3
13 changed files with 309 additions and 43 deletions

View file

@ -87,7 +87,11 @@ storage.parsedValue.label=Parsed Value
# Label of popup menu action to delete storage item. # Label of popup menu action to delete storage item.
storage.popupMenu.deleteLabel=Delete “%S” 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. # Label of popup menu action to delete all storage items.
storage.popupMenu.deleteAllFromLabel=Delete All From “%S” storage.popupMenu.deleteAllFromLabel=Delete All From “%S”

View file

@ -123,6 +123,8 @@ function TableWidget(node, options = {}) {
TableWidget.prototype = { TableWidget.prototype = {
items: null, items: null,
editBookmark: null,
scrollIntoViewOnUpdate: null,
/** /**
* Getter for the headers context menu popup id. * 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 * @param {string} event
* The event name of the event. i.e. EVENTS.ROW_UPDATED * The event name of the event. i.e. EVENTS.ROW_UPDATED
@ -1168,7 +1172,23 @@ Column.prototype = {
*/ */
onRowUpdated: function (event, id) { onRowUpdated: function (event, id) {
this._updateItems(); this._updateItems();
if (this.highlightUpdated && this.items[id] != null) { 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) { if (this.table.editBookmark) {
// A rows position in the table can change as the result of an edit. In // 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 // 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.cells[this.items[id]].flash();
} }
this.updateZebra(); this.updateZebra();
}, },
@ -1594,6 +1615,10 @@ Cell.prototype = {
this.label.focus(); this.label.focus();
}, },
scrollIntoView: function () {
this.label.scrollIntoView(false);
},
destroy: function () { destroy: function () {
this.label.remove(); this.label.remove();
this.label = null; this.label = null;

View file

@ -31,6 +31,7 @@
<menuitem id="storage-tree-popup-delete"/> <menuitem id="storage-tree-popup-delete"/>
</menupopup> </menupopup>
<menupopup id="storage-table-popup"> <menupopup id="storage-table-popup">
<menuitem id="storage-table-popup-add"/>
<menuitem id="storage-table-popup-delete"/> <menuitem id="storage-table-popup-delete"/>
<menuitem id="storage-table-popup-delete-all-from"/> <menuitem id="storage-table-popup-delete-all-from"/>
<menuitem id="storage-table-popup-delete-all" <menuitem id="storage-table-popup-delete-all"
@ -45,6 +46,9 @@
<splitter class="devtools-side-splitter"/> <splitter class="devtools-side-splitter"/>
<vbox flex="1"> <vbox flex="1">
<hbox id="storage-toolbar" class="devtools-toolbar"> <hbox id="storage-toolbar" class="devtools-toolbar">
<button id="add-button"
class="devtools-button add-button"></button>
<spacer flex="1"/>
<textbox id="storage-searchbox" <textbox id="storage-searchbox"
class="devtools-filterinput" class="devtools-filterinput"
type="search" type="search"

View file

@ -22,6 +22,7 @@ support-files =
[browser_storage_basic.js] [browser_storage_basic.js]
[browser_storage_cache_delete.js] [browser_storage_cache_delete.js]
[browser_storage_cache_error.js] [browser_storage_cache_error.js]
[browser_storage_cookies_add.js]
[browser_storage_cookies_delete_all.js] [browser_storage_cookies_delete_all.js]
[browser_storage_cookies_domain.js] [browser_storage_cookies_domain.js]
[browser_storage_cookies_edit.js] [browser_storage_cookies_edit.js]
@ -38,11 +39,13 @@ support-files =
[browser_storage_indexeddb_delete.js] [browser_storage_indexeddb_delete.js]
[browser_storage_indexeddb_delete_blocked.js] [browser_storage_indexeddb_delete_blocked.js]
[browser_storage_indexeddb_duplicate_names.js] [browser_storage_indexeddb_duplicate_names.js]
[browser_storage_localstorage_add.js]
[browser_storage_localstorage_edit.js] [browser_storage_localstorage_edit.js]
[browser_storage_localstorage_error.js] [browser_storage_localstorage_error.js]
[browser_storage_overflow.js] [browser_storage_overflow.js]
[browser_storage_search.js] [browser_storage_search.js]
[browser_storage_search_keyboard_trap.js] [browser_storage_search_keyboard_trap.js]
[browser_storage_sessionstorage_add.js]
[browser_storage_sessionstorage_edit.js] [browser_storage_sessionstorage_edit.js]
[browser_storage_sidebar.js] [browser_storage_sidebar.js]
[browser_storage_sidebar_update.js] [browser_storage_sidebar_update.js]

View file

@ -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();
});

View file

@ -18,8 +18,8 @@ add_task(function* () {
info("do the delete"); info("do the delete");
yield selectTreeItem(["indexedDB", "http://test1.example.org"]); yield selectTreeItem(["indexedDB", "http://test1.example.org"]);
let actor = gUI.getCurrentActor(); let front = gUI.getCurrentFront();
let result = yield actor.removeDatabase("http://test1.example.org", "idb (default)"); let result = yield front.removeDatabase("http://test1.example.org", "idb (default)");
ok(result.blocked, "removeDatabase attempt is blocked"); ok(result.blocked, "removeDatabase attempt is blocked");
@ -47,7 +47,7 @@ add_task(function* () {
info("try to delete database from nonexistent host"); info("try to delete database from nonexistent host");
let errorThrown = false; let errorThrown = false;
try { try {
result = yield actor.removeDatabase("http://test2.example.org", "idb (default)"); result = yield front.removeDatabase("http://test2.example.org", "idb (default)");
} catch (ex) { } catch (ex) {
errorThrown = true; errorThrown = true;
} }

View file

@ -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();
});

View file

@ -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();
});

View file

@ -897,3 +897,39 @@ function setPermission(url, permission) {
.addFromPrincipal(principal, permission, .addFromPrincipal(principal, permission,
nsIPermissionManager.ALLOW_ACTION); 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.`);
}

View file

@ -160,12 +160,20 @@ function StorageUI(front, target, panelWin, toolbox) {
this._tablePopup = this._panelDoc.getElementById("storage-table-popup"); this._tablePopup = this._panelDoc.getElementById("storage-table-popup");
this._tablePopup.addEventListener("popupshowing", this.onTablePopupShowing); this._tablePopup.addEventListener("popupshowing", this.onTablePopupShowing);
this.onAddItem = this.onAddItem.bind(this);
this.onRemoveItem = this.onRemoveItem.bind(this); this.onRemoveItem = this.onRemoveItem.bind(this);
this.onRemoveAllFrom = this.onRemoveAllFrom.bind(this); this.onRemoveAllFrom = this.onRemoveAllFrom.bind(this);
this.onRemoveAll = this.onRemoveAll.bind(this); this.onRemoveAll = this.onRemoveAll.bind(this);
this.onRemoveAllSessionCookies = this.onRemoveAllSessionCookies.bind(this); this.onRemoveAllSessionCookies = this.onRemoveAllSessionCookies.bind(this);
this.onRemoveTreeItem = this.onRemoveTreeItem.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( this._tablePopupDelete = this._panelDoc.getElementById(
"storage-table-popup-delete"); "storage-table-popup-delete");
this._tablePopupDelete.addEventListener("command", this.onRemoveItem); this._tablePopupDelete.addEventListener("command", this.onRemoveItem);
@ -221,6 +229,8 @@ StorageUI.prototype = {
this.searchBox = null; this.searchBox = null;
this._treePopup.removeEventListener("popupshowing", this.onTreePopupShowing); 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._treePopupDeleteAll.removeEventListener("command", this.onRemoveAll);
this._treePopupDeleteAllSessionCookies.removeEventListener("command", this._treePopupDeleteAllSessionCookies.removeEventListener("command",
this.onRemoveAllSessionCookies); this.onRemoveAllSessionCookies);
@ -243,7 +253,7 @@ StorageUI.prototype = {
this.table.clearSelection(); this.table.clearSelection();
}, },
getCurrentActor: function () { getCurrentFront: function () {
let type = this.table.datatype; let type = this.table.datatype;
return this.storageTypes[type]; return this.storageTypes[type];
@ -264,9 +274,9 @@ StorageUI.prototype = {
}, },
editItem: function (eventType, data) { 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. // The indexedDB type could have sub-type data to fetch.
// If having names specified, then it means // If having names specified, then it means
// we are fetching details of specific database or of object store. // 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]); let [ dbName, objectStoreName ] = JSON.parse(names[0]);
if (dbName) { if (dbName) {
subType = "database"; subType = "database";
@ -529,6 +539,15 @@ StorageUI.prototype = {
subType = "object store"; 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); yield this.resetColumns(type, host, subType);
} }
@ -536,12 +555,34 @@ StorageUI.prototype = {
if (data.length) { if (data.length) {
this.populateTable(data, reason); this.populateTable(data, reason);
} }
yield this.updateToolbar();
this.emit("store-objects-updated"); this.emit("store-objects-updated");
} catch (ex) { } catch (ex) {
console.error(ex); console.error(ex);
} }
}), }),
/**
* 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 * Populates the storage tree which displays the list of storages present for
* the page. * the page.
@ -777,11 +818,19 @@ StorageUI.prototype = {
* the storage tree * the storage tree
*/ */
onHostSelect: function (event, item) { onHostSelect: function (event, item) {
if (!item) {
return;
}
this.table.clear(); this.table.clear();
this.hideSidebar(); this.hideSidebar();
this.searchBox.value = ""; this.searchBox.value = "";
let [type, host] = item; let [type, host] = item;
this.table.host = host;
this.table.datatype = type;
this.updateToolbar();
let names = null; let names = null;
if (!host) { if (!host) {
return; return;
@ -814,7 +863,7 @@ StorageUI.prototype = {
let editableFields = []; let editableFields = [];
let hiddenFields = []; let hiddenFields = [];
let privateFields = []; let privateFields = [];
let fields = yield this.getCurrentActor().getFields(subtype); let fields = yield this.getCurrentFront().getFields(subtype);
fields.forEach(f => { fields.forEach(f => {
if (!uniqueKey) { if (!uniqueKey) {
@ -949,33 +998,47 @@ StorageUI.prototype = {
}, },
/** /**
* Fires before a cell context menu with the "Delete" action is shown. * Fires before a cell context menu with the "Add" or "Delete" action is
* If the currently selected storage object doesn't support removing items, prevent * shown. If the currently selected storage object doesn't support adding or
* showing the menu. * removing items, prevent showing the menu.
*/ */
onTablePopupShowing: function (event) { onTablePopupShowing: function (event) {
let selectedItem = this.tree.selectedItem; let selectedItem = this.tree.selectedItem;
let type = selectedItem[0]; let type = selectedItem[0];
let actor = this.getCurrentActor();
// IndexedDB only supports removing items from object stores (level 4 of the tree) // 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(); event.preventDefault();
return; return;
} }
let rowId = this.table.contextMenuRowId; let rowId = this.table.contextMenuRowId;
let data = this.table.items.get(rowId); let data = this.table.items.get(rowId);
let name = data[this.table.uniqueId];
let separatorRegex = new RegExp(SEPARATOR_GUID, "g"); if (this.actorSupportsRemoveItem) {
let label = addEllipsis((name + "").replace(separatorRegex, "-")); let name = data[this.table.uniqueId];
let separatorRegex = new RegExp(SEPARATOR_GUID, "g");
let label = addEllipsis((name + "").replace(separatorRegex, "-"));
this._tablePopupDelete.setAttribute("label", this._tablePopupDelete.hidden = false;
L10N.getFormatStr("storage.popupMenu.deleteLabel", label)); 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; let showDeleteAllSessionCookies = false;
if (selectedItem && actor.removeAllSessionCookies) { if (this.actorSupportsRemoveAllSessionCookies) {
if (type === "cookies" && selectedItem.length === 2) { if (type === "cookies" && selectedItem.length === 2) {
showDeleteAllSessionCookies = true; showDeleteAllSessionCookies = true;
} }
@ -1000,13 +1063,12 @@ StorageUI.prototype = {
if (selectedItem) { if (selectedItem) {
let type = selectedItem[0]; let type = selectedItem[0];
let actor = this.storageTypes[type];
// The delete all (aka clear) action is displayed for IndexedDB object stores // 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) // (level 4 of tree), for Cache objects (level 3) and for the whole host (level 2)
// for other storage types (cookies, localStorage, ...). // for other storage types (cookies, localStorage, ...).
let showDeleteAll = false; let showDeleteAll = false;
if (actor.removeAll) { if (this.actorSupportsRemoveAll) {
let level; let level;
if (type == "indexedDB") { if (type == "indexedDB") {
level = 4; level = 4;
@ -1026,7 +1088,7 @@ StorageUI.prototype = {
// The delete all session cookies action is displayed for cookie object stores // The delete all session cookies action is displayed for cookie object stores
// (level 2 of tree) // (level 2 of tree)
let showDeleteAllSessionCookies = false; let showDeleteAllSessionCookies = false;
if (actor.removeAllSessionCookies) { if (this.actorSupportsRemoveAllSessionCookies) {
if (type === "cookies" && selectedItem.length === 2) { if (type === "cookies" && selectedItem.length === 2) {
showDeleteAllSessionCookies = true; showDeleteAllSessionCookies = true;
} }
@ -1054,45 +1116,55 @@ 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 * Handles removing an item from the storage
*/ */
onRemoveItem: function () { onRemoveItem: function () {
let [, host, ...path] = this.tree.selectedItem; let [, host, ...path] = this.tree.selectedItem;
let actor = this.getCurrentActor(); let front = this.getCurrentFront();
let rowId = this.table.contextMenuRowId; let rowId = this.table.contextMenuRowId;
let data = this.table.items.get(rowId); let data = this.table.items.get(rowId);
let name = data[this.table.uniqueId]; let name = data[this.table.uniqueId];
if (path.length > 0) { if (path.length > 0) {
name = JSON.stringify([...path, name]); name = JSON.stringify([...path, name]);
} }
actor.removeItem(host, name); front.removeItem(host, name);
}, },
/** /**
* Handles removing all items from the storage * Handles removing all items from the storage
*/ */
onRemoveAll: function () { onRemoveAll: function () {
// Cannot use this.currentActor() if the handler is called from the let [, host, ...path] = this.tree.selectedItem;
// tree context menu: it returns the correct value only after the let front = this.getCurrentFront();
// table data from server are successfully fetched (and that's async).
let [type, host, ...path] = this.tree.selectedItem;
let actor = this.storageTypes[type];
let name = path.length > 0 ? JSON.stringify(path) : undefined; 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 * Handles removing all session cookies from the storage
*/ */
onRemoveAllSessionCookies: function () { 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 [, host, ...path] = this.tree.selectedItem;
let actor = this.getCurrentActor(); let front = this.getCurrentFront();
let name = path.length > 0 ? JSON.stringify(path) : undefined; 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 () { onRemoveAllFrom: function () {
let [, host] = this.tree.selectedItem; let [, host] = this.tree.selectedItem;
let actor = this.getCurrentActor(); let front = this.getCurrentFront();
let rowId = this.table.contextMenuRowId; let rowId = this.table.contextMenuRowId;
let data = this.table.items.get(rowId); let data = this.table.items.get(rowId);
actor.removeAll(host, data.host); front.removeAll(host, data.host);
}, },
onRemoveTreeItem: function () { onRemoveTreeItem: function () {
@ -1119,9 +1191,9 @@ StorageUI.prototype = {
}, },
removeDatabase: function (host, dbName) { 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) { if (result.blocked) {
let notificationBox = this._toolbox.getNotificationBox(); let notificationBox = this._toolbox.getNotificationBox();
notificationBox.appendNotification( notificationBox.appendNotification(
@ -1141,8 +1213,17 @@ StorageUI.prototype = {
}, },
removeCache: function (host, cacheName) { 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);
});
}

View file

@ -32,6 +32,20 @@
min-width: 250px; 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 */ /* Responsive sidebar */
@media (max-width: 700px) { @media (max-width: 700px) {
#storage-tree, #storage-tree,

View file

@ -15,6 +15,8 @@ const {isWindowIncluded} = require("devtools/shared/layout/utils");
const specs = require("devtools/shared/specs/storage"); const specs = require("devtools/shared/specs/storage");
const { Task } = require("devtools/shared/task"); const { Task } = require("devtools/shared/task");
const DEFAULT_VALUE = "value";
// GUID to be used as a separator in compound keys. This must match the same // GUID to be used as a separator in compound keys. This must match the same
// constant in devtools/client/storage/ui.js, // constant in devtools/client/storage/ui.js,
// devtools/client/storage/test/head.js and // devtools/client/storage/test/head.js and
@ -616,6 +618,14 @@ StorageActors.createActor({
this.editCookie(data); this.editCookie(data);
}), }),
addItem: Task.async(function* (guid) {
let doc = this.storageActor.document;
let time = new Date().getTime();
let expiry = new Date(time + 3600 * 24 * 1000).toGMTString();
doc.cookie = `${guid}=${DEFAULT_VALUE};expires=${expiry}`;
}),
removeItem: Task.async(function* (host, name) { removeItem: Task.async(function* (host, name) {
let doc = this.storageActor.document; let doc = this.storageActor.document;
this.removeCookie(host, name, doc.nodePrincipal this.removeCookie(host, name, doc.nodePrincipal
@ -954,6 +964,12 @@ var cookieHelpers = {
let rowdata = msg.data.args[0]; let rowdata = msg.data.args[0];
return cookieHelpers.editCookie(rowdata); return cookieHelpers.editCookie(rowdata);
} }
case "createNewCookie": {
let host = msg.data.args[0];
let guid = msg.data.args[1];
let originAttributes = msg.data.args[2];
return cookieHelpers.createNewCookie(host, guid, originAttributes);
}
case "removeCookie": { case "removeCookie": {
let host = msg.data.args[0]; let host = msg.data.args[0];
let name = msg.data.args[1]; let name = msg.data.args[1];
@ -1094,6 +1110,14 @@ function getObjectForLocalOrSessionStorage(type) {
]; ];
}), }),
addItem: Task.async(function* (guid, host) {
let storage = this.hostVsStores.get(host);
if (!storage) {
return;
}
storage.setItem(guid, DEFAULT_VALUE);
}),
/** /**
* Edit localStorage or sessionStorage fields. * Edit localStorage or sessionStorage fields.
* *

View file

@ -90,6 +90,13 @@ createStorageSpec({
methods: Object.assign({}, methods: Object.assign({},
editRemoveMethods, editRemoveMethods,
{ {
addItem: {
request: {
guid: Arg(0, "string"),
},
response: {}
}
}, {
removeAll: { removeAll: {
request: { request: {
host: Arg(0, "string"), host: Arg(0, "string"),
@ -119,6 +126,14 @@ types.addDictType("storageobject", {
const storageMethods = Object.assign({}, const storageMethods = Object.assign({},
editRemoveMethods, editRemoveMethods,
{ {
addItem: {
request: {
guid: Arg(0, "string"),
host: Arg(1, "nullable:string")
},
response: {}
}
}, {
removeAll: { removeAll: {
request: { request: {
host: Arg(0, "string") host: Arg(0, "string")