--- a/devtools/client/storage/test/browser_storage_dynamic_updates_cookies.js
+++ b/devtools/client/storage/test/browser_storage_dynamic_updates_cookies.js
@@ -34,31 +34,32 @@ add_task(function* () {
{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
-
yield checkState([
[
["cookies", "http://test1.example.org"],
[
getCookieId("c1", "test1.example.org", "/browser"),
getCookieId("c2", "test1.example.org", "/browser")
]
],
]);
checkCell(c1id, "value", "1.2.3.4.5.6.7");
gWindow.addCookie("c1", '{"foo": 4,"bar":6}', "/browser");
yield gUI.once("sidebar-updated");
+ yield gUI.once("store-objects-updated");
yield findVariableViewProperties(finalValue[0], false);
yield findVariableViewProperties(finalValue[1], true);
yield checkState([
[
["cookies", "http://test1.example.org"],
[
@@ -67,18 +68,16 @@ add_task(function* () {
]
],
]);
checkCell(c1id, "value", '{"foo": 4,"bar":6}');
// Add a new entry
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");
yield checkState([
[
["cookies", "http://test1.example.org"],
[
getCookieId("c1", "test1.example.org", "/browser"),
getCookieId("c2", "test1.example.org", "/browser"),
@@ -114,16 +113,17 @@ add_task(function* () {
let c4id = getCookieId("c4", "test1.example.org",
"/browser/devtools/client/storage/test/");
checkCell(c4id, "value", "booyeah");
// Removing cookies
gWindow.removeCookie("c1", "/browser");
yield gUI.once("sidebar-updated");
+ yield gUI.once("store-objects-updated");
yield checkState([
[
["cookies", "http://test1.example.org"],
[
getCookieId("c2", "test1.example.org", "/browser"),
getCookieId("c3", "test1.example.org",
"/browser/devtools/client/storage/test/"),
@@ -155,16 +155,17 @@ add_task(function* () {
]);
// 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 gUI.once("store-objects-updated");
yield checkState([
[
["cookies", "http://test1.example.org"],
[
getCookieId("c4", "test1.example.org",
"/browser/devtools/client/storage/test/")
]
--- a/devtools/client/storage/ui.js
+++ b/devtools/client/storage/ui.js
@@ -1,16 +1,15 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* 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";
-const {Task} = require("devtools/shared/task");
const EventEmitter = require("devtools/shared/old-event-emitter");
const {LocalizationHelper, ELLIPSIS} = require("devtools/shared/l10n");
const KeyShortcuts = require("devtools/client/shared/key-shortcuts");
const JSOL = require("devtools/client/shared/vendor/jsol");
const {KeyCodes} = require("devtools/client/shared/keycodes");
// GUID to be used as a separator in compound keys. This must match the same
// constant in devtools/server/actors/storage.js,
@@ -59,183 +58,164 @@ const COOKIE_KEY_MAP = {
creationTime: "CreationTime",
lastAccessed: "LastAccessed"
};
// Maximum length of item name to show in context menu label - will be
// trimmed with ellipsis if it's longer.
const ITEM_NAME_MAX_LENGTH = 32;
-function addEllipsis(name) {
- if (name.length > ITEM_NAME_MAX_LENGTH) {
- if (/^https?:/.test(name)) {
- // For URLs, add ellipsis in the middle
- const halfLen = ITEM_NAME_MAX_LENGTH / 2;
- return name.slice(0, halfLen) + ELLIPSIS + name.slice(-halfLen);
- }
-
- // For other strings, add ellipsis at the end
- return name.substr(0, ITEM_NAME_MAX_LENGTH) + ELLIPSIS;
- }
-
- return name;
-}
-
/**
* StorageUI is controls and builds the UI of the Storage Inspector.
*
* @param {Front} front
* Front for the storage actor
* @param {Target} target
* Interface for the page we're debugging
* @param {Window} panelWin
* Window of the toolbox panel to populate UI in.
*/
-function StorageUI(front, target, panelWin, toolbox) {
- EventEmitter.decorate(this);
+class StorageUI {
+ constructor(front, target, panelWin, toolbox) {
+ EventEmitter.decorate(this);
- this._target = target;
- this._window = panelWin;
- this._panelDoc = panelWin.document;
- this._toolbox = toolbox;
- this.front = front;
+ this._target = target;
+ this._window = panelWin;
+ this._panelDoc = panelWin.document;
+ this._toolbox = toolbox;
+ this.front = front;
+ this.storageTypes = null;
+ this.sidebarToggledOpen = null;
+ this.shouldLoadMoreItems = true;
- let treeNode = this._panelDoc.getElementById("storage-tree");
- this.tree = new TreeWidget(treeNode, {
- defaultType: "dir",
- contextMenuId: "storage-tree-popup"
- });
- this.onHostSelect = this.onHostSelect.bind(this);
- this.tree.on("select", this.onHostSelect);
+ let treeNode = this._panelDoc.getElementById("storage-tree");
+ this.tree = new TreeWidget(treeNode, {
+ defaultType: "dir",
+ contextMenuId: "storage-tree-popup"
+ });
+ this.onHostSelect = this.onHostSelect.bind(this);
+ this.tree.on("select", this.onHostSelect);
- let tableNode = this._panelDoc.getElementById("storage-table");
- this.table = new TableWidget(tableNode, {
- emptyText: L10N.getStr("table.emptyText"),
- highlightUpdated: true,
- cellContextMenuId: "storage-table-popup"
- });
+ let tableNode = this._panelDoc.getElementById("storage-table");
+ this.table = new TableWidget(tableNode, {
+ emptyText: L10N.getStr("table.emptyText"),
+ highlightUpdated: true,
+ cellContextMenuId: "storage-table-popup"
+ });
- this.updateObjectSidebar = this.updateObjectSidebar.bind(this);
- this.table.on(TableWidget.EVENTS.ROW_SELECTED, this.updateObjectSidebar);
-
- this.handleScrollEnd = this.handleScrollEnd.bind(this);
- this.table.on(TableWidget.EVENTS.SCROLL_END, this.handleScrollEnd);
+ this.updateObjectSidebar = this.updateObjectSidebar.bind(this);
+ this.table.on(TableWidget.EVENTS.ROW_SELECTED, this.updateObjectSidebar);
- this.editItem = this.editItem.bind(this);
- this.table.on(TableWidget.EVENTS.CELL_EDIT, this.editItem);
+ this.handleScrollEnd = this.handleScrollEnd.bind(this);
+ this.table.on(TableWidget.EVENTS.SCROLL_END, this.handleScrollEnd);
+
+ this.editItem = this.editItem.bind(this);
+ this.table.on(TableWidget.EVENTS.CELL_EDIT, this.editItem);
- this.sidebar = this._panelDoc.getElementById("storage-sidebar");
- this.sidebar.setAttribute("width", "300");
- this.view = new VariablesView(this.sidebar.firstChild,
- GENERIC_VARIABLES_VIEW_SETTINGS);
+ this.sidebar = this._panelDoc.getElementById("storage-sidebar");
+ this.sidebar.setAttribute("width", "300");
+ this.view = new VariablesView(this.sidebar.firstChild,
+ GENERIC_VARIABLES_VIEW_SETTINGS);
- this.filterItems = this.filterItems.bind(this);
- this.onPaneToggleButtonClicked = this.onPaneToggleButtonClicked.bind(this);
- this.setupToolbar();
+ this.filterItems = this.filterItems.bind(this);
+ this.onPaneToggleButtonClicked = this.onPaneToggleButtonClicked.bind(this);
+ this.setupToolbar();
- let shortcuts = new KeyShortcuts({
- window: this._panelDoc.defaultView,
- });
- let key = L10N.getStr("storage.filter.key");
- shortcuts.on(key, event => {
- event.preventDefault();
- this.searchBox.focus();
- });
+ let shortcuts = new KeyShortcuts({
+ window: this._panelDoc.defaultView,
+ });
+ let key = L10N.getStr("storage.filter.key");
+ shortcuts.on(key, event => {
+ event.preventDefault();
+ this.searchBox.focus();
+ });
- this.front.listStores().then(storageTypes => {
- this.populateStorageTree(storageTypes);
- }).catch(e => {
- if (!this._toolbox || this._toolbox._destroyer) {
- // The toolbox is in the process of being destroyed... in this case throwing here
- // is expected and normal so let's ignore the error.
- return;
- }
-
- // The toolbox is open so the error is unexpected and real so let's log it.
- console.error(e);
- });
+ this.front.listStores().then(storageTypes => {
+ this.populateStorageTree(storageTypes);
+ }).catch(e => {
+ if (!this._toolbox || this._toolbox._destroyer) {
+ // The toolbox is in the process of being destroyed... in this case throwing here
+ // is expected and normal so let's ignore the error.
+ return;
+ }
- this.onUpdate = this.onUpdate.bind(this);
- this.front.on("stores-update", this.onUpdate);
- this.onCleared = this.onCleared.bind(this);
- this.front.on("stores-cleared", this.onCleared);
-
- this.handleKeypress = this.handleKeypress.bind(this);
- this._panelDoc.addEventListener("keypress", this.handleKeypress);
+ // The toolbox is open so the error is unexpected and real so let's log it.
+ console.error(e);
+ });
- this.onTreePopupShowing = this.onTreePopupShowing.bind(this);
- this._treePopup = this._panelDoc.getElementById("storage-tree-popup");
- this._treePopup.addEventListener("popupshowing", this.onTreePopupShowing);
+ this.onUpdate = this.onUpdate.bind(this);
+ this.front.on("stores-update", this.onUpdate);
+ this.onCleared = this.onCleared.bind(this);
+ this.front.on("stores-cleared", this.onCleared);
- this.onTablePopupShowing = this.onTablePopupShowing.bind(this);
- this._tablePopup = this._panelDoc.getElementById("storage-table-popup");
- this._tablePopup.addEventListener("popupshowing", this.onTablePopupShowing);
+ this.handleKeypress = this.handleKeypress.bind(this);
+ this._panelDoc.addEventListener("keypress", this.handleKeypress);
+
+ this.onTreePopupShowing = this.onTreePopupShowing.bind(this);
+ this._treePopup = this._panelDoc.getElementById("storage-tree-popup");
+ this._treePopup.addEventListener("popupshowing", this.onTreePopupShowing);
- this.onRefreshTable = this.onRefreshTable.bind(this);
- 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.onTablePopupShowing = this.onTablePopupShowing.bind(this);
+ this._tablePopup = this._panelDoc.getElementById("storage-table-popup");
+ this._tablePopup.addEventListener("popupshowing", this.onTablePopupShowing);
- this._refreshButton = this._panelDoc.getElementById("refresh-button");
- this._refreshButton.addEventListener("command", this.onRefreshTable);
+ this.onRefreshTable = this.onRefreshTable.bind(this);
+ 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._refreshButton = this._panelDoc.getElementById("refresh-button");
+ this._refreshButton.addEventListener("command", this.onRefreshTable);
- this._tablePopupAddItem = this._panelDoc.getElementById(
- "storage-table-popup-add");
- this._tablePopupAddItem.addEventListener("command", this.onAddItem);
+ this._addButton = this._panelDoc.getElementById("add-button");
+ this._addButton.addEventListener("command", this.onAddItem);
- this._tablePopupDelete = this._panelDoc.getElementById(
- "storage-table-popup-delete");
- this._tablePopupDelete.addEventListener("command", this.onRemoveItem);
+ this._tablePopupAddItem = this._panelDoc.getElementById(
+ "storage-table-popup-add");
+ this._tablePopupAddItem.addEventListener("command", this.onAddItem);
- this._tablePopupDeleteAllFrom = this._panelDoc.getElementById(
- "storage-table-popup-delete-all-from");
- this._tablePopupDeleteAllFrom.addEventListener("command",
- this.onRemoveAllFrom);
+ this._tablePopupDelete = this._panelDoc.getElementById(
+ "storage-table-popup-delete");
+ this._tablePopupDelete.addEventListener("command", this.onRemoveItem);
- this._tablePopupDeleteAll = this._panelDoc.getElementById(
- "storage-table-popup-delete-all");
- this._tablePopupDeleteAll.addEventListener("command", this.onRemoveAll);
+ this._tablePopupDeleteAllFrom = this._panelDoc.getElementById(
+ "storage-table-popup-delete-all-from");
+ this._tablePopupDeleteAllFrom.addEventListener("command",
+ this.onRemoveAllFrom);
- this._tablePopupDeleteAllSessionCookies = this._panelDoc.getElementById(
- "storage-table-popup-delete-all-session-cookies");
- this._tablePopupDeleteAllSessionCookies.addEventListener("command",
- this.onRemoveAllSessionCookies);
+ this._tablePopupDeleteAll = this._panelDoc.getElementById(
+ "storage-table-popup-delete-all");
+ this._tablePopupDeleteAll.addEventListener("command", this.onRemoveAll);
- this._treePopupDeleteAll = this._panelDoc.getElementById(
- "storage-tree-popup-delete-all");
- this._treePopupDeleteAll.addEventListener("command", this.onRemoveAll);
+ this._tablePopupDeleteAllSessionCookies = this._panelDoc.getElementById(
+ "storage-table-popup-delete-all-session-cookies");
+ this._tablePopupDeleteAllSessionCookies.addEventListener("command",
+ this.onRemoveAllSessionCookies);
- this._treePopupDeleteAllSessionCookies = this._panelDoc.getElementById(
- "storage-tree-popup-delete-all-session-cookies");
- this._treePopupDeleteAllSessionCookies.addEventListener("command",
- this.onRemoveAllSessionCookies);
+ this._treePopupDeleteAll = this._panelDoc.getElementById(
+ "storage-tree-popup-delete-all");
+ this._treePopupDeleteAll.addEventListener("command", this.onRemoveAll);
- this._treePopupDelete = this._panelDoc.getElementById("storage-tree-popup-delete");
- this._treePopupDelete.addEventListener("command", this.onRemoveTreeItem);
-}
-
-exports.StorageUI = StorageUI;
+ this._treePopupDeleteAllSessionCookies = this._panelDoc.getElementById(
+ "storage-tree-popup-delete-all-session-cookies");
+ this._treePopupDeleteAllSessionCookies.addEventListener("command",
+ this.onRemoveAllSessionCookies);
-StorageUI.prototype = {
-
- storageTypes: null,
- sidebarToggledOpen: null,
- shouldLoadMoreItems: true,
+ this._treePopupDelete = this._panelDoc.getElementById("storage-tree-popup-delete");
+ this._treePopupDelete.addEventListener("command", this.onRemoveTreeItem);
+ }
set animationsEnabled(value) {
this._panelDoc.documentElement.classList.toggle("no-animate", !value);
- },
+ }
- destroy: function () {
+ destroy() {
this.table.off(TableWidget.EVENTS.ROW_SELECTED, this.updateObjectSidebar);
this.table.off(TableWidget.EVENTS.SCROLL_END, this.handleScrollEnd);
this.table.off(TableWidget.EVENTS.CELL_EDIT, this.editItem);
this.table.destroy();
this.front.off("stores-update", this.onUpdate);
this.front.off("stores-cleared", this.onCleared);
this._panelDoc.removeEventListener("keypress", this.handleKeypress);
@@ -255,114 +235,114 @@ StorageUI.prototype = {
this._treePopupDelete.removeEventListener("command", this.onRemoveTreeItem);
this._tablePopup.removeEventListener("popupshowing", this.onTablePopupShowing);
this._tablePopupDelete.removeEventListener("command", this.onRemoveItem);
this._tablePopupDeleteAllFrom.removeEventListener("command", this.onRemoveAllFrom);
this._tablePopupDeleteAll.removeEventListener("command", this.onRemoveAll);
this._tablePopupDeleteAllSessionCookies.removeEventListener("command",
this.onRemoveAllSessionCookies);
- },
+ }
- setupToolbar: function () {
+ setupToolbar() {
this.searchBox = this._panelDoc.getElementById("storage-searchbox");
this.searchBox.addEventListener("command", this.filterItems);
// Setup the sidebar toggle button.
this.sidebarToggleBtn = this._panelDoc.querySelector(".sidebar-toggle");
this.updateSidebarToggleButton();
this.sidebarToggleBtn.addEventListener("click", this.onPaneToggleButtonClicked);
- },
+ }
- onPaneToggleButtonClicked: function () {
+ onPaneToggleButtonClicked() {
if (this.sidebar.hidden && this.table.selectedRow) {
this.sidebar.hidden = false;
this.sidebarToggledOpen = true;
this.updateSidebarToggleButton();
} else {
this.sidebarToggledOpen = false;
this.hideSidebar();
}
- },
+ }
- updateSidebarToggleButton: function () {
+ updateSidebarToggleButton() {
let title;
this.sidebarToggleBtn.hidden = !this.table.hasSelectedRow;
if (this.sidebar.hidden) {
this.sidebarToggleBtn.classList.add("pane-collapsed");
title = L10N.getStr("storage.expandPane");
} else {
this.sidebarToggleBtn.classList.remove("pane-collapsed");
title = L10N.getStr("storage.collapsePane");
}
this.sidebarToggleBtn.setAttribute("tooltiptext", title);
- },
+ }
/**
* Hide the object viewer sidebar
*/
- hideSidebar: function () {
+ hideSidebar() {
this.sidebar.hidden = true;
this.updateSidebarToggleButton();
- },
+ }
- getCurrentFront: function () {
+ getCurrentFront() {
let type = this.table.datatype;
return this.storageTypes[type];
- },
+ }
/**
* Make column fields editable
*
* @param {Array} editableFields
* An array of keys of columns to be made editable
*/
- makeFieldsEditable: function* (editableFields) {
+ makeFieldsEditable(editableFields) {
if (editableFields && editableFields.length > 0) {
this.table.makeFieldsEditable(editableFields);
} else if (this.table._editableFieldsEngine) {
this.table._editableFieldsEngine.destroy();
}
- },
+ }
- editItem: function (data) {
+ editItem(data) {
let front = this.getCurrentFront();
front.editItem(data);
- },
+ }
/**
* Removes the given item from the storage table. Reselects the next item in
* the table and repopulates the sidebar with that item's data if the item
* being removed was selected.
*/
- removeItemFromTable: function (name) {
+ async removeItemFromTable(name) {
if (this.table.isSelected(name) && this.table.items.size > 1) {
if (this.table.selectedIndex == 0) {
this.table.selectNextRow();
} else {
this.table.selectPreviousRow();
}
}
this.table.remove(name);
- this.updateObjectSidebar();
- },
+ await this.updateObjectSidebar();
+ }
/**
* Event handler for "stores-cleared" event coming from the storage actor.
*
* @param {object} response
* An object containing which storage types were cleared
*/
- onCleared: function (response) {
+ onCleared(response) {
function* enumPaths() {
for (let type in response) {
if (Array.isArray(response[type])) {
// Handle the legacy response with array of hosts
for (let host of response[type]) {
yield [type, host];
}
} else {
@@ -391,17 +371,17 @@ StorageUI.prototype = {
// Find if the path is selected (there is max one) and clear it
if (this.tree.isSelected(path)) {
this.table.clear();
this.hideSidebar();
this.emit("store-objects-cleared");
break;
}
}
- },
+ }
/**
* Event handler for "stores-update" event coming from the storage actor.
*
* @param {object} argument0
* An object containing the details of the added, changed and deleted
* storage objects.
* Each of these 3 objects are of the following format:
@@ -416,74 +396,80 @@ StorageUI.prototype = {
* }, ...
* }
* Where store_type1 and store_type2 is one of cookies, indexedDB,
* sessionStorage and localStorage; host1, host2 are the host in which
* this change happened; and [<store_namesX] is an array of the names
* of the changed store objects. This array is empty for deleted object
* if the host was completely removed.
*/
- onUpdate: function ({ changed, added, deleted }) {
- if (deleted) {
- this.handleDeletedItems(deleted);
- }
-
+ async onUpdate({ changed, added, deleted }) {
if (added) {
- this.handleAddedItems(added);
+ await this.handleAddedItems(added);
}
if (changed) {
- this.handleChangedItems(changed);
+ await this.handleChangedItems(changed);
+ }
+
+ // We are dealing with batches of changes here. Deleted **MUST** come last in case it
+ // is in the same batch as added and changed events e.g.
+ // - An item is changed then deleted in the same batch: deleted then changed will
+ // display an item that has been deleted.
+ // - An item is added then deleted in the same batch: deleted then added will
+ // display an item that has been deleted.
+ if (deleted) {
+ await this.handleDeletedItems(deleted);
}
if (added || deleted || changed) {
this.emit("store-objects-updated");
}
- },
+ }
/**
* Handle added items received by onUpdate
*
* @param {object} See onUpdate docs
*/
- handleAddedItems: function (added) {
+ async handleAddedItems(added) {
for (let type in added) {
for (let host in added[type]) {
this.tree.add([type, {id: host, type: "url"}]);
for (let name of added[type][host]) {
try {
name = JSON.parse(name);
if (name.length == 3) {
name.splice(2, 1);
}
this.tree.add([type, host, ...name]);
if (!this.tree.selectedItem) {
this.tree.selectedItem = [type, host, name[0], name[1]];
- this.fetchStorageObjects(type, host, [JSON.stringify(name)],
- REASON.NEW_ROW);
+ await this.fetchStorageObjects(type, host, [JSON.stringify(name)],
+ REASON.NEW_ROW);
}
} catch (ex) {
// Do nothing
}
}
if (this.tree.isSelected([type, host])) {
- this.fetchStorageObjects(type, host, added[type][host],
- REASON.NEW_ROW);
+ await this.fetchStorageObjects(type, host, added[type][host],
+ REASON.NEW_ROW);
}
}
}
- },
+ }
/**
* Handle deleted items received by onUpdate
*
* @param {object} See onUpdate docs
*/
- handleDeletedItems: function (deleted) {
+ async handleDeletedItems(deleted) {
for (let type in deleted) {
for (let host in deleted[type]) {
if (!deleted[type][host].length) {
// This means that the whole host is deleted, thus the item should
// be removed from the storage tree
if (this.tree.isSelected([type, host])) {
this.table.clear();
this.hideSidebar();
@@ -506,69 +492,69 @@ StorageUI.prototype = {
}
this.tree.remove([type, host, ...names]);
}
// Remove the item from table if currently displayed.
if (names.length > 0) {
let tableItemName = names.pop();
if (this.tree.isSelected([type, host, ...names])) {
- this.removeItemFromTable(tableItemName);
+ await this.removeItemFromTable(tableItemName);
}
}
} catch (ex) {
if (this.tree.isSelected([type, host])) {
- this.removeItemFromTable(name);
+ await this.removeItemFromTable(name);
}
}
}
}
}
}
- },
+ }
/**
* Handle changed items received by onUpdate
*
* @param {object} See onUpdate docs
*/
- handleChangedItems: function (changed) {
+ async handleChangedItems(changed) {
let [type, host, db, objectStore] = this.tree.selectedItem;
if (!changed[type] || !changed[type][host] ||
changed[type][host].length == 0) {
return;
}
try {
let toUpdate = [];
for (let name of changed[type][host]) {
let names = JSON.parse(name);
if (names[0] == db && names[1] == objectStore && names[2]) {
toUpdate.push(name);
}
}
- this.fetchStorageObjects(type, host, toUpdate, REASON.UPDATE);
+ await this.fetchStorageObjects(type, host, toUpdate, REASON.UPDATE);
} catch (ex) {
- this.fetchStorageObjects(type, host, changed[type][host], REASON.UPDATE);
+ await this.fetchStorageObjects(type, host, changed[type][host], REASON.UPDATE);
}
- },
+ }
/**
* Fetches the storage objects from the storage actor and populates the
* storage table with the returned data.
*
* @param {string} type
* The type of storage. Ex. "cookies"
* @param {string} host
* Hostname
* @param {array} names
* Names of particular store objects. Empty if all are requested
* @param {Constant} reason
* See REASON constant at top of file.
*/
- fetchStorageObjects: Task.async(function* (type, host, names, reason) {
+ async fetchStorageObjects(type, host, names, reason) {
let fetchOpts = reason === REASON.NEXT_50_ITEMS ? {offset: this.itemOffset}
: {};
let storageType = this.storageTypes[type];
this.sidebarToggledOpen = null;
if (reason !== REASON.NEXT_50_ITEMS &&
reason !== REASON.UPDATE &&
@@ -588,68 +574,68 @@ StorageUI.prototype = {
if (dbName) {
subType = "database";
}
if (objectStoreName) {
subType = "object store";
}
}
- this.actorSupportsAddItem = yield this._target.actorHasMethod(type, "addItem");
+ this.actorSupportsAddItem = await this._target.actorHasMethod(type, "addItem");
this.actorSupportsRemoveItem =
- yield this._target.actorHasMethod(type, "removeItem");
+ await this._target.actorHasMethod(type, "removeItem");
this.actorSupportsRemoveAll =
- yield this._target.actorHasMethod(type, "removeAll");
+ await this._target.actorHasMethod(type, "removeAll");
this.actorSupportsRemoveAllSessionCookies =
- yield this._target.actorHasMethod(type, "removeAllSessionCookies");
+ await this._target.actorHasMethod(type, "removeAllSessionCookies");
- yield this.resetColumns(type, host, subType);
+ await this.resetColumns(type, host, subType);
}
- let {data} = yield storageType.getStoreObjects(host, names, fetchOpts);
+ let {data} = await storageType.getStoreObjects(host, names, fetchOpts);
if (data.length) {
- this.populateTable(data, reason);
+ await this.populateTable(data, reason);
}
- yield this.updateToolbar();
+ this.updateToolbar();
this.emit("store-objects-updated");
} catch (ex) {
console.error(ex);
}
- }),
+ }
/**
* Updates the toolbar hiding and showing buttons as appropriate.
*/
- updateToolbar: Task.async(function* () {
+ updateToolbar() {
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.
*
* @param {object} storageTypes
* List of storages and their corresponding hosts returned by the
* StorageFront.listStores call.
*/
- populateStorageTree: function (storageTypes) {
+ populateStorageTree(storageTypes) {
this.storageTypes = {};
for (let type in storageTypes) {
// Ignore `from` field, which is just a protocol.js implementation
// artifact.
if (type === "from") {
continue;
}
let typeLabel = type;
@@ -676,29 +662,29 @@ StorageUI.prototype = {
// Do Nothing
}
}
if (!this.tree.selectedItem) {
this.tree.selectedItem = [type, host];
}
}
}
- },
+ }
/**
* Populates the selected entry from the table in the sidebar for a more
* detailed view.
*/
- updateObjectSidebar: Task.async(function* () {
+ async updateObjectSidebar() {
let item = this.table.selectedRow;
let value;
// Get the string value (async action) and the update the UI synchronously.
if (item && item.name && item.valueActor) {
- value = yield item.valueActor.string();
+ value = await item.valueActor.string();
}
// Bail if the selectedRow is no longer selected, the item doesn't exist or the state
// changed in another way during the above yield.
if (this.table.items.size === 0 ||
!item ||
!this.table.selectedRow ||
item.uniqueKey !== this.table.selectedRow.uniqueKey) {
@@ -757,29 +743,29 @@ StorageUI.prototype = {
}
mainScope.addItem(key, {}, true).setGrip(item[key]);
this.parseItemValue(key, item[key]);
}
}
this.emit("sidebar-updated");
- }),
+ }
/**
* Tries to parse a string value into either a json or a key-value separated
* object and populates the sidebar with the parsed value. The value can also
* be a key separated array.
*
* @param {string} name
* The key corresponding to the `value` string in the object
* @param {string} value
* The string to be parsed into an object
*/
- parseItemValue: function (name, originalValue) {
+ parseItemValue(name, originalValue) {
// Find if value is URLEncoded ie
let decodedValue = "";
try {
decodedValue = decodeURIComponent(originalValue);
} catch (e) {
// Unable to decode, nothing to do
}
let value = (decodedValue && decodedValue !== originalValue)
@@ -806,33 +792,33 @@ StorageUI.prototype = {
((json[0] || Object.keys(json)[0]) + "").match(/^(http|file|ftp)/)) {
return;
}
let jsonObject = Object.create(null);
let view = this.view;
jsonObject[name] = json;
let valueScope = view.getScopeAtIndex(1) ||
- view.addScope(L10N.getStr("storage.parsedValue.label"));
+ view.addScope(L10N.getStr("storage.parsedValue.label"));
valueScope.expanded = true;
let jsonVar = valueScope.addItem("", Object.create(null), {relaxed: true});
jsonVar.expanded = true;
jsonVar.twisty = true;
jsonVar.populate(jsonObject, {expanded: true});
- },
+ }
/**
* Tries to parse a string into an object on the basis of key-value pairs,
* separated by various separators. If failed, tries to parse for single
* separator separated values to form an array.
*
* @param {string} value
* The string to be parsed into an object or array
*/
- _extractKeyValPairs: function (value) {
+ _extractKeyValPairs(value) {
let makeObject = (keySep, pairSep) => {
let object = {};
for (let pair of value.split(pairSep)) {
let [key, val] = pair.split(keySep);
object[key] = val;
}
return object;
};
@@ -862,29 +848,29 @@ StorageUI.prototype = {
let word = `[^${p}]*`;
let wordList = `(${word}${p})+${word}`;
let regex = new RegExp(`^${wordList}$`);
if (value.match && value.match(regex)) {
return value.split(p.replace(/\\*/g, ""));
}
}
return null;
- },
+ }
/**
* Select handler for the storage tree. Fetches details of the selected item
* from the storage details and populates the storage tree.
*
* @param {string} event
* The name of the event fired
* @param {array} item
* An array of ids which represent the location of the selected item in
* the storage tree
*/
- onHostSelect: function (item) {
+ async onHostSelect(item) {
this.table.clear();
this.hideSidebar();
this.searchBox.value = "";
let [type, host] = item;
this.table.host = host;
this.table.datatype = type;
@@ -892,42 +878,42 @@ StorageUI.prototype = {
let names = null;
if (!host) {
return;
}
if (item.length > 2) {
names = [JSON.stringify(item.slice(2))];
}
- this.fetchStorageObjects(type, host, names, REASON.POPULATE);
+ await this.fetchStorageObjects(type, host, names, REASON.POPULATE);
this.itemOffset = 0;
- },
+ }
/**
* Resets the column headers in the storage table with the pased object `data`
*
* @param {string} type
* The type of storage corresponding to the after-reset columns in the
* table.
* @param {string} host
* The host name corresponding to the table after reset.
*
* @param {string} [subType]
* The sub type under the given type.
*/
- resetColumns: function* (type, host, subtype) {
+ async resetColumns(type, host, subtype) {
this.table.host = host;
this.table.datatype = type;
let uniqueKey = null;
let columns = {};
let editableFields = [];
let hiddenFields = [];
let privateFields = [];
- let fields = yield this.getCurrentFront().getFields(subtype);
+ let fields = await this.getCurrentFront().getFields(subtype);
fields.forEach(f => {
if (!uniqueKey) {
this.table.uniqueId = uniqueKey = f.name;
}
if (f.editable) {
editableFields.push(f.name);
@@ -957,28 +943,28 @@ StorageUI.prototype = {
} else {
columns[f.name] = columnName;
}
});
this.table.setColumns(columns, null, hiddenFields, privateFields);
this.hideSidebar();
- yield this.makeFieldsEditable(editableFields);
- },
+ this.makeFieldsEditable(editableFields);
+ }
/**
* Populates or updates the rows in the storage table.
*
* @param {array[object]} data
* Array of objects to be populated in the storage table
* @param {Constant} reason
* See REASON constant at top of file.
*/
- populateTable: function (data, reason) {
+ async populateTable(data, reason) {
for (let item of data) {
if (item.value) {
item.valueActor = item.value;
item.value = item.value.initial || "";
}
if (item.expires != null) {
item.expires = item.expires
? new Date(item.expires).toUTCString()
@@ -999,81 +985,81 @@ StorageUI.prototype = {
case REASON.NEW_ROW:
case REASON.NEXT_50_ITEMS:
// Update and flash the row.
this.table.push(item, false);
break;
case REASON.UPDATE:
this.table.update(item);
if (item == this.table.selectedRow && !this.sidebar.hidden) {
- this.updateObjectSidebar();
+ await this.updateObjectSidebar();
}
break;
}
this.shouldLoadMoreItems = true;
}
- },
+ }
/**
* Handles keypress event on the body table to close the sidebar when open
*
* @param {DOMEvent} event
* The event passed by the keypress event.
*/
- handleKeypress: function (event) {
+ handleKeypress(event) {
if (event.keyCode == KeyCodes.DOM_VK_ESCAPE && !this.sidebar.hidden) {
// Stop Propagation to prevent opening up of split console
this.hideSidebar();
this.sidebarToggledOpen = false;
event.stopPropagation();
event.preventDefault();
}
- },
+ }
/**
* Handles filtering the table
*/
filterItems() {
let value = this.searchBox.value;
this.table.filterItems(value, ["valueActor"]);
this._panelDoc.documentElement.classList.toggle("filtering", !!value);
- },
+ }
/**
* Handles endless scrolling for the table
*/
- handleScrollEnd: function () {
+ async handleScrollEnd() {
if (!this.shouldLoadMoreItems) {
return;
}
this.shouldLoadMoreItems = false;
this.itemOffset += 50;
let item = this.tree.selectedItem;
let [type, host] = item;
let names = null;
if (item.length > 2) {
names = [JSON.stringify(item.slice(2))];
}
- this.fetchStorageObjects(type, host, names, REASON.NEXT_50_ITEMS);
- },
+ await this.fetchStorageObjects(type, host, names, REASON.NEXT_50_ITEMS);
+ }
/**
* 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) {
+ onTablePopupShowing(event) {
let selectedItem = this.tree.selectedItem;
let type = selectedItem[0];
// IndexedDB only supports removing items from object stores (level 4 of the tree)
if ((!this.actorSupportsAddItem && !this.actorSupportsRemoveItem &&
- type !== "cookies") ||
+ type !== "cookies") ||
(type === "indexedDB" && selectedItem.length !== 4)) {
event.preventDefault();
return;
}
let rowId = this.table.contextMenuRowId;
let data = this.table.items.get(rowId);
@@ -1110,19 +1096,19 @@ StorageUI.prototype = {
let host = addEllipsis(data.host);
this._tablePopupDeleteAllFrom.hidden = false;
this._tablePopupDeleteAllFrom.setAttribute("label",
L10N.getFormatStr("storage.popupMenu.deleteAllFromLabel", host));
} else {
this._tablePopupDeleteAllFrom.hidden = true;
}
- },
+ }
- onTreePopupShowing: function (event) {
+ onTreePopupShowing(event) {
let showMenu = false;
let selectedItem = this.tree.selectedItem;
if (selectedItem) {
let type = selectedItem[0];
// 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)
@@ -1155,117 +1141,117 @@ StorageUI.prototype = {
}
this._treePopupDeleteAllSessionCookies.hidden = !showDeleteAllSessionCookies;
// The delete action is displayed for:
// - IndexedDB databases (level 3 of the tree)
// - Cache objects (level 3 of the tree)
let showDelete = (type == "indexedDB" || type == "Cache") &&
- selectedItem.length == 3;
+ selectedItem.length == 3;
this._treePopupDelete.hidden = !showDelete;
if (showDelete) {
let itemName = addEllipsis(selectedItem[selectedItem.length - 1]);
this._treePopupDelete.setAttribute("label",
L10N.getFormatStr("storage.popupMenu.deleteLabel", itemName));
}
showMenu = showDeleteAll || showDelete;
}
if (!showMenu) {
event.preventDefault();
}
- },
+ }
/**
* Handles refreshing the selected storage
*/
- onRefreshTable: function () {
- this.onHostSelect(this.tree.selectedItem);
- },
+ async onRefreshTable() {
+ await this.onHostSelect(this.tree.selectedItem);
+ }
/**
* Handles adding an item from the storage
*/
- onAddItem: function () {
+ onAddItem() {
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 () {
+ onRemoveItem() {
let [, host, ...path] = this.tree.selectedItem;
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]);
}
front.removeItem(host, name);
- },
+ }
/**
* Handles removing all items from the storage
*/
- onRemoveAll: function () {
+ onRemoveAll() {
// Cannot use this.currentActor() if the handler is called from the
// tree context menu: it returns correct value only after the table
// data from server are successfully fetched (and that's async).
let [, host, ...path] = this.tree.selectedItem;
let front = this.getCurrentFront();
let name = path.length > 0 ? JSON.stringify(path) : undefined;
front.removeAll(host, name);
- },
+ }
/**
* Handles removing all session cookies from the storage
*/
- onRemoveAllSessionCookies: function () {
+ onRemoveAllSessionCookies() {
// 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 front = this.getCurrentFront();
let name = path.length > 0 ? JSON.stringify(path) : undefined;
front.removeAllSessionCookies(host, name);
- },
+ }
/**
* Handles removing all cookies with exactly the same domain as the
* cookie in the selected row.
*/
- onRemoveAllFrom: function () {
+ onRemoveAllFrom() {
let [, host] = this.tree.selectedItem;
let front = this.getCurrentFront();
let rowId = this.table.contextMenuRowId;
let data = this.table.items.get(rowId);
front.removeAll(host, data.host);
- },
+ }
- onRemoveTreeItem: function () {
+ onRemoveTreeItem() {
let [type, host, ...path] = this.tree.selectedItem;
if (type == "indexedDB" && path.length == 1) {
this.removeDatabase(host, path[0]);
} else if (type == "Cache" && path.length == 1) {
this.removeCache(host, path[0]);
}
- },
+ }
- removeDatabase: function (host, dbName) {
+ removeDatabase(host, dbName) {
let front = this.getCurrentFront();
front.removeDatabase(host, dbName).then(result => {
if (result.blocked) {
let notificationBox = this._toolbox.getNotificationBox();
notificationBox.appendNotification(
L10N.getFormatStr("storage.idb.deleteBlocked", dbName),
"storage-idb-delete-blocked",
@@ -1275,25 +1261,42 @@ StorageUI.prototype = {
}).catch(error => {
let notificationBox = this._toolbox.getNotificationBox();
notificationBox.appendNotification(
L10N.getFormatStr("storage.idb.deleteError", dbName),
"storage-idb-delete-error",
null,
notificationBox.PRIORITY_CRITICAL_LOW);
});
- },
+ }
- removeCache: function (host, cacheName) {
+ removeCache(host, cacheName) {
let front = this.getCurrentFront();
front.removeItem(host, JSON.stringify([ cacheName ]));
- },
-};
+ }
+}
+
+exports.StorageUI = StorageUI;
// 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);
});
}
+
+function addEllipsis(name) {
+ if (name.length > ITEM_NAME_MAX_LENGTH) {
+ if (/^https?:/.test(name)) {
+ // For URLs, add ellipsis in the middle
+ const halfLen = ITEM_NAME_MAX_LENGTH / 2;
+ return name.slice(0, halfLen) + ELLIPSIS + name.slice(-halfLen);
+ }
+
+ // For other strings, add ellipsis at the end
+ return name.substr(0, ITEM_NAME_MAX_LENGTH) + ELLIPSIS;
+ }
+
+ return name;
+}