Merge m-c to inbound. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Thu, 01 Jun 2017 16:45:03 -0400
changeset 361909 fedb93c6e6f7355facca060e96ee97f7f46f3f87
parent 361908 60752371afbb6a9e0631c55ea09a05c0d767d04b (current diff)
parent 361874 b138d2f271fdb598bf8a66c2dcb7fe391ca2a96f (diff)
child 361910 40b3da698ff5f463f4374b7178943124f1851512
push id31948
push userkwierso@gmail.com
push dateFri, 02 Jun 2017 00:27:01 +0000
treeherdermozilla-central@1e229cf8933b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to inbound. a=merge
dom/media/encoder/TrackEncoder.cpp
--- a/browser/base/content/browser-sync.js
+++ b/browser/base/content/browser-sync.js
@@ -299,16 +299,17 @@ var gSync = {
     const fragment = document.createDocumentFragment();
 
     const onTargetDeviceCommand = (event) => {
       let clients = event.target.getAttribute("clientId") ?
         [event.target.getAttribute("clientId")] :
         this.remoteClients.map(client => client.id);
 
       clients.forEach(clientId => this.sendTabToDevice(url, clientId, title));
+      gPageActionButton.panel.hidePopup();
     }
 
     function addTargetDevice(clientId, name, clientType) {
       const targetDevice = createDeviceNodeFn(clientId, name, clientType);
       targetDevice.addEventListener("command", onTargetDeviceCommand, true);
       targetDevice.classList.add("sync-menuitem", "sendtab-target");
       targetDevice.setAttribute("clientId", clientId);
       targetDevice.setAttribute("clientType", clientType);
--- a/browser/base/content/test/static/browser_all_files_referenced.js
+++ b/browser/base/content/test/static/browser_all_files_referenced.js
@@ -124,18 +124,16 @@ var whitelist = new Set([
   {file: "chrome://browser/skin/customizableui/customize-illustration-rtl@2x.png",
    platforms: ["linux", "win"]},
   {file: "chrome://browser/skin/customizableui/customize-illustration@2x.png",
    platforms: ["linux", "win"]},
   {file: "chrome://browser/skin/customizableui/info-icon-customizeTip@2x.png",
    platforms: ["linux", "win"]},
   {file: "chrome://browser/skin/customizableui/panelarrow-customizeTip@2x.png",
    platforms: ["linux", "win"]},
-  // Bug 1320058
-  {file: "chrome://browser/skin/preferences/saveFile.png", platforms: ["win"]},
   // Bug 1316187
   {file: "chrome://global/content/customizeToolbar.xul"},
   // Bug 1343837
   {file: "chrome://global/content/findUtils.js"},
   // Bug 1343843
   {file: "chrome://global/content/url-classifier/unittests.xul"},
   // Bug 1343839
   {file: "chrome://global/locale/headsUpDisplay.properties"},
--- a/browser/themes/shared/sidebar.inc.css
+++ b/browser/themes/shared/sidebar.inc.css
@@ -50,17 +50,16 @@
 
 #sidebar-title {
   margin: 0;
   padding: 0;
   padding-inline-start: 8px;
   padding-inline-end: 4px;
 }
 
-#sidebar-icon,
 #sidebar-switcher-arrow,
 #sidebar-close > .toolbarbutton-icon {
   -moz-context-properties: fill;
   fill: currentColor;
   opacity: 0.8;
 }
 
 #sidebar-switcher-arrow {
@@ -101,24 +100,36 @@
 #sidebarMenu-popup .subviewbutton-iconic > .toolbarbutton-icon {
   padding-inline-start: 16px;
 }
 #sidebarMenu-popup .subviewbutton-iconic > .toolbarbutton-text {
   padding-inline-start: 0;
 }
 %endif
 
-/* Use bookmarks star as default icon for the sidebar box (including when opening a web page) */
+
+#sidebar-box[sidebarcommand="viewWebPanelsSidebar"] > #sidebar-header > #sidebar-switcher-target > #sidebar-icon {
+  list-style-image: url(chrome://mozapps/skin/places/defaultFavicon.svg);
+}
+
 #sidebar-switcher-bookmarks > .toolbarbutton-icon,
-#sidebar-box[sidebarcommand="viewWebPanelsSidebar"] > #sidebar-header > #sidebar-switcher-target > #sidebar-icon,
 #sidebar-box[sidebarcommand="viewBookmarksSidebar"] > #sidebar-header > #sidebar-switcher-target > #sidebar-icon {
   list-style-image: url(chrome://browser/skin/bookmark.svg);
+  -moz-context-properties: fill;
+  fill: currentColor;
+  opacity: 0.8;
 }
 
 #sidebar-switcher-history > .toolbarbutton-icon,
 #sidebar-box[sidebarcommand="viewHistorySidebar"] > #sidebar-header > #sidebar-switcher-target > #sidebar-icon {
   list-style-image: url(chrome://browser/skin/history.svg);
+  -moz-context-properties: fill;
+  fill: currentColor;
+  opacity: 0.8;
 }
 
 #sidebar-switcher-tabs > .toolbarbutton-icon,
 #sidebar-box[sidebarcommand="viewTabsSidebar"] > #sidebar-header > #sidebar-switcher-target > #sidebar-icon {
   list-style-image: url(chrome://browser/skin/sync.svg);
+  -moz-context-properties: fill;
+  fill: currentColor;
+  opacity: 0.8;
 }
--- a/browser/themes/windows/preferences/applications.css
+++ b/browser/themes/windows/preferences/applications.css
@@ -32,17 +32,17 @@
 
 richlistitem[appHandlerIcon="ask"],
 menuitem[appHandlerIcon="ask"] {
   list-style-image: url("chrome://browser/skin/preferences/alwaysAsk.png");
 }
 
 richlistitem[appHandlerIcon="save"],
 menuitem[appHandlerIcon="save"] {
-  list-style-image: url("chrome://browser/skin/preferences/application.png");
+  list-style-image: url("chrome://browser/skin/preferences/saveFile.png");
 }
 
 richlistitem[appHandlerIcon="feed"],
 menuitem[appHandlerIcon="feed"] {
   list-style-image: url("chrome://browser/skin/page-livemarks.png");
 }
 
 richlistitem[appHandlerIcon="plugin"],
--- a/devtools/client/aboutdebugging/test/browser_addons_remove.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_remove.js
@@ -23,18 +23,18 @@ add_task(function* removeLegacyExtension
     path: "addons/unpacked/install.rdf",
     name: addonName,
   });
 
   ok(getTargetEl(document, addonID), "add-on is shown");
 
   // Click the remove button and wait for the DOM to change.
   const addonListMutation = waitForMutation(
-    getTemporaryAddonList(document),
-    { childList: true });
+    getTemporaryAddonList(document).parentNode,
+    { childList: true, subtree: true });
   getRemoveButton(document, addonID).click();
   yield addonListMutation;
 
   ok(!getTargetEl(document, addonID), "add-on is not shown");
 
   yield closeAboutDebugging(tab);
 });
 
@@ -52,18 +52,18 @@ add_task(function* removeWebextension() 
     name: addonName,
     isWebExtension: true,
   });
 
   ok(getTargetEl(document, addonID), "add-on is shown");
 
   // Click the remove button and wait for the DOM to change.
   const addonListMutation = waitForMutation(
-    getTemporaryAddonList(document),
-    { childList: true });
+    getTemporaryAddonList(document).parentNode,
+    { childList: true, subtree: true });
   getRemoveButton(document, addonID).click();
   yield addonListMutation;
 
   ok(!getTargetEl(document, addonID), "add-on is not shown");
 
   yield closeAboutDebugging(tab);
 });
 
