Bug 1443461 - Change devtools/client/storage/ui.js to ES6 classes r?ochameau draft
authorMichael Ratcliffe <mratcliffe@mozilla.com>
Tue, 06 Mar 2018 16:06:04 +0000
changeset 764929 8d0845729e55c9fc8c9107845ec239c4268d184d
parent 764770 a6a32fb286fa9e5d5f6d5b3b77423ab6b96c9502
push id101896
push userbmo:mratcliffe@mozilla.com
push dateThu, 08 Mar 2018 16:43:51 +0000
reviewersochameau
bugs1443461
milestone60.0a1
Bug 1443461 - Change devtools/client/storage/ui.js to ES6 classes r?ochameau Small changes due to conflicts on rebase... async test runs fine but I will do another try run just in case. MozReview-Commit-ID: I9TwRArZl2f
devtools/client/storage/panel.js
devtools/client/storage/test/browser_storage_dynamic_updates_cookies.js
devtools/client/storage/ui.js
--- a/devtools/client/storage/panel.js
+++ b/devtools/client/storage/panel.js
@@ -8,42 +8,39 @@
 
 const EventEmitter = require("devtools/shared/old-event-emitter");
 
 loader.lazyRequireGetter(this, "StorageFront",
                          "devtools/shared/fronts/storage", true);
 loader.lazyRequireGetter(this, "StorageUI",
                          "devtools/client/storage/ui", true);
 
-var StoragePanel = this.StoragePanel =
-function StoragePanel(panelWin, toolbox) {
-  EventEmitter.decorate(this);
+class StoragePanel {
+  constructor(panelWin, toolbox) {
+    EventEmitter.decorate(this);
 
-  this._toolbox = toolbox;
-  this._target = toolbox.target;
-  this._panelWin = panelWin;
+    this._toolbox = toolbox;
+    this._target = toolbox.target;
+    this._panelWin = panelWin;
 
-  this.destroy = this.destroy.bind(this);
-};
+    this.destroy = this.destroy.bind(this);
+  }
 
-exports.StoragePanel = StoragePanel;
-
-StoragePanel.prototype = {
   get target() {
     return this._toolbox.target;
-  },
+  }
 
   get panelWindow() {
     return this._panelWin;
-  },
+  }
 
   /**
    * open is effectively an asynchronous constructor
    */
-  open: function () {
+  open() {
     let targetPromise;
     // We always interact with the target as if it were remote
     if (!this.target.isRemote) {
       targetPromise = this.target.makeRemote();
     } else {
       targetPromise = Promise.resolve(this.target);
     }
 
@@ -56,32 +53,34 @@ StoragePanel.prototype = {
       this.isReady = true;
       this.emit("ready");
 
       return this;
     }).catch(e => {
       console.log("error while opening storage panel", e);
       this.destroy();
     });
-  },
+  }
 
   /**
    * Destroy the storage inspector.
    */
-  destroy: function () {
+  destroy() {
     if (!this._destroyed) {
       this.UI.destroy();
       this.UI = null;
 
       // Destroy front to ensure packet handler is removed from client
       this._front.destroy();
       this._front = null;
       this._destroyed = true;
 
       this._target.off("close", this.destroy);
       this._target = null;
       this._toolbox = null;
       this._panelWin = null;
     }
 
     return Promise.resolve(null);
-  },
-};
+  }
+}
+
+exports.StoragePanel = StoragePanel;
--- 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;
+}