--- a/devtools/client/aboutdebugging/test/head.js
+++ b/devtools/client/aboutdebugging/test/head.js
@@ -208,17 +208,18 @@ function* installAddon({document, path, 
   let names = [...addonList.querySelectorAll(".target-name")];
   names = names.map(element => element.textContent);
   ok(names.includes(name),
     "The addon name appears in the list of addons: " + names);
 }
 
 function* uninstallAddon({document, id, name}) {
   let addonList = getAddonListWithAddon(document, id);
-  let addonListMutation = waitForMutation(addonList, { childList: true });
+  let addonListMutation = waitForMutation(addonList.parentNode,
+                                          { childList: true, subtree: true });
 
   // Now uninstall this addon
   yield new Promise(done => {
     AddonManager.getAddonByID(id, addon => {
       let listener = {
         onUninstalled: function (uninstalledAddon) {
           if (uninstalledAddon != addon) {
             return;
@@ -228,23 +229,28 @@ function* uninstallAddon({document, id, 
           done();
         }
       };
       AddonManager.addAddonListener(listener);
       addon.uninstall();
     });
   });
 
-  // Ensure that the UI removes the addon from the list
   yield addonListMutation;
-  let names = [...addonList.querySelectorAll(".target-name")];
-  names = names.map(element => element.textContent);
-  ok(!names.includes(name),
-    "After uninstall, the addon name disappears from the list of addons: "
-    + names);
+
+  // If parentNode is none, that means the entire addonList was removed from the
+  // document. This happens when the addon we are removing is the last one.
+  if (addonList.parentNode !== null) {
+    // Ensure that the UI removes the addon from the list
+    let names = [...addonList.querySelectorAll(".target-name")];
+    names = names.map(element => element.textContent);
+    ok(!names.includes(name),
+      "After uninstall, the addon name disappears from the list of addons: "
+      + names);
+  }
 }
 
 /**
  * Returns a promise that will resolve when the add-on list has been updated.
  *
  * @param {Node} document
  * @return {Promise}
  */
--- a/devtools/client/inspector/shared/highlighters-overlay.js
+++ b/devtools/client/inspector/shared/highlighters-overlay.js
@@ -272,17 +272,19 @@ HighlightersOverlay.prototype = {
     if (this.highlighters[type]) {
       return this.highlighters[type];
     }
 
     let highlighter;
 
     try {
       highlighter = yield utils.getHighlighterByType(type);
-    } catch (e) {}
+    } catch (e) {
+      // Ignore any error
+    }
 
     if (!highlighter) {
       return null;
     }
 
     this.highlighters[type] = highlighter;
     return highlighter;
   }),
--- a/devtools/client/shared/widgets/tooltip/EventTooltipHelper.js
+++ b/devtools/client/shared/widgets/tooltip/EventTooltipHelper.js
@@ -106,26 +106,30 @@ EventTooltip.prototype = {
       if (listener.hide.filename) {
         text = L10N.getStr("eventsTooltip.unknownLocation");
         title = L10N.getStr("eventsTooltip.unknownLocationExplanation");
       } else if (sourceMapService) {
         const location = this._parseLocation(text);
         if (location) {
           sourceMapService.originalPositionFor(location.url, location.line)
             .then((originalLocation) => {
-              if (originalLocation) {
-                const { sourceUrl, line } = originalLocation;
-                let newURI = sourceUrl + ":" + line;
-                filename.textContent = newURI;
-                filename.setAttribute("title", newURI);
-                let eventEditor = this._eventEditors.get(content);
-                eventEditor.uri = newURI;
+              // Do nothing if the tooltip was destroyed while we were
+              // waiting for a response.
+              if (this._tooltip) {
+                if (originalLocation) {
+                  const { sourceUrl, line } = originalLocation;
+                  let newURI = sourceUrl + ":" + line;
+                  filename.textContent = newURI;
+                  filename.setAttribute("title", newURI);
+                  let eventEditor = this._eventEditors.get(content);
+                  eventEditor.uri = newURI;
+                }
+                // This is emitted for testing.
+                this._tooltip.emit("event-tooltip-source-map-ready");
               }
-              // This is emitted for testing.
-              this._tooltip.emit("event-tooltip-source-map-ready");
             });
         }
       }
 
       filename.textContent = text;
       filename.setAttribute("title", title);
       header.appendChild(filename);
 
--- a/devtools/client/webconsole/new-console-output/components/console-output.js
+++ b/devtools/client/webconsole/new-console-output/components/console-output.js
@@ -7,37 +7,37 @@ const {
   createClass,
   createFactory,
   DOM: dom,
   PropTypes
 } = require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 
 const {
-  getAllMessages,
   getAllMessagesUiById,
   getAllMessagesTableDataById,
+  getVisibleMessages,
 } = require("devtools/client/webconsole/new-console-output/selectors/messages");
 const MessageContainer = createFactory(require("devtools/client/webconsole/new-console-output/components/message-container").MessageContainer);
 
 const ConsoleOutput = createClass({
 
   displayName: "ConsoleOutput",
 
   propTypes: {
-    messages: PropTypes.object.isRequired,
     messagesUi: PropTypes.object.isRequired,
     serviceContainer: PropTypes.shape({
       attachRefToHud: PropTypes.func.isRequired,
       openContextMenu: PropTypes.func.isRequired,
       sourceMapService: PropTypes.object,
     }),
     dispatch: PropTypes.func.isRequired,
     timestampsVisible: PropTypes.bool,
     messagesTableData: PropTypes.object.isRequired,
+    visibleMessages: PropTypes.array.isRequired,
   },
 
   componentDidMount() {
     // Do the scrolling in the nextTick since this could hit console startup performances.
     // See https://bugzilla.mozilla.org/show_bug.cgi?id=1355869
     setTimeout(() => {
       scrollToBottom(this.outputNode);
     }, 0);
@@ -48,17 +48,17 @@ const ConsoleOutput = createClass({
     const outputNode = this.outputNode;
     if (!outputNode || !outputNode.lastChild) {
       return;
     }
 
     // Figure out if we are at the bottom. If so, then any new message should be scrolled
     // into view.
     const lastChild = outputNode.lastChild;
-    const delta = nextProps.messages.size - this.props.messages.size;
+    const delta = nextProps.visibleMessages.length - this.props.visibleMessages.length;
     this.shouldScrollBottom = delta > 0 && isScrolledToBottom(lastChild, outputNode);
   },
 
   componentDidUpdate() {
     if (this.shouldScrollBottom) {
       scrollToBottom(this.outputNode);
     }
   },
@@ -67,24 +67,24 @@ const ConsoleOutput = createClass({
     this.props.serviceContainer.openContextMenu(e);
     e.stopPropagation();
     e.preventDefault();
   },
 
   render() {
     let {
       dispatch,
-      messages,
+      visibleMessages,
       messagesUi,
       messagesTableData,
       serviceContainer,
       timestampsVisible,
     } = this.props;
 
-    let messageNodes = messages.map((message) => {
+    let messageNodes = visibleMessages.map((message) => {
       return (
         MessageContainer({
           dispatch,
           message,
           key: message.id,
           serviceContainer,
           open: messagesUi.includes(message.id),
           tableData: messagesTableData.get(message.id),
@@ -115,16 +115,16 @@ function isScrolledToBottom(outputNode, 
   let lastNodeHeight = outputNode.lastChild ?
                        outputNode.lastChild.clientHeight : 0;
   return scrollNode.scrollTop + scrollNode.clientHeight >=
          scrollNode.scrollHeight - lastNodeHeight / 2;
 }
 
 function mapStateToProps(state, props) {
   return {
-    messages: getAllMessages(state),
+    visibleMessages: getVisibleMessages(state),
     messagesUi: getAllMessagesUiById(state),
     messagesTableData: getAllMessagesTableDataById(state),
     timestampsVisible: state.ui.timestampsVisible,
   };
 }
 
 module.exports = connect(mapStateToProps)(ConsoleOutput);
--- a/devtools/client/webconsole/new-console-output/reducers/messages.js
+++ b/devtools/client/webconsole/new-console-output/reducers/messages.js
@@ -1,49 +1,59 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* 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 Immutable = require("devtools/client/shared/vendor/immutable");
+const { l10n } = require("devtools/client/webconsole/new-console-output/utils/messages");
+
 const constants = require("devtools/client/webconsole/new-console-output/constants");
 const {isGroupType} = require("devtools/client/webconsole/new-console-output/utils/messages");
-const Services = require("Services");
-
-const logLimit = Math.max(Services.prefs.getIntPref("devtools.hud.loglimit"), 1);
+const {
+  MESSAGE_TYPE,
+  MESSAGE_SOURCE
+} = require("devtools/client/webconsole/new-console-output/constants");
+const { getGripPreviewItems } = require("devtools/client/shared/components/reps/reps");
+const { getSourceNames } = require("devtools/client/shared/source-utils");
 
 const MessageState = Immutable.Record({
   // List of all the messages added to the console.
-  messagesById: Immutable.List(),
+  messagesById: Immutable.OrderedMap(),
+  // Array of the visible messages.
+  visibleMessages: [],
   // List of the message ids which are opened.
   messagesUiById: Immutable.List(),
   // Map of the form {messageId : tableData}, which represent the data passed
   // as an argument in console.table calls.
   messagesTableDataById: Immutable.Map(),
   // Map of the form {groupMessageId : groupArray},
   // where groupArray is the list of of all the parent groups' ids of the groupMessageId.
   groupsById: Immutable.Map(),
   // Message id of the current group (no corresponding console.groupEnd yet).
   currentGroup: null,
   // List of removed messages is used to release related (parameters) actors.
   // This array is not supposed to be consumed by any UI component.
   removedMessages: [],
 });
 
-function messages(state = new MessageState(), action) {
+function messages(state = new MessageState(), action, filtersState, prefsState) {
   const {
     messagesById,
     messagesUiById,
     messagesTableDataById,
     groupsById,
     currentGroup,
+    visibleMessages,
   } = state;
 
+  const {logLimit} = prefsState;
+
   switch (action.type) {
     case constants.MESSAGE_ADD:
       let newMessage = action.message;
 
       if (newMessage.type === constants.MESSAGE_TYPE.NULL_MESSAGE) {
         // When the message has a NULL type, we don't add it.
         return state;
       }
@@ -51,80 +61,154 @@ function messages(state = new MessageSta
       if (newMessage.type === constants.MESSAGE_TYPE.END_GROUP) {
         // Compute the new current group.
         return state.set("currentGroup", getNewCurrentGroup(currentGroup, groupsById));
       }
 
       if (newMessage.allowRepeating && messagesById.size > 0) {
         let lastMessage = messagesById.last();
         if (lastMessage.repeatId === newMessage.repeatId) {
-          return state.withMutations(function (record) {
-            record.set("messagesById", messagesById.pop().push(
-              newMessage.set("repeat", lastMessage.repeat + 1)
-            ));
-          });
+          return state.set(
+            "messagesById",
+            messagesById.set(
+              lastMessage.id,
+              lastMessage.set("repeat", lastMessage.repeat + 1)
+            )
+          );
         }
       }
 
-      let parentGroups = getParentGroups(currentGroup, groupsById);
-      newMessage = newMessage.withMutations(function (message) {
-        message.set("groupId", currentGroup);
-        message.set("indent", parentGroups.length);
-      });
-
       return state.withMutations(function (record) {
         // Add the new message with a reference to the parent group.
-        record.set("messagesById", messagesById.push(newMessage));
+        let parentGroups = getParentGroups(currentGroup, groupsById);
+        const addedMessage = newMessage.withMutations(function (message) {
+          message.set("groupId", currentGroup);
+          message.set("indent", parentGroups.length);
+        });
+        record.set(
+          "messagesById",
+          messagesById.set(newMessage.id, addedMessage)
+        );
 
         if (newMessage.type === "trace") {
           // We want the stacktrace to be open by default.
           record.set("messagesUiById", messagesUiById.push(newMessage.id));
         } else if (isGroupType(newMessage.type)) {
           record.set("currentGroup", newMessage.id);
           record.set("groupsById", groupsById.set(newMessage.id, parentGroups));
 
           if (newMessage.type === constants.MESSAGE_TYPE.START_GROUP) {
             // We want the group to be open by default.
             record.set("messagesUiById", messagesUiById.push(newMessage.id));
           }
         }
 
+        if (shouldMessageBeVisible(addedMessage, record, filtersState)) {
+          record.set("visibleMessages", [...visibleMessages, newMessage.id]);
+        }
+
         // Remove top level message if the total count of top level messages
         // exceeds the current limit.
-        limitTopLevelMessageCount(state, record);
+        if (record.messagesById.size > logLimit) {
+          limitTopLevelMessageCount(state, record, logLimit);
+        }
       });
+
     case constants.MESSAGES_CLEAR:
-      return state.withMutations(function (record) {
+      return new MessageState({
         // Store all removed messages associated with some arguments.
         // This array is used by `releaseActorsEnhancer` to release
         // all related backend actors.
-        record.set("removedMessages",
-          record.messagesById.filter(msg => msg.parameters).toArray());
+        "removedMessages": [...state.messagesById].reduce((res, [id, msg]) => {
+          if (msg.parameters) {
+            res.push(msg);
+          }
+          return res;
+        }, [])
+      });
+
+    case constants.MESSAGE_OPEN:
+      return state.withMutations(function (record) {
+        record.set("messagesUiById", messagesUiById.push(action.id));
 
-        // Clear immutable state.
-        record.set("messagesById", Immutable.List());
-        record.set("messagesUiById", Immutable.List());
-        record.set("groupsById", Immutable.Map());
-        record.set("currentGroup", null);
+        // If the message is a group
+        if (isGroupType(messagesById.get(action.id).type)) {
+          // We want to make its children visible
+          const messagesToShow = [...messagesById].reduce((res, [id, message]) => {
+            if (
+              !visibleMessages.includes(message.id)
+              && getParentGroups(message.groupId, groupsById).includes(action.id)
+              && shouldMessageBeVisible(
+                message,
+                record,
+                filtersState,
+                // We want to check if the message is in an open group
+                // only if it is not a direct child of the group we're opening.
+                message.groupId !== action.id
+              )
+            ) {
+              res.push(id);
+            }
+            return res;
+          }, []);
+
+          // We can then insert the messages ids right after the one of the group.
+          const insertIndex = visibleMessages.indexOf(action.id) + 1;
+          record.set("visibleMessages", [
+            ...visibleMessages.slice(0, insertIndex),
+            ...messagesToShow,
+            ...visibleMessages.slice(insertIndex),
+          ]);
+        }
       });
-    case constants.MESSAGE_OPEN:
-      return state.set("messagesUiById", messagesUiById.push(action.id));
+
     case constants.MESSAGE_CLOSE:
-      let index = state.messagesUiById.indexOf(action.id);
-      return state.deleteIn(["messagesUiById", index]);
+      return state.withMutations(function (record) {
+        let messageId = action.id;
+        let index = record.messagesUiById.indexOf(messageId);
+        record.deleteIn(["messagesUiById", index]);
+
+        // If the message is a group
+        if (isGroupType(messagesById.get(messageId).type)) {
+          // Hide all its children
+          record.set(
+            "visibleMessages",
+            [...visibleMessages].filter(id => getParentGroups(
+                messagesById.get(id).groupId,
+                groupsById
+              ).includes(messageId) === false
+            )
+          );
+        }
+      });
+
     case constants.MESSAGE_TABLE_RECEIVE:
       const {id, data} = action;
       return state.set("messagesTableDataById", messagesTableDataById.set(id, data));
     case constants.NETWORK_MESSAGE_UPDATE:
       let updateMessage = action.message;
-      return state.set("messagesById", messagesById.map((message) =>
-        (message.id === updateMessage.id) ? updateMessage : message
+      return state.set("messagesById", messagesById.set(
+        updateMessage.id,
+        updateMessage
       ));
+
     case constants.REMOVED_MESSAGES_CLEAR:
       return state.set("removedMessages", []);
+
+    case constants.FILTER_TOGGLE:
+    case constants.FILTER_TEXT_SET:
+      return state.set(
+        "visibleMessages",
+        [...messagesById].reduce((res, [messageId, message]) => {
+          if (shouldMessageBeVisible(message, state, filtersState)) {
+            res.push(messageId);
+          }
+          return res;
+        }, [])
+      );
   }
 
   return state;
 }
 
 function getNewCurrentGroup(currentGoup, groupsById) {
   let newCurrentGroup = null;
   if (currentGoup) {
@@ -151,90 +235,303 @@ function getParentGroups(currentGroup, g
     }
   }
 
   return groups;
 }
 
 /**
  * Remove all top level messages that exceeds message limit.
- * Also release all backend actors associated with these
- * messages.
+ * Also populate an array of all backend actors associated with these
+ * messages so they can be released.
  */
-function limitTopLevelMessageCount(state, record) {
-  let tempRecord = {
-    messagesById: record.messagesById,
-    messagesUiById: record.messagesUiById,
-    messagesTableDataById: record.messagesTableDataById,
-    groupsById: record.groupsById,
-  };
+function limitTopLevelMessageCount(state, record, logLimit) {
+  let topLevelCount = record.groupsById.size === 0
+    ? record.messagesById.size
+    : getToplevelMessageCount(record);
 
-  let removedMessages = state.removedMessages;
-
-  // Remove top level messages logged over the limit.
-  let topLevelCount = getToplevelMessageCount(tempRecord);
-  while (topLevelCount > logLimit) {
-    removedMessages.push(...removeFirstMessage(tempRecord));
-    topLevelCount--;
+  if (topLevelCount <= logLimit) {
+    return record;
   }
 
-  // Filter out messages with no arguments. Only actual arguments
-  // can be associated with backend actors.
-  removedMessages = state.removedMessages.filter(msg => msg.parameters);
+  const removedMessagesId = [];
+  const removedMessages = [];
+  let visibleMessages = [...record.visibleMessages];
+
+  let cleaningGroup = false;
+  record.messagesById.forEach((message, id) => {
+    // If we were cleaning a group and the current message does not have
+    // a groupId, we're done cleaning.
+    if (cleaningGroup === true && !message.groupId) {
+      cleaningGroup = false;
+    }
+
+    // If we're not cleaning a group and the message count is below the logLimit,
+    // we exit the forEach iteration.
+    if (cleaningGroup === false && topLevelCount <= logLimit) {
+      return false;
+    }
+
+    // If we're not currently cleaning a group, and the current message is identified
+    // as a group, set the cleaning flag to true.
+    if (cleaningGroup === false && record.groupsById.has(id)) {
+      cleaningGroup = true;
+    }
+
+    if (!message.groupId) {
+      topLevelCount--;
+    }
+
+    removedMessagesId.push(id);
 
-  // Update original record object
-  record.set("messagesById", tempRecord.messagesById);
-  record.set("messagesUiById", tempRecord.messagesUiById);
-  record.set("messagesTableDataById", tempRecord.messagesTableDataById);
-  record.set("groupsById", tempRecord.groupsById);
-  record.set("removedMessages", removedMessages);
+    // Filter out messages with no arguments. Only actual arguments
+    // can be associated with backend actors.
+    if (message && message.parameters) {
+      removedMessages.push(message);
+    }
+
+    const index = visibleMessages.indexOf(id);
+    if (index > -1) {
+      visibleMessages.splice(index, 1);
+    }
+
+    return true;
+  });
+
+  if (removedMessages.length > 0) {
+    record.set("removedMessages", record.removedMessages.concat(removedMessages));
+  }
+
+  if (record.visibleMessages.length > visibleMessages.length) {
+    record.set("visibleMessages", visibleMessages);
+  }
+
+  const isInRemovedId = id => removedMessagesId.includes(id);
+  const mapHasRemovedIdKey = map => map.findKey((value, id) => isInRemovedId(id));
+  const cleanUpCollection = map => removedMessagesId.forEach(id => map.remove(id));
+
+  record.set("messagesById", record.messagesById.withMutations(cleanUpCollection));
+
+  if (record.messagesUiById.find(isInRemovedId)) {
+    record.set("messagesUiById", record.messagesUiById.withMutations(cleanUpCollection));
+  }
+  if (mapHasRemovedIdKey(record.messagesTableDataById)) {
+    record.set("messagesTableDataById",
+      record.messagesTableDataById.withMutations(cleanUpCollection));
+  }
+  if (mapHasRemovedIdKey(record.groupsById)) {
+    record.set("groupsById", record.groupsById.withMutations(cleanUpCollection));
+  }
+
+  return record;
 }
 
 /**
  * Returns total count of top level messages (those which are not
  * within a group).
  */
 function getToplevelMessageCount(record) {
-  return [...record.messagesById].filter(message => !message.groupId).length;
+  return record.messagesById.count(message => !message.groupId);
+}
+
+function shouldMessageBeVisible(message, messagesState, filtersState, checkGroup = true) {
+  return (
+    (
+      checkGroup === false
+      || isInOpenedGroup(message, messagesState.groupsById, messagesState.messagesUiById)
+    )
+    && (
+      isUnfilterable(message)
+      || (
+        matchLevelFilters(message, filtersState)
+        && matchCssFilters(message, filtersState)
+        && matchNetworkFilters(message, filtersState)
+        && matchSearchFilters(message, filtersState)
+      )
+    )
+  );
+}
+
+function isUnfilterable(message) {
+  return [
+    MESSAGE_TYPE.COMMAND,
+    MESSAGE_TYPE.RESULT,
+    MESSAGE_TYPE.START_GROUP,
+    MESSAGE_TYPE.START_GROUP_COLLAPSED,
+  ].includes(message.type);
+}
+
+function isInOpenedGroup(message, groupsById, messagesUI) {
+  return !message.groupId
+    || (
+      !isGroupClosed(message.groupId, messagesUI)
+      && !hasClosedParentGroup(groupsById.get(message.groupId), messagesUI)
+    );
+}
+
+function hasClosedParentGroup(group, messagesUI) {
+  return group.some(groupId => isGroupClosed(groupId, messagesUI));
+}
+
+function isGroupClosed(groupId, messagesUI) {
+  return messagesUI.includes(groupId) === false;
+}
+
+function matchLevelFilters(message, filters) {
+  return filters.get(message.level) === true;
+}
+
+function matchNetworkFilters(message, filters) {
+  return (
+    message.source !== MESSAGE_SOURCE.NETWORK
+    || (filters.get("net") === true && message.isXHR === false)
+    || (filters.get("netxhr") === true && message.isXHR === true)
+  );
+}
+
+function matchCssFilters(message, filters) {
+  return (
+    message.source != MESSAGE_SOURCE.CSS
+    || filters.get("css") === true
+  );
+}
+
+function matchSearchFilters(message, filters) {
+  let text = filters.text || "";
+  return (
+    text === ""
+    // Look for a match in parameters.
+    || isTextInParameters(text, message.parameters)
+    // Look for a match in location.
+    || isTextInFrame(text, message.frame)
+    // Look for a match in net events.
+    || isTextInNetEvent(text, message.request)
+    // Look for a match in stack-trace.
+    || isTextInStackTrace(text, message.stacktrace)
+    // Look for a match in messageText.
+    || isTextInMessageText(text, message.messageText)
+    // Look for a match in notes.
+    || isTextInNotes(text, message.notes)
+  );
 }
 
 /**
- * Remove first (the oldest) message from the store. The methods removes
- * also all its references and children from the store.
- *
- * @return {Array} Flat array of removed messages.
- */
-function removeFirstMessage(record) {
-  let firstMessage = record.messagesById.first();
-  record.messagesById = record.messagesById.shift();
+* Returns true if given text is included in provided stack frame.
+*/
+function isTextInFrame(text, frame) {
+  if (!frame) {
+    return false;
+  }
+
+  const {
+    functionName,
+    line,
+    column,
+    source
+  } = frame;
+  const { short } = getSourceNames(source);
+
+  return `${functionName ? functionName + " " : ""}${short}:${line}:${column}`
+    .toLocaleLowerCase()
+    .includes(text.toLocaleLowerCase());
+}
+
+/**
+* Returns true if given text is included in provided parameters.
+*/
+function isTextInParameters(text, parameters) {
+  if (!parameters) {
+    return false;
+  }
 
-  // Remove from list of opened groups.
-  let uiIndex = record.messagesUiById.indexOf(firstMessage);
-  if (uiIndex >= 0) {
-    record.messagesUiById = record.messagesUiById.delete(uiIndex);
+  text = text.toLocaleLowerCase();
+  return getAllProps(parameters).some(prop =>
+    (prop + "").toLocaleLowerCase().includes(text)
+  );
+}
+
+/**
+* Returns true if given text is included in provided net event grip.
+*/
+function isTextInNetEvent(text, request) {
+  if (!request) {
+    return false;
+  }
+
+  text = text.toLocaleLowerCase();
+
+  let method = request.method.toLocaleLowerCase();
+  let url = request.url.toLocaleLowerCase();
+  return method.includes(text) || url.includes(text);
+}
+
+/**
+* Returns true if given text is included in provided stack trace.
+*/
+function isTextInStackTrace(text, stacktrace) {
+  if (!Array.isArray(stacktrace)) {
+    return false;
   }
 
-  // Remove from list of tables.
-  if (record.messagesTableDataById.has(firstMessage.id)) {
-    record.messagesTableDataById = record.messagesTableDataById.delete(firstMessage.id);
+  // isTextInFrame expect the properties of the frame object to be in the same
+  // order they are rendered in the Frame component.
+  return stacktrace.some(frame => isTextInFrame(text, {
+    functionName: frame.functionName || l10n.getStr("stacktrace.anonymousFunction"),
+    source: frame.filename,
+    lineNumber: frame.lineNumber,
+    columnNumber: frame.columnNumber
+  }));
+}
+
+/**
+* Returns true if given text is included in `messageText` field.
+*/
+function isTextInMessageText(text, messageText) {
+  if (!messageText) {
+    return false;
   }
 
-  // Remove from list of parent groups.
-  if (record.groupsById.has(firstMessage.id)) {
-    record.groupsById = record.groupsById.delete(firstMessage.id);
+  return messageText.toLocaleLowerCase().includes(text.toLocaleLowerCase());
+}
+
+/**
+* Returns true if given text is included in notes.
+*/
+function isTextInNotes(text, notes) {
+  if (!Array.isArray(notes)) {
+    return false;
   }
 
-  let removedMessages = [firstMessage];
+  return notes.some(note =>
+    // Look for a match in location.
+    isTextInFrame(text, note.frame) ||
+    // Look for a match in messageBody.
+    (
+      note.messageBody &&
+      note.messageBody.toLocaleLowerCase().includes(text.toLocaleLowerCase())
+    )
+  );
+}
 
-  // Remove all children. This loop assumes that children of removed
-  // group immediately follows the group. We use recursion since
-  // there might be inner groups.
-  let message = record.messagesById.first();
-  while (message.groupId == firstMessage.id) {
-    removedMessages.push(...removeFirstMessage(record));
-    message = record.messagesById.first();
-  }
+/**
+ * Get a flat array of all the grips and their properties.
+ *
+ * @param {Array} Grips
+ * @return {Array} Flat array of the grips and their properties.
+ */
+function getAllProps(grips) {
+  let result = grips.reduce((res, grip) => {
+    let previewItems = getGripPreviewItems(grip);
+    let allProps = previewItems.length > 0 ? getAllProps(previewItems) : [];
+    return [...res, grip, grip.class, ...allProps];
+  }, []);
 
-  // Return array with all removed messages.
-  return removedMessages;
+  // We are interested only in primitive props (to search for)
+  // not in objects and undefined previews.
+  result = result.filter(grip =>
+    typeof grip != "object" &&
+    typeof grip != "undefined"
+  );
+
+  return [...new Set(result)];
 }
 
 exports.messages = messages;
--- a/devtools/client/webconsole/new-console-output/selectors/messages.js
+++ b/devtools/client/webconsole/new-console-output/selectors/messages.js
@@ -1,49 +1,21 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* 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 { l10n } = require("devtools/client/webconsole/new-console-output/utils/messages");
-const { getAllFilters } = require("devtools/client/webconsole/new-console-output/selectors/filters");
-const { getGripPreviewItems } = require("devtools/client/shared/components/reps/reps");
-const { getSourceNames } = require("devtools/client/shared/source-utils");
-const {
-  MESSAGE_TYPE,
-  MESSAGE_SOURCE
-} = require("devtools/client/webconsole/new-console-output/constants");
-
-function getAllMessages(state) {
-  let messages = getAllMessagesById(state);
-  let filters = getAllFilters(state);
-
-  let groups = getAllGroupsById(state);
-  let messagesUI = getAllMessagesUiById(state);
-
-  return messages.filter(message => {
-    return (
-      isInOpenedGroup(message, groups, messagesUI)
-      && (
-        isUnfilterable(message)
-        || (
-          matchLevelFilters(message, filters)
-          && matchCssFilters(message, filters)
-          && matchNetworkFilters(message, filters)
-          && matchSearchFilters(message, filters)
-        )
-      )
-    );
-  });
+function getAllMessagesById(state) {
+  return state.messages.messagesById;
 }
 
-function getAllMessagesById(state) {
-  return state.messages.messagesById;
+function getMessage(state, id) {
+  return getAllMessagesById(state).get(id);
 }
 
 function getAllMessagesUiById(state) {
   return state.messages.messagesUiById;
 }
 
 function getAllMessagesTableDataById(state) {
   return state.messages.messagesTableDataById;
@@ -52,189 +24,21 @@ function getAllMessagesTableDataById(sta
 function getAllGroupsById(state) {
   return state.messages.groupsById;
 }
 
 function getCurrentGroup(state) {
   return state.messages.currentGroup;
 }
 
-function isUnfilterable(message) {
-  return [
-    MESSAGE_TYPE.COMMAND,
-    MESSAGE_TYPE.RESULT,
-    MESSAGE_TYPE.START_GROUP,
-    MESSAGE_TYPE.START_GROUP_COLLAPSED,
-  ].includes(message.type);
-}
-
-function isInOpenedGroup(message, groups, messagesUI) {
-  return !message.groupId
-    || (
-      !isGroupClosed(message.groupId, messagesUI)
-      && !hasClosedParentGroup(groups.get(message.groupId), messagesUI)
-    );
-}
-
-function hasClosedParentGroup(group, messagesUI) {
-  return group.some(groupId => isGroupClosed(groupId, messagesUI));
-}
-
-function isGroupClosed(groupId, messagesUI) {
-  return messagesUI.includes(groupId) === false;
-}
-
-function matchLevelFilters(message, filters) {
-  return filters.get(message.level) === true;
-}
-
-function matchNetworkFilters(message, filters) {
-  return (
-    message.source !== MESSAGE_SOURCE.NETWORK
-    || (filters.get("net") === true && message.isXHR === false)
-    || (filters.get("netxhr") === true && message.isXHR === true)
-  );
-}
-
-function matchCssFilters(message, filters) {
-  return (
-    message.source != MESSAGE_SOURCE.CSS
-    || filters.get("css") === true
-  );
-}
-
-function matchSearchFilters(message, filters) {
-  let text = filters.text || "";
-  return (
-    text === ""
-    // Look for a match in parameters.
-    || isTextInParameters(text, message.parameters)
-    // Look for a match in location.
-    || isTextInFrame(text, message.frame)
-    // Look for a match in net events.
-    || isTextInNetEvent(text, message.request)
-    // Look for a match in stack-trace.
-    || isTextInStackTrace(text, message.stacktrace)
-    // Look for a match in messageText.
-    || isTextInMessageText(text, message.messageText)
-    // Look for a match in notes.
-    || isTextInNotes(text, message.notes)
-  );
-}
-
-/**
- * Returns true if given text is included in provided stack frame.
- */
-function isTextInFrame(text, frame) {
-  if (!frame) {
-    return false;
-  }
-
-  const { short } = getSourceNames(frame.source);
-  return `${short}:${frame.line}:${frame.column}`
-    .toLocaleLowerCase()
-    .includes(text.toLocaleLowerCase());
-}
-
-/**
- * Returns true if given text is included in provided parameters.
- */
-function isTextInParameters(text, parameters) {
-  if (!parameters) {
-    return false;
-  }
-
-  text = text.toLocaleLowerCase();
-  return getAllProps(parameters).find(prop =>
-    (prop + "").toLocaleLowerCase().includes(text)
-  );
+function getVisibleMessages(state) {
+  return state.messages.visibleMessages.map(id => getMessage(state, id));
 }
 
-/**
- * Returns true if given text is included in provided net event grip.
- */
-function isTextInNetEvent(text, request) {
-  if (!request) {
-    return false;
-  }
-
-  text = text.toLocaleLowerCase();
-
-  let method = request.method.toLocaleLowerCase();
-  let url = request.url.toLocaleLowerCase();
-  return method.includes(text) || url.includes(text);
-}
-
-/**
- * Returns true if given text is included in provided stack trace.
- */
-function isTextInStackTrace(text, stacktrace) {
-  if (!Array.isArray(stacktrace)) {
-    return false;
-  }
-
-  // isTextInFrame expect the properties of the frame object to be in the same
-  // order they are rendered in the Frame component.
-  return stacktrace.some(frame => isTextInFrame(text, {
-    functionName: frame.functionName || l10n.getStr("stacktrace.anonymousFunction"),
-    filename: frame.filename,
-    lineNumber: frame.lineNumber,
-    columnNumber: frame.columnNumber
-  }));
-}
-
-/**
- * Returns true if given text is included in `messageText` field.
- */
-function isTextInMessageText(text, messageText) {
-  if (!messageText) {
-    return false;
-  }
-
-  return messageText.toLocaleLowerCase().includes(text.toLocaleLowerCase());
-}
-
-/**
- * Returns true if given text is included in notes.
- */
-function isTextInNotes(text, notes) {
-  if (!Array.isArray(notes)) {
-    return false;
-  }
-
-  return notes.some(note =>
-    // Look for a match in location.
-    isTextInFrame(text, note.frame) ||
-    // Look for a match in messageBody.
-    (note.messageBody &&
-        note.messageBody.toLocaleLowerCase()
-          .includes(text.toLocaleLowerCase()))
-  );
-}
-
-/**
- * Get a flat array of all the grips and their properties.
- *
- * @param {Array} Grips
- * @return {Array} Flat array of the grips and their properties.
- */
-function getAllProps(grips) {
-  let result = grips.reduce((res, grip) => {
-    let previewItems = getGripPreviewItems(grip);
-    let allProps = previewItems.length > 0 ? getAllProps(previewItems) : [];
-    return [...res, grip, grip.class, ...allProps];
-  }, []);
-
-  // We are interested only in primitive props (to search for)
-  // not in objects and undefined previews.
-  result = result.filter(grip =>
-    typeof grip != "object" &&
-    typeof grip != "undefined"
-  );
-
-  return [...new Set(result)];
-}
-
-exports.getAllMessages = getAllMessages;
-exports.getAllMessagesUiById = getAllMessagesUiById;
-exports.getAllMessagesTableDataById = getAllMessagesTableDataById;
-exports.getAllGroupsById = getAllGroupsById;
-exports.getCurrentGroup = getCurrentGroup;
+module.exports = {
+  getMessage,
+  getAllMessagesById,
+  getAllMessagesUiById,
+  getAllMessagesTableDataById,
+  getAllGroupsById,
+  getCurrentGroup,
+  getVisibleMessages,
+};
--- a/devtools/client/webconsole/new-console-output/store.js
+++ b/devtools/client/webconsole/new-console-output/store.js
@@ -3,58 +3,80 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const {FilterState} = require("devtools/client/webconsole/new-console-output/reducers/filters");
 const {PrefState} = require("devtools/client/webconsole/new-console-output/reducers/prefs");
 const {UiState} = require("devtools/client/webconsole/new-console-output/reducers/ui");
 const {
   applyMiddleware,
-  combineReducers,
   compose,
   createStore
 } = require("devtools/client/shared/vendor/redux");
 const { thunk } = require("devtools/client/shared/redux/middleware/thunk");
 const {
   MESSAGE_ADD,
   MESSAGES_CLEAR,
   REMOVED_MESSAGES_CLEAR,
   BATCH_ACTIONS,
   PREFS,
 } = require("devtools/client/webconsole/new-console-output/constants");
 const { reducers } = require("./reducers/index");
 const Services = require("Services");
 
-function configureStore(hud) {
+function configureStore(hud, options = {}) {
+  const logLimit = options.logLimit
+    || Math.max(Services.prefs.getIntPref("devtools.hud.loglimit"), 1);
+
   const initialState = {
-    prefs: new PrefState({
-      logLimit: Math.max(Services.prefs.getIntPref("devtools.hud.loglimit"), 1),
-    }),
+    prefs: new PrefState({ logLimit }),
     filters: new FilterState({
       error: Services.prefs.getBoolPref(PREFS.FILTER.ERROR),
       warn: Services.prefs.getBoolPref(PREFS.FILTER.WARN),
       info: Services.prefs.getBoolPref(PREFS.FILTER.INFO),
       debug: Services.prefs.getBoolPref(PREFS.FILTER.DEBUG),
       log: Services.prefs.getBoolPref(PREFS.FILTER.LOG),
       css: Services.prefs.getBoolPref(PREFS.FILTER.CSS),
       net: Services.prefs.getBoolPref(PREFS.FILTER.NET),
       netxhr: Services.prefs.getBoolPref(PREFS.FILTER.NETXHR),
     }),
     ui: new UiState({
       filterBarVisible: Services.prefs.getBoolPref(PREFS.UI.FILTER_BAR),
     })
   };
 
   return createStore(
-    combineReducers(reducers),
+    createRootReducer(),
     initialState,
     compose(applyMiddleware(thunk), enableActorReleaser(hud), enableBatching())
   );
 }
 
+function createRootReducer() {
+  return function rootReducer(state, action) {
+    // We want to compute the new state for all properties except "messages".
+    const newState = [...Object.entries(reducers)].reduce((res, [key, reducer]) => {
+      if (key !== "messages") {
+        res[key] = reducer(state[key], action);
+      }
+      return res;
+    }, {});
+
+    return Object.assign(newState, {
+      // specifically pass the updated filters and prefs state as additional arguments.
+      messages: reducers.messages(
+        state.messages,
+        action,
+        newState.filters,
+        newState.prefs,
+      ),
+    });
+  };
+}
+
 /**
  * A enhancer for the store to handle batched actions.
  */
 function enableBatching() {
   return next => (reducer, initialState, enhancer) => {
     function batchingReducer(state, action) {
       switch (action.type) {
         case BATCH_ACTIONS:
--- a/devtools/client/webconsole/new-console-output/test/helpers.js
+++ b/devtools/client/webconsole/new-console-output/test/helpers.js
@@ -26,18 +26,18 @@ function setupActions() {
   };
 
   return wrappedActions;
 }
 
 /**
  * Prepare the store for use in testing.
  */
-function setupStore(input, hud) {
-  const store = configureStore(hud);
+function setupStore(input, hud, options) {
+  const store = configureStore(hud, options);
 
   // Add the messages from the input commands to the store.
   input.forEach((cmd) => {
     store.dispatch(actions.messageAdd(stubPackets.get(cmd)));
   });
 
   return store;
 }
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_console_group.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_console_group.js
@@ -11,29 +11,29 @@
 const TEST_URI = "http://example.com/browser/devtools/client/webconsole/new-console-output/test/mochitest/test-console-group.html";
 const { INDENT_WIDTH } = require("devtools/client/webconsole/new-console-output/components/message-indent");
 
 add_task(function* () {
   let toolbox = yield openNewTabAndToolbox(TEST_URI, "webconsole");
   let hud = toolbox.getCurrentPanel().hud;
 
   const store = hud.ui.newConsoleOutput.getStore();
-  // Adding loggin each time the store is modified in order to check
+  // Adding logging each time the store is modified in order to check
   // the store state in case of failure.
   store.subscribe(() => {
-    const messages = store.getState().messages.messagesById.toJS()
-      .map(message => {
-        return {
+    const messages = store.getState().messages.messagesById
+      .reduce(function (res, message) {
+        res.push({
           id: message.id,
           type: message.type,
           parameters: message.parameters,
           messageText: message.messageText
-        };
-      }
-    );
+        });
+        return res;
+      }, []);
     info("messages : " + JSON.stringify(messages));
   });
 
   yield ContentTask.spawn(gBrowser.selectedBrowser, null, function () {
     content.wrappedJSObject.doLog();
   });
 
   info("Test a group at root level");
--- a/devtools/client/webconsole/new-console-output/test/store/filters.test.js
+++ b/devtools/client/webconsole/new-console-output/test/store/filters.test.js
@@ -3,169 +3,168 @@
 
 "use strict";
 
 const expect = require("expect");
 
 const actions = require("devtools/client/webconsole/new-console-output/actions/index");
 const { messageAdd } = require("devtools/client/webconsole/new-console-output/actions/index");
 const { ConsoleCommand } = require("devtools/client/webconsole/new-console-output/types");
-const { getAllMessages } = require("devtools/client/webconsole/new-console-output/selectors/messages");
+const { getVisibleMessages } = require("devtools/client/webconsole/new-console-output/selectors/messages");
 const { getAllFilters } = require("devtools/client/webconsole/new-console-output/selectors/filters");
 const { setupStore } = require("devtools/client/webconsole/new-console-output/test/helpers");
 const { MESSAGE_LEVEL } = require("devtools/client/webconsole/new-console-output/constants");
 const { stubPackets } = require("devtools/client/webconsole/new-console-output/test/fixtures/stubs/index");
 const { stubPreparedMessages } = require("devtools/client/webconsole/new-console-output/test/fixtures/stubs/index");
 
 describe("Filtering", () => {
   let store;
   let numMessages;
   // Number of messages in prepareBaseStore which are not filtered out, i.e. Evaluation
   // Results, console commands and console.groups .
   const numUnfilterableMessages = 3;
 
   beforeEach(() => {
     store = prepareBaseStore();
     store.dispatch(actions.filtersClear());
-    numMessages = getAllMessages(store.getState()).size;
+    numMessages = getVisibleMessages(store.getState()).length;
   });
 
   describe("Level filter", () => {
     it("filters log messages", () => {
       store.dispatch(actions.filterToggle(MESSAGE_LEVEL.LOG));
 
-      let messages = getAllMessages(store.getState());
-      expect(messages.size).toEqual(numMessages - 3);
+      let messages = getVisibleMessages(store.getState());
+      expect(messages.length).toEqual(numMessages - 3);
     });
 
     it("filters debug messages", () => {
       store.dispatch(actions.filterToggle(MESSAGE_LEVEL.DEBUG));
 
-      let messages = getAllMessages(store.getState());
-      expect(messages.size).toEqual(numMessages - 1);
+      let messages = getVisibleMessages(store.getState());
+      expect(messages.length).toEqual(numMessages - 1);
     });
 
     // @TODO add info stub
     it("filters info messages");
 
     it("filters warning messages", () => {
       store.dispatch(actions.filterToggle(MESSAGE_LEVEL.WARN));
 
-      let messages = getAllMessages(store.getState());
-      expect(messages.size).toEqual(numMessages - 1);
+      let messages = getVisibleMessages(store.getState());
+      expect(messages.length).toEqual(numMessages - 1);
     });
 
     it("filters error messages", () => {
       store.dispatch(actions.filterToggle(MESSAGE_LEVEL.ERROR));
 
-      let messages = getAllMessages(store.getState());
-      expect(messages.size).toEqual(numMessages - 1);
+      let messages = getVisibleMessages(store.getState());
+      expect(messages.length).toEqual(numMessages - 1);
     });
 
     it("filters css messages", () => {
       let message = stubPreparedMessages.get(
         "Unknown property ‘such-unknown-property’.  Declaration dropped."
       );
       store.dispatch(messageAdd(message));
 
-      let messages = getAllMessages(store.getState());
-      expect(messages.size).toEqual(numMessages);
+      let messages = getVisibleMessages(store.getState());
+      expect(messages.length).toEqual(numMessages);
 
       store.dispatch(actions.filterToggle("css"));
-      messages = getAllMessages(store.getState());
-      expect(messages.size).toEqual(numMessages + 1);
+      messages = getVisibleMessages(store.getState());
+      expect(messages.length).toEqual(numMessages + 1);
     });
 
     it("filters xhr messages", () => {
       let message = stubPreparedMessages.get("XHR GET request");
       store.dispatch(messageAdd(message));
 
-      let messages = getAllMessages(store.getState());
-      expect(messages.size).toEqual(numMessages);
+      let messages = getVisibleMessages(store.getState());
+      expect(messages.length).toEqual(numMessages);
 
       store.dispatch(actions.filterToggle("netxhr"));
-      messages = getAllMessages(store.getState());
-      expect(messages.size).toEqual(numMessages + 1);
+      messages = getVisibleMessages(store.getState());
+      expect(messages.length).toEqual(numMessages + 1);
     });
 
     it("filters network messages", () => {
       let message = stubPreparedMessages.get("GET request");
       store.dispatch(messageAdd(message));
 
-      let messages = getAllMessages(store.getState());
-      expect(messages.size).toEqual(numMessages);
+      let messages = getVisibleMessages(store.getState());
+      expect(messages.length).toEqual(numMessages);
 
       store.dispatch(actions.filterToggle("net"));
-      messages = getAllMessages(store.getState());
-      expect(messages.size).toEqual(numMessages + 1);
+      messages = getVisibleMessages(store.getState());
+      expect(messages.length).toEqual(numMessages + 1);
     });
   });
 
   describe("Text filter", () => {
     it("matches on value grips", () => {
       store.dispatch(actions.filterTextSet("danger"));
-
-      let messages = getAllMessages(store.getState());
-      expect(messages.size - numUnfilterableMessages).toEqual(1);
+      let messages = getVisibleMessages(store.getState());
+      expect(messages.length - numUnfilterableMessages).toEqual(1);
     });
 
     it("matches unicode values", () => {
       store.dispatch(actions.filterTextSet("鼬"));
 
-      let messages = getAllMessages(store.getState());
-      expect(messages.size - numUnfilterableMessages).toEqual(1);
+      let messages = getVisibleMessages(store.getState());
+      expect(messages.length - numUnfilterableMessages).toEqual(1);
     });
 
     it("matches locations", () => {
       // Add a message with a different filename.
       let locationMsg =
         Object.assign({}, stubPackets.get("console.log('foobar', 'test')"));
       locationMsg.message =
         Object.assign({}, locationMsg.message, { filename: "search-location-test.js" });
       store.dispatch(messageAdd(locationMsg));
 
       store.dispatch(actions.filterTextSet("search-location-test.js"));
 
-      let messages = getAllMessages(store.getState());
-      expect(messages.size - numUnfilterableMessages).toEqual(1);
+      let messages = getVisibleMessages(store.getState());
+      expect(messages.length - numUnfilterableMessages).toEqual(1);
     });
 
     it("matches stacktrace functionName", () => {
       let traceMessage = stubPackets.get("console.trace()");
       store.dispatch(messageAdd(traceMessage));
 
       store.dispatch(actions.filterTextSet("testStacktraceFiltering"));
 
-      let messages = getAllMessages(store.getState());
-      expect(messages.size - numUnfilterableMessages).toEqual(1);
+      let messages = getVisibleMessages(store.getState());
+      expect(messages.length - numUnfilterableMessages).toEqual(1);
     });
 
     it("matches stacktrace location", () => {
       let traceMessage = stubPackets.get("console.trace()");
       traceMessage.message =
         Object.assign({}, traceMessage.message, {
           filename: "search-location-test.js",
           lineNumber: 85,
           columnNumber: 13
         });
 
       store.dispatch(messageAdd(traceMessage));
 
       store.dispatch(actions.filterTextSet("search-location-test.js:85:13"));
 
-      let messages = getAllMessages(store.getState());
-      expect(messages.size - numUnfilterableMessages).toEqual(1);
+      let messages = getVisibleMessages(store.getState());
+      expect(messages.length - numUnfilterableMessages).toEqual(1);
     });
 
     it("restores all messages once text is cleared", () => {
       store.dispatch(actions.filterTextSet("danger"));
       store.dispatch(actions.filterTextSet(""));
 
-      let messages = getAllMessages(store.getState());
-      expect(messages.size).toEqual(numMessages);
+      let messages = getVisibleMessages(store.getState());
+      expect(messages.length).toEqual(numMessages);
     });
   });
 
   describe("Combined filters", () => {
     // @TODO add test
     it("filters");
   });
 });
@@ -184,32 +183,32 @@ describe("Clear filters", () => {
       "css": true,
       "debug": true,
       "error": false,
       "info": true,
       "log": true,
       "net": false,
       "netxhr": true,
       "warn": true,
-      "text": "foobar"
+      "text": "foobar",
     });
 
     store.dispatch(actions.filtersClear());
 
     filters = getAllFilters(store.getState());
     expect(filters.toJS()).toEqual({
       "css": false,
       "debug": true,
       "error": true,
       "info": true,
       "log": true,
       "net": false,
       "netxhr": false,
       "warn": true,
-      "text": ""
+      "text": "",
     });
   });
 });
 
 function prepareBaseStore() {
   const store = setupStore([
     // Console API
     "console.log('foobar', 'test')",
--- a/devtools/client/webconsole/new-console-output/test/store/messages.test.js
+++ b/devtools/client/webconsole/new-console-output/test/store/messages.test.js
@@ -1,17 +1,19 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
 const {
-  getAllMessages,
+  getAllGroupsById,
+  getAllMessagesById,
+  getAllMessagesTableDataById,
   getAllMessagesUiById,
-  getAllGroupsById,
   getCurrentGroup,
+  getVisibleMessages,
 } = require("devtools/client/webconsole/new-console-output/selectors/messages");
 const {
   setupActions,
   setupStore,
   clonePacket
 } = require("devtools/client/webconsole/new-console-output/test/helpers");
 const { stubPackets, stubPreparedMessages } = require("devtools/client/webconsole/new-console-output/test/fixtures/stubs/index");
 const {
@@ -30,233 +32,300 @@ describe("Message reducer:", () => {
   describe("messagesById", () => {
     it("adds a message to an empty store", () => {
       const { dispatch, getState } = setupStore([]);
 
       const packet = stubPackets.get("console.log('foobar', 'test')");
       const message = stubPreparedMessages.get("console.log('foobar', 'test')");
       dispatch(actions.messageAdd(packet));
 
-      const messages = getAllMessages(getState());
+      const messages = getAllMessagesById(getState());
 
       expect(messages.first()).toEqual(message);
     });
 
     it("increments repeat on a repeating message", () => {
-      const { dispatch, getState } = setupStore([
-        "console.log('foobar', 'test')",
-        "console.log('foobar', 'test')"
-      ]);
+      const key1 = "console.log('foobar', 'test')";
+      const { dispatch, getState } = setupStore([key1, key1]);
 
-      const packet = clonePacket(stubPackets.get("console.log('foobar', 'test')"));
+      const packet = clonePacket(stubPackets.get(key1));
 
       // Repeat ID must be the same even if the timestamp is different.
       packet.message.timeStamp = 1;
       dispatch(actions.messageAdd(packet));
       packet.message.timeStamp = 2;
       dispatch(actions.messageAdd(packet));
 
-      const messages = getAllMessages(getState());
+      const messages = getAllMessagesById(getState());
 
       expect(messages.size).toBe(1);
       expect(messages.first().repeat).toBe(4);
     });
 
     it("does not clobber a unique message", () => {
-      const { dispatch, getState } = setupStore([
-        "console.log('foobar', 'test')",
-        "console.log('foobar', 'test')"
-      ]);
+      const key1 = "console.log('foobar', 'test')";
+      const { dispatch, getState } = setupStore([key1, key1]);
 
-      const packet = stubPackets.get("console.log('foobar', 'test')");
+      const packet = stubPackets.get(key1);
       dispatch(actions.messageAdd(packet));
 
       const packet2 = stubPackets.get("console.log(undefined)");
       dispatch(actions.messageAdd(packet2));
 
-      const messages = getAllMessages(getState());
-
+      const messages = getAllMessagesById(getState());
       expect(messages.size).toBe(2);
       expect(messages.first().repeat).toBe(3);
       expect(messages.last().repeat).toBe(1);
     });
 
     it("adds a message in response to console.clear()", () => {
       const { dispatch, getState } = setupStore([]);
 
       dispatch(actions.messageAdd(stubPackets.get("console.clear()")));
 
-      const messages = getAllMessages(getState());
+      const messages = getAllMessagesById(getState());
 
       expect(messages.size).toBe(1);
       expect(messages.first().parameters[0]).toBe("Console was cleared.");
     });
 
     it("clears the messages list in response to MESSAGES_CLEAR action", () => {
       const { dispatch, getState } = setupStore([
         "console.log('foobar', 'test')",
-        "console.log(undefined)"
+        "console.log(undefined)",
+        "console.table(['red', 'green', 'blue']);",
+        "console.group('bar')",
       ]);
 
       dispatch(actions.messagesClear());
 
-      const messages = getAllMessages(getState());
-      expect(messages.size).toBe(0);
+      const state = getState();
+      expect(getAllMessagesById(state).size).toBe(0);
+      expect(getVisibleMessages(state).length).toBe(0);
+      expect(getAllMessagesUiById(state).size).toBe(0);
+      expect(getAllGroupsById(state).size).toBe(0);
+      expect(getAllMessagesTableDataById(state).size).toBe(0);
+      expect(getCurrentGroup(state)).toBe(null);
     });
 
     it("properly limits number of messages", () => {
       const { dispatch, getState } = setupStore([]);
 
       const logLimit = 1000;
       const packet = clonePacket(stubPackets.get("console.log(undefined)"));
 
       for (let i = 1; i <= logLimit + 2; i++) {
         packet.message.arguments = [`message num ${i}`];
         dispatch(actions.messageAdd(packet));
       }
 
-      const messages = getAllMessages(getState());
+      const messages = getAllMessagesById(getState());
       expect(messages.count()).toBe(logLimit);
       expect(messages.first().parameters[0]).toBe(`message num 3`);
       expect(messages.last().parameters[0]).toBe(`message num ${logLimit + 2}`);
     });
 
-    it("properly limits number of groups", () => {
+    it("properly limits number of messages when there are nested groups", () => {
       const { dispatch, getState } = setupStore([]);
 
       const logLimit = 1000;
 
       const packet = clonePacket(stubPackets.get("console.log(undefined)"));
       const packetGroup = clonePacket(stubPackets.get("console.group('bar')"));
       const packetGroupEnd = clonePacket(stubPackets.get("console.groupEnd()"));
 
+      packetGroup.message.arguments = [`group-1`];
+      dispatch(actions.messageAdd(packetGroup));
+      packetGroup.message.arguments = [`group-1-1`];
+      dispatch(actions.messageAdd(packetGroup));
+      packetGroup.message.arguments = [`group-1-1-1`];
+      dispatch(actions.messageAdd(packetGroup));
+      packet.message.arguments = [`message-in-group-1`];
+      dispatch(actions.messageAdd(packet));
+      packet.message.arguments = [`message-in-group-2`];
+      dispatch(actions.messageAdd(packet));
+      // Closing group-1-1-1
+      dispatch(actions.messageAdd(packetGroupEnd));
+      // Closing group-1-1
+      dispatch(actions.messageAdd(packetGroupEnd));
+      // Closing group-1
+      dispatch(actions.messageAdd(packetGroupEnd));
+
+      for (let i = 0; i < logLimit; i++) {
+        packet.message.arguments = [`message-${i}`];
+        dispatch(actions.messageAdd(packet));
+      }
+
+      const visibleMessages = getVisibleMessages(getState());
+      const messages = getAllMessagesById(getState());
+
+      expect(messages.count()).toBe(logLimit);
+      expect(visibleMessages.length).toBe(logLimit);
+      expect(visibleMessages[0].parameters[0]).toBe(`message-0`);
+      expect(visibleMessages[logLimit - 1].parameters[0]).toBe(`message-${logLimit - 1}`);
+
+      // The groups were cleaned up.
+      const groups = getAllGroupsById(getState());
+      expect(groups.count()).toBe(0);
+    });
+
+    it("properly limits number of groups", () => {
+      const logLimit = 100;
+      const { dispatch, getState } = setupStore([], null, {logLimit});
+
+      const packet = clonePacket(stubPackets.get("console.log(undefined)"));
+      const packetGroup = clonePacket(stubPackets.get("console.group('bar')"));
+      const packetGroupEnd = clonePacket(stubPackets.get("console.groupEnd()"));
+
       for (let i = 0; i < logLimit + 2; i++) {
-        packet.message.arguments = [`message num ${i}`];
         dispatch(actions.messageAdd(packetGroup));
+        packet.message.arguments = [`message-${i}-a`];
+        dispatch(actions.messageAdd(packet));
+        packet.message.arguments = [`message-${i}-b`];
         dispatch(actions.messageAdd(packet));
         dispatch(actions.messageAdd(packetGroupEnd));
       }
 
-      const messages = getAllMessages(getState());
-      expect(messages.count()).toBe(logLimit * 2);
-      expect(messages.get(1).parameters[0]).toBe(`message num 2`);
-      expect(messages.last().parameters[0]).toBe(`message num ${logLimit + 1}`);
+      const visibleMessages = getVisibleMessages(getState());
+      const messages = getAllMessagesById(getState());
+      // We should have three times the logLimit since each group has one message inside.
+      expect(messages.count()).toBe(logLimit * 3);
+
+      // We should have logLimit number of groups
+      const groups = getAllGroupsById(getState());
+      expect(groups.count()).toBe(logLimit);
+
+      expect(visibleMessages[1].parameters[0]).toBe(`message-2-a`);
+      expect(messages.last().parameters[0]).toBe(`message-${logLimit + 1}-b`);
     });
 
     it("properly limits number of collapsed groups", () => {
-      const { dispatch, getState } = setupStore([]);
-
-      const logLimit = 1000;
+      const logLimit = 100;
+      const { dispatch, getState } = setupStore([], null, {logLimit});
 
       const packet = clonePacket(stubPackets.get("console.log(undefined)"));
       const packetGroupCollapsed = clonePacket(
         stubPackets.get("console.groupCollapsed('foo')"));
       const packetGroupEnd = clonePacket(stubPackets.get("console.groupEnd()"));
 
       for (let i = 0; i < logLimit + 2; i++) {
-        packetGroupCollapsed.message.arguments = [`message num ${i}`];
+        packetGroupCollapsed.message.arguments = [`group-${i}`];
         dispatch(actions.messageAdd(packetGroupCollapsed));
+        packet.message.arguments = [`message-${i}-a`];
+        dispatch(actions.messageAdd(packet));
+        packet.message.arguments = [`message-${i}-b`];
         dispatch(actions.messageAdd(packet));
         dispatch(actions.messageAdd(packetGroupEnd));
       }
 
-      const messages = getAllMessages(getState());
-      expect(messages.count()).toBe(logLimit);
-      expect(messages.first().parameters[0]).toBe(`message num 2`);
-      expect(messages.last().parameters[0]).toBe(`message num ${logLimit + 1}`);
+      const messages = getAllMessagesById(getState());
+      // We should have three times the logLimit since each group has two message inside.
+      expect(messages.size).toBe(logLimit * 3);
+
+      // We should have logLimit number of groups
+      const groups = getAllGroupsById(getState());
+      expect(groups.count()).toBe(logLimit);
+
+      expect(messages.first().parameters[0]).toBe(`group-2`);
+      expect(messages.last().parameters[0]).toBe(`message-${logLimit + 1}-b`);
+
+      const visibleMessages = getVisibleMessages(getState());
+      expect(visibleMessages.length).toBe(logLimit);
+      const lastVisibleMessage = visibleMessages[visibleMessages.length - 1];
+      expect(lastVisibleMessage.parameters[0]).toBe(`group-${logLimit + 1}`);
     });
 
     it("does not add null messages to the store", () => {
       const { dispatch, getState } = setupStore([]);
 
       const message = stubPackets.get("console.time('bar')");
       dispatch(actions.messageAdd(message));
 
-      const messages = getAllMessages(getState());
+      const messages = getAllMessagesById(getState());
       expect(messages.size).toBe(0);
     });
 
     it("adds console.table call with unsupported type as console.log", () => {
       const { dispatch, getState } = setupStore([]);
 
       const packet = stubPackets.get("console.table('bar')");
       dispatch(actions.messageAdd(packet));
 
-      const messages = getAllMessages(getState());
+      const messages = getAllMessagesById(getState());
       const tableMessage = messages.last();
       expect(tableMessage.level).toEqual(MESSAGE_TYPE.LOG);
     });
 
     it("adds console.group messages to the store", () => {
       const { dispatch, getState } = setupStore([]);
 
       const message = stubPackets.get("console.group('bar')");
       dispatch(actions.messageAdd(message));
 
-      const messages = getAllMessages(getState());
+      const messages = getAllMessagesById(getState());
       expect(messages.size).toBe(1);
     });
 
     it("sets groupId property as expected", () => {
       const { dispatch, getState } = setupStore([]);
 
       dispatch(actions.messageAdd(
         stubPackets.get("console.group('bar')")));
 
       const packet = stubPackets.get("console.log('foobar', 'test')");
       dispatch(actions.messageAdd(packet));
 
-      const messages = getAllMessages(getState());
+      const messages = getAllMessagesById(getState());
       expect(messages.size).toBe(2);
       expect(messages.last().groupId).toBe(messages.first().id);
     });
 
     it("does not display console.groupEnd messages to the store", () => {
       const { dispatch, getState } = setupStore([]);
 
       const message = stubPackets.get("console.groupEnd('bar')");
       dispatch(actions.messageAdd(message));
 
-      const messages = getAllMessages(getState());
+      const messages = getAllMessagesById(getState());
       expect(messages.size).toBe(0);
     });
 
     it("filters out message added after a console.groupCollapsed message", () => {
       const { dispatch, getState } = setupStore([]);
 
       const message = stubPackets.get("console.groupCollapsed('foo')");
       dispatch(actions.messageAdd(message));
 
       dispatch(actions.messageAdd(
         stubPackets.get("console.log('foobar', 'test')")));
 
-      const messages = getAllMessages(getState());
-      expect(messages.size).toBe(1);
+      const messages = getVisibleMessages(getState());
+      expect(messages.length).toBe(1);
     });
 
     it("adds console.dirxml call as console.log", () => {
       const { dispatch, getState } = setupStore([]);
 
       const packet = stubPackets.get("console.dirxml(window)");
       dispatch(actions.messageAdd(packet));
 
-      const messages = getAllMessages(getState());
+      const messages = getAllMessagesById(getState());
       const dirxmlMessage = messages.last();
       expect(dirxmlMessage.level).toEqual(MESSAGE_TYPE.LOG);
     });
   });
 
   describe("messagesUiById", () => {
     it("opens console.trace messages when they are added", () => {
       const { dispatch, getState } = setupStore([]);
 
       const message = stubPackets.get("console.trace()");
       dispatch(actions.messageAdd(message));
 
-      const messages = getAllMessages(getState());
+      const messages = getAllMessagesById(getState());
       const messagesUi = getAllMessagesUiById(getState());
       expect(messagesUi.size).toBe(1);
       expect(messagesUi.first()).toBe(messages.first().id);
     });
 
     it("clears the messages UI list in response to MESSAGES_CLEAR action", () => {
       const { dispatch, getState } = setupStore([
         "console.log('foobar', 'test')",
@@ -273,17 +342,17 @@ describe("Message reducer:", () => {
     });
 
     it("opens console.group messages when they are added", () => {
       const { dispatch, getState } = setupStore([]);
 
       const message = stubPackets.get("console.group('bar')");
       dispatch(actions.messageAdd(message));
 
-      const messages = getAllMessages(getState());
+      const messages = getAllMessagesById(getState());
       const messagesUi = getAllMessagesUiById(getState());
       expect(messagesUi.size).toBe(1);
       expect(messagesUi.first()).toBe(messages.first().id);
     });
 
     it("does not open console.groupCollapsed messages when they are added", () => {
       const { dispatch, getState } = setupStore([]);
 
@@ -297,40 +366,40 @@ describe("Message reducer:", () => {
 
   describe("currentGroup", () => {
     it("sets the currentGroup when console.group message is added", () => {
       const { dispatch, getState } = setupStore([]);
 
       const packet = stubPackets.get("console.group('bar')");
       dispatch(actions.messageAdd(packet));
 
-      const messages = getAllMessages(getState());
+      const messages = getAllMessagesById(getState());
       const currentGroup = getCurrentGroup(getState());
       expect(currentGroup).toBe(messages.first().id);
     });
 
     it("sets currentGroup to expected value when console.groupEnd is added", () => {
       const { dispatch, getState } = setupStore([
         "console.group('bar')",
         "console.groupCollapsed('foo')"
       ]);
 
-      let messages = getAllMessages(getState());
+      let messages = getAllMessagesById(getState());
       let currentGroup = getCurrentGroup(getState());
       expect(currentGroup).toBe(messages.last().id);
 
       const endFooPacket = stubPackets.get("console.groupEnd('foo')");
       dispatch(actions.messageAdd(endFooPacket));
-      messages = getAllMessages(getState());
+      messages = getAllMessagesById(getState());
       currentGroup = getCurrentGroup(getState());
       expect(currentGroup).toBe(messages.first().id);
 
       const endBarPacket = stubPackets.get("console.groupEnd('bar')");
       dispatch(actions.messageAdd(endBarPacket));
-      messages = getAllMessages(getState());
+      messages = getAllMessagesById(getState());
       currentGroup = getCurrentGroup(getState());
       expect(currentGroup).toBe(null);
     });
 
     it("resets the currentGroup to null in response to MESSAGES_CLEAR action", () => {
       const { dispatch, getState } = setupStore([
         "console.group('bar')"
       ]);
@@ -344,25 +413,25 @@ describe("Message reducer:", () => {
 
   describe("groupsById", () => {
     it("adds the group with expected array when console.group message is added", () => {
       const { dispatch, getState } = setupStore([]);
 
       const barPacket = stubPackets.get("console.group('bar')");
       dispatch(actions.messageAdd(barPacket));
 
-      let messages = getAllMessages(getState());
+      let messages = getAllMessagesById(getState());
       let groupsById = getAllGroupsById(getState());
       expect(groupsById.size).toBe(1);
       expect(groupsById.has(messages.first().id)).toBe(true);
       expect(groupsById.get(messages.first().id)).toEqual([]);
 
       const fooPacket = stubPackets.get("console.groupCollapsed('foo')");
       dispatch(actions.messageAdd(fooPacket));
-      messages = getAllMessages(getState());
+      messages = getAllMessagesById(getState());
       groupsById = getAllGroupsById(getState());
       expect(groupsById.size).toBe(2);
       expect(groupsById.has(messages.last().id)).toBe(true);
       expect(groupsById.get(messages.last().id)).toEqual([messages.first().id]);
     });
 
     it("resets groupsById in response to MESSAGES_CLEAR action", () => {
       const { dispatch, getState } = setupStore([
--- a/devtools/client/webconsole/new-console-output/test/store/search.test.js
+++ b/devtools/client/webconsole/new-console-output/test/store/search.test.js
@@ -1,86 +1,86 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 const expect = require("expect");
 
 const actions = require("devtools/client/webconsole/new-console-output/actions/index");
-const { getAllMessages } = require("devtools/client/webconsole/new-console-output/selectors/messages");
+const { getVisibleMessages } = require("devtools/client/webconsole/new-console-output/selectors/messages");
 const { setupStore } = require("devtools/client/webconsole/new-console-output/test/helpers");
 
 describe("Searching in grips", () => {
   let store;
 
   beforeEach(() => {
     store = prepareBaseStore();
     store.dispatch(actions.filtersClear());
   });
 
   describe("Search in table & array & object props", () => {
     it("matches on value grips", () => {
       store.dispatch(actions.filterTextSet("red"));
-      expect(getAllMessages(store.getState()).size).toEqual(3);
+      expect(getVisibleMessages(store.getState()).length).toEqual(3);
     });
   });
 
   describe("Search in object value", () => {
     it("matches on value grips", () => {
       store.dispatch(actions.filterTextSet("redValue"));
-      expect(getAllMessages(store.getState()).size).toEqual(1);
+      expect(getVisibleMessages(store.getState()).length).toEqual(1);
     });
   });
 
   describe("Search in regex", () => {
     it("matches on value grips", () => {
       store.dispatch(actions.filterTextSet("a.b.c"));
-      expect(getAllMessages(store.getState()).size).toEqual(1);
+      expect(getVisibleMessages(store.getState()).length).toEqual(1);
     });
   });
 
   describe("Search in map values", () => {
     it("matches on value grips", () => {
       store.dispatch(actions.filterTextSet("value1"));
-      expect(getAllMessages(store.getState()).size).toEqual(1);
+      expect(getVisibleMessages(store.getState()).length).toEqual(1);
     });
   });
 
   describe("Search in map keys", () => {
     it("matches on value grips", () => {
       store.dispatch(actions.filterTextSet("key1"));
-      expect(getAllMessages(store.getState()).size).toEqual(1);
+      expect(getVisibleMessages(store.getState()).length).toEqual(1);
     });
   });
 
   describe("Search in text", () => {
     it("matches on value grips", () => {
       store.dispatch(actions.filterTextSet("myobj"));
-      expect(getAllMessages(store.getState()).size).toEqual(1);
+      expect(getVisibleMessages(store.getState()).length).toEqual(1);
     });
   });
 
   describe("Search in logs with net messages", () => {
     it("matches on network messages", () => {
       store.dispatch(actions.filterToggle("net"));
       store.dispatch(actions.filterTextSet("get"));
-      expect(getAllMessages(store.getState()).size).toEqual(1);
+      expect(getVisibleMessages(store.getState()).length).toEqual(1);
     });
   });
 
   describe("Search in frame", () => {
     it("matches on file name", () => {
       store.dispatch(actions.filterTextSet("test-console-api.html:1:27"));
-      expect(getAllMessages(store.getState()).size).toEqual(7);
+      expect(getVisibleMessages(store.getState()).length).toEqual(7);
     });
 
     it("do not match on full url", () => {
       store.dispatch(actions.filterTextSet("http://example.com/browser/devtools"));
-      expect(getAllMessages(store.getState()).size).toEqual(0);
+      expect(getVisibleMessages(store.getState()).length).toEqual(0);
     });
   });
 });
 
 function prepareBaseStore() {
   const store = setupStore([
     "console.log('foobar', 'test')",
     "console.warn('danger, will robinson!')",
--- a/devtools/client/webide/modules/addons.js
+++ b/devtools/client/webide/modules/addons.js
@@ -1,15 +1,14 @@
 /* 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 promise = require("promise");
 const {AddonManager} = require("resource://gre/modules/AddonManager.jsm");
 const Services = require("Services");
 const {getJSON} = require("devtools/client/shared/getjson");
 const EventEmitter = require("devtools/shared/event-emitter");
 
 const ADDONS_URL = "devtools.webide.addonsURL";
 
 var SIMULATOR_LINK = Services.prefs.getCharPref("devtools.webide.simulatorAddonsURL");
@@ -46,34 +45,34 @@ addonsListener.onUninstalled = (updatedA
     }
   });
 };
 AddonManager.addAddonListener(addonsListener);
 
 var GetAvailableAddons_promise = null;
 var GetAvailableAddons = exports.GetAvailableAddons = function () {
   if (!GetAvailableAddons_promise) {
-    let deferred = promise.defer();
-    GetAvailableAddons_promise = deferred.promise;
-    let addons = {
-      simulators: [],
-      adb: null
-    };
-    getJSON(ADDONS_URL).then(json => {
-      for (let stability in json) {
-        for (let version of json[stability]) {
-          addons.simulators.push(new SimulatorAddon(stability, version));
+    GetAvailableAddons_promise = new Promise((resolve, reject) => {
+      let addons = {
+        simulators: [],
+        adb: null
+      };
+      getJSON(ADDONS_URL).then(json => {
+        for (let stability in json) {
+          for (let version of json[stability]) {
+            addons.simulators.push(new SimulatorAddon(stability, version));
+          }
         }
-      }
-      addons.adb = new ADBAddon();
-      addons.adapters = new AdaptersAddon();
-      deferred.resolve(addons);
-    }, e => {
-      GetAvailableAddons_promise = null;
-      deferred.reject(e);
+        addons.adb = new ADBAddon();
+        addons.adapters = new AdaptersAddon();
+        resolve(addons);
+      }, e => {
+        GetAvailableAddons_promise = null;
+        reject(e);
+      });
     });
   }
   return GetAvailableAddons_promise;
 };
 
 exports.ForgetAddonsList = function () {
   GetAvailableAddons_promise = null;
 };
--- a/devtools/client/webide/modules/app-manager.js
+++ b/devtools/client/webide/modules/app-manager.js
@@ -1,15 +1,14 @@
 /* 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/. */
 
 const {Cu} = require("chrome");
 
-const promise = require("promise");
 const {TargetFactory} = require("devtools/client/framework/target");
 const Services = require("Services");
 const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm", {});
 const EventEmitter = require("devtools/shared/event-emitter");
 const {OS} = Cu.import("resource://gre/modules/osfile.jsm", {});
 const {AppProjects} = require("devtools/client/webide/modules/app-projects");
 const TabStore = require("devtools/client/webide/modules/tab-store");
 const {AppValidator} = require("devtools/client/webide/modules/app-validator");
@@ -272,17 +271,17 @@ var AppManager = exports.AppManager = {
     if (this.selectedProject.type !== "tab") {
       return;
     }
     this.selectedProject = null;
   },
 
   reloadTab: function () {
     if (this.selectedProject && this.selectedProject.type != "tab") {
-      return promise.reject("tried to reload non-tab project");
+      return Promise.reject("tried to reload non-tab project");
     }
     return this.getTarget().then(target => {
       target.activeTab.reload();
     }, console.error.bind(console));
   },
 
   getTarget: function () {
     if (this.selectedProject.type == "mainProcess") {
@@ -308,31 +307,31 @@ var AppManager = exports.AppManager = {
     }
 
     if (this.selectedProject.type == "tab") {
       return this.tabStore.getTargetForTab();
     }
 
     let app = this._getProjectFront(this.selectedProject);
     if (!app) {
-      return promise.reject("Can't find app front for selected project");
+      return Promise.reject("Can't find app front for selected project");
     }
 
     return Task.spawn(function* () {
       // Once we asked the app to launch, the app isn't necessary completely loaded.
       // launch request only ask the app to launch and immediatly returns.
       // We have to keep trying to get app tab actors required to create its target.
 
       for (let i = 0; i < 10; i++) {
         try {
           return yield app.getTarget();
         } catch (e) {}
-        let deferred = promise.defer();
-        setTimeout(deferred.resolve, 500);
-        yield deferred.promise;
+        return new Promise(resolve => {
+          setTimeout(resolve, 500);
+        });
       }
 
       AppManager.reportError("error_cantConnectToApp", app.manifest.manifestURL);
       throw new Error("can't connect to app");
     });
   },
 
   getProjectManifestURL: function (project) {
@@ -455,71 +454,71 @@ var AppManager = exports.AppManager = {
   get selectedRuntime() {
     return this._selectedRuntime;
   },
 
   connectToRuntime: function (runtime) {
 
     if (this.connected && this.selectedRuntime === runtime) {
       // Already connected
-      return promise.resolve();
+      return Promise.resolve();
     }
 
-    let deferred = promise.defer();
-
-    this.disconnectRuntime().then(() => {
-      this.selectedRuntime = runtime;
+    let deferred = new Promise((resolve, reject) => {
+      this.disconnectRuntime().then(() => {
+        this.selectedRuntime = runtime;
 
-      let onConnectedOrDisconnected = () => {
-        this.connection.off(Connection.Events.CONNECTED, onConnectedOrDisconnected);
-        this.connection.off(Connection.Events.DISCONNECTED, onConnectedOrDisconnected);
-        if (this.connected) {
-          deferred.resolve();
-        } else {
-          deferred.reject();
+        let onConnectedOrDisconnected = () => {
+          this.connection.off(Connection.Events.CONNECTED, onConnectedOrDisconnected);
+          this.connection.off(Connection.Events.DISCONNECTED, onConnectedOrDisconnected);
+          if (this.connected) {
+            resolve();
+          } else {
+            reject();
+          }
+        };
+        this.connection.on(Connection.Events.CONNECTED, onConnectedOrDisconnected);
+        this.connection.on(Connection.Events.DISCONNECTED, onConnectedOrDisconnected);
+        try {
+          // Reset the connection's state to defaults
+          this.connection.resetOptions();
+          // Only watch for errors here.  Final resolution occurs above, once
+          // we've reached the CONNECTED state.
+          this.selectedRuntime.connect(this.connection)
+                              .then(null, e => reject(e));
+        } catch (e) {
+          reject(e);
         }
-      };
-      this.connection.on(Connection.Events.CONNECTED, onConnectedOrDisconnected);
-      this.connection.on(Connection.Events.DISCONNECTED, onConnectedOrDisconnected);
-      try {
-        // Reset the connection's state to defaults
-        this.connection.resetOptions();
-        // Only watch for errors here.  Final resolution occurs above, once
-        // we've reached the CONNECTED state.
-        this.selectedRuntime.connect(this.connection)
-                            .then(null, e => deferred.reject(e));
-      } catch (e) {
-        deferred.reject(e);
-      }
-    }, deferred.reject);
+      }, reject);
+    });
 
     // Record connection result in telemetry
     let logResult = result => {
       this._telemetry.log("DEVTOOLS_WEBIDE_CONNECTION_RESULT", result);
       if (runtime.type) {
         this._telemetry.log("DEVTOOLS_WEBIDE_" + runtime.type +
                             "_CONNECTION_RESULT", result);
       }
     };
-    deferred.promise.then(() => logResult(true), () => logResult(false));
+    deferred.then(() => logResult(true), () => logResult(false));
 
     // If successful, record connection time in telemetry
-    deferred.promise.then(() => {
+    deferred.then(() => {
       const timerId = "DEVTOOLS_WEBIDE_CONNECTION_TIME_SECONDS";
       this._telemetry.startTimer(timerId);
       this.connection.once(Connection.Events.STATUS_CHANGED, () => {
         this._telemetry.stopTimer(timerId);
       });
     }).catch(() => {
       // Empty rejection handler to silence uncaught rejection warnings
       // |connectToRuntime| caller should listen for rejections.
       // Bug 1121100 may find a better way to silence these.
     });
 
-    return deferred.promise;
+    return deferred;
   },
 
   _recordRuntimeInfo: Task.async(function* () {
     if (!this.connected) {
       return;
     }
     let runtime = this.selectedRuntime;
     this._telemetry.logKeyed("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_TYPE",
@@ -565,35 +564,36 @@ var AppManager = exports.AppManager = {
     if (!this._listTabsResponse) {
       return null;
     }
     return getPreferenceFront(this.connection.client, this._listTabsResponse);
   },
 
   disconnectRuntime: function () {
     if (!this.connected) {
-      return promise.resolve();
+      return Promise.resolve();
     }
-    let deferred = promise.defer();
-    this.connection.once(Connection.Events.DISCONNECTED, () => deferred.resolve());
-    this.connection.disconnect();
-    return deferred.promise;
+
+    return new Promise(resolve => {
+      this.connection.once(Connection.Events.DISCONNECTED, () => resolve());
+      this.connection.disconnect();
+    });
   },
 
   launchRuntimeApp: function () {
     if (this.selectedProject && this.selectedProject.type != "runtimeApp") {
-      return promise.reject("attempting to launch a non-runtime app");
+      return Promise.reject("attempting to launch a non-runtime app");
     }
     let app = this._getProjectFront(this.selectedProject);
     return app.launch();
   },
 
   launchOrReloadRuntimeApp: function () {
     if (this.selectedProject && this.selectedProject.type != "runtimeApp") {
-      return promise.reject("attempting to launch / reload a non-runtime app");
+      return Promise.reject("attempting to launch / reload a non-runtime app");
     }
     let app = this._getProjectFront(this.selectedProject);
     if (!app.running) {
       return app.launch();
     } else {
       return app.reload();
     }
   },
@@ -602,27 +602,27 @@ var AppManager = exports.AppManager = {
     return !!this._appsFront;
   },
 
   installAndRunProject: function () {
     let project = this.selectedProject;
 
     if (!project || (project.type != "packaged" && project.type != "hosted")) {
       console.error("Can't install project. Unknown type of project.");
-      return promise.reject("Can't install");
+      return Promise.reject("Can't install");
     }
 
     if (!this._listTabsResponse) {
       this.reportError("error_cantInstallNotFullyConnected");
-      return promise.reject("Can't install");
+      return Promise.reject("Can't install");
     }
 
     if (!this._appsFront) {
       console.error("Runtime doesn't have a webappsActor");
-      return promise.reject("Can't install");
+      return Promise.reject("Can't install");
     }
 
     return Task.spawn(function* () {
       let self = AppManager;
 
       // Package and validate project
       yield self.packageProject(project);
       yield self.validateAndUpdateProject(project);
@@ -630,17 +630,17 @@ var AppManager = exports.AppManager = {
       if (project.errorsCount > 0) {
         self.reportError("error_cantInstallValidationErrors");
         return;
       }
 
       let installPromise;
 
       if (project.type != "packaged" && project.type != "hosted") {
-        return promise.reject("Don't know how to install project");
+        return Promise.reject("Don't know how to install project");
       }
 
       let response;
       if (project.type == "packaged") {
         let packageDir = yield ProjectBuilding.getPackageDir(project);
         console.log("Installing app from " + packageDir);
 
         response = yield self._appsFront.installPackaged(packageDir,
@@ -669,41 +669,42 @@ var AppManager = exports.AppManager = {
       // Addons don't have any document to load (yet?)
       // So that there is no need to run them, installing is enough
       if (project.manifest.manifest_version || project.manifest.role === "addon") {
         return;
       }
 
       let {app} = response;
       if (!app.running) {
-        let deferred = promise.defer();
-        self.on("app-manager-update", function onUpdate(event, what) {
-          if (what == "project-started") {
-            self.off("app-manager-update", onUpdate);
-            deferred.resolve();
-          }
+        let deferred = new Promise(resolve => {
+          self.on("app-manager-update", function onUpdate(event, what) {
+            if (what == "project-started") {
+              self.off("app-manager-update", onUpdate);
+              resolve();
+            }
+          });
         });
         yield app.launch();
-        yield deferred.promise;
+        yield deferred;
       } else {
         yield app.reload();
       }
     });
   },
 
   stopRunningApp: function () {
     let app = this._getProjectFront(this.selectedProject);
     return app.close();
   },
 
   /* PROJECT VALIDATION */
 
   validateAndUpdateProject: function (project) {
     if (!project) {
-      return promise.reject();
+      return Promise.reject();
     }
 
     return Task.spawn(function* () {
 
       let packageDir = yield ProjectBuilding.getPackageDir(project);
       let validation = new AppValidator({
         type: project.type,
         // Build process may place the manifest in a non-root directory
@@ -818,17 +819,17 @@ var AppManager = exports.AppManager = {
     this.update("runtime-details");
     this.update("runtime-list");
   },
 
   /* MANIFEST UTILS */
 
   writeManifest: function (project) {
     if (project.type != "packaged") {
-      return promise.reject("Not a packaged app");
+      return Promise.reject("Not a packaged app");
     }
 
     if (!project.manifest) {
       project.manifest = {};
     }
 
     let folder = project.location;
     let manifestPath = OS.Path.join(folder, "manifest.webapp");
--- a/devtools/client/webide/modules/app-projects.js
+++ b/devtools/client/webide/modules/app-projects.js
@@ -1,14 +1,13 @@
 /* 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/. */
 
 const {Cc, Ci, Cu, Cr} = require("chrome");
-const promise = require("promise");
 
 const EventEmitter = require("devtools/shared/event-emitter");
 const {generateUUID} = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
 const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm", {});
 
 /**
  * IndexedDB wrapper that just save project objects
  *
@@ -16,155 +15,145 @@ const {FileUtils} = Cu.import("resource:
  * a unique `location` object.
  */
 
 const IDB = {
   _db: null,
   databaseName: "AppProjects",
 
   open: function () {
-    let deferred = promise.defer();
-
-    let request = indexedDB.open(IDB.databaseName, 5);
-    request.onerror = function (event) {
-      deferred.reject("Unable to open AppProjects indexedDB: " +
-                      this.error.name + " - " + this.error.message);
-    };
-    request.onupgradeneeded = function (event) {
-      let db = event.target.result;
-      db.createObjectStore("projects", { keyPath: "location" });
-    };
+    return new Promise((resolve, reject) => {
+      let request = indexedDB.open(IDB.databaseName, 5);
+      request.onerror = function (event) {
+        reject("Unable to open AppProjects indexedDB: " +
+                        this.error.name + " - " + this.error.message);
+      };
+      request.onupgradeneeded = function (event) {
+        let db = event.target.result;
+        db.createObjectStore("projects", { keyPath: "location" });
+      };
 
-    request.onsuccess = function () {
-      let db = IDB._db = request.result;
-      let objectStore = db.transaction("projects").objectStore("projects");
-      let projects = [];
-      let toRemove = [];
-      objectStore.openCursor().onsuccess = function (event) {
-        let cursor = event.target.result;
-        if (cursor) {
-          if (cursor.value.location) {
+      request.onsuccess = function () {
+        let db = IDB._db = request.result;
+        let objectStore = db.transaction("projects").objectStore("projects");
+        let projects = [];
+        let toRemove = [];
+        objectStore.openCursor().onsuccess = function (event) {
+          let cursor = event.target.result;
+          if (cursor) {
+            if (cursor.value.location) {
 
-            // We need to make sure this object has a `.location` property.
-            // The UI depends on this property.
-            // This should not be needed as we make sure to register valid
-            // projects, but in the past (before bug 924568), we might have
-            // registered invalid objects.
+              // We need to make sure this object has a `.location` property.
+              // The UI depends on this property.
+              // This should not be needed as we make sure to register valid
+              // projects, but in the past (before bug 924568), we might have
+              // registered invalid objects.
 
 
-            // We also want to make sure the location is valid.
-            // If the location doesn't exist, we remove the project.
+              // We also want to make sure the location is valid.
+              // If the location doesn't exist, we remove the project.
 
-            try {
-              let file = FileUtils.File(cursor.value.location);
-              if (file.exists()) {
-                projects.push(cursor.value);
-              } else {
-                toRemove.push(cursor.value.location);
-              }
-            } catch (e) {
-              if (e.result == Cr.NS_ERROR_FILE_UNRECOGNIZED_PATH) {
-                // A URL
-                projects.push(cursor.value);
+              try {
+                let file = FileUtils.File(cursor.value.location);
+                if (file.exists()) {
+                  projects.push(cursor.value);
+                } else {
+                  toRemove.push(cursor.value.location);
+                }
+              } catch (e) {
+                if (e.result == Cr.NS_ERROR_FILE_UNRECOGNIZED_PATH) {
+                  // A URL
+                  projects.push(cursor.value);
+                }
               }
             }
-          }
-          cursor.continue();
-        } else {
-          let removePromises = [];
-          for (let location of toRemove) {
-            removePromises.push(IDB.remove(location));
+            cursor.continue();
+          } else {
+            let removePromises = [];
+            for (let location of toRemove) {
+              removePromises.push(IDB.remove(location));
+            }
+            Promise.all(removePromises).then(() => {
+              resolve(projects);
+            });
           }
-          promise.all(removePromises).then(() => {
-            deferred.resolve(projects);
-          });
-        }
+        };
       };
-    };
-
-    return deferred.promise;
+    });
   },
 
   add: function (project) {
-    let deferred = promise.defer();
-
-    if (!project.location) {
-      // We need to make sure this object has a `.location` property.
-      deferred.reject("Missing location property on project object.");
-    } else {
-      let transaction = IDB._db.transaction(["projects"], "readwrite");
-      let objectStore = transaction.objectStore("projects");
-      let request = objectStore.add(project);
-      request.onerror = function (event) {
-        deferred.reject("Unable to add project to the AppProjects indexedDB: " +
-                        this.error.name + " - " + this.error.message);
-      };
-      request.onsuccess = function () {
-        deferred.resolve();
-      };
-    }
-
-    return deferred.promise;
+    return new Promise((resolve, reject) => {
+      if (!project.location) {
+        // We need to make sure this object has a `.location` property.
+        reject("Missing location property on project object.");
+      } else {
+        let transaction = IDB._db.transaction(["projects"], "readwrite");
+        let objectStore = transaction.objectStore("projects");
+        let request = objectStore.add(project);
+        request.onerror = function (event) {
+          reject("Unable to add project to the AppProjects indexedDB: " +
+                 this.error.name + " - " + this.error.message);
+        };
+        request.onsuccess = function () {
+          resolve();
+        };
+      }
+    });
   },
 
   update: function (project) {
-    let deferred = promise.defer();
-
-    var transaction = IDB._db.transaction(["projects"], "readwrite");
-    var objectStore = transaction.objectStore("projects");
-    var request = objectStore.put(project);
-    request.onerror = function (event) {
-      deferred.reject("Unable to update project to the AppProjects indexedDB: " +
-                      this.error.name + " - " + this.error.message);
-    };
-    request.onsuccess = function () {
-      deferred.resolve();
-    };
-
-    return deferred.promise;
+    return new Promise((resolve, reject) => {
+      var transaction = IDB._db.transaction(["projects"], "readwrite");
+      var objectStore = transaction.objectStore("projects");
+      var request = objectStore.put(project);
+      request.onerror = function (event) {
+        reject("Unable to update project to the AppProjects indexedDB: " +
+               this.error.name + " - " + this.error.message);
+      };
+      request.onsuccess = function () {
+        resolve();
+      };
+    });
   },
 
   remove: function (location) {
-    let deferred = promise.defer();
-
-    let request = IDB._db.transaction(["projects"], "readwrite")
+    return new Promise((resolve, reject) => {
+      let request = IDB._db.transaction(["projects"], "readwrite")
                     .objectStore("projects")
                     .delete(location);
-    request.onsuccess = function (event) {
-      deferred.resolve();
-    };
-    request.onerror = function () {
-      deferred.reject("Unable to delete project to the AppProjects indexedDB: " +
-                      this.error.name + " - " + this.error.message);
-    };
-
-    return deferred.promise;
+      request.onsuccess = function (event) {
+        resolve();
+      };
+      request.onerror = function () {
+        reject("Unable to delete project to the AppProjects indexedDB: " +
+               this.error.name + " - " + this.error.message);
+      };
+    });
   }
 };
 
-var loadDeferred = promise.defer();
-
-loadDeferred.resolve(IDB.open().then(function (projects) {
+var loadDeferred = IDB.open().then(function (projects) {
   AppProjects.projects = projects;
   AppProjects.emit("ready", projects);
-}));
+});
 
 const AppProjects = {
   load: function () {
-    return loadDeferred.promise;
+    return loadDeferred;
   },
 
   addPackaged: function (folder) {
     let file = FileUtils.File(folder.path);
     if (!file.exists()) {
-      return promise.reject("path doesn't exist");
+      return Promise.reject("path doesn't exist");
     }
     let existingProject = this.get(folder.path);
     if (existingProject) {
-      return promise.reject("Already added");
+      return Promise.reject("Already added");
     }
     let project = {
       type: "packaged",
       location: folder.path,
       // We need a unique id, that is the app origin,
       // in order to identify the app when being installed on the device.
       // The packaged app local path is a valid id, but only on the client.
       // This origin will be used to generate the true id of an app:
@@ -177,17 +166,17 @@ const AppProjects = {
       this.projects.push(project);
       return project;
     });
   },
 
   addHosted: function (manifestURL) {
     let existingProject = this.get(manifestURL);
     if (existingProject) {
-      return promise.reject("Already added");
+      return Promise.reject("Already added");
     }
     let project = {
       type: "hosted",
       location: manifestURL
     };
     return IDB.add(project).then(() => {
       this.projects.push(project);
       return project;
--- a/devtools/client/webide/modules/app-validator.js
+++ b/devtools/client/webide/modules/app-validator.js
@@ -1,15 +1,14 @@
 /* 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";
 
 var {Ci, Cu, CC} = require("chrome");
-const promise = require("promise");
 
 const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm", {});
 const Services = require("Services");
 const {Task} = require("devtools/shared/task");
 var XMLHttpRequest = CC("@mozilla.org/xmlextras/xmlhttprequest;1");
 var strings = Services.strings.createBundle("chrome://devtools/locale/app-manager.properties");
 
 function AppValidator({ type, location }) {
@@ -59,127 +58,122 @@ AppValidator.prototype._getPackagedManif
   let manifestFile = this._getPackagedManifestFile();
   if (!manifestFile) {
     return null;
   }
   return Services.io.newFileURI(manifestFile).spec;
 };
 
 AppValidator.checkManifest = function (manifestURL) {
-  let deferred = promise.defer();
-  let error;
+  return new Promise((resolve, reject) => {
+    let error;
 
-  let req = new XMLHttpRequest();
-  req.overrideMimeType("text/plain");
+    let req = new XMLHttpRequest();
+    req.overrideMimeType("text/plain");
 
-  try {
-    req.open("GET", manifestURL, true);
-    req.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE | Ci.nsIRequest.INHIBIT_CACHING;
-  } catch (e) {
-    error = strings.formatStringFromName("validator.invalidManifestURL", [manifestURL], 1);
-    deferred.reject(error);
-    return deferred.promise;
-  }
-
-  req.onload = function () {
-    let manifest = null;
     try {
-      manifest = JSON.parse(req.responseText);
+      req.open("GET", manifestURL, true);
+      req.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE | Ci.nsIRequest.INHIBIT_CACHING;
     } catch (e) {
-      error = strings.formatStringFromName("validator.invalidManifestJSON", [e, manifestURL], 2);
-      deferred.reject(error);
+      error = strings.formatStringFromName("validator.invalidManifestURL", [manifestURL], 1);
+      return reject(error);
     }
 
-    deferred.resolve({manifest, manifestURL});
-  };
+    req.onload = function () {
+      let manifest = null;
+      try {
+        manifest = JSON.parse(req.responseText);
+      } catch (e) {
+        error = strings.formatStringFromName("validator.invalidManifestJSON", [e, manifestURL], 2);
+        reject(error);
+      }
 
-  req.onerror = function () {
-    error = strings.formatStringFromName("validator.noAccessManifestURL", [req.statusText, manifestURL], 2);
-    deferred.reject(error);
-  };
+      resolve({manifest, manifestURL});
+    };
 
-  try {
-    req.send(null);
-  } catch (e) {
-    error = strings.formatStringFromName("validator.noAccessManifestURL", [e, manifestURL], 2);
-    deferred.reject(error);
-  }
+    req.onerror = function () {
+      error = strings.formatStringFromName("validator.noAccessManifestURL", [req.statusText, manifestURL], 2);
+      reject(error);
+    };
 
-  return deferred.promise;
+    try {
+      req.send(null);
+    } catch (e) {
+      error = strings.formatStringFromName("validator.noAccessManifestURL", [e, manifestURL], 2);
+      reject(error);
+    }
+  });
 };
 
 AppValidator.findManifestAtOrigin = function (manifestURL) {
   let fixedManifest = Services.io.newURI(manifestURL).prePath + "/manifest.webapp";
   return AppValidator.checkManifest(fixedManifest);
 };
 
 AppValidator.findManifestPath = function (manifestURL) {
-  let deferred = promise.defer();
-
-  if (manifestURL.endsWith("manifest.webapp")) {
-    deferred.reject();
-  } else {
-    let fixedManifest = manifestURL + "/manifest.webapp";
-    deferred.resolve(AppValidator.checkManifest(fixedManifest));
-  }
-
-  return deferred.promise;
+  return new Promise((resolve, reject) => {
+    if (manifestURL.endsWith("manifest.webapp")) {
+      reject();
+    } else {
+      let fixedManifest = manifestURL + "/manifest.webapp";
+      resolve(AppValidator.checkManifest(fixedManifest));
+    }
+  });
 };
 
 AppValidator.checkAlternateManifest = function (manifestURL) {
   return Task.spawn(function* () {
     let result;
     try {
       result = yield AppValidator.findManifestPath(manifestURL);
     } catch (e) {
       result = yield AppValidator.findManifestAtOrigin(manifestURL);
     }
 
     return result;
   });
 };
 
 AppValidator.prototype._fetchManifest = function (manifestURL) {
-  let deferred = promise.defer();
-  this.manifestURL = manifestURL;
+  return new Promise(resolve => {
+    this.manifestURL = manifestURL;
 
-  AppValidator.checkManifest(manifestURL)
-              .then(({manifest, manifestURL}) => {
-                deferred.resolve(manifest);
-              }, error => {
-                AppValidator.checkAlternateManifest(manifestURL)
-                            .then(({manifest, manifestURL}) => {
-                              this.manifestURL = manifestURL;
-                              deferred.resolve(manifest);
-                            }, () => {
-                              this.error(error);
-                              deferred.resolve(null);
-                            });
-              });
-
-  return deferred.promise;
+    AppValidator.checkManifest(manifestURL)
+                .then(({manifest, manifestURL}) => {
+                  resolve(manifest);
+                }, error => {
+                  AppValidator.checkAlternateManifest(manifestURL)
+                              .then(({manifest, manifestURL}) => {
+                                this.manifestURL = manifestURL;
+                                resolve(manifest);
+                              }, () => {
+                                this.error(error);
+                                resolve(null);
+                              });
+                });
+  });
 };
 
 AppValidator.prototype._getManifest = function () {
   let manifestURL;
   if (this.type == "packaged") {
     manifestURL = this._getPackagedManifestURL();
     if (!manifestURL)
-      return promise.resolve(null);
+      return Promise.resolve(null);
   } else if (this.type == "hosted") {
     manifestURL = this.location;
     try {
       Services.io.newURI(manifestURL);
     } catch (e) {
       this.error(strings.formatStringFromName("validator.invalidHostedManifestURL", [manifestURL, e.message], 2));
-      return promise.resolve(null);
+      return Promise.resolve(null);
     }
   } else {
     this.error(strings.formatStringFromName("validator.invalidProjectType", [this.type], 1));
-    return promise.resolve(null);
+    return Promise.resolve(null);
   }
   return this._fetchManifest(manifestURL);
 };
 
 AppValidator.prototype.validateManifest = function (manifest) {
   if (!manifest.name) {
     this.error(strings.GetStringFromName("validator.missNameManifestProperty"));
   }
@@ -196,67 +190,63 @@ AppValidator.prototype._getOriginURL = f
     let manifestURL = Services.io.newURI(this.manifestURL);
     return Services.io.newURI(".", null, manifestURL).spec;
   } else if (this.type == "hosted") {
     return Services.io.newURI(this.location).prePath;
   }
 };
 
 AppValidator.prototype.validateLaunchPath = function (manifest) {
-  let deferred = promise.defer();
-  // The launch_path field has to start with a `/`
-  if (manifest.launch_path && manifest.launch_path[0] !== "/") {
-    this.error(strings.formatStringFromName("validator.nonAbsoluteLaunchPath", [manifest.launch_path], 1));
-    deferred.resolve();
-    return deferred.promise;
-  }
-  let origin = this._getOriginURL();
-  let path;
-  if (this.type == "packaged") {
-    path = "." + (manifest.launch_path || "/index.html");
-  } else if (this.type == "hosted") {
-    path = manifest.launch_path || "/";
-  }
-  let indexURL;
-  try {
-    indexURL = Services.io.newURI(path, null, Services.io.newURI(origin)).spec;
-  } catch (e) {
-    this.error(strings.formatStringFromName("validator.accessFailedLaunchPath", [origin + path], 1));
-    deferred.resolve();
-    return deferred.promise;
-  }
+  return new Promise(resolve => {
+    // The launch_path field has to start with a `/`
+    if (manifest.launch_path && manifest.launch_path[0] !== "/") {
+      this.error(strings.formatStringFromName("validator.nonAbsoluteLaunchPath", [manifest.launch_path], 1));
+      resolve();
+    }
+    let origin = this._getOriginURL();
+    let path;
+    if (this.type == "packaged") {
+      path = "." + (manifest.launch_path || "/index.html");
+    } else if (this.type == "hosted") {
+      path = manifest.launch_path || "/";
+    }
+    let indexURL;
+    try {
+      indexURL = Services.io.newURI(path, null, Services.io.newURI(origin)).spec;
+    } catch (e) {
+      this.error(strings.formatStringFromName("validator.accessFailedLaunchPath", [origin + path], 1));
+      return resolve();
+    }
 
-  let req = new XMLHttpRequest();
-  req.overrideMimeType("text/plain");
-  try {
-    req.open("HEAD", indexURL, true);
-    req.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE | Ci.nsIRequest.INHIBIT_CACHING;
-  } catch (e) {
-    this.error(strings.formatStringFromName("validator.accessFailedLaunchPath", [indexURL], 1));
-    deferred.resolve();
-    return deferred.promise;
-  }
-  req.onload = () => {
-    if (req.status >= 400)
-      this.error(strings.formatStringFromName("validator.accessFailedLaunchPathBadHttpCode", [indexURL, req.status], 2));
-    deferred.resolve();
-  };
-  req.onerror = () => {
-    this.error(strings.formatStringFromName("validator.accessFailedLaunchPath", [indexURL], 1));
-    deferred.resolve();
-  };
+    let req = new XMLHttpRequest();
+    req.overrideMimeType("text/plain");
+    try {
+      req.open("HEAD", indexURL, true);
+      req.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE | Ci.nsIRequest.INHIBIT_CACHING;
+    } catch (e) {
+      this.error(strings.formatStringFromName("validator.accessFailedLaunchPath", [indexURL], 1));
+      return resolve();
+    }
+    req.onload = () => {
+      if (req.status >= 400)
+        this.error(strings.formatStringFromName("validator.accessFailedLaunchPathBadHttpCode", [indexURL, req.status], 2));
+      resolve();
+    };
+    req.onerror = () => {
+      this.error(strings.formatStringFromName("validator.accessFailedLaunchPath", [indexURL], 1));
+      resolve();
+    };
 
-  try {
-    req.send(null);
-  } catch (e) {
-    this.error(strings.formatStringFromName("validator.accessFailedLaunchPath", [indexURL], 1));
-    deferred.resolve();
-  }
-
-  return deferred.promise;
+    try {
+      req.send(null);
+    } catch (e) {
+      this.error(strings.formatStringFromName("validator.accessFailedLaunchPath", [indexURL], 1));
+      resolve();
+    }
+  });
 };
 
 AppValidator.prototype.validateType = function (manifest) {
   let appType = manifest.type || "web";
   if (["web", "privileged", "certified"].indexOf(appType) === -1) {
     this.error(strings.formatStringFromName("validator.invalidAppType", [appType], 1));
   } else if (this.type == "hosted" &&
              ["certified", "privileged"].indexOf(appType) !== -1) {
@@ -274,17 +264,17 @@ AppValidator.prototype.validate = functi
   this.warnings = [];
   return this._getManifest().
     then((manifest) => {
       if (manifest) {
         this.manifest = manifest;
 
         // Skip validations for add-ons
         if (manifest.role === "addon" || manifest.manifest_version) {
-          return promise.resolve();
+          return Promise.resolve();
         }
 
         this.validateManifest(manifest);
         this.validateType(manifest);
         return this.validateLaunchPath(manifest);
       }
     });
 };
--- a/devtools/client/webide/modules/build.js
+++ b/devtools/client/webide/modules/build.js
@@ -1,15 +1,14 @@
 /* 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/. */
 
 const {Cu, Cc, Ci} = require("chrome");
 
-const promise = require("promise");
 const { Task } = require("devtools/shared/task");
 const { OS } = Cu.import("resource://gre/modules/osfile.jsm", {});
 const Subprocess = require("sdk/system/child_process/subprocess");
 
 const ProjectBuilding = exports.ProjectBuilding = {
   fetchPackageManifest: Task.async(function* (project) {
     let manifestPath = OS.Path.join(project.location, "package.json");
     let exists = yield OS.File.exists(manifestPath);
@@ -144,41 +143,40 @@ const ProjectBuilding = exports.ProjectB
       args.unshift("/C");
     } else {
       args.unshift("-c");
     }
 
     // Subprocess changes CWD, we have to save and restore it.
     let originalCwd = yield OS.File.getCurrentDirectory();
     try {
-      let defer = promise.defer();
-      Subprocess.call({
-        command: shell,
-        arguments: args,
-        environment: env,
-        workdir: cwd,
+      yield new Promise((resolve, reject) => {
+        Subprocess.call({
+          command: shell,
+          arguments: args,
+          environment: env,
+          workdir: cwd,
 
-        stdout: data =>
-          logger(data),
-        stderr: data =>
-          logger(data),
+          stdout: data =>
+            logger(data),
+          stderr: data =>
+            logger(data),
 
-        done: result => {
-          logger("Terminated with error code: " + result.exitCode);
-          if (result.exitCode == 0) {
-            defer.resolve();
-          } else {
-            defer.reject("pre-package command failed with error code " + result.exitCode);
+          done: result => {
+            logger("Terminated with error code: " + result.exitCode);
+            if (result.exitCode == 0) {
+              resolve();
+            } else {
+              reject("pre-package command failed with error code " + result.exitCode);
+            }
           }
-        }
-      });
-      defer.promise.then(() => {
+        });
+      }).then(() => {
         OS.File.setCurrentDirectory(originalCwd);
       });
-      yield defer.promise;
     } catch (e) {
       throw new Error("Unable to run pre-package command '" + command + "' " +
                       args.join(" ") + ":\n" + (e.message || e));
     }
   }),
 
   getPackageDir: Task.async(function* (project) {
     let manifest = yield ProjectBuilding.fetchPackageManifest(project);
--- a/devtools/client/webide/modules/project-list.js
+++ b/devtools/client/webide/modules/project-list.js
@@ -2,17 +2,16 @@
 * 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/. */
 
 const {Cu} = require("chrome");
 
 const Services = require("Services");
 const {AppProjects} = require("devtools/client/webide/modules/app-projects");
 const {AppManager} = require("devtools/client/webide/modules/app-manager");
-const promise = require("promise");
 const EventEmitter = require("devtools/shared/event-emitter");
 const {Task} = require("devtools/shared/task");
 const utils = require("devtools/client/webide/modules/utils");
 const Telemetry = require("devtools/client/shared/telemetry");
 
 const Strings = Services.strings.createBundle("chrome://devtools/locale/webide.properties");
 
 var ProjectList;
@@ -195,17 +194,17 @@ ProjectList.prototype = {
           app: tab,
           icon: tab.favicon || AppManager.DEFAULT_PROJECT_ICON,
           location: tab.url,
           name: tab.name
         };
       }, true);
     }
 
-    return promise.resolve();
+    return Promise.resolve();
   },
 
   updateApps: function () {
     let doc = this._doc;
     let runtimeappsHeaderNode = doc.querySelector("#panel-header-runtimeapps");
     let sortedApps = [];
     for (let [manifestURL, app] of AppManager.apps) {
       sortedApps.push(app);
@@ -258,17 +257,17 @@ ProjectList.prototype = {
           type: "runtimeApp",
           app: app.manifest,
           icon: app.iconURL || AppManager.DEFAULT_PROJECT_ICON,
           name: app.manifest.name
         };
       }, true);
     }
 
-    return promise.resolve();
+    return Promise.resolve();
   },
 
   updateCommands: function () {
     let doc = this._doc;
     let newAppCmd;
     let packagedAppCmd;
     let hostedAppCmd;
 
@@ -294,79 +293,77 @@ ProjectList.prototype = {
 
   /**
    * Trigger an update of the project and remote runtime list.
    * @param options object (optional)
    *        An |options| object containing a type of |apps| or |tabs| will limit
    *        what is updated to only those sections.
    */
   update: function (options) {
-    let deferred = promise.defer();
-
     if (options && options.type === "apps") {
       return this.updateApps();
     } else if (options && options.type === "tabs") {
       return this.updateTabs();
     }
 
-    let doc = this._doc;
-    let projectsNode = doc.querySelector("#project-panel-projects");
+    return new Promise((resolve, reject) => {
+      let doc = this._doc;
+      let projectsNode = doc.querySelector("#project-panel-projects");
 
-    while (projectsNode.hasChildNodes()) {
-      projectsNode.firstChild.remove();
-    }
+      while (projectsNode.hasChildNodes()) {
+        projectsNode.firstChild.remove();
+      }
 
-    AppProjects.load().then(() => {
-      let projects = AppProjects.projects;
-      for (let i = 0; i < projects.length; i++) {
-        let project = projects[i];
-        let panelItemNode = doc.createElement(this._panelNodeEl);
-        panelItemNode.className = "panel-item";
-        projectsNode.appendChild(panelItemNode);
-        if (!project.validationStatus) {
-          // The result of the validation process (storing names, icons, …) is not stored in
-          // the IndexedDB database when App Manager v1 is used.
-          // We need to run the validation again and update the name and icon of the app.
-          AppManager.validateAndUpdateProject(project).then(() => {
+      AppProjects.load().then(() => {
+        let projects = AppProjects.projects;
+        for (let i = 0; i < projects.length; i++) {
+          let project = projects[i];
+          let panelItemNode = doc.createElement(this._panelNodeEl);
+          panelItemNode.className = "panel-item";
+          projectsNode.appendChild(panelItemNode);
+          if (!project.validationStatus) {
+            // The result of the validation process (storing names, icons, …) is not stored in
+            // the IndexedDB database when App Manager v1 is used.
+            // We need to run the validation again and update the name and icon of the app.
+            AppManager.validateAndUpdateProject(project).then(() => {
+              this._renderProjectItem({
+                panel: panelItemNode,
+                name: project.name,
+                icon: project.icon
+              });
+            });
+          } else {
             this._renderProjectItem({
               panel: panelItemNode,
-              name: project.name,
-              icon: project.icon
+              name: project.name || AppManager.DEFAULT_PROJECT_NAME,
+              icon: project.icon || AppManager.DEFAULT_PROJECT_ICON
             });
-          });
-        } else {
-          this._renderProjectItem({
-            panel: panelItemNode,
-            name: project.name || AppManager.DEFAULT_PROJECT_NAME,
-            icon: project.icon || AppManager.DEFAULT_PROJECT_ICON
-          });
+          }
+          panelItemNode.addEventListener("click", () => {
+            AppManager.selectedProject = project;
+          }, true);
         }
-        panelItemNode.addEventListener("click", () => {
-          AppManager.selectedProject = project;
-        }, true);
-      }
+
+        resolve();
+      }, reject);
 
-      deferred.resolve();
-    }, deferred.reject);
+      // List remote apps and the main process, if they exist
+      this.updateApps();
 
-    // List remote apps and the main process, if they exist
-    this.updateApps();
-
-    // Build the tab list right now, so it's fast...
-    this.updateTabs();
+      // Build the tab list right now, so it's fast...
+      this.updateTabs();
 
-    // But re-list them and rebuild, in case any tabs navigated since the last
-    // time they were listed.
-    if (AppManager.connected) {
-      AppManager.listTabs().then(() => {
-        this.updateTabs();
-      }).catch(console.error);
-    }
-
-    return deferred.promise;
+      // But re-list them and rebuild, in case any tabs navigated since the last
+      // time they were listed.
+      if (AppManager.connected) {
+        AppManager.listTabs().then(() => {
+          this.updateTabs();
+        }).catch(console.error);
+      }
+    });
   },
 
   destroy: function () {
     this._doc = null;
     AppManager.off("app-manager-update", this.appManagerUpdate);
     this._UI.off("webide-update", this.onWebIDEUpdate);
     this._UI = null;
     this._parentWindow = null;
--- a/devtools/client/webide/modules/simulator-process.js
+++ b/devtools/client/webide/modules/simulator-process.js
@@ -4,17 +4,16 @@
  */
 
 "use strict";
 
 const { Cc, Ci, Cu } = require("chrome");
 
 const Environment = require("sdk/system/environment").env;
 const EventEmitter = require("devtools/shared/event-emitter");
-const promise = require("promise");
 const Subprocess = require("sdk/system/child_process/subprocess");
 const Services = require("Services");
 
 loader.lazyGetter(this, "OS", () => {
   const Runtime = require("sdk/system/runtime");
   switch (Runtime.OS) {
     case "Darwin":
       return "mac64";
@@ -99,31 +98,31 @@ SimulatorProcess.prototype = {
         this.process = null;
         this.emit("exit", result.exitCode);
       }
     });
   },
 
   // Request a B2G instance kill.
   kill() {
-    let deferred = promise.defer();
-    if (this.process) {
-      this.once("exit", (e, exitCode) => {
-        this.shuttingDown = false;
-        deferred.resolve(exitCode);
-      });
-      if (!this.shuttingDown) {
-        this.shuttingDown = true;
-        this.emit("kill", null);
-        this.process.kill();
+    return new Promise(resolve => {
+      if (this.process) {
+        this.once("exit", (e, exitCode) => {
+          this.shuttingDown = false;
+          resolve(exitCode);
+        });
+        if (!this.shuttingDown) {
+          this.shuttingDown = true;
+          this.emit("kill", null);
+          this.process.kill();
+        }
+      } else {
+        return resolve(undefined);
       }
-      return deferred.promise;
-    } else {
-      return promise.resolve(undefined);
-    }
+    });
   },
 
   // Maybe log output messages.
   log(level, message) {
     if (!Services.prefs.getBoolPref("devtools.webide.logSimulatorOutput")) {
       return;
     }
     if (level === "stderr" || level === "error") {
--- a/devtools/client/webide/modules/simulators.js
+++ b/devtools/client/webide/modules/simulators.js
@@ -7,17 +7,16 @@
 const { AddonManager } = require("resource://gre/modules/AddonManager.jsm");
 const { Task } = require("devtools/shared/task");
 loader.lazyRequireGetter(this, "ConnectionManager", "devtools/shared/client/connection-manager", true);
 loader.lazyRequireGetter(this, "AddonSimulatorProcess", "devtools/client/webide/modules/simulator-process", true);
 loader.lazyRequireGetter(this, "OldAddonSimulatorProcess", "devtools/client/webide/modules/simulator-process", true);
 loader.lazyRequireGetter(this, "CustomSimulatorProcess", "devtools/client/webide/modules/simulator-process", true);
 const asyncStorage = require("devtools/shared/async-storage");
 const EventEmitter = require("devtools/shared/event-emitter");
-const promise = require("promise");
 const Services = require("Services");
 
 const SimulatorRegExp = new RegExp(Services.prefs.getCharPref("devtools.webide.simulatorAddonRegExp"));
 const LocaleCompare = (a, b) => {
   return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
 };
 
 var Simulators = {
@@ -41,28 +40,29 @@ var Simulators = {
       let value = yield asyncStorage.getItem("simulators");
       if (Array.isArray(value)) {
         value.forEach(options => {
           let simulator = new Simulator(options);
           Simulators.add(simulator, true);
 
           // If the simulator had a reference to an addon, fix it.
           if (options.addonID) {
-            let deferred = promise.defer();
-            AddonManager.getAddonByID(options.addonID, addon => {
-              simulator.addon = addon;
-              delete simulator.options.addonID;
-              deferred.resolve();
+            let deferred = new Promise(resolve => {
+              AddonManager.getAddonByID(options.addonID, addon => {
+                simulator.addon = addon;
+                delete simulator.options.addonID;
+                resolve();
+              });
             });
-            jobs.push(deferred.promise);
+            jobs.push(deferred);
           }
         });
       }
 
-      yield promise.all(jobs);
+      yield Promise.all(jobs);
       yield Simulators._addUnusedAddons();
       Simulators.emitUpdated();
       return Simulators._simulators;
     });
 
     return this._loadingPromise;
   },
 
@@ -74,17 +74,17 @@ var Simulators = {
   _addUnusedAddons: Task.async(function* () {
     let jobs = [];
 
     let addons = yield Simulators.findSimulatorAddons();
     addons.forEach(addon => {
       jobs.push(Simulators.addIfUnusedAddon(addon, true));
     });
 
-    yield promise.all(jobs);
+    yield Promise.all(jobs);
   }),
 
   /**
    * Save the current list of configurations.
    *
    * @return Promise.
    */
   _save: Task.async(function* () {
@@ -112,39 +112,39 @@ var Simulators = {
   }),
 
   /**
    * List all installed simulator addons.
    *
    * @return Promised addon list.
    */
   findSimulatorAddons() {
-    let deferred = promise.defer();
-    AddonManager.getAllAddons(all => {
-      let addons = [];
-      for (let addon of all) {
-        if (Simulators.isSimulatorAddon(addon)) {
-          addons.push(addon);
+    return new Promise(resolve => {
+      AddonManager.getAllAddons(all => {
+        let addons = [];
+        for (let addon of all) {
+          if (Simulators.isSimulatorAddon(addon)) {
+            addons.push(addon);
+          }
         }
-      }
-      // Sort simulator addons by name.
-      addons.sort(LocaleCompare);
-      deferred.resolve(addons);
+        // Sort simulator addons by name.
+        addons.sort(LocaleCompare);
+        resolve(addons);
+      });
     });
-    return deferred.promise;
   },
 
   /**
    * Add a new simulator for `addon` if no other simulator uses it.
    */
   addIfUnusedAddon(addon, silently = false) {
     let simulators = this._simulators;
     let matching = simulators.filter(s => s.addon && s.addon.id == addon.id);
     if (matching.length > 0) {
-      return promise.resolve();
+      return Promise.resolve();
     }
     let options = {};
     options.name = addon.name.replace(" Simulator", "");
     // Some addons specify a simulator type at the end of their version string,
     // e.g. "2_5_tv".
     let type = this.simulatorAddonVersion(addon).split("_")[2];
     if (type) {
       // "tv" is shorthand for type "television".
@@ -171,17 +171,17 @@ var Simulators = {
   add(simulator, silently = false) {
     let simulators = this._simulators;
     let uniqueName = this.uniqueName(simulator.options.name);
     simulator.options.name = uniqueName;
     simulators.push(simulator);
     if (!silently) {
       this.emitUpdated();
     }
-    return promise.resolve(simulator);
+    return Promise.resolve(simulator);
   },
 
   /**
    * Remove a simulator from the list.
    */
   remove(simulator) {
     let simulators = this._simulators;
     let remaining = simulators.filter(s => s !== simulator);
@@ -327,23 +327,23 @@ Simulator.prototype = {
       // Recent simulator addon.
       this.process = new AddonSimulatorProcess(this.addon, this.options);
     } else {
       // Old simulator addon.
       this.process = new OldAddonSimulatorProcess(this.addon, this.options);
     }
     this.process.run();
 
-    return promise.resolve(this.options.port);
+    return Promise.resolve(this.options.port);
   },
 
   kill() {
     let process = this.process;
     if (!process) {
-      return promise.resolve();
+      return Promise.resolve();
     }
     this.process = null;
     return process.kill();
   },
 
   get defaults() {
     let defaults = this._defaults;
     return defaults[this.type] || defaults[this._defaultType];
--- a/devtools/client/webide/modules/tab-store.js
+++ b/devtools/client/webide/modules/tab-store.js
@@ -2,17 +2,16 @@
  * 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/. */
 
 const { Cu } = require("chrome");
 
 const { TargetFactory } = require("devtools/client/framework/target");
 const EventEmitter = require("devtools/shared/event-emitter");
 const { Connection } = require("devtools/shared/client/connection-manager");
-const promise = require("promise");
 const { Task } = require("devtools/shared/task");
 
 const _knownTabStores = new WeakMap();
 
 var TabStore;
 
 module.exports = TabStore = function (connection) {
   // If we already know about this connection,
@@ -90,35 +89,36 @@ TabStore.prototype = {
     }
     this._selectedTab.url = url;
     this._selectedTab.title = title;
     this.emit("navigate");
   },
 
   listTabs: function () {
     if (!this._connection || !this._connection.client) {
-      return promise.reject(new Error("Can't listTabs, not connected."));
+      return Promise.reject(new Error("Can't listTabs, not connected."));
     }
-    let deferred = promise.defer();
-    this._connection.client.listTabs(response => {
-      if (response.error) {
-        this._connection.disconnect();
-        deferred.reject(response.error);
-        return;
-      }
-      let tabsChanged = JSON.stringify(this.tabs) !== JSON.stringify(response.tabs);
-      this.response = response;
-      this.tabs = response.tabs;
-      this._checkSelectedTab();
-      if (tabsChanged) {
-        this.emit("tab-list");
-      }
-      deferred.resolve(response);
+
+    return new Promise((resolve, reject) => {
+      this._connection.client.listTabs(response => {
+        if (response.error) {
+          this._connection.disconnect();
+          reject(response.error);
+          return;
+        }
+        let tabsChanged = JSON.stringify(this.tabs) !== JSON.stringify(response.tabs);
+        this.response = response;
+        this.tabs = response.tabs;
+        this._checkSelectedTab();
+        if (tabsChanged) {
+          this.emit("tab-list");
+        }
+        resolve(response);
+      });
     });
-    return deferred.promise;
   },
 
   // TODO: Tab "selection" should really take place by creating a TabProject
   // which is the selected project.  This should be done as part of the
   // project-agnostic work.
   _selectedTab: null,
   _selectedTabTargetPromise: null,
   get selectedTab() {
--- a/devtools/client/webide/test/browser_tabs.js
+++ b/devtools/client/webide/test/browser_tabs.js
@@ -59,22 +59,22 @@ function test() {
     yield win.Cmds.disconnectRuntime();
     yield closeWebIDE(win);
 
     DebuggerServer.destroy();
   }).then(finish, handleError);
 }
 
 function connectToLocal(win, docRuntime) {
-  let deferred = promise.defer();
-  win.AppManager.connection.once(
+  return new Promise(resolve => {
+    win.AppManager.connection.once(
       win.Connection.Events.CONNECTED,
-      () => deferred.resolve());
-  docRuntime.querySelectorAll(".runtime-panel-item-other")[1].click();
-  return deferred.promise;
+      resolve);
+    docRuntime.querySelectorAll(".runtime-panel-item-other")[1].click();
+  });
 }
 
 function selectTabProject(win, docProject) {
   return Task.spawn(function* () {
     yield waitForUpdate(win, "runtime-targets");
     let tabsNode = docProject.querySelector("#project-panel-tabs");
     let tabNode = tabsNode.querySelectorAll(".panel-item")[1];
     let project = waitForUpdate(win, "project");
--- a/devtools/client/webide/test/head.js
+++ b/devtools/client/webide/test/head.js
@@ -3,17 +3,16 @@
 
 "use strict";
 
 var {utils: Cu, classes: Cc, interfaces: Ci} = Components;
 
 const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
 const { FileUtils } = require("resource://gre/modules/FileUtils.jsm");
 const { gDevTools } = require("devtools/client/framework/devtools");
-const promise = require("promise");
 const Services = require("Services");
 const { Task } = require("devtools/shared/task");
 const { AppProjects } = require("devtools/client/webide/modules/app-projects");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const { DebuggerServer } = require("devtools/server/main");
 const flags = require("devtools/shared/flags");
 flags.testing = true;
 
@@ -66,130 +65,120 @@ var openWebIDE = Task.async(function* (a
   info("WebIDE open");
 
   return win;
 });
 
 function closeWebIDE(win) {
   info("Closing WebIDE");
 
-  let deferred = promise.defer();
+  return new Promise(resolve => {
+    win.addEventListener("unload", function () {
+      info("WebIDE closed");
+      SimpleTest.executeSoon(resolve);
+    }, {once: true});
 
-  win.addEventListener("unload", function () {
-    info("WebIDE closed");
-    SimpleTest.executeSoon(() => {
-      deferred.resolve();
-    });
-  }, {once: true});
-
-  win.close();
-
-  return deferred.promise;
+    win.close();
+  });
 }
 
 function removeAllProjects() {
   return Task.spawn(function* () {
     yield AppProjects.load();
     // use a new array so we're not iterating over the same
     // underlying array that's being modified by AppProjects
     let projects = AppProjects.projects.map(p => p.location);
     for (let i = 0; i < projects.length; i++) {
       yield AppProjects.remove(projects[i]);
     }
   });
 }
 
 function nextTick() {
-  let deferred = promise.defer();
-  SimpleTest.executeSoon(() => {
-    deferred.resolve();
+  return new Promise(resolve => {
+    SimpleTest.executeSoon(resolve);
   });
-
-  return deferred.promise;
 }
 
 function waitForUpdate(win, update) {
   info("Wait: " + update);
-  let deferred = promise.defer();
-  win.AppManager.on("app-manager-update", function onUpdate(e, what) {
-    info("Got: " + what);
-    if (what !== update) {
-      return;
-    }
-    win.AppManager.off("app-manager-update", onUpdate);
-    deferred.resolve(win.UI._updatePromise);
+  return new Promise(resolve => {
+    win.AppManager.on("app-manager-update", function onUpdate(e, what) {
+      info("Got: " + what);
+      if (what !== update) {
+        return;
+      }
+      win.AppManager.off("app-manager-update", onUpdate);
+      resolve(win.UI._updatePromise);
+    });
   });
-  return deferred.promise;
 }
 
 function waitForTime(time) {
-  let deferred = promise.defer();
-  setTimeout(() => {
-    deferred.resolve();
-  }, time);
-  return deferred.promise;
+  return new Promise(resolve => {
+    setTimeout(resolve, time);
+  });
 }
 
 function documentIsLoaded(doc) {
-  let deferred = promise.defer();
-  if (doc.readyState == "complete") {
-    deferred.resolve();
-  } else {
-    doc.addEventListener("readystatechange", function onChange() {
-      if (doc.readyState == "complete") {
-        doc.removeEventListener("readystatechange", onChange);
-        deferred.resolve();
-      }
-    });
-  }
-  return deferred.promise;
+  return new Promise(resolve => {
+    if (doc.readyState == "complete") {
+      resolve();
+    } else {
+      doc.addEventListener("readystatechange", function onChange() {
+        if (doc.readyState == "complete") {
+          doc.removeEventListener("readystatechange", onChange);
+          resolve();
+        }
+      });
+    }
+  });
 }
 
 function lazyIframeIsLoaded(iframe) {
-  let deferred = promise.defer();
-  iframe.addEventListener("load", function () {
-    deferred.resolve(nextTick());
-  }, {capture: true, once: true});
-  return deferred.promise;
+  return new Promise(resolve => {
+    iframe.addEventListener("load", function () {
+      resolve(nextTick());
+    }, {capture: true, once: true});
+  });
 }
 
 function addTab(aUrl, aWindow) {
   info("Adding tab: " + aUrl);
 
-  let deferred = promise.defer();
-  let targetWindow = aWindow || window;
-  let targetBrowser = targetWindow.gBrowser;
+  return new Promise(resolve => {
+    let targetWindow = aWindow || window;
+    let targetBrowser = targetWindow.gBrowser;
 
-  targetWindow.focus();
-  let tab = targetBrowser.selectedTab = targetBrowser.addTab(aUrl);
-  let linkedBrowser = tab.linkedBrowser;
+    targetWindow.focus();
+    let tab = targetBrowser.selectedTab = targetBrowser.addTab(aUrl);
+    let linkedBrowser = tab.linkedBrowser;
 
-  BrowserTestUtils.browserLoaded(linkedBrowser).then(function () {
-    info("Tab added and finished loading: " + aUrl);
-    deferred.resolve(tab);
+    BrowserTestUtils.browserLoaded(linkedBrowser).then(function () {
+      info("Tab added and finished loading: " + aUrl);
+      resolve(tab);
+    });
   });
-
-  return deferred.promise;
 }
 
 function removeTab(aTab, aWindow) {
   info("Removing tab.");
 
-  let deferred = promise.defer();
-  let targetWindow = aWindow || window;
-  let targetBrowser = targetWindow.gBrowser;
-  let tabContainer = targetBrowser.tabContainer;
+  return new Promise(resolve => {
+    let targetWindow = aWindow || window;
+    let targetBrowser = targetWindow.gBrowser;
+    let tabContainer = targetBrowser.tabContainer;
 
-  tabContainer.addEventListener("TabClose", function (aEvent) {
-    info("Tab removed and finished closing.");
-    deferred.resolve();
-  }, {once: true});
+    tabContainer.addEventListener("TabClose", function (aEvent) {
+      info("Tab removed and finished closing.");
+      resolve();
+    }, {once: true});
 
-  targetBrowser.removeTab(aTab);
-  return deferred.promise;
+    targetBrowser.removeTab(aTab);
+  });
 }
 
 function getRuntimeDocument(win) {
   return win.document.querySelector("#runtime-listing-panel-details").contentDocument;
 }
 
 function getProjectDocument(win) {
   return win.document.querySelector("#project-listing-panel-details").contentDocument;
--- a/devtools/client/webide/test/test_addons.html
+++ b/devtools/client/webide/test/test_addons.html
@@ -17,92 +17,93 @@
     <script type="application/javascript">
       window.onload = function() {
         SimpleTest.waitForExplicitFinish();
 
         const {GetAvailableAddons} = require("devtools/client/webide/modules/addons");
         const {Devices} = Cu.import("resource://devtools/shared/apps/Devices.jsm");
         const {Simulators} = require("devtools/client/webide/modules/simulators");
 
-        let adbAddonsInstalled = promise.defer();
-        Devices.on("addon-status-updated", function onUpdate1() {
-          Devices.off("addon-status-updated", onUpdate1);
-          adbAddonsInstalled.resolve();
-        });
+        let adbAddonsInstalled;
 
         function getVersion(name) {
           return name.match(/(\d+\.\d+)/)[0];
         }
 
         function onSimulatorInstalled(name) {
-          let deferred = promise.defer();
-          Simulators.on("updated", function onUpdate() {
-            Simulators.findSimulatorAddons().then(addons => {
-              for (let addon of addons) {
-                if (name == addon.name.replace(" Simulator", "")) {
-                  Simulators.off("updated", onUpdate);
-                  nextTick().then(deferred.resolve);
-                  return;
+          return new Promise(resolve => {
+            Simulators.on("updated", function onUpdate() {
+              Simulators.findSimulatorAddons().then(addons => {
+                for (let addon of addons) {
+                  if (name == addon.name.replace(" Simulator", "")) {
+                    Simulators.off("updated", onUpdate);
+                    nextTick().then(resolve);
+                    return;
+                  }
                 }
-              }
+              });
             });
           });
-          return deferred.promise;
         }
 
         function installSimulatorFromUI(doc, name) {
           let li = doc.querySelector('[addon="simulator-' + getVersion(name) + '"]');
           li.querySelector(".install-button").click();
           return onSimulatorInstalled(name);
         }
 
         function uninstallSimulatorFromUI(doc, name) {
-          let deferred = promise.defer();
-          Simulators.on("updated", function onUpdate() {
-            nextTick().then(() => {
-              let li = doc.querySelector('[status="uninstalled"][addon="simulator-' + getVersion(name) + '"]');
-              if (li) {
-                Simulators.off("updated", onUpdate);
-                deferred.resolve();
-              } else {
-                deferred.reject("Can't find item");
-              }
+          return new Promise((resolve, reject) => {
+            Simulators.on("updated", function onUpdate() {
+              nextTick().then(() => {
+                let li = doc.querySelector('[status="uninstalled"][addon="simulator-' + getVersion(name) + '"]');
+                if (li) {
+                  Simulators.off("updated", onUpdate);
+                  resolve();
+                } else {
+                  reject("Can't find item");
+                }
+              });
             });
+            let li = doc.querySelector('[status="installed"][addon="simulator-' + getVersion(name) + '"]');
+            li.querySelector(".uninstall-button").click();
           });
-          let li = doc.querySelector('[status="installed"][addon="simulator-' + getVersion(name) + '"]');
-          li.querySelector(".uninstall-button").click();
-          return deferred.promise;
         }
 
         function uninstallADBFromUI(doc) {
-          let deferred = promise.defer();
-          Devices.on("addon-status-updated", function onUpdate() {
-            nextTick().then(() => {
-              let li = doc.querySelector('[status="uninstalled"][addon="adb"]');
-              if (li) {
-                Devices.off("addon-status-updated", onUpdate);
-                deferred.resolve();
-              } else {
-                deferred.reject("Can't find item");
-              }
-            })
+          return new Promise((resolve, reject) => {
+            Devices.on("addon-status-updated", function onUpdate() {
+              nextTick().then(() => {
+                let li = doc.querySelector('[status="uninstalled"][addon="adb"]');
+                if (li) {
+                  Devices.off("addon-status-updated", onUpdate);
+                  resolve();
+                } else {
+                  reject("Can't find item");
+                }
+              })
+            });
+            let li = doc.querySelector('[status="installed"][addon="adb"]');
+            li.querySelector(".uninstall-button").click();
           });
-          let li = doc.querySelector('[status="installed"][addon="adb"]');
-          li.querySelector(".uninstall-button").click();
-          return deferred.promise;
         }
 
         Task.spawn(function*() {
 
           ok(!Devices.helperAddonInstalled, "Helper not installed");
 
           let win = yield openWebIDE(true);
           let docRuntime = getRuntimeDocument(win);
 
-          yield adbAddonsInstalled.promise;
+          adbAddonsInstalled = new Promise(resolve => {
+            Devices.on("addon-status-updated", function onUpdate1() {
+              Devices.off("addon-status-updated", onUpdate1);
+              resolve();
+            });
+          });
 
           ok(Devices.helperAddonInstalled, "Helper has been auto-installed");
 
           yield nextTick();
 
           let addons = yield GetAvailableAddons();
 
           is(addons.simulators.length, 3, "3 simulator addons to install");
--- a/devtools/client/webide/test/test_autoconnect_runtime.html
+++ b/devtools/client/webide/test/test_autoconnect_runtime.html
@@ -28,17 +28,17 @@
           let docRuntime = getRuntimeDocument(win);
 
           let fakeRuntime = {
             type: "USB",
             connect: function(connection) {
               is(connection, win.AppManager.connection, "connection is valid");
               connection.host = null; // force connectPipe
               connection.connect();
-              return promise.resolve();
+              return Promise.resolve();
             },
 
             get id() {
               return "fakeRuntime";
             },
 
             get name() {
               return "fakeRuntime";
--- a/devtools/client/webide/test/test_basic.html
+++ b/devtools/client/webide/test/test_basic.html
@@ -30,22 +30,23 @@
             let appmgr = win.AppManager;
             ok(appmgr.connection, "App Manager connection ready");
             ok(appmgr.runtimeList, "Runtime list ready");
 
             // test error reporting
             let nbox = win.document.querySelector("#notificationbox");
             let notification =  nbox.getNotificationWithValue("webide:errornotification");
             ok(!notification, "No notification yet");
-            let deferred = promise.defer();
-            nextTick().then(() => {
-              deferred.reject("BOOM!");
+            let deferred = new Promise((resolve, reject) => {
+              nextTick().then(() => {
+                reject("BOOM!");
+              });
             });
             try {
-              yield win.UI.busyUntil(deferred.promise, "xx");
+              yield win.UI.busyUntil(deferred, "xx");
             } catch(e) {/* This *will* fail */}
             notification =  nbox.getNotificationWithValue("webide:errornotification");
             ok(notification, "Error has been reported");
 
             yield closeWebIDE(win);
 
             SimpleTest.finish();
         });
--- a/devtools/client/webide/test/test_fullscreenToolbox.html
+++ b/devtools/client/webide/test/test_fullscreenToolbox.html
@@ -11,34 +11,34 @@
     <script type="application/javascript" src="head.js"></script>
     <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
   </head>
 
   <body>
 
     <script type="application/javascript">
       function connectToLocal(win, docRuntime) {
-        let deferred = promise.defer();
-        win.AppManager.connection.once(
-            win.Connection.Events.CONNECTED,
-            () => deferred.resolve());
-        docRuntime.querySelectorAll(".runtime-panel-item-other")[1].click();
-        return deferred.promise;
+        return new Promise(resolve => {
+          win.AppManager.connection.once(
+              win.Connection.Events.CONNECTED,
+              resolve);
+          docRuntime.querySelectorAll(".runtime-panel-item-other")[1].click();
+        });
       }
 
       window.onload = function() {
         SimpleTest.waitForExplicitFinish();
 
         Task.spawn(function* () {
           let win = yield openWebIDE();
           let docProject = getProjectDocument(win);
           let docRuntime = getRuntimeDocument(win);
           win.AppManager.update("runtime-list");
 
-          yield connectToLocal(win, docRuntime);
+          connectToLocal(win, docRuntime);
 
           // Select main process
           yield waitForUpdate(win, "runtime-targets");
           SimpleTest.executeSoon(() => {
             docProject.querySelectorAll("#project-panel-runtimeapps .panel-item")[0].click();
           });
 
           yield waitForUpdate(win, "project");
--- a/devtools/client/webide/test/test_runtime.html
+++ b/devtools/client/webide/test/test_runtime.html
@@ -57,39 +57,37 @@
           yield onValidated;
           yield onDetails;
 
           win.AppManager.runtimeList.usb.push({
             connect: function(connection) {
               is(connection, win.AppManager.connection, "connection is valid");
               connection.host = null; // force connectPipe
               connection.connect();
-              return promise.resolve();
+              return Promise.resolve();
             },
 
             get name() {
               return "fakeRuntime";
             }
           });
 
           win.AppManager.runtimeList.usb.push({
             connect: function(connection) {
-              let deferred = promise.defer();
-              return deferred.promise;
+              return new Promise(() => {});
             },
 
             get name() {
               return "infiniteRuntime";
             }
           });
 
           win.AppManager.runtimeList.usb.push({
             connect: function(connection) {
-              let deferred = promise.defer();
-              return deferred.promise;
+              return new Promise(() => {});
             },
 
             prolongedConnection: true,
 
             get name() {
               return "prolongedRuntime";
             }
           });
@@ -159,45 +157,44 @@
           // Toolbox opens automatically for main process / runtime apps
           ok(win.UI.toolboxPromise, "Toolbox promise exists");
           yield win.UI.toolboxPromise;
 
           yield win.Cmds.disconnectRuntime();
 
           Services.prefs.setIntPref("devtools.webide.busyTimeout", 100);
 
-          // Wait for error message since connection never completes
-          let errorDeferred = promise.defer();
-          win.UI.reportError = errorName => {
-            if (errorName === "error_operationTimeout") {
-              errorDeferred.resolve();
-            }
-          };
-
           // Click the infinite runtime
           items[1].click();
           ok(win.document.querySelector("window").className, "busy", "UI is busy");
-          yield errorDeferred.promise;
 
-          // Check for unexpected error message since this is prolonged
-          let noErrorDeferred = promise.defer();
-          win.UI.reportError = errorName => {
-            if (errorName === "error_operationTimeout") {
-              noErrorDeferred.reject();
-            }
-          };
+          // Wait for error message since connection never completes
+          let errorDeferred = new Promise(resolve => {
+            win.UI.reportError = errorName => {
+              if (errorName === "error_operationTimeout") {
+                resolve();
+              }
+            };
+          });
 
           // Click the prolonged runtime
           items[2].click();
           ok(win.document.querySelector("window").className, "busy", "UI is busy");
 
-          setTimeout(() => {
-            noErrorDeferred.resolve();
-          }, 1000);
+          // Check for unexpected error message since this is prolonged
+          let noErrorDeferred = new Promise((resolve, reject) => {
+            win.UI.reportError = errorName => {
+              if (errorName === "error_operationTimeout") {
+                reject();
+              }
+            };
 
-          yield noErrorDeferred.promise;
+            setTimeout(() => {
+              resolve();
+            }, 1000);
+          });
 
           SimpleTest.finish();
         });
       }
     </script>
   </body>
 </html>
--- a/devtools/client/webide/test/test_simulators.html
+++ b/devtools/client/webide/test/test_simulators.html
@@ -24,75 +24,76 @@
         const { getDevices } = require("devtools/client/shared/devices");
         const { Simulator, Simulators } = require("devtools/client/webide/modules/simulators");
         const { AddonSimulatorProcess,
                 OldAddonSimulatorProcess,
                 CustomSimulatorProcess } = require("devtools/client/webide/modules/simulator-process");
 
         function addonStatus(addon, status) {
           if (addon.status == status) {
-            return promise.resolve();
+            return Promise.resolve();
           }
-          let deferred = promise.defer();
-          addon.on("update", function onUpdate() {
-            if (addon.status == status) {
-              addon.off("update", onUpdate);
-              nextTick().then(() => deferred.resolve());
-            }
+          return new Promise(resolve => {
+            addon.on("update", function onUpdate() {
+              if (addon.status == status) {
+                addon.off("update", onUpdate);
+                nextTick().then(() => resolve());
+              }
+            });
           });
-          return deferred.promise;
         }
 
         function waitForUpdate(length) {
           info(`Wait for update with length ${length}`);
-          let deferred = promise.defer();
-          let handler = (_, data) => {
-            if (data.length != length) {
-              return;
-            }
-            info(`Got update with length ${length}`);
-            Simulators.off("updated", handler);
-            deferred.resolve();
-          };
-          Simulators.on("updated", handler);
-          return deferred.promise;
+          return new Promise(resolve => {
+            let handler = (_, data) => {
+              if (data.length != length) {
+                return;
+              }
+              info(`Got update with length ${length}`);
+              Simulators.off("updated", handler);
+              resolve();
+            };
+            Simulators.on("updated", handler);
+          });
         }
 
         Task.spawn(function* () {
           let win = yield openWebIDE(false);
 
           yield Simulators._load();
 
           let docRuntime = getRuntimeDocument(win);
           let find = win.document.querySelector.bind(docRuntime);
           let findAll = win.document.querySelectorAll.bind(docRuntime);
 
           let simulatorList = find("#runtime-panel-simulator");
           let simulatorPanel = win.document.querySelector("#deck-panel-simulator");
 
           // Hack SimulatorProcesses to spy on simulation parameters.
 
-          let runPromise;
-          function fakeRun() {
-            runPromise.resolve({
+          let resolver;
+	  function fakeRun() {
+            resolver({
               path: this.b2gBinary.path,
               args: this.args
             });
             // Don't actually try to connect to the fake simulator.
             throw new Error("Aborting on purpose before connection.");
           }
 
           AddonSimulatorProcess.prototype.run = fakeRun;
           OldAddonSimulatorProcess.prototype.run = fakeRun;
           CustomSimulatorProcess.prototype.run = fakeRun;
 
           function runSimulator(i) {
-            runPromise = promise.defer();
-            findAll(".runtime-panel-item-simulator")[i].click();
-            return runPromise.promise;
+            return new Promise(resolve => {
+              resolver = resolve;
+              findAll(".runtime-panel-item-simulator")[i].click();
+            });
           }
 
           // Install fake "Firefox OS 1.0" simulator addon.
 
           let addons = yield GetAvailableAddons();
 
           let sim10 = addons.simulators.filter(a => a.version == "1.0")[0];
 
--- a/devtools/client/webide/test/test_toolbox.html
+++ b/devtools/client/webide/test/test_toolbox.html
@@ -37,20 +37,21 @@
           }
 
           win = yield openWebIDE();
           let docRuntime = getRuntimeDocument(win);
           let docProject = getProjectDocument(win);
 
           win.AppManager.update("runtime-list");
 
-          let deferred = promise.defer();
-          win.AppManager.connection.once(
-              win.Connection.Events.CONNECTED,
-              () => deferred.resolve());
+          let deferred = new Promise(resolve => {
+             win.AppManager.connection.once(
+                 win.Connection.Events.CONNECTED,
+                 resolve);
+          });
 
           docRuntime.querySelectorAll(".runtime-panel-item-other")[1].click();
 
           ok(win.document.querySelector("window").className, "busy", "UI is busy");
           yield win.UI._busyPromise;
 
           is(Object.keys(DebuggerServer._connections).length, 1, "Connected");
 
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -4444,22 +4444,23 @@ nsContentUtils::MatchElementId(nsIConten
 nsIDocument*
 nsContentUtils::GetSubdocumentWithOuterWindowId(nsIDocument *aDocument,
                                                 uint64_t aOuterWindowId)
 {
   if (!aDocument || !aOuterWindowId) {
     return nullptr;
   }
 
-  nsCOMPtr<nsPIDOMWindowOuter> window = nsGlobalWindow::GetOuterWindowWithId(aOuterWindowId)->AsOuter();
+  RefPtr<nsGlobalWindow> window = nsGlobalWindow::GetOuterWindowWithId(aOuterWindowId);
   if (!window) {
     return nullptr;
   }
 
-  nsCOMPtr<nsIDocument> foundDoc = window->GetDoc();
+  nsCOMPtr<nsPIDOMWindowOuter> outerWindow = window->AsOuter();
+  nsCOMPtr<nsIDocument> foundDoc = outerWindow->GetDoc();
   if (nsContentUtils::ContentIsCrossDocDescendantOf(foundDoc, aDocument)) {
     // Note that ContentIsCrossDocDescendantOf will return true if
     // foundDoc == aDocument.
     return foundDoc;
   }
 
   return nullptr;
 }
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -7626,16 +7626,17 @@ protected:
   DoDatabaseWork() = 0;
 
   virtual nsresult
   BeginVersionChange() = 0;
 
   virtual nsresult
   DispatchToWorkThread() = 0;
 
+  // Should only be called by Run().
   virtual void
   SendResults() = 0;
 
   NS_DECL_ISUPPORTS_INHERITED
 
   // Common nsIRunnable implementation that subclasses may not override.
   NS_IMETHOD
   Run() final;
@@ -21574,16 +21575,21 @@ FactoryOp::NoteDatabaseBlocked(Database*
 
   if (sendBlockedEvent) {
     SendBlockedNotification();
   }
 }
 
 NS_IMPL_ISUPPORTS_INHERITED0(FactoryOp, DatabaseOperationBase)
 
+// Run() assumes that the caller holds a strong reference to the object that
+// can't be cleared while Run() is being executed.
+// So if you call Run() directly (as opposed to dispatching to an event queue)
+// you need to make sure there's such a reference.
+// See bug 1356824 for more details.
 NS_IMETHODIMP
 FactoryOp::Run()
 {
   nsresult rv;
 
   switch (mState) {
     case State::Initial:
       rv = Open();
@@ -21658,18 +21664,21 @@ FactoryOp::DirectoryLockAcquired(Directo
   mDirectoryLock = aLock;
 
   nsresult rv = DirectoryOpen();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     if (NS_SUCCEEDED(mResultCode)) {
       mResultCode = rv;
     }
 
+    // The caller holds a strong reference to us, no need for a self reference
+    // before calling Run().
+
     mState = State::SendingResults;
-    SendResults();
+    MOZ_ALWAYS_SUCCEEDS(Run());
 
     return;
   }
 }
 
 void
 FactoryOp::DirectoryLockFailed()
 {
@@ -21677,18 +21686,21 @@ FactoryOp::DirectoryLockFailed()
   MOZ_ASSERT(mState == State::DirectoryOpenPending);
   MOZ_ASSERT(!mDirectoryLock);
 
   if (NS_SUCCEEDED(mResultCode)) {
     IDB_REPORT_INTERNAL_ERR();
     mResultCode = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
+  // The caller holds a strong reference to us, no need for a self reference
+  // before calling Run().
+
   mState = State::SendingResults;
-  SendResults();
+  MOZ_ALWAYS_SUCCEEDS(Run());
 }
 
 void
 FactoryOp::ActorDestroy(ActorDestroyReason aWhy)
 {
   AssertIsOnBackgroundThread();
 
   NoteActorDestroyed();
@@ -22470,33 +22482,42 @@ OpenDatabaseOp::NoteDatabaseClosed(Datab
   nsresult rv;
   if (actorDestroyed) {
     IDB_REPORT_INTERNAL_ERR();
     rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   } else {
     rv = NS_OK;
   }
 
+  // We are being called with an assuption that mWaitingFactoryOp holds a strong
+  // reference to us.
+  RefPtr<OpenDatabaseOp> kungFuDeathGrip;
+
   if (mMaybeBlockedDatabases.RemoveElement(aDatabase) &&
       mMaybeBlockedDatabases.IsEmpty()) {
     if (actorDestroyed) {
       DatabaseActorInfo* info;
       MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(mDatabaseId, &info));
       MOZ_ASSERT(info->mWaitingFactoryOp == this);
+      kungFuDeathGrip =
+        static_cast<OpenDatabaseOp*>(info->mWaitingFactoryOp.get());
       info->mWaitingFactoryOp = nullptr;
     } else {
       WaitForTransactions();
     }
   }
 
   if (NS_WARN_IF(NS_FAILED(rv))) {
     if (NS_SUCCEEDED(mResultCode)) {
       mResultCode = rv;
     }
 
+    // A strong reference is held in kungFuDeathGrip, so it's safe to call Run()
+    // directly.
+
     mState = State::SendingResults;
     MOZ_ALWAYS_SUCCEEDS(Run());
   }
 }
 
 void
 OpenDatabaseOp::SendBlockedNotification()
 {
@@ -22606,27 +22627,25 @@ OpenDatabaseOp::SendResults()
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State::SendingResults);
   MOZ_ASSERT_IF(NS_SUCCEEDED(mResultCode), mMaybeBlockedDatabases.IsEmpty());
   MOZ_ASSERT_IF(NS_SUCCEEDED(mResultCode), !mVersionChangeTransaction);
 
   mMaybeBlockedDatabases.Clear();
 
-  // Only needed if we're being called from within NoteDatabaseDone() since this
-  // OpenDatabaseOp is only held alive by the gLiveDatabaseHashtable.
-  RefPtr<OpenDatabaseOp> kungFuDeathGrip;
-
   DatabaseActorInfo* info;
   if (gLiveDatabaseHashtable &&
       gLiveDatabaseHashtable->Get(mDatabaseId, &info) &&
       info->mWaitingFactoryOp) {
     MOZ_ASSERT(info->mWaitingFactoryOp == this);
-    kungFuDeathGrip =
-      static_cast<OpenDatabaseOp*>(info->mWaitingFactoryOp.get());
+    // SendResults() should only be called by Run() and Run() should only be
+    // called if there's a strong reference to the object that can't be cleared
+    // here, so it's safe to clear mWaitingFactoryOp without adding additional
+    // strong reference.
     info->mWaitingFactoryOp = nullptr;
   }
 
   if (mVersionChangeTransaction) {
     MOZ_ASSERT(NS_FAILED(mResultCode));
 
     mVersionChangeTransaction->Abort(mResultCode, /* aForce */ true);
     mVersionChangeTransaction = nullptr;
@@ -23290,33 +23309,42 @@ DeleteDatabaseOp::NoteDatabaseClosed(Dat
   nsresult rv;
   if (actorDestroyed) {
     IDB_REPORT_INTERNAL_ERR();
     rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   } else {
     rv = NS_OK;
   }
 
+  // We are being called with an assuption that mWaitingFactoryOp holds a strong
+  // reference to us.
+  RefPtr<OpenDatabaseOp> kungFuDeathGrip;
+
   if (mMaybeBlockedDatabases.RemoveElement(aDatabase) &&
       mMaybeBlockedDatabases.IsEmpty()) {
     if (actorDestroyed) {
       DatabaseActorInfo* info;
       MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(mDatabaseId, &info));
       MOZ_ASSERT(info->mWaitingFactoryOp == this);
+      kungFuDeathGrip =
+        static_cast<OpenDatabaseOp*>(info->mWaitingFactoryOp.get());
       info->mWaitingFactoryOp = nullptr;
     } else {
       WaitForTransactions();
     }
   }
 
   if (NS_WARN_IF(NS_FAILED(rv))) {
     if (NS_SUCCEEDED(mResultCode)) {
       mResultCode = rv;
     }
 
+    // A strong reference is held in kungFuDeathGrip, so it's safe to call Run()
+    // directly.
+
     mState = State::SendingResults;
     MOZ_ALWAYS_SUCCEEDS(Run());
   }
 }
 
 void
 DeleteDatabaseOp::SendBlockedNotification()
 {
@@ -23632,16 +23660,18 @@ VersionChangeOp::RunOnOwningThread()
           }
 
           MOZ_ASSERT(!gLiveDatabaseHashtable->Get(deleteOp->mDatabaseId));
         }
       }
     }
   }
 
+  // We hold a strong ref to the deleteOp, so it's safe to call Run() directly.
+
   deleteOp->mState = State::SendingResults;
   MOZ_ALWAYS_SUCCEEDS(deleteOp->Run());
 
 #ifdef DEBUG
   // A bit hacky but the DeleteDatabaseOp::VersionChangeOp is not really a
   // normal database operation that is tied to an actor. Do this to make our
   // assertions happy.
   NoteActorDestroyed();
@@ -23846,16 +23876,21 @@ TransactionDatabaseOperationBase::SendPr
 void
 TransactionDatabaseOperationBase::NoteContinueReceived()
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mInternalState == InternalState::WaitingForContinue);
 
   mInternalState = InternalState::SendingResults;
 
+  // This TransactionDatabaseOperationBase can only be held alive by the IPDL.
+  // Run() can end up with clearing that last reference. So we need to add
+  // a self reference here.
+  RefPtr<TransactionDatabaseOperationBase> kungFuDeathGrip = this;
+
   Unused << this->Run();
 }
 
 void
 TransactionDatabaseOperationBase::SendToConnectionPool()
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mInternalState == InternalState::Initial);
@@ -23891,32 +23926,23 @@ void
 TransactionDatabaseOperationBase::SendPreprocessInfoOrResults(
                                                        bool aSendPreprocessInfo)
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mInternalState == InternalState::SendingPreprocess ||
              mInternalState == InternalState::SendingResults);
   MOZ_ASSERT(mTransaction);
 
-  // Only needed if we're being called from within NoteContinueReceived() since
-  // this TransactionDatabaseOperationBase is only held alive by the IPDL.
-  // SendSuccessResult/SendFailureResult releases that last reference.
-  RefPtr<TransactionDatabaseOperationBase> kungFuDeathGrip;
-
   if (NS_WARN_IF(IsActorDestroyed())) {
     // Don't send any notifications if the actor was destroyed already.
     if (NS_SUCCEEDED(mResultCode)) {
       IDB_REPORT_INTERNAL_ERR();
       mResultCode = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     }
   } else {
-    if (!aSendPreprocessInfo) {
-      kungFuDeathGrip = this;
-    }
-
     if (mTransaction->IsInvalidated() || mTransaction->IsAborted()) {
       // Aborted transactions always see their requests fail with ABORT_ERR,
       // even if the request succeeded or failed with another error.
       mResultCode = NS_ERROR_DOM_INDEXEDDB_ABORT_ERR;
     } else if (NS_SUCCEEDED(mResultCode)) {
       if (aSendPreprocessInfo) {
         // This should not release the IPDL reference.
         mResultCode = SendPreprocessInfo();
--- a/dom/media/platforms/agnostic/AOMDecoder.cpp
+++ b/dom/media/platforms/agnostic/AOMDecoder.cpp
@@ -216,16 +216,29 @@ bool
 AOMDecoder::IsAV1(const nsACString& aMimeType)
 {
   return aMimeType.EqualsLiteral("video/webm; codecs=av1")
          || aMimeType.EqualsLiteral("video/av1");
 }
 
 /* static */
 bool
+AOMDecoder::IsSupportedCodec(const nsAString& aCodecType)
+{
+  // While AV1 is under development, we describe support
+  // for a specific aom commit hash so sites can check
+  // compatibility.
+  auto version = NS_ConvertASCIItoUTF16("av1.experimental.");
+  version.AppendLiteral("4d668d7feb1f8abd809d1bca0418570a7f142a36");
+  return aCodecType.EqualsLiteral("av1") ||
+         aCodecType.Equals(version);
+}
+
+/* static */
+bool
 AOMDecoder::IsKeyframe(Span<const uint8_t> aBuffer) {
   aom_codec_stream_info_t info;
   PodZero(&info);
   info.sz = sizeof(info);
 
   auto res = aom_codec_peek_stream_info(aom_codec_av1_dx(),
                                         aBuffer.Elements(),
                                         aBuffer.Length(),
--- a/dom/media/platforms/agnostic/AOMDecoder.h
+++ b/dom/media/platforms/agnostic/AOMDecoder.h
@@ -28,16 +28,19 @@ public:
   {
     return "libaom (AV1) video decoder";
   }
 
   // Return true if aMimeType is a one of the strings used
   // by our demuxers to identify AV1 streams.
   static bool IsAV1(const nsACString& aMimeType);
 
+  // Return true if aCodecType is a supported codec description.
+  static bool IsSupportedCodec(const nsAString& aCodecType);
+
   // Return true if a sample is a keyframe.
   static bool IsKeyframe(Span<const uint8_t> aBuffer);
 
   // Return the frame dimensions for a sample.
   static nsIntSize GetFrameSize(Span<const uint8_t> aBuffer);
 
 private:
   ~AOMDecoder();
--- a/dom/media/webm/WebMDecoder.cpp
+++ b/dom/media/webm/WebMDecoder.cpp
@@ -1,15 +1,18 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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/. */
 
 #include "mozilla/Preferences.h"
+#ifdef MOZ_AV1
+#include "AOMDecoder.h"
+#endif
 #include "MediaContainerType.h"
 #include "MediaDecoderStateMachine.h"
 #include "WebMDemuxer.h"
 #include "WebMDecoder.h"
 #include "VideoUtils.h"
 
 namespace mozilla {
 
@@ -46,16 +49,21 @@ WebMDecoder::IsSupportedType(const Media
     }
     // Note: Only accept VP8/VP9 in a video container type, not in an audio
     // container type.
     if (isVideo &&
         (codec.EqualsLiteral("vp8") || codec.EqualsLiteral("vp8.0") ||
          codec.EqualsLiteral("vp9") || codec.EqualsLiteral("vp9.0"))) {
       continue;
     }
+#ifdef MOZ_AV1
+    if (isVideo && AOMDecoder::IsSupportedCodec(codec)) {
+      continue;
+    }
+#endif
     // Some unsupported codec.
     return false;
   }
   return true;
 }
 
 void
 WebMDecoder::GetMozDebugReaderData(nsACString& aString)
--- a/services/sync/modules/UIState.jsm
+++ b/services/sync/modules/UIState.jsm
@@ -25,16 +25,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 
 const TOPICS = [
   "weave:service:login:change",
   "weave:service:login:error",
   "weave:service:ready",
   "weave:service:sync:start",
   "weave:service:sync:finish",
   "weave:service:sync:error",
+  "fxaccounts:onverified",
   "fxaccounts:onlogin", // Defined in FxAccountsCommon, pulling it is expensive.
   "fxaccounts:onlogout",
   "fxaccounts:profilechange",
 ];
 
 const ON_UPDATE = "sync-ui-state:update"
 
 const STATUS_NOT_CONFIGURED = "not_configured";
--- a/services/sync/tests/unit/test_uistate.js
+++ b/services/sync/tests/unit/test_uistate.js
@@ -164,18 +164,19 @@ add_task(async function test_refreshStat
   loginFailed.restore();
   UIStateInternal.fxAccounts = fxAccountsOrig;
 });
 
 add_task(async function test_observer_refreshState() {
   let refreshState = sinon.spy(UIStateInternal, "refreshState");
 
   let shouldRefresh = ["weave:service:login:change", "weave:service:login:error",
-                       "weave:service:ready", "fxaccounts:onlogin",
-                       "fxaccounts:onlogout", "fxaccounts:profilechange"];
+                       "weave:service:ready", "fxaccounts:onverified",
+                       "fxaccounts:onlogin", "fxaccounts:onlogout",
+                       "fxaccounts:profilechange"];
 
   for (let topic of shouldRefresh) {
     let uiUpdateObserved = observeUIUpdate();
     Services.obs.notifyObservers(null, topic);
     await uiUpdateObserved;
     ok(refreshState.calledOnce);
     refreshState.reset();
   }
--- a/testing/mochitest/runtests.py
+++ b/testing/mochitest/runtests.py
@@ -771,16 +771,27 @@ def parseKeyValue(strings, separator='='
     missing = [string for string in strings if separator not in string]
     if missing:
         raise KeyValueParseError(
             "Error: syntax error in %s" %
             (context, ','.join(missing)), errors=missing)
     return [string.split(separator, 1) for string in strings]
 
 
+def create_zip(path):
+    """
+    Takes a `path` on disk and creates a zipfile with its contents. Returns a
+    path to the location of the temporary zip file.
+    """
+    with tempfile.NamedTemporaryFile() as f:
+        # `shutil.make_archive` writes to "{f.name}.zip", so we're really just
+        # using `NamedTemporaryFile` as a way to get a random path.
+        return shutil.make_archive(f.name, "zip", path)
+
+
 class MochitestDesktop(object):
     """
     Mochitest class for desktop firefox.
     """
     oldcwd = os.getcwd()
     mochijar = os.path.join(SCRIPT_DIR, 'mochijar')
 
     # Path to the test script on the server
@@ -2101,22 +2112,24 @@ toolbar#nav-bar {
             self.message_logger.gecko_id = gecko_id
 
             # start marionette and kick off the tests
             marionette_args = marionette_args or {}
             port_timeout = marionette_args.pop('port_timeout', 60)
             self.marionette = Marionette(**marionette_args)
             self.marionette.start_session(timeout=port_timeout)
 
-            # install specialpowers and mochikit as temporary addons
+            # install specialpowers and mochikit addons
             addons = Addons(self.marionette)
 
             if mozinfo.info.get('toolkit') != 'gonk':
-                addons.install(os.path.join(here, 'extensions', 'specialpowers'), temp=True)
-                addons.install(self.mochijar, temp=True)
+                addons.install(create_zip(
+                    os.path.join(here, 'extensions', 'specialpowers')
+                ))
+                addons.install(create_zip(self.mochijar))
 
             self.execute_start_script()
 
             # an open marionette session interacts badly with mochitest,
             # delete it until we figure out why.
             self.marionette.delete_session()
             del self.marionette
 
--- a/toolkit/mozapps/extensions/test/browser/browser_update.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_update.js
@@ -3,17 +3,19 @@
  */
 
 // Tests that updates correctly flush caches and that new files gets updated.
 
 function test() {
   requestLongerTimeout(2);
   waitForExplicitFinish();
 
-  Services.prefs.setBoolPref("extensions.checkUpdateSecurity", false);
+  SpecialPowers.pushPrefEnv({"set": [
+    ["extensions.checkUpdateSecurity", false],
+  ]});
 
   run_next_test();
 }
 
 // Install a first version
 add_test(function() {
   AddonManager.getInstallForURL(TESTROOT + "addons/browser_update1_1.xpi",
                                 function(aInstall) {
@@ -38,16 +40,14 @@ add_test(function() {
     Services.ppmm.removeMessageListener("my-addon-2", messageListener);
     ok(true, "second version sent frame script message");
     run_next_test();
   });
 });
 
 // Finally, cleanup things
 add_test(function() {
-  Services.prefs.setBoolPref("xpinstall.signatures.required", true);
-
   AddonManager.getAddonByID("update1@tests.mozilla.org", function(aAddon) {
     aAddon.uninstall();
 
     finish();
   });
 });