merge mozilla-central to mozilla-inbound. r=merge a=merge
authorSebastian Hengst <archaeopteryx@coole-files.de>
Fri, 27 Oct 2017 00:00:25 +0200
changeset 388673 2a4d68ea7350a2aa6a3723065d5d338215e34e9e
parent 388672 d7d02c88c7ba1d210364d296162f3ac2740672c4 (current diff)
parent 388523 0213562857403b8b52f75075c4d59a7326dc2cfa (diff)
child 388674 bdf3e6d2e4a22d7184ef06478cfab99429c57f63
push id54222
push userarchaeopteryx@coole-files.de
push dateFri, 27 Oct 2017 09:58:15 +0000
treeherderautoland@bf990261dea7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone58.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 mozilla-central to mozilla-inbound. r=merge a=merge
browser/locales/searchplugins/findbook-zh-TW.xml
devtools/client/inspector/inspector.js
devtools/client/jsonview/test/browser.ini
devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_autocomplete_keys.js
layout/generic/nsFrame.cpp
layout/painting/RetainedDisplayListBuilder.cpp
layout/painting/nsDisplayList.h
media/ffvpx/COPYING.GPLv2
media/ffvpx/COPYING.GPLv3
media/ffvpx/CREDITS
media/ffvpx/Changelog
media/ffvpx/INSTALL.md
media/ffvpx/LICENSE.md
media/ffvpx/MAINTAINERS
media/ffvpx/README.md
media/ffvpx/RELEASE
media/ffvpx/RELEASE_NOTES
media/ffvpx/libavcodec/avdct.h
media/ffvpx/libavcodec/dct.h
media/ffvpx/libavcodec/ff_options_table.h
media/ffvpx/libavcodec/x86/h264_i386.h
media/ffvpx/libavutil/atomic_suncc.h
media/ffvpx/libavutil/display.c
media/ffvpx/libavutil/display.h
media/ffvpx/libavutil/lzo.c
media/ffvpx/libavutil/lzo.h
media/ffvpx/libavutil/motion_vector.h
media/ffvpx/libavutil/wchar_filename.h
mobile/android/bouncer/AndroidManifest.xml.in
mobile/android/bouncer/Makefile.in
mobile/android/bouncer/assets/example_asset.txt
mobile/android/bouncer/build.gradle
mobile/android/bouncer/java/org/mozilla/bouncer/BouncerService.java
mobile/android/bouncer/java/org/mozilla/gecko/BrowserApp.java
mobile/android/bouncer/moz.build
mobile/android/bouncer/res/drawable-v21/logo.xml
mobile/android/bouncer/res/drawable/logo.xml
mobile/android/docs/bouncer.rst
moz.build
netwerk/protocol/http/HttpChannelChild.cpp
testing/mozharness/configs/releases/bouncer_fennec.py
testing/mozharness/configs/releases/bouncer_fennec_beta.py
--- a/accessible/tests/mochitest/actions/test_media.html
+++ b/accessible/tests/mochitest/actions/test_media.html
@@ -59,34 +59,32 @@ https://bugzilla.mozilla.org/show_bug.cg
       var playBtn = audioElm.firstChild;
       // var scrubber = playBtn.nextSibling.nextSibling.nextSibling;
       var muteBtn = audioElm.lastChild.previousSibling;
 
       var actions = [
         {
           ID: muteBtn,
           actionName: "press",
-          events: CLICK_EVENTS,
           eventSeq: [
      //       new focusChecker(muteBtn),
             new nameChecker(muteBtn, "Unmute"),
           ]
         },
      //   {
      //     ID: scrubber,
      //     actionName: "activate",
      //     events: null,
      //     eventSeq: [
      //       new focusChecker(scrubber)
      //     ]
      //   },
         {
           ID: playBtn,
           actionName: "press",
-          events: CLICK_EVENTS,
           eventSeq: [
      //       new focusChecker(playBtn),
             new nameChecker(playBtn, "Pause"),
           ]
         }
       ];
 
       testActions(actions); // Will call SimpleTest.finish();
--- a/browser/base/content/browser-tabsintitlebar.js
+++ b/browser/base/content/browser-tabsintitlebar.js
@@ -180,22 +180,20 @@ var TabsInTitlebar = {
 
       // And get the height of what's in the titlebar:
       let titlebarContentHeight = rect(titlebarContent).height;
 
       // Begin setting CSS properties which will cause a reflow
 
       // On Windows 10, adjust the window controls to span the entire
       // tab strip height if we're not showing a menu bar.
-      if (AppConstants.isPlatformAndVersionAtLeast("win", "10.0")) {
-        if (!menuHeight) {
-          // Add a pixel to slightly overlap the navbar border.
-          titlebarContentHeight = fullTabsHeight + 1;
-          $("titlebar-buttonbox").style.height = titlebarContentHeight + "px";
-        }
+      if (AppConstants.isPlatformAndVersionAtLeast("win", "10.0") &&
+          !menuHeight) {
+        titlebarContentHeight = fullTabsHeight;
+        $("titlebar-buttonbox").style.height = titlebarContentHeight + "px";
       }
 
       // If the menubar is around (menuHeight is non-zero), try to adjust
       // its full height (i.e. including margins) to match the titlebar,
       // by changing the menubar's bottom padding
       if (menuHeight) {
         // Calculate the difference between the titlebar's height and that of the menubar
         let menuTitlebarDelta = titlebarContentHeight - fullMenuHeight;
--- a/browser/components/preferences/cookies.js
+++ b/browser/components/preferences/cookies.js
@@ -721,16 +721,24 @@ var gCookiesWindow = {
   },
 
   onCookieKeyPress(aEvent) {
     if (aEvent.keyCode == KeyEvent.DOM_VK_DELETE ||
         (AppConstants.platform == "macosx" &&
         aEvent.keyCode == KeyEvent.DOM_VK_BACK_SPACE)) {
       this.deleteCookie();
       aEvent.preventDefault();
+    } else if (aEvent.getModifierState("Accel") &&
+               document.getElementById("key_selectAll")
+                       .getAttribute("key")
+                       .toLocaleLowerCase()
+                       .charCodeAt(0) == aEvent.charCode) {
+      let view = gCookiesWindow._view;
+      view.selection.selectAll();
+      aEvent.preventDefault();
     }
   },
 
   _lastSortProperty: "",
   _lastSortAscending: false,
   sort(aProperty) {
     var ascending = (aProperty == this._lastSortProperty) ? !this._lastSortAscending : true;
     // Sort the Non-Filtered Host Collections
--- a/browser/components/preferences/cookies.xul
+++ b/browser/components/preferences/cookies.xul
@@ -3,17 +3,22 @@
 # -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 # 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/.
 
 <?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css" type="text/css"?>
 
-<!DOCTYPE dialog SYSTEM "chrome://browser/locale/preferences/cookies.dtd" >
+<!DOCTYPE dialog [
+<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
+%browserDTD;
+<!ENTITY % cookiesDTD SYSTEM "chrome://browser/locale/preferences/cookies.dtd">
+%cookiesDTD;
+]>
 
 <window id="CookiesDialog" windowtype="Browser:Cookies"
         class="windowDialog" title="&window.title;"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         style="width: &window.width;;"
         onload="gCookiesWindow.init();"
         onunload="gCookiesWindow.uninit();"
         persist="screenX screenY width height"
@@ -22,16 +27,17 @@
   <script src="chrome://browser/content/preferences/cookies.js"/>
 
   <stringbundle id="bundlePreferences"
                 src="chrome://browser/locale/preferences/preferences.properties"/>
 
   <keyset>
     <key key="&windowClose.key;" modifiers="accel" oncommand="window.close();"/>
     <key key="&focusSearch1.key;" modifiers="accel" oncommand="gCookiesWindow.focusFilterBox();"/>
+    <key id="key_selectAll" key="&selectAllCmd.key;" modifiers="accel"/>
   </keyset>
 
   <vbox flex="1" class="contentPane largeDialogContainer">
     <hbox align="center">
       <textbox type="search" id="filter" flex="1"
                aria-controls="cookiesList"
                oncommand="gCookiesWindow.filter();"
                placeholder="&searchFilter.label;"
--- a/browser/components/uitour/UITour.jsm
+++ b/browser/components/uitour/UITour.jsm
@@ -110,23 +110,17 @@ this.UITour = {
         return aDocument.getAnonymousElementByAttribute(statusButton,
                                                         "class",
                                                         "toolbarbutton-icon");
       },
       // This is a fake widgetName starting with the "appMenu-" prefix so we know
       // to automatically open the appMenu when annotating this target.
       widgetName: "appMenu-fxa-label",
     }],
-    ["addons",      {
-      query: (aDocument) => {
-        // select toolbar icon if exist, fallback to appMenu item
-        let node = aDocument.getElementById("add-ons-button");
-        return node ? node : aDocument.getElementById("appMenu-addons-button");
-      },
-    }],
+    ["addons",      {query: "#appMenu-addons-button"}],
     ["appMenu",     {
       addTargetListener: (aDocument, aCallback) => {
         let panelPopup = aDocument.defaultView.PanelUI.panel;
         panelPopup.addEventListener("popupshown", aCallback);
       },
       query: "#PanelUI-button",
       removeTargetListener: (aDocument, aCallback) => {
         let panelPopup = aDocument.defaultView.PanelUI.panel;
@@ -147,23 +141,17 @@ this.UITour = {
     }],
     ["forget", {
       allowAdd: true,
       query: "#panic-button",
       widgetName: "panic-button",
     }],
     ["help",        {query: "#appMenu-help-button"}],
     ["home",        {query: "#home-button"}],
-    ["library",     {
-      query: (aDocument) => {
-        // select toolbar icon if exist, fallback to appMenu item
-        let node = aDocument.getElementById("library-button");
-        return node ? node : aDocument.getElementById("appMenu-library-button");
-      },
-    }],
+    ["library",     {query: "#appMenu-library-button"}],
     ["pocket", {
       allowAdd: true,
       query: (aDocument) => {
         // The pocket's urlbar page action button is pre-defined in the DOM.
         // It would be hidden if toggled off from the urlbar.
         let node = aDocument.getElementById("pocket-button-box");
         if (node && node.hidden == false) {
           return node;
--- a/browser/components/uitour/test/browser_UITour5.js
+++ b/browser/components/uitour/test/browser_UITour5.js
@@ -1,51 +1,17 @@
 "use strict";
 
 var gTestTab;
 var gContentAPI;
 var gContentWindow;
 
 add_task(setup_UITourTest);
 
-add_UITour_task(async function test_highlight_library_icon_in_toolbar() {
-  let highlight = document.getElementById("UITourHighlight");
-  is_element_hidden(highlight, "Highlight should initially be hidden");
-
-  // Test highlighting the library button
-  let highlightVisiblePromise = elementVisiblePromise(highlight, "Should show highlight");
-  gContentAPI.showHighlight("library");
-  await highlightVisiblePromise;
-  UITour.getTarget(window, "library").then((target) => {
-    is("library-button", target.node.id, "Should highlight the right target");
-  });
-});
-
-add_UITour_task(async function test_highlight_addons_icon_in_toolbar() {
-  CustomizableUI.addWidgetToArea("add-ons-button", CustomizableUI.AREA_NAVBAR, 0);
-  ok(!UITour.availableTargetsCache.has(window),
-     "Targets should be evicted from cache after widget change");
-  let highlight = document.getElementById("UITourHighlight");
-  is_element_hidden(highlight, "Highlight should initially be hidden");
-
-  // Test highlighting the addons button on toolbar
-  let highlightVisiblePromise = elementVisiblePromise(highlight, "Should show highlight");
-  gContentAPI.showHighlight("addons");
-  await highlightVisiblePromise;
-  UITour.getTarget(window, "addons").then((target) => {
-    is("add-ons-button", target.node.id, "Should highlight the right target");
-    CustomizableUI.removeWidgetFromArea("add-ons-button");
-  });
-});
-
 add_UITour_task(async function test_highlight_library_and_show_library_subview() {
-  CustomizableUI.removeWidgetFromArea("library-button");
-
-  ok(!UITour.availableTargetsCache.has(window),
-     "Targets should be evicted from cache after widget change");
   let highlight = document.getElementById("UITourHighlight");
   is_element_hidden(highlight, "Highlight should initially be hidden");
 
   // Test highlighting the library button
   let appMenu = PanelUI.panel;
   let appMenuShownPromise = promisePanelElementShown(window, appMenu);
   let highlightVisiblePromise = elementVisiblePromise(highlight, "Should show highlight");
   gContentAPI.showHighlight("library");
@@ -66,11 +32,9 @@ add_UITour_task(async function test_high
   is(PanelUI.multiView.current.id, "appMenu-libraryView", "Should show the library subview");
   is(appMenu.state, "open", "Should still open the app menu for the library subview");
 
   // Clean up
   let appMenuHiddenPromise = promisePanelElementHidden(window, appMenu);
   gContentAPI.hideMenu("appMenu");
   await appMenuHiddenPromise;
   is(appMenu.state, "closed", "Should close the app menu");
-  CustomizableUI.addWidgetToArea("library", CustomizableUI.AREA_NAVBAR, 0);
 });
-
--- a/browser/extensions/formautofill/FormAutofillDoorhanger.jsm
+++ b/browser/extensions/formautofill/FormAutofillDoorhanger.jsm
@@ -118,16 +118,40 @@ const CONTENT = {
       label: GetStringFromName("neverSaveCreditCardLabel"),
       accessKey: "N",
       callbackState: "disable",
     }],
     options: {
       persistWhileVisible: true,
       popupIconURL: "chrome://formautofill/content/icon-credit-card.svg",
       hideClose: true,
+      checkbox: {
+        get checked() {
+          return Services.prefs.getBoolPref("services.sync.engine.creditcards");
+        },
+        get label() {
+          // Only set the label when the fallowing conditions existed:
+          // - sync account is set
+          // - credit card sync is disabled
+          // - credit card sync is available
+          // otherwise return null label to hide checkbox.
+          return Services.prefs.prefHasUserValue("services.sync.username") &&
+            !Services.prefs.getBoolPref("services.sync.engine.creditcards") &&
+            Services.prefs.getBoolPref("services.sync.engine.creditcards.available") ?
+            GetStringFromName("creditCardsSyncCheckbox") : null;
+        },
+        callback(event) {
+          let {secondaryButton, menubutton} = event.target.parentNode.parentNode.parentNode;
+          let checked = event.target.checked;
+          Services.prefs.setBoolPref("services.sync.engine.creditcards", checked);
+          secondaryButton.disabled = checked;
+          menubutton.disabled = checked;
+          log.debug("Set creditCard sync to", checked);
+        },
+      },
     },
   },
 };
 
 let FormAutofillDoorhanger = {
   /**
    * Generate the main action and secondary actions from content parameters and
    * promise resolve.
@@ -161,16 +185,21 @@ let FormAutofillDoorhanger = {
         label: params.label,
         accessKey: params.accessKey,
         callback: cb,
       });
     }
 
     return [mainAction, secondaryActions];
   },
+  _getNotificationElm(browser, id) {
+    let notificationId = id + "-notification";
+    let chromeDoc = browser.ownerDocument;
+    return chromeDoc.getElementById(notificationId);
+  },
   /**
    * Append the link label element to the popupnotificationcontent.
    * @param  {XULElement} browser
    *         Target browser element for showing doorhanger.
    * @param  {string} id
    *         The ID of the doorhanger.
    * @param  {string} message
    *         The localized string for link title.
@@ -220,36 +249,30 @@ let FormAutofillDoorhanger = {
       anchorElement.setAttribute("tooltiptext", tooltiptext);
       notificationPopupBox.appendChild(anchorElement);
     }
   },
   _addCheckboxListener(browser, {notificationId, options}) {
     if (!options.checkbox) {
       return;
     }
-    let id = notificationId + "-notification";
-    let chromeDoc = browser.ownerDocument;
-    let notification = chromeDoc.getElementById(id);
-    let cb = notification.checkbox;
+    let {checkbox} = this._getNotificationElm(browser, notificationId);
 
-    if (cb) {
-      cb.addEventListener("command", options.checkbox.callback);
+    if (checkbox && !checkbox.hidden) {
+      checkbox.addEventListener("command", options.checkbox.callback);
     }
   },
   _removeCheckboxListener(browser, {notificationId, options}) {
     if (!options.checkbox) {
       return;
     }
-    let id = notificationId + "-notification";
-    let chromeDoc = browser.ownerDocument;
-    let notification = chromeDoc.getElementById(id);
-    let cb = notification.checkbox;
+    let {checkbox} = this._getNotificationElm(browser, notificationId);
 
-    if (cb) {
-      cb.removeEventListener("command", options.checkbox.callback);
+    if (checkbox && !checkbox.hidden) {
+      checkbox.removeEventListener("command", options.checkbox.callback);
     }
   },
   /**
    * Show different types of doorhanger by leveraging PopupNotifications.
    * @param  {XULElement} browser
    *         Target browser element for showing doorhanger.
    * @param  {string} type
    *         The type of the doorhanger. There will have first time use/update/credit card.
--- a/browser/extensions/formautofill/FormAutofillHeuristics.jsm
+++ b/browser/extensions/formautofill/FormAutofillHeuristics.jsm
@@ -534,21 +534,24 @@ this.FormAutofillHeuristics = {
    *        the elements in this form to be predicted the field info.
    * @param {boolean} allowDuplicates
    *        true to remain any duplicated field details otherwise to remove the
    *        duplicated ones.
    * @returns {Array<Object>}
    *        all field details in the form.
    */
   getFormInfo(form, allowDuplicates = false) {
-    if (form.elements.length <= 0) {
+    const eligibleFields = Array.from(form.elements)
+      .filter(elem => FormAutofillUtils.isFieldEligibleForAutofill(elem));
+
+    if (eligibleFields.length <= 0) {
       return [];
     }
 
-    let fieldScanner = new FieldScanner(form.elements);
+    let fieldScanner = new FieldScanner(eligibleFields);
     while (!fieldScanner.parsingFinished) {
       let parsedPhoneFields = this._parsePhoneFields(fieldScanner);
       let parsedAddressFields = this._parseAddressFields(fieldScanner);
       let parsedExpirationDateFields = this._parseCreditCardExpirationDateFields(fieldScanner);
 
       // If there is no any field parsed, the parsing cursor can be moved
       // forward to the next one.
       if (!parsedPhoneFields && !parsedAddressFields && !parsedExpirationDateFields) {
@@ -624,20 +627,16 @@ this.FormAutofillHeuristics = {
       FormAutofillUtils.isAutofillCreditCardsAvailable,
       isSelectElem
     );
 
     return regexps;
   },
 
   getInfo(element) {
-    if (!FormAutofillUtils.isFieldEligibleForAutofill(element)) {
-      return null;
-    }
-
     let info = element.getAutocompleteInfo();
     // An input[autocomplete="on"] will not be early return here since it stll
     // needs to find the field name.
     if (info && info.fieldName && info.fieldName != "on" && info.fieldName != "off") {
       info._reason = "autocomplete";
       return info;
     }
 
--- a/browser/extensions/formautofill/FormAutofillParent.jsm
+++ b/browser/extensions/formautofill/FormAutofillParent.jsm
@@ -198,17 +198,22 @@ FormAutofillParent.prototype = {
         if (data.guid) {
           this.profileStorage.addresses.update(data.guid, data.address);
         } else {
           this.profileStorage.addresses.add(data.address);
         }
         break;
       }
       case "FormAutofill:SaveCreditCard": {
-        await this.profileStorage.creditCards.normalizeCCNumberFields(data.creditcard);
+        // TODO: "MasterPassword.ensureLoggedIn" can be removed after the storage
+        // APIs are refactored to be async functions (bug 1399367).
+        if (!await MasterPassword.ensureLoggedIn()) {
+          log.warn("User canceled master password entry");
+          return;
+        }
         this.profileStorage.creditCards.add(data.creditcard);
         break;
       }
       case "FormAutofill:RemoveAddresses": {
         data.guids.forEach(guid => this.profileStorage.addresses.remove(guid));
         break;
       }
       case "FormAutofill:RemoveCreditCards": {
@@ -449,25 +454,24 @@ FormAutofillParent.prototype = {
       return;
     }
 
     if (state == "disable") {
       Services.prefs.setBoolPref("extensions.formautofill.creditCards.enabled", false);
       return;
     }
 
-    try {
-      await this.profileStorage.creditCards.normalizeCCNumberFields(creditCard.record);
-      this.profileStorage.creditCards.add(creditCard.record);
-    } catch (e) {
-      if (e.result != Cr.NS_ERROR_ABORT) {
-        throw e;
-      }
+    // TODO: "MasterPassword.ensureLoggedIn" can be removed after the storage
+    // APIs are refactored to be async functions (bug 1399367).
+    if (!await MasterPassword.ensureLoggedIn()) {
       log.warn("User canceled master password entry");
+      return;
     }
+
+    this.profileStorage.creditCards.add(creditCard.record);
   },
 
   _onFormSubmit(data, target) {
     let {profile: {address, creditCard}, timeStartedFillingMS} = data;
 
     if (address) {
       this._onAddressSubmit(address, target, timeStartedFillingMS);
     }
--- a/browser/extensions/formautofill/MasterPassword.jsm
+++ b/browser/extensions/formautofill/MasterPassword.jsm
@@ -31,29 +31,52 @@ this.MasterPassword = {
   /**
    * @returns {boolean} True if a master password is set and false otherwise.
    */
   get isEnabled() {
     return this._token.hasPassword;
   },
 
   /**
-   * Display the master password login prompt no matter it's logged in or not.
-   * If an existing MP prompt is already open, the result from it will be used instead.
+   * @returns {boolean} True if master password is logged in and false if not.
+   */
+  get isLoggedIn() {
+    return Services.logins.isLoggedIn;
+  },
+
+  /**
+   * @returns {boolean} True if there is another master password login dialog
+   *                    existing and false otherwise.
+   */
+  get isUIBusy() {
+    return Services.logins.uiBusy;
+  },
+
+  /**
+   * Ensure the master password is logged in. It will display the master password
+   * login prompt or do nothing if it's logged in already. If an existing MP
+   * prompt is already prompted, the result from it will be used instead.
    *
-   * @returns {Promise<boolean>} True if it's logged in or no password is set and false
-   *                             if it's still not logged in (prompt canceled or other error).
+   * @param   {boolean} reauth Prompt the login dialog no matter it's logged in
+   *                           or not if it's set to true.
+   * @returns {Promise<boolean>} True if it's logged in or no password is set
+   *                             and false if it's still not logged in (prompt
+   *                             canceled or other error).
    */
-  async prompt() {
+  async ensureLoggedIn(reauth = false) {
     if (!this.isEnabled) {
       return true;
     }
 
+    if (this.isLoggedIn && !reauth) {
+      return true;
+    }
+
     // If a prompt is already showing then wait for and focus it.
-    if (Services.logins.uiBusy) {
+    if (this.isUIBusy) {
       return this.waitForExistingDialog();
     }
 
     let token = this._token;
     try {
       // 'true' means always prompt for token password. User will be prompted until
       // clicking 'Cancel' or entering the correct password.
       token.login(true);
@@ -76,57 +99,86 @@ this.MasterPassword = {
    * Decrypts cipherText.
    *
    * @param   {string} cipherText Encrypted string including the algorithm details.
    * @param   {boolean} reauth True if we want to force the prompt to show up
    *                    even if the user is already logged in.
    * @returns {Promise<string>} resolves to the decrypted string, or rejects otherwise.
    */
   async decrypt(cipherText, reauth = false) {
-    let loggedIn = false;
-    if (reauth) {
-      loggedIn = await this.prompt();
-    } else {
-      loggedIn = await this.waitForExistingDialog();
-    }
-
-    if (!loggedIn) {
+    if (!await this.ensureLoggedIn(reauth)) {
       throw Components.Exception("User canceled master password entry", Cr.NS_ERROR_ABORT);
     }
+    return cryptoSDR.decrypt(cipherText);
+  },
 
+  /**
+   * Decrypts cipherText synchronously. "ensureLoggedIn()" needs to be called
+   * outside in case another dialog is showing.
+   *
+   * NOTE: This method will be removed soon once the ProfileStorage APIs are
+   *       refactored to be async functions (bug 1399367). Please use async
+   *       version instead.
+   *
+   * @deprecated
+   * @param   {string} cipherText Encrypted string including the algorithm details.
+   * @returns {string} The decrypted string.
+   */
+  decryptSync(cipherText) {
+    if (this.isUIBusy) {
+      throw Components.Exception("\"ensureLoggedIn()\" should be called first", Cr.NS_ERROR_UNEXPECTED);
+    }
     return cryptoSDR.decrypt(cipherText);
   },
 
   /**
    * Encrypts a string and returns cipher text containing algorithm information used for decryption.
    *
    * @param   {string} plainText Original string without encryption.
    * @returns {Promise<string>} resolves to the encrypted string (with algorithm), otherwise rejects.
    */
   async encrypt(plainText) {
-    if (Services.logins.uiBusy && !await this.waitForExistingDialog()) {
+    if (!await this.ensureLoggedIn()) {
       throw Components.Exception("User canceled master password entry", Cr.NS_ERROR_ABORT);
     }
 
     return cryptoSDR.encrypt(plainText);
   },
 
   /**
+   * Encrypts plainText synchronously. "ensureLoggedIn()" needs to be called
+   * outside in case another dialog is showing.
+   *
+   * NOTE: This method will be removed soon once the ProfileStorage APIs are
+   *       refactored to be async functions (bug 1399367). Please use async
+   *       version instead.
+   *
+   * @deprecated
+   * @param   {string} plainText A plain string to be encrypted.
+   * @returns {string} The encrypted cipher string.
+   */
+  encryptSync(plainText) {
+    if (this.isUIBusy) {
+      throw Components.Exception("\"ensureLoggedIn()\" should be called first", Cr.NS_ERROR_UNEXPECTED);
+    }
+    return cryptoSDR.encrypt(plainText);
+  },
+
+  /**
    * Resolve when master password dialogs are closed, immediately if none are open.
    *
    * An existing MP dialog will be focused and will request attention.
    *
    * @returns {Promise<boolean>}
    *          Resolves with whether the user is logged in to MP.
    */
   async waitForExistingDialog() {
-    if (!Services.logins.uiBusy) {
-      log.debug("waitForExistingDialog: Dialog isn't showing. isLoggedIn:",
-                Services.logins.isLoggedIn);
-      return Services.logins.isLoggedIn;
+    if (!this.isUIBusy) {
+      log.debug("waitForExistingDialog: Dialog isn't showing. isLoggedIn:", this.isLoggedIn);
+      return this.isLoggedIn;
     }
 
     return new Promise((resolve) => {
       log.debug("waitForExistingDialog: Observing the open dialog");
       let observer = {
         QueryInterface: XPCOMUtils.generateQI([
           Ci.nsIObserver,
           Ci.nsISupportsWeakReference,
--- a/browser/extensions/formautofill/ProfileStorage.jsm
+++ b/browser/extensions/formautofill/ProfileStorage.jsm
@@ -53,39 +53,57 @@
  *   ],
  *   creditCards: [
  *     {
  *       guid,                 // 12 characters
  *       version,              // schema version in integer
  *
  *       // credit card fields
  *       cc-name,
- *       cc-number,            // e.g. ************1234
- *       cc-number-encrypted,
+ *       cc-number,            // will be stored in masked format (************1234)
+ *                             // (see details below)
  *       cc-exp-month,
  *       cc-exp-year,          // 2-digit year will be converted to 4 digits
  *                             // upon saving
  *
  *       // computed fields (These fields are computed based on the above fields
  *       // and are not allowed to be modified directly.)
  *       cc-given-name,
  *       cc-additional-name,
  *       cc-family-name,
+ *       cc-number-encrypted,  // encrypted from the original unmasked "cc-number"
+ *                             // (see details below)
  *       cc-exp,
  *
  *       // metadata
  *       timeCreated,          // in ms
  *       timeLastUsed,         // in ms
  *       timeLastModified,     // in ms
  *       timesUsed
  *       _sync: { ... optional sync metadata },
  *     }
  *   ]
  * }
  *
+ *
+ * Encrypt-related Credit Card Fields (cc-number & cc-number-encrypted):
+ *
+ * When saving or updating a credit-card record, the storage will encrypt the
+ * value of "cc-number", store the encrypted number in "cc-number-encrypted"
+ * field, and replace "cc-number" field with the masked number. These all happen
+ * in "_computeFields". We do reverse actions in "_stripComputedFields", which
+ * decrypts "cc-number-encrypted", restores it to "cc-number", and deletes
+ * "cc-number-encrypted". Therefore, calling "_stripComputedFields" followed by
+ * "_computeFields" can make sure the encrypt-related fields are up-to-date.
+ *
+ * In general, you have to decrypt the number by your own outside ProfileStorage
+ * when necessary. However, you will get the decrypted records when querying
+ * data with "rawData=true" to ensure they're ready to sync.
+ *
+ *
  * Sync Metadata:
  *
  * Records may also have a _sync field, which consists of:
  * {
  *   changeCounter,    // integer - the number of changes made since the last
  *                     // sync.
  *   lastSyncedFields, // object - hashes of the original values for fields
  *                     // changed since the last sync.
@@ -177,25 +195,25 @@ const TEL_COMPONENTS = [
 const VALID_ADDRESS_COMPUTED_FIELDS = [
   "name",
   "country-name",
 ].concat(STREET_ADDRESS_COMPONENTS, TEL_COMPONENTS);
 
 const VALID_CREDIT_CARD_FIELDS = [
   "cc-name",
   "cc-number",
-  "cc-number-encrypted",
   "cc-exp-month",
   "cc-exp-year",
 ];
 
 const VALID_CREDIT_CARD_COMPUTED_FIELDS = [
   "cc-given-name",
   "cc-additional-name",
   "cc-family-name",
+  "cc-number-encrypted",
   "cc-exp",
 ];
 
 const INTERNAL_FIELDS = [
   "guid",
   "version",
   "timeCreated",
   "timeLastUsed",
@@ -242,31 +260,41 @@ class AutofillRecords {
     this.VALID_FIELDS = validFields;
     this.VALID_COMPUTED_FIELDS = validComputedFields;
 
     this._store = store;
     this._collectionName = collectionName;
     this._schemaVersion = schemaVersion;
 
     let hasChanges = (result, record) => this._migrateRecord(record) || result;
-    if (this._store.data[this._collectionName].reduce(hasChanges, false)) {
+    if (this.data.reduce(hasChanges, false)) {
       this._store.saveSoon();
     }
   }
 
   /**
    * Gets the schema version number.
    *
    * @returns {number}
    *          The current schema version number.
    */
   get version() {
     return this._schemaVersion;
   }
 
+  /**
+   * Gets the data of this collection.
+   *
+   * @returns {array}
+   *          The data object.
+   */
+  get data() {
+    return this._store.data[this._collectionName];
+  }
+
   // Ensures that we don't try to apply synced records with newer schema
   // versions. This is a temporary measure to ensure we don't accidentally
   // bump the schema version without a syncing strategy in place (bug 1377204).
   _ensureMatchingVersion(record) {
     if (record.version != this.version) {
       throw new Error(`Got unknown record version ${
         record.version}; want ${this.version}`);
     }
@@ -287,19 +315,19 @@ class AutofillRecords {
 
     if (sourceSync) {
       // Remove tombstones for incoming items that were changed on another
       // device. Local deletions always lose to avoid data loss.
       let index = this._findIndexByGUID(record.guid, {
         includeDeleted: true,
       });
       if (index > -1) {
-        let existing = this._store.data[this._collectionName][index];
+        let existing = this.data[index];
         if (existing.deleted) {
-          this._store.data[this._collectionName].splice(index, 1);
+          this.data.splice(index, 1);
         } else {
           throw new Error(`Record ${record.guid} already exists`);
         }
       }
       let recordToSave = this._clone(record);
       return this._saveRecord(recordToSave, {sourceSync});
     }
 
@@ -344,17 +372,17 @@ class AutofillRecords {
       this._computeFields(recordToSave);
     }
 
     if (sourceSync) {
       let sync = this._getSyncMetaData(recordToSave, true);
       sync.changeCounter = 0;
     }
 
-    this._store.data[this._collectionName].push(recordToSave);
+    this.data.push(recordToSave);
 
     this._store.saveSoon();
 
     Services.obs.notifyObservers({wrappedJSObject: {sourceSync}}, "formautofill-storage-changed", "add");
     return recordToSave.guid;
   }
 
   _generateGUID() {
@@ -374,23 +402,26 @@ class AutofillRecords {
    * @param  {Object} record
    *         The new record used to overwrite the old one.
    * @param  {boolean} [preserveOldProperties = false]
    *         Preserve old record's properties if they don't exist in new record.
    */
   update(guid, record, preserveOldProperties = false) {
     this.log.debug("update:", guid, record);
 
-    let recordFound = this._findByGUID(guid);
-    if (!recordFound) {
+    let recordFoundIndex = this._findIndexByGUID(guid);
+    if (recordFoundIndex == -1) {
       throw new Error("No matching record.");
     }
 
-    // Clone the record by Object assign API to preserve the property with empty string.
-    let recordToUpdate = Object.assign({}, record);
+    // Clone the record before modifying it to avoid exposing incomplete changes.
+    let recordFound = this._clone(this.data[recordFoundIndex]);
+    this._stripComputedFields(recordFound);
+
+    let recordToUpdate = this._clone(record);
     this._normalizeRecord(recordToUpdate);
 
     for (let field of this.VALID_FIELDS) {
       let oldValue = recordFound[field];
       let newValue = recordToUpdate[field];
 
       // Resume the old field value in the perserve case
       if (preserveOldProperties && newValue === undefined) {
@@ -407,18 +438,18 @@ class AutofillRecords {
     }
 
     recordFound.timeLastModified = Date.now();
     let syncMetadata = this._getSyncMetaData(recordFound);
     if (syncMetadata) {
       syncMetadata.changeCounter += 1;
     }
 
-    this._stripComputedFields(recordFound);
     this._computeFields(recordFound);
+    this.data[recordFoundIndex] = recordFound;
 
     this._store.saveSoon();
     Services.obs.notifyObservers(null, "formautofill-storage-changed", "update");
   }
 
   /**
    * Notifies the storage of the use of the specified record, so we can update
    * the metadata accordingly. This does not bump the Sync change counter, since
@@ -456,35 +487,35 @@ class AutofillRecords {
     if (sourceSync) {
       this._removeSyncedRecord(guid);
     } else {
       let index = this._findIndexByGUID(guid, {includeDeleted: false});
       if (index == -1) {
         this.log.warn("attempting to remove non-existing entry", guid);
         return;
       }
-      let existing = this._store.data[this._collectionName][index];
+      let existing = this.data[index];
       if (existing.deleted) {
         return; // already a tombstone - don't touch it.
       }
       let existingSync = this._getSyncMetaData(existing);
       if (existingSync) {
         // existing sync metadata means it has been synced. This means we must
         // leave a tombstone behind.
-        this._store.data[this._collectionName][index] = {
+        this.data[index] = {
           guid,
           timeLastModified: Date.now(),
           deleted: true,
           _sync: existingSync,
         };
         existingSync.changeCounter++;
       } else {
         // If there's no sync meta-data, this record has never been synced, so
         // we can delete it.
-        this._store.data[this._collectionName].splice(index, 1);
+        this.data.splice(index, 1);
       }
     }
 
     this._store.saveSoon();
     Services.obs.notifyObservers({wrappedJSObject: {sourceSync}}, "formautofill-storage-changed", "remove");
   }
 
   /**
@@ -502,17 +533,17 @@ class AutofillRecords {
     this.log.debug("get:", guid, rawData);
 
     let recordFound = this._findByGUID(guid);
     if (!recordFound) {
       return null;
     }
 
     // The record is cloned to avoid accidental modifications from outside.
-    let clonedRecord = this._clone(recordFound);
+    let clonedRecord = this._cloneAndCleanUp(recordFound);
     if (rawData) {
       this._stripComputedFields(clonedRecord);
     } else {
       this._recordReadProcessor(clonedRecord);
     }
     return clonedRecord;
   }
 
@@ -524,19 +555,19 @@ class AutofillRecords {
    * @param   {boolean} [options.includeDeleted = false]
    *          Also return any tombstone records.
    * @returns {Array.<Object>}
    *          An array containing clones of all records.
    */
   getAll({rawData = false, includeDeleted = false} = {}) {
     this.log.debug("getAll", rawData, includeDeleted);
 
-    let records = this._store.data[this._collectionName].filter(r => !r.deleted || includeDeleted);
+    let records = this.data.filter(r => !r.deleted || includeDeleted);
     // Records are cloned to avoid accidental modifications from outside.
-    let clonedRecords = records.map(r => this._clone(r));
+    let clonedRecords = records.map(r => this._cloneAndCleanUp(r));
     clonedRecords.forEach(record => {
       if (rawData) {
         this._stripComputedFields(record);
       } else {
         this._recordReadProcessor(record);
       }
     });
     return clonedRecords;
@@ -590,26 +621,27 @@ class AutofillRecords {
     }
   }
 
   /**
    * Attempts a three-way merge between a changed local record, an incoming
    * remote record, and the shared parent that we synthesize from the last
    * synced fields - see _maybeStoreLastSyncedField.
    *
-   * @param   {Object} localRecord
-   *          The changed local record, currently in storage.
+   * @param   {Object} strippedLocalRecord
+   *          The changed local record, currently in storage. Computed fields
+   *          are stripped.
    * @param   {Object} remoteRecord
    *          The remote record.
    * @returns {Object|null}
    *          The merged record, or `null` if there are conflicts and the
    *          records can't be merged.
    */
-  _mergeSyncedRecords(localRecord, remoteRecord) {
-    let sync = this._getSyncMetaData(localRecord, true);
+  _mergeSyncedRecords(strippedLocalRecord, remoteRecord) {
+    let sync = this._getSyncMetaData(strippedLocalRecord, true);
 
     // Copy all internal fields from the remote record. We'll update their
     // values in `_replaceRecordAt`.
     let mergedRecord = {};
     for (let field of INTERNAL_FIELDS) {
       if (remoteRecord[field] != null) {
         mergedRecord[field] = remoteRecord[field];
       }
@@ -618,40 +650,40 @@ class AutofillRecords {
     for (let field of this.VALID_FIELDS) {
       let isLocalSame = false;
       let isRemoteSame = false;
       if (field in sync.lastSyncedFields) {
         // If the field has changed since the last sync, compare hashes to
         // determine if the local and remote values are different. Hashing is
         // expensive, but we don't expect this to happen frequently.
         let lastSyncedValue = sync.lastSyncedFields[field];
-        isLocalSame = lastSyncedValue == sha512(localRecord[field]);
+        isLocalSame = lastSyncedValue == sha512(strippedLocalRecord[field]);
         isRemoteSame = lastSyncedValue == sha512(remoteRecord[field]);
       } else {
         // Otherwise, if the field hasn't changed since the last sync, we know
         // it's the same locally.
         isLocalSame = true;
-        isRemoteSame = localRecord[field] == remoteRecord[field];
+        isRemoteSame = strippedLocalRecord[field] == remoteRecord[field];
       }
 
       let value;
       if (isLocalSame && isRemoteSame) {
         // Local and remote are the same; doesn't matter which one we pick.
-        value = localRecord[field];
+        value = strippedLocalRecord[field];
       } else if (isLocalSame && !isRemoteSame) {
         value = remoteRecord[field];
       } else if (!isLocalSame && isRemoteSame) {
         // We don't need to bump the change counter when taking the local
         // change, because the counter must already be > 0 if we're attempting
         // a three-way merge.
-        value = localRecord[field];
-      } else if (localRecord[field] == remoteRecord[field]) {
+        value = strippedLocalRecord[field];
+      } else if (strippedLocalRecord[field] == remoteRecord[field]) {
         // Shared parent doesn't match either local or remote, but the values
         // are identical, so there's no conflict.
-        value = localRecord[field];
+        value = strippedLocalRecord[field];
       } else {
         // Both local and remote changed to different values. We'll need to fork
         // the local record to resolve the conflict.
         return null;
       }
 
       if (value != null) {
         mergedRecord[field] = value;
@@ -671,22 +703,22 @@ class AutofillRecords {
    *          Should we copy Sync metadata? This is true if `remoteRecord` is a
    *          merged record with local changes that we need to upload. Passing
    *          `keepSyncMetadata` retains the record's change counter and
    *          last synced fields, so that we don't clobber the local change if
    *          the sync is interrupted after the record is merged, but before
    *          it's uploaded.
    */
   _replaceRecordAt(index, remoteRecord, {keepSyncMetadata = false} = {}) {
-    let localRecord = this._store.data[this._collectionName][index];
+    let localRecord = this.data[index];
     let newRecord = this._clone(remoteRecord);
 
     this._stripComputedFields(newRecord);
 
-    this._store.data[this._collectionName][index] = newRecord;
+    this.data[index] = newRecord;
 
     if (keepSyncMetadata) {
       // It's safe to move the Sync metadata from the old record to the new
       // record, since we always clone records when we return them, and we
       // never hand out references to the metadata object via public methods.
       newRecord._sync = localRecord._sync;
     } else {
       // As a side effect, `_getSyncMetaData` marks the record as syncing if the
@@ -715,36 +747,33 @@ class AutofillRecords {
 
     this._computeFields(newRecord);
   }
 
   /**
    * Clones a local record, giving the clone a new GUID and Sync metadata. The
    * original record remains unchanged in storage.
    *
-   * @param   {Object} localRecord
-   *          The local record.
+   * @param   {Object} strippedLocalRecord
+   *          The local record. Computed fields are stripped.
    * @returns {string}
    *          A clone of the local record with a new GUID.
    */
-  _forkLocalRecord(localRecord) {
-    let forkedLocalRecord = this._clone(localRecord);
-
-    this._stripComputedFields(forkedLocalRecord);
-
+  _forkLocalRecord(strippedLocalRecord) {
+    let forkedLocalRecord = this._cloneAndCleanUp(strippedLocalRecord);
     forkedLocalRecord.guid = this._generateGUID();
-    this._store.data[this._collectionName].push(forkedLocalRecord);
 
     // Give the record fresh Sync metadata and bump its change counter as a
     // side effect. This also excludes the forked record from de-duping on the
     // next sync, if the current sync is interrupted before the record can be
     // uploaded.
     this._getSyncMetaData(forkedLocalRecord, true);
 
     this._computeFields(forkedLocalRecord);
+    this.data.push(forkedLocalRecord);
 
     return forkedLocalRecord;
   }
 
   /**
    * Reconciles an incoming remote record into the matching local record. This
    * method is only used by Sync; other callers should use `merge`.
    *
@@ -765,38 +794,41 @@ class AutofillRecords {
       throw new Error(`Can't reconcile tombstone ${remoteRecord.guid}`);
     }
 
     let localIndex = this._findIndexByGUID(remoteRecord.guid);
     if (localIndex < 0) {
       throw new Error(`Record ${remoteRecord.guid} not found`);
     }
 
-    let localRecord = this._store.data[this._collectionName][localIndex];
+    let localRecord = this.data[localIndex];
     let sync = this._getSyncMetaData(localRecord, true);
 
     let forkedGUID = null;
 
     if (sync.changeCounter === 0) {
       // Local not modified. Replace local with remote.
       this._replaceRecordAt(localIndex, remoteRecord, {
         keepSyncMetadata: false,
       });
     } else {
-      let mergedRecord = this._mergeSyncedRecords(localRecord, remoteRecord);
+      let strippedLocalRecord = this._clone(localRecord);
+      this._stripComputedFields(strippedLocalRecord);
+
+      let mergedRecord = this._mergeSyncedRecords(strippedLocalRecord, remoteRecord);
       if (mergedRecord) {
         // Local and remote modified, but we were able to merge. Replace the
         // local record with the merged record.
         this._replaceRecordAt(localIndex, mergedRecord, {
           keepSyncMetadata: true,
         });
       } else {
         // Merge conflict. Fork the local record, then replace the original
         // with the merged record.
-        let forkedLocalRecord = this._forkLocalRecord(localRecord);
+        let forkedLocalRecord = this._forkLocalRecord(strippedLocalRecord);
         forkedGUID = forkedLocalRecord.guid;
         this._replaceRecordAt(localIndex, remoteRecord, {
           keepSyncMetadata: false,
         });
       }
     }
 
     this._store.saveSoon();
@@ -816,38 +848,38 @@ class AutofillRecords {
       let tombstone = {
         guid,
         timeLastModified: Date.now(),
         deleted: true,
       };
 
       let sync = this._getSyncMetaData(tombstone, true);
       sync.changeCounter = 0;
-      this._store.data[this._collectionName].push(tombstone);
+      this.data.push(tombstone);
       return;
     }
 
-    let existing = this._store.data[this._collectionName][index];
+    let existing = this.data[index];
     let sync = this._getSyncMetaData(existing, true);
     if (sync.changeCounter > 0) {
       // Deleting a record with unsynced local changes. To avoid potential
       // data loss, we ignore the deletion in favor of the changed record.
       this.log.info("Ignoring deletion for record with local changes",
                     existing);
       return;
     }
 
     if (existing.deleted) {
       this.log.info("Ignoring deletion for tombstone", existing);
       return;
     }
 
     // Removing a record that's not changed locally, and that's not already
     // deleted. Replace the record with a synced tombstone.
-    this._store.data[this._collectionName][index] = {
+    this.data[index] = {
       guid,
       timeLastModified: Date.now(),
       deleted: true,
       _sync: sync,
     };
   }
 
   /**
@@ -859,17 +891,17 @@ class AutofillRecords {
    * the object to pushSyncChanges, which will apply the changes to the store.
    *
    * @returns {object}
    *          An object describing the changes to sync.
    */
   pullSyncChanges() {
     let changes = {};
 
-    let profiles = this._store.data[this._collectionName];
+    let profiles = this.data;
     for (let profile of profiles) {
       let sync = this._getSyncMetaData(profile, true);
       if (sync.changeCounter < 1) {
         if (sync.changeCounter != 0) {
           this.log.error("negative change counter", profile);
         }
         continue;
       }
@@ -916,17 +948,17 @@ class AutofillRecords {
 
   /**
    * Reset all sync metadata for all items.
    *
    * This is called when Sync is disconnected from this device. All sync
    * metadata for all items is removed.
    */
   resetSync() {
-    for (let record of this._store.data[this._collectionName]) {
+    for (let record of this.data) {
       delete record._sync;
     }
     // XXX - we should probably also delete all tombstones?
     this.log.info("All sync metadata was reset");
   }
 
   /**
    * Changes the GUID of an item. This should be called only by Sync. There
@@ -946,17 +978,17 @@ class AutofillRecords {
     if (oldID == newID) {
       throw new Error("changeGUID: old and new IDs are the same");
     }
     if (this._findIndexByGUID(newID) >= 0) {
       throw new Error("changeGUID: record with destination id exists already");
     }
 
     let index = this._findIndexByGUID(oldID);
-    let profile = this._store.data[this._collectionName][index];
+    let profile = this.data[index];
     if (!profile) {
       throw new Error("changeGUID: no source record");
     }
     if (this._getSyncMetaData(profile)) {
       throw new Error("changeGUID: existing record has already been synced");
     }
 
     profile.guid = newID;
@@ -980,105 +1012,110 @@ class AutofillRecords {
   }
 
   /**
    * Finds a local record with matching common fields and a different GUID.
    * Sync uses this method to find and update unsynced local records with
    * fields that match incoming remote records. This avoids creating
    * duplicate profiles with the same information.
    *
-   * @param   {Object} record
+   * @param   {Object} remoteRecord
    *          The remote record.
    * @returns {string|null}
    *          The GUID of the matching local record, or `null` if no records
    *          match.
    */
-  findDuplicateGUID(record) {
-    if (!record.guid) {
+  findDuplicateGUID(remoteRecord) {
+    if (!remoteRecord.guid) {
       throw new Error("Record missing GUID");
     }
-    this._ensureMatchingVersion(record);
-    if (record.deleted) {
+    this._ensureMatchingVersion(remoteRecord);
+    if (remoteRecord.deleted) {
       // Tombstones don't carry enough info to de-dupe, and we should have
       // handled them separately when applying the record.
       throw new Error("Tombstones can't have duplicates");
     }
-    let records = this._store.data[this._collectionName];
-    for (let profile of records) {
-      if (profile.deleted) {
+    let localRecords = this.data;
+    for (let localRecord of localRecords) {
+      if (localRecord.deleted) {
         continue;
       }
-      if (profile.guid == record.guid) {
-        throw new Error(`Record ${record.guid} already exists`);
+      if (localRecord.guid == remoteRecord.guid) {
+        throw new Error(`Record ${remoteRecord.guid} already exists`);
       }
-      if (this._getSyncMetaData(profile)) {
-        // This record has already been uploaded, so it can't be a dupe of
+      if (this._getSyncMetaData(localRecord)) {
+        // This local record has already been uploaded, so it can't be a dupe of
         // another incoming item.
         continue;
       }
-      let keys = new Set(Object.keys(record));
-      for (let key of Object.keys(profile)) {
+
+      // Ignore computed fields when matching records as they aren't synced at all.
+      let strippedLocalRecord = this._clone(localRecord);
+      this._stripComputedFields(strippedLocalRecord);
+
+      let keys = new Set(Object.keys(remoteRecord));
+      for (let key of Object.keys(strippedLocalRecord)) {
         keys.add(key);
       }
-      // Ignore internal and computed fields when matching records. Internal
-      // fields are synced, but almost certainly have different values than the
-      // local record, and we'll update them in `reconcile`. Computed fields
-      // aren't synced at all.
+      // Ignore internal fields when matching records. Internal fields are synced,
+      // but almost certainly have different values than the local record, and
+      // we'll update them in `reconcile`.
       for (let field of INTERNAL_FIELDS) {
         keys.delete(field);
       }
-      for (let field of this.VALID_COMPUTED_FIELDS) {
-        keys.delete(field);
-      }
       if (!keys.size) {
         // This shouldn't ever happen; a valid record will always have fields
         // that aren't computed or internal. Sync can't do anything about that,
         // so we ignore the dubious local record instead of throwing.
         continue;
       }
       let same = true;
       for (let key of keys) {
         // For now, we ensure that both (or neither) records have the field
         // with matching values. This doesn't account for the version yet
         // (bug 1377204).
-        same = key in profile == key in record && profile[key] == record[key];
+        same = key in strippedLocalRecord == key in remoteRecord && strippedLocalRecord[key] == remoteRecord[key];
         if (!same) {
           break;
         }
       }
       if (same) {
-        return profile.guid;
+        return strippedLocalRecord.guid;
       }
     }
     return null;
   }
 
   /**
    * Internal helper functions.
    */
 
   _clone(record) {
+    return Object.assign({}, record);
+  }
+
+  _cloneAndCleanUp(record) {
     let result = {};
     for (let key in record) {
       // Do not expose hidden fields and fields with empty value (mainly used
       // as placeholders of the computed fields).
       if (!key.startsWith("_") && record[key] !== "") {
         result[key] = record[key];
       }
     }
     return result;
   }
 
   _findByGUID(guid, {includeDeleted = false} = {}) {
     let found = this._findIndexByGUID(guid, {includeDeleted});
-    return found < 0 ? undefined : this._store.data[this._collectionName][found];
+    return found < 0 ? undefined : this.data[found];
   }
 
   _findIndexByGUID(guid, {includeDeleted = false} = {}) {
-    return this._store.data[this._collectionName].findIndex(record => {
+    return this.data.findIndex(record => {
       return record.guid == guid && (!record.deleted || includeDeleted);
     });
   }
 
   _migrateRecord(record) {
     let hasChanges = false;
 
     if (!record.version || isNaN(record.version) || record.version < 1) {
@@ -1405,31 +1442,38 @@ class Addresses extends AutofillRecords 
    * Merge the address if storage has multiple mergeable records.
    * @param {Object} targetAddress
    *        The address for merge.
    * @returns {Array.<string>}
    *          Return an array of the merged GUID string.
    */
   mergeToStorage(targetAddress) {
     let mergedGUIDs = [];
-    for (let address of this._store.data[this._collectionName]) {
+    for (let address of this.data) {
       if (!address.deleted && this.mergeIfPossible(address.guid, targetAddress)) {
         mergedGUIDs.push(address.guid);
       }
     }
     this.log.debug("Existing records matching and merging count is", mergedGUIDs.length);
     return mergedGUIDs;
   }
 }
 
 class CreditCards extends AutofillRecords {
   constructor(store) {
     super(store, "creditCards", VALID_CREDIT_CARD_FIELDS, VALID_CREDIT_CARD_COMPUTED_FIELDS, CREDIT_CARD_SCHEMA_VERSION);
   }
 
+  _getMaskedCCNumber(ccNumber) {
+    if (ccNumber.length <= 4) {
+      throw new Error(`Invalid credit card number`);
+    }
+    return "*".repeat(ccNumber.length - 4) + ccNumber.substr(-4);
+  }
+
   _computeFields(creditCard) {
     // NOTE: Remember to bump the schema version number if any of the existing
     //       computing algorithm changes. (No need to bump when just adding new
     //       computed fields)
 
     let hasNewComputedFields = false;
 
     // Compute split names
@@ -1443,25 +1487,41 @@ class CreditCards extends AutofillRecord
 
     let year = creditCard["cc-exp-year"];
     let month = creditCard["cc-exp-month"];
     if (!creditCard["cc-exp"] && month && year) {
       creditCard["cc-exp"] = String(year) + "-" + String(month).padStart(2, "0");
       hasNewComputedFields = true;
     }
 
+    // Encrypt credit card number
+    if (!("cc-number-encrypted" in creditCard)) {
+      let ccNumber = (creditCard["cc-number"] || "").replace(/\s/g, "");
+      if (FormAutofillUtils.isCCNumber(ccNumber)) {
+        creditCard["cc-number"] = this._getMaskedCCNumber(ccNumber);
+        creditCard["cc-number-encrypted"] = MasterPassword.encryptSync(ccNumber);
+      } else {
+        delete creditCard["cc-number"];
+        // Computed fields are always present in the storage no matter it's
+        // empty or not.
+        creditCard["cc-number-encrypted"] = "";
+      }
+    }
+
     return hasNewComputedFields;
   }
 
+  _stripComputedFields(creditCard) {
+    if (creditCard["cc-number-encrypted"]) {
+      creditCard["cc-number"] = MasterPassword.decryptSync(creditCard["cc-number-encrypted"]);
+    }
+    super._stripComputedFields(creditCard);
+  }
+
   _normalizeFields(creditCard) {
-    // Check if cc-number is normalized(normalizeCCNumberFields should be called first).
-    if (!creditCard["cc-number-encrypted"] || !creditCard["cc-number"].includes("*")) {
-      throw new Error("Credit card number needs to be normalized first.");
-    }
-
     // Normalize name
     if (creditCard["cc-given-name"] || creditCard["cc-additional-name"] || creditCard["cc-family-name"]) {
       if (!creditCard["cc-name"]) {
         creditCard["cc-name"] = FormAutofillNameUtils.joinNameParts({
           given: creditCard["cc-given-name"],
           middle: creditCard["cc-additional-name"],
           family: creditCard["cc-family-name"],
         });
@@ -1488,41 +1548,16 @@ class CreditCards extends AutofillRecord
       } else if (expYear < 100) {
         // Enforce 4 digits years.
         creditCard["cc-exp-year"] = expYear + 2000;
       } else {
         creditCard["cc-exp-year"] = expYear;
       }
     }
   }
-
-  /**
-   * Normalize credit card number related field for saving. It should always be
-   * called before adding/updating credit card records.
-   *
-   * @param  {Object} creditCard
-   *         The creditCard record with plaintext number only.
-   */
-  async normalizeCCNumberFields(creditCard) {
-    // Fields that should not be set by content.
-    delete creditCard["cc-number-encrypted"];
-
-    // Validate and encrypt credit card numbers, and calculate the masked numbers
-    if (creditCard["cc-number"]) {
-      let ccNumber = creditCard["cc-number"].replace(/\s/g, "");
-      delete creditCard["cc-number"];
-
-      if (!FormAutofillUtils.isCCNumber(ccNumber)) {
-        throw new Error("Credit card number contains invalid characters or is under 12 digits.");
-      }
-
-      creditCard["cc-number-encrypted"] = await MasterPassword.encrypt(ccNumber);
-      creditCard["cc-number"] = "*".repeat(ccNumber.length - 4) + ccNumber.substr(-4);
-    }
-  }
 }
 
 function ProfileStorage(path) {
   this._path = path;
   this._initializePromise = null;
   this.INTERNAL_FIELDS = INTERNAL_FIELDS;
 }
 
--- a/browser/extensions/formautofill/content/editDialog.js
+++ b/browser/extensions/formautofill/content/editDialog.js
@@ -258,19 +258,22 @@ class EditCreditCard extends EditDialog 
 
   async handleSubmit() {
     let creditCard = this.buildFormObject();
     // Show error on the cc-number field if it's empty or invalid
     if (!FormAutofillUtils.isCCNumber(creditCard["cc-number"])) {
       this._elements.ccNumber.setCustomValidity(true);
       return;
     }
-    let storage = await this.getStorage();
-    await storage.normalizeCCNumberFields(creditCard);
-    await this.saveRecord(creditCard, this._record ? this._record.guid : null);
+
+    // TODO: "MasterPassword.ensureLoggedIn" can be removed after the storage
+    // APIs are refactored to be async functions (bug 1399367).
+    if (await MasterPassword.ensureLoggedIn()) {
+      await this.saveRecord(creditCard, this._record ? this._record.guid : null);
+    }
     window.close();
   }
 
   handleInput(event) {
     // Clear the error message if cc-number is valid
     if (event.target == this._elements.ccNumber &&
         FormAutofillUtils.isCCNumber(this._elements.ccNumber.value)) {
       this._elements.ccNumber.setCustomValidity("");
--- a/browser/extensions/formautofill/content/manageDialog.js
+++ b/browser/extensions/formautofill/content/manageDialog.js
@@ -343,17 +343,17 @@ class ManageCreditCards extends ManageRe
   /**
    * Open the edit address dialog to create/edit a credit card.
    *
    * @param  {object} creditCard [optional]
    */
   async openEditDialog(creditCard) {
     // If master password is set, ask for password if user is trying to edit an
     // existing credit card.
-    if (!this._hasMasterPassword || !creditCard || await MasterPassword.prompt()) {
+    if (!creditCard || !this._hasMasterPassword || await MasterPassword.ensureLoggedIn(true)) {
       this.prefWin.gSubDialog.open(EDIT_CREDIT_CARD_URL, null, creditCard);
     }
   }
 
   /**
    * Get credit card display label. It should display masked numbers and the
    * cardholder's name, separated by a comma. If `showCreditCards` is set to
    * true, decrypted credit card numbers are shown instead.
--- a/browser/extensions/formautofill/locales/en-US/formautofill.properties
+++ b/browser/extensions/formautofill/locales/en-US/formautofill.properties
@@ -15,16 +15,19 @@ autofillSecurityOptionsLink = Form Autof
 autofillSecurityOptionsLinkOSX = Form Autofill & Security Preferences
 # LOCALIZATION NOTE (changeAutofillOptions, changeAutofillOptionsOSX): These strings are used on the doorhanger
 # that notifies users that addresses are saved. The button leads users to Form Autofill browser preferences.
 changeAutofillOptions = Change Form Autofill Options
 changeAutofillOptionsOSX = Change Form Autofill Preferences
 # LOCALIZATION NOTE (addressesSyncCheckbox): If Sync is enabled, this checkbox is displayed on the doorhanger
 # shown when saving addresses.
 addressesSyncCheckbox = Share addresses with synced devices
+# LOCALIZATION NOTE (creditCardsSyncCheckbox): If Sync is enabled and credit card sync is available,
+# this checkbox is displayed on the doorhanger shown when saving credit card.
+creditCardsSyncCheckbox = Share credit cards with synced devices
 # LOCALIZATION NOTE (updateAddressMessage, createAddressLabel, updateAddressLabel): Used on the doorhanger
 # when an address change is detected.
 updateAddressMessage = Would you like to update your address with this new information?
 createAddressLabel = Create New Address
 updateAddressLabel = Update Address
 # LOCALIZATION NOTE (saveCreditCardMessage, saveCreditCardLabel, cancelCreditCardLabel, neverSaveCreditCardLabel):
 # Used on the doorhanger when users submit payment with credit card.
 # LOCALIZATION NOTE (saveCreditCardMessage): %S is brandShortName.
--- a/browser/extensions/formautofill/test/browser/browser_creditCard_doorhanger.js
+++ b/browser/extensions/formautofill/test/browser/browser_creditCard_doorhanger.js
@@ -46,17 +46,16 @@ add_task(async function test_submit_cred
 
         // Wait 1000ms before submission to make sure the input value applied
         await new Promise(resolve => setTimeout(resolve, 1000));
         form.querySelector("input[type=submit]").click();
       });
 
       await promiseShown;
       await clickDoorhangerButton(MAIN_BUTTON);
-      await TestUtils.topicObserved("formautofill-storage-changed");
     }
   );
 
   let creditCards = await getCreditCards();
   is(creditCards.length, 1, "1 credit card in storage");
   is(creditCards[0]["cc-name"], "User 1", "Verify the name field");
 });
 
@@ -158,8 +157,134 @@ add_task(async function test_submit_cred
     }
   );
 
   await sleep(1000);
   let creditCards = await getCreditCards();
   is(creditCards.length, 2, "Still 2 credit cards in storage");
   LoginTestUtils.masterPassword.disable();
 });
+
+add_task(async function test_submit_creditCard_unavailable_with_sync_account() {
+  await SpecialPowers.pushPrefEnv({
+    "set": [
+      [SYNC_USERNAME_PREF, "foo@bar.com"],
+    ],
+  });
+
+  await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
+    async function(browser) {
+      let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
+                                                       "popupshown");
+      is(SpecialPowers.getBoolPref(SYNC_CREDITCARDS_AVAILABLE_PREF), false,
+         "creditCards sync should be unavailable by default");
+      await ContentTask.spawn(browser, null, async function() {
+        let form = content.document.getElementById("form");
+        let name = form.querySelector("#cc-name");
+        name.focus();
+        name.setUserInput("User 2");
+
+        let number = form.querySelector("#cc-number");
+        number.setUserInput("1234123412341234");
+
+        // Wait 500ms before submission to make sure the input value applied
+        await new Promise(resolve => setTimeout(resolve, 500));
+        form.querySelector("input[type=submit]").click();
+      });
+
+      await promiseShown;
+      let cb = getDoorhangerCheckbox();
+      ok(cb.hidden, "Sync checkbox should be hidden");
+
+      await clickDoorhangerButton(SECONDARY_BUTTON);
+    }
+  );
+});
+
+add_task(async function test_submit_creditCard_with_sync_account() {
+  await SpecialPowers.pushPrefEnv({
+    "set": [
+      [SYNC_USERNAME_PREF, "foo@bar.com"],
+      [SYNC_CREDITCARDS_AVAILABLE_PREF, true],
+    ],
+  });
+
+  await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
+    async function(browser) {
+      let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
+                                                       "popupshown");
+      await ContentTask.spawn(browser, null, async function() {
+        let form = content.document.getElementById("form");
+        let name = form.querySelector("#cc-name");
+        name.focus();
+        name.setUserInput("User 2");
+
+        let number = form.querySelector("#cc-number");
+        number.setUserInput("1234123412341234");
+
+        // Wait 500ms before submission to make sure the input value applied
+        await new Promise(resolve => setTimeout(resolve, 500));
+        form.querySelector("input[type=submit]").click();
+      });
+
+      await promiseShown;
+      let cb = getDoorhangerCheckbox();
+      ok(!cb.hidden, "Sync checkbox should be visible");
+      is(SpecialPowers.getBoolPref(SYNC_CREDITCARDS_PREF), false,
+         "creditCards sync should be disabled by default");
+
+      // Verify if the checkbox and button state is changed.
+      let secondaryButton = getDoorhangerButton(SECONDARY_BUTTON);
+      let menuButton = getDoorhangerButton(MENU_BUTTON);
+      is(cb.checked, false, "Checkbox state should match creditCards sync state");
+      is(secondaryButton.disabled, false, "Not saving button should be enabled");
+      is(menuButton.disabled, false, "Never saving menu button should be enabled");
+      // Click the checkbox to enable credit card sync.
+      cb.click();
+      is(SpecialPowers.getBoolPref(SYNC_CREDITCARDS_PREF), true,
+         "creditCards sync should be enabled after checked");
+      is(secondaryButton.disabled, true, "Not saving button should be disabled");
+      is(menuButton.disabled, true, "Never saving menu button should be disabled");
+      // Click the checkbox again to disable credit card sync.
+      cb.click();
+      is(SpecialPowers.getBoolPref(SYNC_CREDITCARDS_PREF), false,
+         "creditCards sync should be disabled after unchecked");
+      is(secondaryButton.disabled, false, "Not saving button should be enabled again");
+      is(menuButton.disabled, false, "Never saving menu button should be enabled again");
+      await clickDoorhangerButton(MAIN_BUTTON);
+    }
+  );
+});
+
+add_task(async function test_submit_creditCard_with_synced_already() {
+  await SpecialPowers.pushPrefEnv({
+    "set": [
+      [SYNC_CREDITCARDS_PREF, true],
+      [SYNC_USERNAME_PREF, "foo@bar.com"],
+      [SYNC_CREDITCARDS_AVAILABLE_PREF, true],
+    ],
+  });
+
+  await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
+    async function(browser) {
+      let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
+                                                       "popupshown");
+      await ContentTask.spawn(browser, null, async function() {
+        let form = content.document.getElementById("form");
+        let name = form.querySelector("#cc-name");
+        name.focus();
+        name.setUserInput("User 2");
+
+        let number = form.querySelector("#cc-number");
+        number.setUserInput("1234123412341234");
+
+        // Wait 500ms before submission to make sure the input value applied
+        await new Promise(resolve => setTimeout(resolve, 500));
+        form.querySelector("input[type=submit]").click();
+      });
+
+      await promiseShown;
+      let cb = getDoorhangerCheckbox();
+      ok(cb.hidden, "Sync checkbox should be hidden");
+      await clickDoorhangerButton(MAIN_BUTTON);
+    }
+  );
+});
--- a/browser/extensions/formautofill/test/browser/head.js
+++ b/browser/extensions/formautofill/test/browser/head.js
@@ -1,16 +1,17 @@
 /* exported MANAGE_ADDRESSES_DIALOG_URL, MANAGE_CREDIT_CARDS_DIALOG_URL, EDIT_ADDRESS_DIALOG_URL, EDIT_CREDIT_CARD_DIALOG_URL,
             BASE_URL, TEST_ADDRESS_1, TEST_ADDRESS_2, TEST_ADDRESS_3, TEST_ADDRESS_4, TEST_ADDRESS_5,
             TEST_CREDIT_CARD_1, TEST_CREDIT_CARD_2, TEST_CREDIT_CARD_3, FORM_URL, CREDITCARD_FORM_URL,
             FTU_PREF, ENABLED_AUTOFILL_ADDRESSES_PREF, AUTOFILL_CREDITCARDS_AVAILABLE_PREF, ENABLED_AUTOFILL_CREDITCARDS_PREF,
-            SYNC_USERNAME_PREF, SYNC_ADDRESSES_PREF,
+            SYNC_USERNAME_PREF, SYNC_ADDRESSES_PREF, SYNC_CREDITCARDS_PREF, SYNC_CREDITCARDS_AVAILABLE_PREF,
             sleep, expectPopupOpen, openPopupOn, expectPopupClose, closePopup, clickDoorhangerButton,
             getAddresses, saveAddress, removeAddresses, saveCreditCard,
-            getDisplayedPopupItems, getDoorhangerCheckbox, waitForMasterPasswordDialog */
+            getDisplayedPopupItems, getDoorhangerCheckbox, waitForMasterPasswordDialog,
+            getNotification, getDoorhangerButton */
 
 "use strict";
 
 Cu.import("resource://testing-common/LoginTestUtils.jsm", this);
 
 const MANAGE_ADDRESSES_DIALOG_URL = "chrome://formautofill/content/manageAddresses.xhtml";
 const MANAGE_CREDIT_CARDS_DIALOG_URL = "chrome://formautofill/content/manageCreditCards.xhtml";
 const EDIT_ADDRESS_DIALOG_URL = "chrome://formautofill/content/editAddress.xhtml";
@@ -20,16 +21,18 @@ const FORM_URL = "http://mochi.test:8888
 const CREDITCARD_FORM_URL =
   "http://mochi.test:8888/browser/browser/extensions/formautofill/test/browser/autocomplete_creditcard_basic.html";
 const FTU_PREF = "extensions.formautofill.firstTimeUse";
 const ENABLED_AUTOFILL_ADDRESSES_PREF = "extensions.formautofill.addresses.enabled";
 const AUTOFILL_CREDITCARDS_AVAILABLE_PREF = "extensions.formautofill.creditCards.available";
 const ENABLED_AUTOFILL_CREDITCARDS_PREF = "extensions.formautofill.creditCards.enabled";
 const SYNC_USERNAME_PREF = "services.sync.username";
 const SYNC_ADDRESSES_PREF = "services.sync.engine.addresses";
+const SYNC_CREDITCARDS_PREF = "services.sync.engine.creditcards";
+const SYNC_CREDITCARDS_AVAILABLE_PREF = "services.sync.engine.creditcards.available";
 
 const TEST_ADDRESS_1 = {
   "given-name": "John",
   "additional-name": "R.",
   "family-name": "Smith",
   organization: "World Wide Web Consortium",
   "street-address": "32 Vassar Street\nMIT Room 32-G524",
   "address-level2": "Cambridge",
@@ -243,20 +246,25 @@ async function clickDoorhangerButton(but
     await dropdownPromise;
 
     let actionMenuItem = notification.querySelectorAll("menuitem")[index];
     await EventUtils.synthesizeMouseAtCenter(actionMenuItem, {});
   }
   await popuphidden;
 }
 
+
 function getDoorhangerCheckbox() {
   return getNotification().checkbox;
 }
 
+function getDoorhangerButton(button) {
+  return getNotification()[button];
+}
+
 
 // Wait for the master password dialog to popup and enter the password to log in
 // if "login" is "true" or dismiss it directly if otherwise.
 function waitForMasterPasswordDialog(login = false) {
   let dialogShown = TestUtils.topicObserved("common-dialog-loaded");
   return dialogShown.then(([subject]) => {
     let dialog = subject.Dialog;
     is(dialog.args.title, "Password Required", "Master password dialog shown");
--- a/browser/extensions/formautofill/test/unit/head.js
+++ b/browser/extensions/formautofill/test/unit/head.js
@@ -85,30 +85,30 @@ function getTempFile(leafName) {
     if (file.exists()) {
       file.remove(false);
     }
   });
 
   return file;
 }
 
-async function initProfileStorage(fileName, records) {
+async function initProfileStorage(fileName, records, collectionName = "addresses") {
   let {ProfileStorage} = Cu.import("resource://formautofill/ProfileStorage.jsm", {});
   let path = getTempFile(fileName).path;
   let profileStorage = new ProfileStorage(path);
   await profileStorage.initialize();
 
   if (!records || !Array.isArray(records)) {
     return profileStorage;
   }
 
   let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
                                           (subject, data) => data == "add");
   for (let record of records) {
-    do_check_true(profileStorage.addresses.add(record));
+    do_check_true(profileStorage[collectionName].add(record));
     await onChanged;
   }
   await profileStorage._saveImmediately();
   return profileStorage;
 }
 
 function runHeuristicsTest(patterns, fixturePathPrefix) {
   Cu.import("resource://formautofill/FormAutofillHeuristics.jsm");
--- a/browser/extensions/formautofill/test/unit/test_creditCardRecords.js
+++ b/browser/extensions/formautofill/test/unit/test_creditCardRecords.js
@@ -47,51 +47,39 @@ const TEST_CREDIT_CARD_WITH_INVALID_EXPI
   "cc-exp-year": -3,
 };
 
 const TEST_CREDIT_CARD_WITH_SPACES_BETWEEN_DIGITS = {
   "cc-name": "John Doe",
   "cc-number": "1111 2222 3333 4444",
 };
 
-const TEST_CREDIT_CARD_WITH_INVALID_NUMBERS = {
-  "cc-name": "John Doe",
-  "cc-number": "abcdefg",
-};
-
-const TEST_CREDIT_CARD_WITH_SHORT_NUMBERS = {
-  "cc-name": "John Doe",
-  "cc-number": "1234567890",
-};
-
 let prepareTestCreditCards = async function(path) {
   let profileStorage = new ProfileStorage(path);
   await profileStorage.initialize();
 
   let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
                                           (subject, data) => data == "add");
-  let encryptedCC_1 = Object.assign({}, TEST_CREDIT_CARD_1);
-  await profileStorage.creditCards.normalizeCCNumberFields(encryptedCC_1);
-  do_check_true(profileStorage.creditCards.add(encryptedCC_1));
+  do_check_true(profileStorage.creditCards.add(TEST_CREDIT_CARD_1));
   await onChanged;
-  let encryptedCC_2 = Object.assign({}, TEST_CREDIT_CARD_2);
-  await profileStorage.creditCards.normalizeCCNumberFields(encryptedCC_2);
-  do_check_true(profileStorage.creditCards.add(encryptedCC_2));
+  do_check_true(profileStorage.creditCards.add(TEST_CREDIT_CARD_2));
+  await onChanged;
   await profileStorage._saveImmediately();
 };
 
 let reCCNumber = /^(\*+)(.{4})$/;
 
 let do_check_credit_card_matches = (creditCardWithMeta, creditCard) => {
   for (let key in creditCard) {
     if (key == "cc-number") {
       let matches = reCCNumber.exec(creditCardWithMeta["cc-number"]);
       do_check_neq(matches, null);
       do_check_eq(creditCardWithMeta["cc-number"].length, creditCard["cc-number"].length);
       do_check_eq(creditCard["cc-number"].endsWith(matches[2]), true);
+      do_check_neq(creditCard["cc-number-encrypted"], "");
     } else {
       do_check_eq(creditCardWithMeta[key], creditCard[key]);
     }
   }
 };
 
 add_task(async function test_initialize() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
@@ -177,19 +165,17 @@ add_task(async function test_add() {
 
   do_check_neq(creditCards[0].guid, undefined);
   do_check_eq(creditCards[0].version, 1);
   do_check_neq(creditCards[0].timeCreated, undefined);
   do_check_eq(creditCards[0].timeLastModified, creditCards[0].timeCreated);
   do_check_eq(creditCards[0].timeLastUsed, 0);
   do_check_eq(creditCards[0].timesUsed, 0);
 
-  let encryptedCC_invalid = Object.assign({}, TEST_CREDIT_CARD_WITH_INVALID_FIELD);
-  await profileStorage.creditCards.normalizeCCNumberFields(encryptedCC_invalid);
-  Assert.throws(() => profileStorage.creditCards.add(encryptedCC_invalid),
+  Assert.throws(() => profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_INVALID_FIELD),
     /"invalidField" is not a valid field\./);
 });
 
 add_task(async function test_update() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
   await prepareTestCreditCards(path);
 
   let profileStorage = new ProfileStorage(path);
@@ -198,17 +184,16 @@ add_task(async function test_update() {
   let creditCards = profileStorage.creditCards.getAll();
   let guid = creditCards[1].guid;
   let timeLastModified = creditCards[1].timeLastModified;
 
   let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
                                           (subject, data) => data == "update");
 
   do_check_neq(creditCards[1]["cc-name"], undefined);
-  await profileStorage.creditCards.normalizeCCNumberFields(TEST_CREDIT_CARD_3);
   profileStorage.creditCards.update(guid, TEST_CREDIT_CARD_3);
   await onChanged;
   await profileStorage._saveImmediately();
 
   profileStorage = new ProfileStorage(path);
   await profileStorage.initialize();
 
   let creditCard = profileStorage.creditCards.get(guid);
@@ -217,64 +202,45 @@ add_task(async function test_update() {
   do_check_neq(creditCard.timeLastModified, timeLastModified);
   do_check_credit_card_matches(creditCard, TEST_CREDIT_CARD_3);
 
   Assert.throws(
     () => profileStorage.creditCards.update("INVALID_GUID", TEST_CREDIT_CARD_3),
     /No matching record\./
   );
 
-  let encryptedCC_invalid = Object.assign({}, TEST_CREDIT_CARD_WITH_INVALID_FIELD);
-  await profileStorage.creditCards.normalizeCCNumberFields(encryptedCC_invalid);
   Assert.throws(
-    () => profileStorage.creditCards.update(guid, encryptedCC_invalid),
+    () => profileStorage.creditCards.update(guid, TEST_CREDIT_CARD_WITH_INVALID_FIELD),
     /"invalidField" is not a valid field\./
   );
 });
 
 add_task(async function test_validate() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
 
   let profileStorage = new ProfileStorage(path);
   await profileStorage.initialize();
 
-  await profileStorage.creditCards.normalizeCCNumberFields(TEST_CREDIT_CARD_WITH_INVALID_EXPIRY_DATE);
   profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_INVALID_EXPIRY_DATE);
-  await profileStorage.creditCards.normalizeCCNumberFields(TEST_CREDIT_CARD_WITH_2_DIGITS_YEAR);
   profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_2_DIGITS_YEAR);
-  await profileStorage.creditCards.normalizeCCNumberFields(TEST_CREDIT_CARD_WITH_SPACES_BETWEEN_DIGITS);
   profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_SPACES_BETWEEN_DIGITS);
 
   let creditCards = profileStorage.creditCards.getAll();
 
   do_check_eq(creditCards[0]["cc-exp-month"], undefined);
   do_check_eq(creditCards[0]["cc-exp-year"], undefined);
   do_check_eq(creditCards[0]["cc-exp"], undefined);
 
   let month = TEST_CREDIT_CARD_WITH_2_DIGITS_YEAR["cc-exp-month"];
   let year = parseInt(TEST_CREDIT_CARD_WITH_2_DIGITS_YEAR["cc-exp-year"], 10) + 2000;
   do_check_eq(creditCards[1]["cc-exp-month"], month);
   do_check_eq(creditCards[1]["cc-exp-year"], year);
   do_check_eq(creditCards[1]["cc-exp"], year + "-" + month.toString().padStart(2, "0"));
 
   do_check_eq(creditCards[2]["cc-number"].length, 16);
-
-  try {
-    await profileStorage.creditCards.normalizeCCNumberFields(TEST_CREDIT_CARD_WITH_INVALID_NUMBERS);
-    throw new Error("Not receiving invalid characters error");
-  } catch (e) {
-    Assert.equal(e.message, "Credit card number contains invalid characters or is under 12 digits.");
-  }
-
-  try {
-    await profileStorage.creditCards.normalizeCCNumberFields(TEST_CREDIT_CARD_WITH_SHORT_NUMBERS);
-    throw new Error("Not receiving invalid characters error");
-  } catch (e) {
-    Assert.equal(e.message, "Credit card number contains invalid characters or is under 12 digits.");
-  }
 });
 
 add_task(async function test_notifyUsed() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
   await prepareTestCreditCards(path);
 
   let profileStorage = new ProfileStorage(path);
   await profileStorage.initialize();
--- a/browser/extensions/formautofill/test/unit/test_getInfo.js
+++ b/browser/extensions/formautofill/test/unit/test_getInfo.js
@@ -153,22 +153,16 @@ const TESTCASES = [
     expectedReturnValue: {
       fieldName: "name",
       section: "",
       addressType: "",
       contactType: "",
     },
   },
   {
-    description: "non-input element",
-    document: `<label id="targetElement">street</label>`,
-    elementId: "targetElement",
-    expectedReturnValue: null,
-  },
-  {
     description: "input element with \"submit\" type",
     document: `<input id="targetElement" type="submit" />`,
     elementId: "targetElement",
     expectedReturnValue: null,
   },
   {
     description: "The signature in 'name' attr of an email input",
     document: `<input id="targetElement" name="email" type="number">`,
--- a/browser/extensions/formautofill/test/unit/test_getRecords.js
+++ b/browser/extensions/formautofill/test/unit/test_getRecords.js
@@ -1,15 +1,16 @@
 /*
  * Test for make sure getRecords can retrieve right collection from storage.
  */
 
 "use strict";
 
 Cu.import("resource://formautofill/FormAutofillParent.jsm");
+Cu.import("resource://formautofill/MasterPassword.jsm");
 Cu.import("resource://formautofill/ProfileStorage.jsm");
 
 const TEST_ADDRESS_1 = {
   "given-name": "Timothy",
   "additional-name": "John",
   "family-name": "Berners-Lee",
   organization: "World Wide Web Consortium",
   "street-address": "32 Vassar Street\nMIT Room 32-G524",
@@ -161,23 +162,26 @@ add_task(async function test_getRecords_
 });
 
 add_task(async function test_getRecords_creditCards() {
   let formAutofillParent = new FormAutofillParent();
 
   await formAutofillParent.init();
   await formAutofillParent.profileStorage.initialize();
   let collection = profileStorage.creditCards;
-  let decryptedCCNumber = [TEST_CREDIT_CARD_1["cc-number"], TEST_CREDIT_CARD_2["cc-number"]];
-  await collection.normalizeCCNumberFields(TEST_CREDIT_CARD_1);
-  await collection.normalizeCCNumberFields(TEST_CREDIT_CARD_2);
-  sinon.stub(collection, "getAll", () => [Object.assign({}, TEST_CREDIT_CARD_1), Object.assign({}, TEST_CREDIT_CARD_2)]);
+  let encryptedCCRecords = [TEST_CREDIT_CARD_1, TEST_CREDIT_CARD_2].map(record => {
+    let clonedRecord = Object.assign({}, record);
+    clonedRecord["cc-number"] = collection._getMaskedCCNumber(record["cc-number"]);
+    clonedRecord["cc-number-encrypted"] = MasterPassword.encryptSync(record["cc-number"]);
+    return clonedRecord;
+  });
+  sinon.stub(collection, "getAll", () => [Object.assign({}, encryptedCCRecords[0]), Object.assign({}, encryptedCCRecords[1])]);
   let CreditCardsWithDecryptedNumber = [
-    Object.assign({}, TEST_CREDIT_CARD_1, {"cc-number-decrypted": decryptedCCNumber[0]}),
-    Object.assign({}, TEST_CREDIT_CARD_2, {"cc-number-decrypted": decryptedCCNumber[1]}),
+    Object.assign({}, encryptedCCRecords[0], {"cc-number-decrypted": TEST_CREDIT_CARD_1["cc-number"]}),
+    Object.assign({}, encryptedCCRecords[1], {"cc-number-decrypted": TEST_CREDIT_CARD_2["cc-number"]}),
   ];
 
   let testCases = [
     {
       description: "If the search string could match 1 creditCard (without masterpassword)",
       filter: {
         collectionName: "creditCards",
         info: {fieldName: "cc-name"},
@@ -224,27 +228,27 @@ add_task(async function test_getRecords_
     {
       description: "If the search string could match 1 creditCard (with masterpassword)",
       filter: {
         collectionName: "creditCards",
         info: {fieldName: "cc-name"},
         searchString: "John Doe",
       },
       mpEnabled: true,
-      expectedResult: [TEST_CREDIT_CARD_1],
+      expectedResult: encryptedCCRecords.slice(0, 1),
     },
     {
       description: "Return all creditCards if focused field is cc number (with masterpassword)",
       filter: {
         collectionName: "creditCards",
         info: {fieldName: "cc-number"},
         searchString: "123",
       },
       mpEnabled: true,
-      expectedResult: [TEST_CREDIT_CARD_1, TEST_CREDIT_CARD_2],
+      expectedResult: encryptedCCRecords,
     },
   ];
 
   for (let testCase of testCases) {
     do_print("Starting testcase: " + testCase.description);
     if (testCase.mpEnabled) {
       let tokendb = Cc["@mozilla.org/security/pk11tokendb;1"].createInstance(Ci.nsIPK11TokenDB);
       let token = tokendb.getInternalKeyToken();
--- a/browser/extensions/formautofill/test/unit/test_reconcile.js
+++ b/browser/extensions/formautofill/test/unit/test_reconcile.js
@@ -6,17 +6,17 @@ const TEST_STORE_FILE_NAME = "test-profi
 // parent: What the local record looked like the last time we wrote the
 //         record to the Sync server.
 // local:  What the local record looks like now. IOW, the differences between
 //         'parent' and 'local' are changes recently made which we wish to sync.
 // remote: An incoming record we need to apply (ie, a record that was possibly
 //         changed on a remote device)
 //
 // To further help understanding this, a few of the testcases are annotated.
-const RECONCILE_TESTCASES = [
+const ADDRESS_RECONCILE_TESTCASES = [
   {
     description: "Local change",
     parent: {
       // So when we last wrote the record to the server, it had these values.
       "guid": "2bbd2d8fbc6b",
       "version": 1,
       "given-name": "Mark",
       "family-name": "Hammond",
@@ -459,16 +459,468 @@ const RECONCILE_TESTCASES = [
       "family-name": "Hammond",
       "timeCreated": 1234,
       "timeLastUsed": 5678,
       "timesUsed": 6,
     },
   },
 ];
 
+const CREDIT_CARD_RECONCILE_TESTCASES = [
+  {
+    description: "Local change",
+    parent: {
+      // So when we last wrote the record to the server, it had these values.
+      "guid": "2bbd2d8fbc6b",
+      "version": 1,
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+    },
+    local: [{
+      // The current local record - by comparing against parent we can see that
+      // only the cc-number has changed locally.
+      "cc-name": "John Doe",
+      "cc-number": "4444333322221111",
+    }],
+    remote: {
+      // This is the incoming record. It has the same values as "parent", so
+      // we can deduce the record hasn't actually been changed remotely so we
+      // can safely ignore the incoming record and write our local changes.
+      "guid": "2bbd2d8fbc6b",
+      "version": 1,
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+    },
+    reconciled: {
+      "guid": "2bbd2d8fbc6b",
+      "cc-name": "John Doe",
+      "cc-number": "4444333322221111",
+    },
+  },
+  {
+    description: "Remote change",
+    parent: {
+      "guid": "e3680e9f890d",
+      "version": 1,
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+    },
+    local: [{
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+    }],
+    remote: {
+      "guid": "e3680e9f890d",
+      "version": 1,
+      "cc-name": "John Doe",
+      "cc-number": "4444333322221111",
+    },
+    reconciled: {
+      "guid": "e3680e9f890d",
+      "cc-name": "John Doe",
+      "cc-number": "4444333322221111",
+    },
+  },
+
+  {
+    description: "New local field",
+    parent: {
+      "guid": "0cba738b1be0",
+      "version": 1,
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+    },
+    local: [{
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+      "cc-exp-month": 12,
+    }],
+    remote: {
+      "guid": "0cba738b1be0",
+      "version": 1,
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+    },
+    reconciled: {
+      "guid": "0cba738b1be0",
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+      "cc-exp-month": 12,
+    },
+  },
+  {
+    description: "New remote field",
+    parent: {
+      "guid": "be3ef97f8285",
+      "version": 1,
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+    },
+    local: [{
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+    }],
+    remote: {
+      "guid": "be3ef97f8285",
+      "version": 1,
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+      "cc-exp-month": 12,
+    },
+    reconciled: {
+      "guid": "be3ef97f8285",
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+      "cc-exp-month": 12,
+    },
+  },
+  {
+    description: "Deleted field locally",
+    parent: {
+      "guid": "9627322248ec",
+      "version": 1,
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+      "cc-exp-month": 12,
+    },
+    local: [{
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+    }],
+    remote: {
+      "guid": "9627322248ec",
+      "version": 1,
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+      "cc-exp-month": 12,
+    },
+    reconciled: {
+      "guid": "9627322248ec",
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+    },
+  },
+  {
+    description: "Deleted field remotely",
+    parent: {
+      "guid": "7d7509f3eeb2",
+      "version": 1,
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+      "cc-exp-month": 12,
+    },
+    local: [{
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+      "cc-exp-month": 12,
+    }],
+    remote: {
+      "guid": "7d7509f3eeb2",
+      "version": 1,
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+    },
+    reconciled: {
+      "guid": "7d7509f3eeb2",
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+    },
+  },
+  {
+    description: "Local and remote changes to unrelated fields",
+    parent: {
+      // The last time we wrote this to the server, "cc-exp-month" was 12.
+      "guid": "e087a06dfc57",
+      "version": 1,
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+      "cc-exp-month": 12,
+    },
+    local: [{
+      // The current local record - so locally we've changed "cc-number".
+      "cc-name": "John Doe",
+      "cc-number": "4444333322221111",
+      "cc-exp-month": 12,
+    }],
+    remote: {
+      // Remotely, we've changed "cc-exp-month" to 1.
+      "guid": "e087a06dfc57",
+      "version": 1,
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+      "cc-exp-month": 1,
+    },
+    reconciled: {
+      "guid": "e087a06dfc57",
+      "cc-name": "John Doe",
+      "cc-number": "4444333322221111",
+      "cc-exp-month": 1,
+    },
+  },
+  {
+    description: "Multiple local changes",
+    parent: {
+      "guid": "340a078c596f",
+      "version": 1,
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+    },
+    local: [{
+      "cc-name": "Skip",
+      "cc-number": "1111222233334444",
+    }, {
+      "cc-name": "Skip",
+      "cc-number": "1111222233334444",
+      "cc-exp-month": 12,
+    }],
+    remote: {
+      "guid": "340a078c596f",
+      "version": 1,
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+      "cc-exp-year": 2000,
+    },
+    reconciled: {
+      "guid": "340a078c596f",
+      "cc-name": "Skip",
+      "cc-number": "1111222233334444",
+      "cc-exp-month": 12,
+      "cc-exp-year": 2000,
+    },
+  },
+  {
+    // Local and remote diverged from the shared parent, but the values are the
+    // same, so we shouldn't fork.
+    description: "Same change to local and remote",
+    parent: {
+      "guid": "0b3a72a1bea2",
+      "version": 1,
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+    },
+    local: [{
+      "cc-name": "John Doe",
+      "cc-number": "4444333322221111",
+    }],
+    remote: {
+      "guid": "0b3a72a1bea2",
+      "version": 1,
+      "cc-name": "John Doe",
+      "cc-number": "4444333322221111",
+    },
+    reconciled: {
+      "guid": "0b3a72a1bea2",
+      "cc-name": "John Doe",
+      "cc-number": "4444333322221111",
+    },
+  },
+  {
+    description: "Conflicting changes to single field",
+    parent: {
+      // This is what we last wrote to the sync server.
+      "guid": "62068784d089",
+      "version": 1,
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+    },
+    local: [{
+      // The current version of the local record - the cc-number has changed locally.
+      "cc-name": "John Doe",
+      "cc-number": "1111111111111111",
+    }],
+    remote: {
+      // An incoming record has a different cc-number than any of the above!
+      "guid": "62068784d089",
+      "version": 1,
+      "cc-name": "John Doe",
+      "cc-number": "4444333322221111",
+    },
+    forked: {
+      // So we've forked the local record to a new GUID (and the next sync is
+      // going to write this as a new record)
+      "cc-name": "John Doe",
+      "cc-number": "1111111111111111",
+    },
+    reconciled: {
+      // And we've updated the local version of the record to be the remote version.
+      guid: "62068784d089",
+      "cc-name": "John Doe",
+      "cc-number": "4444333322221111",
+    },
+  },
+  {
+    description: "Conflicting changes to multiple fields",
+    parent: {
+      "guid": "244dbb692e94",
+      "version": 1,
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+      "cc-exp-month": 12,
+    },
+    local: [{
+      "cc-name": "John Doe",
+      "cc-number": "1111111111111111",
+      "cc-exp-month": 1,
+    }],
+    remote: {
+      "guid": "244dbb692e94",
+      "version": 1,
+      "cc-name": "John Doe",
+      "cc-number": "4444333322221111",
+      "cc-exp-month": 3,
+    },
+    forked: {
+      "cc-name": "John Doe",
+      "cc-number": "1111111111111111",
+      "cc-exp-month": 1,
+    },
+    reconciled: {
+      "guid": "244dbb692e94",
+      "cc-name": "John Doe",
+      "cc-number": "4444333322221111",
+      "cc-exp-month": 3,
+    },
+  },
+  {
+    description: "Field deleted locally, changed remotely",
+    parent: {
+      "guid": "6fc45e03d19a",
+      "version": 1,
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+      "cc-exp-month": 12,
+    },
+    local: [{
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+    }],
+    remote: {
+      "guid": "6fc45e03d19a",
+      "version": 1,
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+      "cc-exp-month": 3,
+    },
+    forked: {
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+    },
+    reconciled: {
+      "guid": "6fc45e03d19a",
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+      "cc-exp-month": 3,
+    },
+  },
+  {
+    description: "Field changed locally, deleted remotely",
+    parent: {
+      "guid": "fff9fa27fa18",
+      "version": 1,
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+      "cc-exp-month": 12,
+    },
+    local: [{
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+      "cc-exp-month": 3,
+    }],
+    remote: {
+      "guid": "fff9fa27fa18",
+      "version": 1,
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+    },
+    forked: {
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+      "cc-exp-month": 3,
+    },
+    reconciled: {
+      "guid": "fff9fa27fa18",
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+    },
+  },
+  {
+    // Created, last modified should be synced; last used and times used should
+    // be local. Remote created time older than local, remote modified time
+    // newer than local.
+    description: "Created, last modified time reconciliation without local changes",
+    parent: {
+      "guid": "5113f329c42f",
+      "version": 1,
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+      "timeCreated": 1234,
+      "timeLastModified": 5678,
+      "timeLastUsed": 5678,
+      "timesUsed": 6,
+    },
+    local: [],
+    remote: {
+      "guid": "5113f329c42f",
+      "version": 1,
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+      "timeCreated": 1200,
+      "timeLastModified": 5700,
+      "timeLastUsed": 5700,
+      "timesUsed": 3,
+    },
+    reconciled: {
+      "guid": "5113f329c42f",
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+      "timeCreated": 1200,
+      "timeLastModified": 5700,
+      "timeLastUsed": 5678,
+      "timesUsed": 6,
+    },
+  },
+  {
+    // Local changes, remote created time newer than local, remote modified time
+    // older than local.
+    description: "Created, last modified time reconciliation with local changes",
+    parent: {
+      "guid": "791e5608b80a",
+      "version": 1,
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+      "timeCreated": 1234,
+      "timeLastModified": 5678,
+      "timeLastUsed": 5678,
+      "timesUsed": 6,
+    },
+    local: [{
+      "cc-name": "John Doe",
+      "cc-number": "4444333322221111",
+    }],
+    remote: {
+      "guid": "791e5608b80a",
+      "version": 1,
+      "cc-name": "John Doe",
+      "cc-number": "1111222233334444",
+      "timeCreated": 1300,
+      "timeLastModified": 5000,
+      "timeLastUsed": 5000,
+      "timesUsed": 3,
+    },
+    reconciled: {
+      "guid": "791e5608b80a",
+      "cc-name": "John Doe",
+      "cc-number": "4444333322221111",
+      "timeCreated": 1234,
+      "timeLastUsed": 5678,
+      "timesUsed": 6,
+    },
+  },
+];
+
 add_task(async function test_reconcile_unknown_version() {
   let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME);
 
   // Cross-version reconciliation isn't supported yet. See bug 1377204.
   throws(() => {
     profileStorage.addresses.reconcile({
       "guid": "31d83d2725ec",
       "version": 2,
@@ -531,43 +983,52 @@ add_task(async function test_reconcile_i
       "family-name": "Hammond",
       "organization": "Mozilla",
       "tel": "123456",
     }), "Second merge should not change record");
   }
 });
 
 add_task(async function test_reconcile_three_way_merge() {
-  let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME);
-
-  for (let test of RECONCILE_TESTCASES) {
-    do_print(test.description);
+  let TESTCASES = {
+    addresses: ADDRESS_RECONCILE_TESTCASES,
+    creditCards: CREDIT_CARD_RECONCILE_TESTCASES,
+  };
 
-    profileStorage.addresses.add(test.parent, {sourceSync: true});
+  for (let collectionName in TESTCASES) {
+    do_print(`Start to test reconcile on ${collectionName}`);
 
-    for (let updatedRecord of test.local) {
-      profileStorage.addresses.update(test.parent.guid, updatedRecord);
-    }
+    let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, null, collectionName);
 
-    let localRecord = profileStorage.addresses.get(test.parent.guid, {
-      rawData: true,
-    });
+    for (let test of TESTCASES[collectionName]) {
+      do_print(test.description);
+
+      profileStorage[collectionName].add(test.parent, {sourceSync: true});
 
-    let {forkedGUID} = profileStorage.addresses.reconcile(test.remote);
-    let reconciledRecord = profileStorage.addresses.get(test.parent.guid, {
-      rawData: true,
-    });
-    if (forkedGUID) {
-      let forkedRecord = profileStorage.addresses.get(forkedGUID, {
+      for (let updatedRecord of test.local) {
+        profileStorage[collectionName].update(test.parent.guid, updatedRecord);
+      }
+
+      let localRecord = profileStorage[collectionName].get(test.parent.guid, {
         rawData: true,
       });
 
-      notEqual(forkedRecord.guid, reconciledRecord.guid);
-      equal(forkedRecord.timeLastModified, localRecord.timeLastModified);
-      ok(objectMatches(forkedRecord, test.forked),
-        `${test.description} should fork record`);
-    } else {
-      ok(!test.forked, `${test.description} should not fork record`);
+      let {forkedGUID} = profileStorage[collectionName].reconcile(test.remote);
+      let reconciledRecord = profileStorage[collectionName].get(test.parent.guid, {
+        rawData: true,
+      });
+      if (forkedGUID) {
+        let forkedRecord = profileStorage[collectionName].get(forkedGUID, {
+          rawData: true,
+        });
+
+        notEqual(forkedRecord.guid, reconciledRecord.guid);
+        equal(forkedRecord.timeLastModified, localRecord.timeLastModified);
+        ok(objectMatches(forkedRecord, test.forked),
+          `${test.description} should fork record`);
+      } else {
+        ok(!test.forked, `${test.description} should not fork record`);
+      }
+
+      ok(objectMatches(reconciledRecord, test.reconciled));
     }
-
-    ok(objectMatches(reconciledRecord, test.reconciled));
   }
 });
--- a/browser/extensions/formautofill/test/unit/test_storage_tombstones.js
+++ b/browser/extensions/formautofill/test/unit/test_storage_tombstones.js
@@ -40,19 +40,16 @@ function add_storage_task(test_function)
   add_task(async function() {
     let path = getTempFile(TEST_STORE_FILE_NAME).path;
     let profileStorage = new ProfileStorage(path);
     let testCC1 = Object.assign({}, TEST_CC_1);
     await profileStorage.initialize();
 
     for (let [storage, record] of [[profileStorage.addresses, TEST_ADDRESS_1],
                                    [profileStorage.creditCards, testCC1]]) {
-      if (storage.normalizeCCNumberFields) {
-        await storage.normalizeCCNumberFields(record);
-      }
       await test_function(storage, record);
     }
   });
 }
 
 add_storage_task(async function test_simple_tombstone(storage, record) {
   do_print("check simple tombstone semantics");
 
--- a/browser/extensions/formautofill/test/unit/test_transformFields.js
+++ b/browser/extensions/formautofill/test/unit/test_transformFields.js
@@ -616,19 +616,17 @@ add_task(async function test_normalizeAd
 
 add_task(async function test_computeCreditCardFields() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
 
   let profileStorage = new ProfileStorage(path);
   await profileStorage.initialize();
 
   for (let testcase of CREDIT_CARD_COMPUTE_TESTCASES) {
-    let encryptedCC = Object.assign({}, testcase.creditCard);
-    await profileStorage.creditCards.normalizeCCNumberFields(encryptedCC);
-    profileStorage.creditCards.add(encryptedCC);
+    profileStorage.creditCards.add(testcase.creditCard);
   }
   await profileStorage._saveImmediately();
 
   profileStorage = new ProfileStorage(path);
   await profileStorage.initialize();
 
   let creditCards = profileStorage.creditCards.getAll();
 
@@ -640,19 +638,17 @@ add_task(async function test_computeCred
 
 add_task(async function test_normalizeCreditCardFields() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
 
   let profileStorage = new ProfileStorage(path);
   await profileStorage.initialize();
 
   for (let testcase of CREDIT_CARD_NORMALIZE_TESTCASES) {
-    let encryptedCC = Object.assign({}, testcase.creditCard);
-    await profileStorage.creditCards.normalizeCCNumberFields(encryptedCC);
-    profileStorage.creditCards.add(encryptedCC);
+    profileStorage.creditCards.add(testcase.creditCard);
   }
   await profileStorage._saveImmediately();
 
   profileStorage = new ProfileStorage(path);
   await profileStorage.initialize();
 
   let creditCards = profileStorage.creditCards.getAll();
 
--- a/browser/extensions/onboarding/test/browser/browser_onboarding_uitour.js
+++ b/browser/extensions/onboarding/test/browser/browser_onboarding_uitour.js
@@ -48,22 +48,23 @@ add_task(async function test_clean_up_ui
 
   // Trigger UITour showHighlight
   highlightOpenPromise = promisePopupChange(highlight, "open");
   await triggerUITourHighlight("library", tab);
   await highlightOpenPromise;
   is(highlight.state, "open", "Should show UITour highlight");
   is(highlight.getAttribute("targetName"), "library", "UITour should highlight library");
 
-  // Close the overlay by clicking the overlay close button
+  // Close the overlay by clicking the overlay
+  // Should not click the close button here since the close button is hovered by appmenu and can't be clicked on win7
   highlightClosePromise = promisePopupChange(highlight, "closed");
-  BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-close-btn", {}, tab.linkedBrowser);
+  BrowserTestUtils.synthesizeMouseAtPoint(2, 2, {}, tab.linkedBrowser);
   await promiseOnboardingOverlayClosed(tab.linkedBrowser);
   await highlightClosePromise;
-  is(highlight.state, "closed", "Should close UITour highlight after closing the overlay by clicking the overlay close button");
+  is(highlight.state, "closed", "Should close UITour highlight after closing the overlay by clicking the overlay");
 
   // Trigger UITour showHighlight again
   highlightOpenPromise = promisePopupChange(highlight, "open");
   await triggerUITourHighlight("library", tab);
   await highlightOpenPromise;
   is(highlight.state, "open", "Should show UITour highlight");
   is(highlight.getAttribute("targetName"), "library", "UITour should highlight library");
 
--- a/browser/locales/search/list.json
+++ b/browser/locales/search/list.json
@@ -778,14 +778,14 @@
         "visibleDefaultEngines": [
           "baidu", "google", "bing", "ddg", "wikipedia-zh-CN", "amazondotcn"
         ]
       }
     },
     "zh-TW": {
       "default": {
         "visibleDefaultEngines": [
-          "yahoo-zh-TW", "google", "ddg", "findbook-zh-TW", "wikipedia-zh-TW", "yahoo-zh-TW-HK", "yahoo-bid-zh-TW", "yahoo-answer-zh-TW"
+          "yahoo-zh-TW", "google", "ddg", "wikipedia-zh-TW", "yahoo-zh-TW-HK", "yahoo-bid-zh-TW", "yahoo-answer-zh-TW"
         ]
       }
     }
   }
 }
deleted file mode 100644
--- a/browser/locales/searchplugins/findbook-zh-TW.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<!-- 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/. -->
-
-<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
-<ShortName>Findbook</ShortName>
-<Description>Findbook 書籍搜尋</Description>
-<InputEncoding>UTF-8</InputEncoding>
-<Image width="16" height="16">data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAQAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAArHEA/6xxAP+scQD/rHEA/6xxAP+scQD/rHEA/6xxAP+scQD/rHEA/6xxAP+scQD/rHEA/7a2tv8AAAAArHEA/LZ3AP/LhgT/1YwF/9SMBP/UjAT/1IwE/9SMBP/UjAT/1IwE/9SMBf/UjAX/1IwF/6xxAP+2trb/ypMqDqxxAP+2eAD/y4YC/9OMBf/QgQD/9uvV////////////uXAA/9WMBP/UjAX/1IwF/9SMBf+scQD/tra2/61zAuKscQD/tngA/8uGAv/UjAX/z4EA//Xmyv///////////7pwAP/VjAT/1IwF/9SMBf/UjAX/rHEA/7a2tv+scQD/rHEA/7Z4AP/LhgT/1IwF/8+BAP/15sr///////////+6cAD/1YwE/9SMBf/UjAX/1IwF/6xxAP+2trb/rHEA/6xxAP+2eAD/y4YC/9OMBf/QgQD/9ebK////////////tGQA/9GCAP/PgAD/0YQA/9SMBf+scQD/tra2/6xxAP+scQD/tngA/8uGBP/UjAX/z4EA//Xmyv/////////////////////////////////QggD/rHEA/7a2tv+scQD/rHEA/7Z4AP/LhgL/1IwF/9CBAP/15sr///////////+5hCL/yJg5/8iYO//BjjD/04kA/6xxAP+2trb/rHEA/6xxAP+2eAD/y4YE/9SMBf/OgQD/9ebK////////////um0A/9aKAP/UiQD/1YoA/9SLA/+scQD/tra2/6xxAP+scQD/tngA/8uGBP/UjAX/0IEA//Xmyf///////////7pwAP/VjAT/1IwF/9SLA//Tiwb/rHEA/7a2tv+scQD/rHEA/7Z4AP/LhgL/1IwE/859AP/15sr///////////+1ZwD/04YA/9GEAP/RgAD/zKVa/6xxAP+2trb/rHEA/6xxAP+2eAD/y4YB/9WMBf////////////////////////////////////////////////+scQD/tra2/6xxAP+scQD/tXUA/8uDAP/SkRj/yJc5/8iXOf/Ilzn/yJc5/8iWN//Ilzn/x5c5/8iXOf/Ilzn/rHEA/7a2tv+scQD/rHEA/7B2CP+scQD/rHEA/6xxAP+scQD/rHEA/6xxAP+scQD/rHEA/6xxAP+scQD/rHEA/61xAP+2trb/rHEA/6xyAfvp+///6PD//+z2///w+///9f////v//////////////////////////////////////////////6xxAP8AAAAArncM8axxAP+scQD/rHEA/6xxAP+scQD/rHEA/6xxAP+scQD/rHEA/6xxAP+scQD/rHEA/691B/mtcwPugAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAA%3D%3D</Image>
-<Url type="application/x-suggestions+json" method="GET" template="http://findbook.tw/search/suggest?q={searchTerms}&amp;utm_source=ff-bundled&amp;utm_medium=mozsearch&amp;utm_campaign=search" />
-<Url type="text/html" method="GET" template="http://findbook.tw/search" resultdomain="findbook.tw">
-	<Param name="q" value="{searchTerms}"/>
-	<Param name="utm_source" value="ff-bundled"/>
-	<Param name="utm_medium" value="mozsearch"/>
-	<Param name="utm_campaign" value="search"/>
-</Url>
-<SearchForm>http://findbook.tw/search</SearchForm>
-</SearchPlugin>
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -127,37 +127,30 @@
 }
 
 /* Draw the bottom border of the tabs toolbar when it's not using
    -moz-appearance: toolbar. */
 #main-window:-moz-any([sizemode="fullscreen"],[customize-entered]) #TabsToolbar:not([collapsed="true"]) + #nav-bar,
 #main-window:not([tabsintitlebar]) #TabsToolbar:not([collapsed="true"]) + #nav-bar,
 #TabsToolbar:not([collapsed="true"]) + #nav-bar:-moz-lwtheme {
   box-shadow: 0 calc(-1 * var(--tab-toolbar-navbar-overlap)) 0 var(--tabs-border);
-  /* Position the toolbar above the bottom of background tabs */
-  position: relative;
-  z-index: 1;
 }
 
 /* Always draw a border on Yosemite to ensure the border is well-defined there
  * (the default border is too light). */
 @media (-moz-mac-yosemite-theme) {
   :root:not(:-moz-lwtheme) {
     --tabs-border: rgba(0,0,0,.2);
   }
   :root:not(:-moz-lwtheme):-moz-window-inactive {
     --tabs-border: rgba(0,0,0,.05);
   }
 
   #main-window[tabsintitlebar] #TabsToolbar:not([collapsed="true"]) + #nav-bar:not(:-moz-lwtheme) {
-    border-top: 1px solid var(--tabs-border);
-    background-clip: padding-box;
-    /* Position the toolbar above the bottom of background tabs */
-    position: relative;
-    z-index: 1;
+    box-shadow: 0 calc(-1 * var(--tab-toolbar-navbar-overlap)) 0 var(--tabs-border);
   }
 }
 
 #TabsToolbar:not([collapsed="true"]) + #nav-bar {
   /* The toolbar buttons that animate are only visible when the #TabsToolbar is not collapsed.
      The animations use position:absolute and require a positioned #nav-bar. */
   position: relative;
 }
--- a/browser/themes/shared/incontent-icons/tab-crashed.svg
+++ b/browser/themes/shared/incontent-icons/tab-crashed.svg
@@ -1,13 +1,13 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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/. -->
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60">
-  <defs>
-    <linearGradient id="gradient" gradientUnits="userSpaceOnUse" x1="30" y1="12.85" x2="30" y2="47.15">
-      <stop offset="0" style="stop-color: #e63b2e"/>
-      <stop offset="1" style="stop-color: #c33931"/>
-    </linearGradient>
-  </defs>
-  <path fill-rule="evenodd" clip-rule="evenodd" fill="url(#gradient)" d="M49.048,17.648H29.004 c-2.289-0.016-2.809-1.142-3.165-2.401c-0.359-1.269-1.076-2.397-3.229-2.397c-5.775,0-5.42,0-6.167,0 c-2.153,0-2.87,1.127-3.229,2.397c-0.359,1.269-0.882,2.403-3.214,2.403h0.94c-0.519,0.008-0.937,0.433-0.937,0.958v27.583 c0,0.53,0.426,0.959,0.952,0.959h38.093c0.526,0,0.952-0.429,0.952-0.959V18.607C50,18.077,49.574,17.648,49.048,17.648z M18.441,27.932c0-2.119,1.705-3.837,3.809-3.837c2.103,0,3.809,1.718,3.809,3.837c0,2.119-1.705,3.837-3.809,3.837 C20.146,31.769,18.441,30.051,18.441,27.932z M36.717,41.83c-1.525,0-1.525-2.305-6.864-2.305c-5.339,0-5.339,2.305-6.864,2.305 c-0.842,0-1.526-0.512-1.526-1.537c0-1.024,1.271-3.842,8.39-3.842c7.119,0,8.39,2.804,8.39,3.842 C38.243,41.331,37.56,41.83,36.717,41.83z M37.485,31.769c-2.104,0-3.809-1.718-3.809-3.837c0-2.119,1.705-3.837,3.809-3.837 c2.104,0,3.809,1.718,3.809,3.837C41.294,30.051,39.588,31.769,37.485,31.769z"/>
-</svg>
+<?xml version="1.0"?>
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40">
+  <defs>
+    <linearGradient id="gradient" gradientUnits="userSpaceOnUse" x1="20" y1="2.85" x2="20" y2="37.15">
+      <stop offset="0" stop-color="#e63b2e"/>
+      <stop offset="1" stop-color="#c33931"/>
+    </linearGradient>
+  </defs>
+  <path fill-rule="evenodd" clip-rule="evenodd" fill="url(#gradient)" d="M39.048,6.798H19.004 C16.715,6.782 16.195,5.656 15.839,4.397 15.48,3.128 14.763,2 12.61,2 6.835,2 7.1899998,2 6.443,2 4.29,2 3.573,3.127 3.214,4.397 2.855,5.666 2.332,6.8 0,6.8H0.94 C0.421,6.808 0.003,7.233 0.003,7.758v27.583c0,0.53 0.426,0.959 0.952,0.959H39.048 C39.574,36.3 40,35.871 40,35.341 V 7.757 C40,7.227 39.574,6.798 39.048,6.798 Z M8.4409998,17.082c0,-2.119 1.7050002,-3.837 3.8090002,-3.837 2.103,0 3.809,1.718 3.809,3.837 0,2.119 -1.705,3.837 -3.809,3.837 -2.104,0 -3.8090002,-1.718 -3.8090002,-3.837z M26.717,30.98c-1.525,0 -1.525,-2.305 -6.864,-2.305 -5.339,0 -5.339,2.305 -6.864,2.305 -0.842,0 -1.526,-0.512 -1.526,-1.537 0,-1.024 1.271,-3.842 8.39,-3.842 7.119,0 8.39,2.804 8.39,3.842 0,1.038 -0.683,1.537 -1.526,1.537z m 0.768,-10.061c-2.104,0 -3.809,-1.718 -3.809,-3.837 0,-2.119 1.705,-3.837 3.809,-3.837 2.104,0 3.809,1.718 3.809,3.837 0,2.119 -1.706,3.837 -3.809,3.837z"/>
+</svg>
--- a/devtools/client/jsonview/components/JsonPanel.js
+++ b/devtools/client/jsonview/components/JsonPanel.js
@@ -3,29 +3,26 @@
 /* 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";
 
 define(function (require, exports, module) {
   const { DOM: dom, createFactory, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
-  const TreeViewClass = require("devtools/client/shared/components/tree/TreeView");
-  const TreeView = createFactory(TreeViewClass);
+  const TreeView = createFactory(require("devtools/client/shared/components/tree/TreeView"));
 
   const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
   const { createFactories } = require("devtools/client/shared/react-utils");
   const { Rep } = REPS;
 
   const { SearchBox } = createFactories(require("./SearchBox"));
   const { Toolbar, ToolbarButton } = createFactories(require("./reps/Toolbar"));
 
   const { div } = dom;
-  const AUTO_EXPAND_MAX_SIZE = 100 * 1024;
-  const AUTO_EXPAND_MAX_LEVEL = 7;
 
   function isObject(value) {
     return Object(value) === value;
   }
 
   /**
    * This template represents the 'JSON' panel. The panel is
    * responsible for rendering an expandable tree that allows simple
@@ -37,17 +34,17 @@ define(function (require, exports, modul
     propTypes: {
       data: PropTypes.oneOfType([
         PropTypes.string,
         PropTypes.array,
         PropTypes.object,
         PropTypes.bool,
         PropTypes.number
       ]),
-      jsonTextLength: PropTypes.number,
+      expandedNodes: PropTypes.instanceOf(Set),
       searchFilter: PropTypes.string,
       actions: PropTypes.object,
     },
 
     getInitialState: function () {
       return {};
     },
 
@@ -91,33 +88,24 @@ define(function (require, exports, modul
     renderTree: function () {
       // Append custom column for displaying values. This column
       // Take all available horizontal space.
       let columns = [{
         id: "value",
         width: "100%"
       }];
 
-      // Expand the document by default if its size isn't bigger than 100KB.
-      let expandedNodes = new Set();
-      if (this.props.jsonTextLength <= AUTO_EXPAND_MAX_SIZE) {
-        expandedNodes = TreeViewClass.getExpandedNodes(
-          this.props.data,
-          {maxLevel: AUTO_EXPAND_MAX_LEVEL}
-        );
-      }
-
       // Render tree component.
       return TreeView({
         object: this.props.data,
         mode: MODE.TINY,
         onFilter: this.onFilter,
         columns: columns,
         renderValue: this.renderValue,
-        expandedNodes: expandedNodes,
+        expandedNodes: this.props.expandedNodes,
       });
     },
 
     render: function () {
       let content;
       let data = this.props.data;
 
       if (!isObject(data)) {
--- a/devtools/client/jsonview/components/MainTabbedArea.js
+++ b/devtools/client/jsonview/components/MainTabbedArea.js
@@ -29,17 +29,18 @@ define(function (require, exports, modul
       headers: PropTypes.object,
       searchFilter: PropTypes.string,
       json: PropTypes.oneOfType([
         PropTypes.string,
         PropTypes.object,
         PropTypes.array,
         PropTypes.bool,
         PropTypes.number
-      ])
+      ]),
+      expandedNodes: PropTypes.instanceOf(Set),
     },
 
     getInitialState: function () {
       return {
         json: {},
         headers: {},
         jsonText: this.props.jsonText,
         tabActive: this.props.tabActive
@@ -55,17 +56,17 @@ define(function (require, exports, modul
         Tabs({
           tabActive: this.state.tabActive,
           onAfterChange: this.onTabChanged},
           TabPanel({
             className: "json",
             title: JSONView.Locale.$STR("jsonViewer.tab.JSON")},
             JsonPanel({
               data: this.props.json,
-              jsonTextLength: this.props.jsonText.length,
+              expandedNodes: this.props.expandedNodes,
               actions: this.props.actions,
               searchFilter: this.state.searchFilter
             })
           ),
           TabPanel({
             className: "rawdata",
             title: JSONView.Locale.$STR("jsonViewer.tab.RawData")},
             TextPanel({
--- a/devtools/client/jsonview/converter-child.js
+++ b/devtools/client/jsonview/converter-child.js
@@ -196,34 +196,33 @@ Converter.prototype = {
     this.listener.onDataAvailable(request, context, stream, 0, stream.available());
   }
 };
 
 // Lets "save as" save the original JSON, not the viewer.
 // To save with the proper extension we need the original content type,
 // which has been replaced by application/vnd.mozilla.json.view
 function fixSave(request) {
-  let originalType;
+  let match;
   if (request instanceof Ci.nsIHttpChannel) {
     try {
       let header = request.getResponseHeader("Content-Type");
-      originalType = header.split(";")[0];
+      match = header.match(/^(application\/(?:[^;]+\+)?json)(?:;|$)/);
     } catch (err) {
       // Handled below
     }
   } else {
     let uri = request.QueryInterface(Ci.nsIChannel).URI.spec;
-    let match = uri.match(/^data:(.*?)[,;]/);
-    if (match) {
-      originalType = match[1];
-    }
+    match = uri.match(/^data:(application\/(?:[^;,]+\+)?json)[;,]/);
   }
-  const JSON_TYPES = ["application/json", "application/manifest+json"];
-  if (!JSON_TYPES.includes(originalType)) {
-    originalType = JSON_TYPES[0];
+  let originalType;
+  if (match) {
+    originalType = match[1];
+  } else {
+    originalType = "application/json";
   }
   request.QueryInterface(Ci.nsIWritablePropertyBag);
   request.setProperty("contentType", originalType);
 }
 
 // Exports variables that will be accessed by the non-privileged scripts.
 function exportData(win, request) {
   let data = Cu.createObjectIn(win, {
--- a/devtools/client/jsonview/converter-observer.js
+++ b/devtools/client/jsonview/converter-observer.js
@@ -85,18 +85,17 @@ JsonViewSniffer.prototype = {
         }
       } catch (e) {
         // Channel doesn't support content dispositions
       }
 
       // Check the response content type and if it's a valid type
       // such as application/json or application/manifest+json
       // change it to new internal type consumed by JSON View.
-      const JSON_TYPES = ["application/json", "application/manifest+json"];
-      if (JSON_TYPES.includes(request.contentType)) {
+      if (/^application\/(?:.+\+)?json$/.test(request.contentType)) {
         return JSON_VIEW_MIME_TYPE;
       }
     }
 
     return "";
   }
 };
 
--- a/devtools/client/jsonview/json-viewer.js
+++ b/devtools/client/jsonview/json-viewer.js
@@ -5,18 +5,21 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 define(function (require, exports, module) {
   const { render } = require("devtools/client/shared/vendor/react-dom");
   const { createFactories } = require("devtools/client/shared/react-utils");
   const { MainTabbedArea } = createFactories(require("./components/MainTabbedArea"));
+  const TreeViewClass = require("devtools/client/shared/components/tree/TreeView");
 
   const json = document.getElementById("json");
+  const AUTO_EXPAND_MAX_SIZE = 100 * 1024;
+  const AUTO_EXPAND_MAX_LEVEL = 7;
 
   let prettyURL;
 
   // Application state object.
   let input = {
     jsonText: json.textContent,
     jsonPretty: null,
     headers: JSONView.headers,
@@ -30,16 +33,26 @@ define(function (require, exports, modul
   }
 
   try {
     input.json = JSON.parse(input.jsonText);
   } catch (err) {
     input.json = err;
   }
 
+  // Expand the document by default if its size isn't bigger than 100KB.
+  if (!(input.json instanceof Error) && input.jsonText.length <= AUTO_EXPAND_MAX_SIZE) {
+    input.expandedNodes = TreeViewClass.getExpandedNodes(
+      input.json,
+      {maxLevel: AUTO_EXPAND_MAX_LEVEL}
+    );
+  } else {
+    input.expandedNodes = new Set();
+  }
+
   json.remove();
 
   /**
    * Application actions/commands. This list implements all commands
    * available for the JSON viewer.
    */
   input.actions = {
     onCopyJson: function () {
--- a/devtools/client/jsonview/test/browser.ini
+++ b/devtools/client/jsonview/test/browser.ini
@@ -19,16 +19,17 @@ support-files =
   valid_json.json
   valid_json.json^headers^
   !/devtools/client/commandline/test/head.js
   !/devtools/client/framework/test/head.js
   !/devtools/client/framework/test/shared-head.js
 
 [browser_jsonview_bug_1380828.js]
 [browser_jsonview_ignore_charset.js]
+[browser_jsonview_content_type.js]
 [browser_jsonview_copy_headers.js]
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_jsonview_copy_json.js]
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_jsonview_copy_rawdata.js]
 subsuite = clipboard
new file mode 100644
--- /dev/null
+++ b/devtools/client/jsonview/test/browser_jsonview_content_type.js
@@ -0,0 +1,80 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const mimeSvc = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
+const handlerSvc = Cc["@mozilla.org/uriloader/handler-service;1"]
+                     .getService(Ci.nsIHandlerService);
+
+let contentTypes = {
+  valid: [
+    "application/json",
+    "application/manifest+json",
+    "application/vnd.api+json",
+    "application/hal+json",
+    "application/json+json",
+    "application/whatever+json",
+  ],
+  invalid: [
+    "text/json",
+    "text/hal+json",
+    "application/jsona",
+    "application/whatever+jsona",
+  ],
+};
+
+add_task(function* () {
+  info("Test JSON content types started");
+
+  // Prevent saving files to disk.
+  let useDownloadDir = SpecialPowers.getBoolPref("browser.download.useDownloadDir");
+  SpecialPowers.setBoolPref("browser.download.useDownloadDir", false);
+  let { MockFilePicker } = SpecialPowers;
+  MockFilePicker.init(window);
+  MockFilePicker.returnValue = MockFilePicker.returnCancel;
+
+  for (let kind of Object.keys(contentTypes)) {
+    let isValid = kind === "valid";
+    for (let type of contentTypes[kind]) {
+      // Prevent "Open or Save" dialogs, which would make the test fail.
+      let mimeInfo = mimeSvc.getFromTypeAndExtension(type, null);
+      let exists = handlerSvc.exists(mimeInfo);
+      let {alwaysAskBeforeHandling} = mimeInfo;
+      mimeInfo.alwaysAskBeforeHandling = false;
+      handlerSvc.store(mimeInfo);
+
+      yield testType(isValid, type);
+      yield testType(isValid, type, ";foo=bar+json");
+
+      // Restore old nsIMIMEInfo
+      if (exists) {
+        Object.assign(mimeInfo, {alwaysAskBeforeHandling});
+        handlerSvc.store(mimeInfo);
+      } else {
+        handlerSvc.remove(mimeInfo);
+      }
+    }
+  }
+
+  // Restore old pref
+  registerCleanupFunction(function () {
+    MockFilePicker.cleanup();
+    SpecialPowers.setBoolPref("browser.download.useDownloadDir", useDownloadDir);
+  });
+});
+
+function testType(isValid, type, params = "") {
+  const TEST_JSON_URL = "data:" + type + params + ",[1,2,3]";
+  return addJsonViewTab(TEST_JSON_URL).then(async function () {
+    ok(isValid, "The JSON Viewer should only load for valid content types.");
+    is(await evalInContent("document.contentType"), type, "Got the right content type");
+
+    let count = await getElementCount(".jsonPanelBox .treeTable .treeRow");
+    is(count, 3, "There must be expected number of rows");
+  }, function () {
+    ok(!isValid, "The JSON Viewer should only not load for invalid content types.");
+  });
+}
--- a/devtools/client/jsonview/test/browser_jsonview_valid_json.js
+++ b/devtools/client/jsonview/test/browser_jsonview_valid_json.js
@@ -26,13 +26,18 @@ add_task(function* () {
 
   // Clicking the value does not collapse it (so that it can be selected and copied).
   yield clickJsonNode(".jsonPanelBox .treeTable .treeValueCell");
   is(yield countRows(), 3, "There must still be three rows");
 
   // Clicking the label collapses the auto-expanded node.
   yield clickJsonNode(".jsonPanelBox .treeTable .treeLabel");
   is(yield countRows(), 1, "There must be one row");
+
+  // Collapsed nodes are preserved when switching panels.
+  yield selectJsonViewContentTab("headers");
+  yield selectJsonViewContentTab("json");
+  is(yield countRows(), 1, "There must still be one row");
 });
 
 function countRows() {
   return getElementCount(".jsonPanelBox .treeTable .treeRow");
 }
--- a/devtools/client/jsonview/test/doc_frame_script.js
+++ b/devtools/client/jsonview/test/doc_frame_script.js
@@ -109,8 +109,13 @@ addMessageListener("Test:JsonView:WaitFo
           break;
         }
       }
     }
   });
 
   observer.observe(firstRow, { attributes: true });
 });
+
+addMessageListener("Test:JsonView:Eval", function (msg) {
+  let result = content.eval(msg.data.code);
+  sendAsyncMessage(msg.name, {result});
+});
--- a/devtools/client/jsonview/test/head.js
+++ b/devtools/client/jsonview/test/head.js
@@ -42,16 +42,22 @@ function addJsonViewTab(url, timeout = -
     // Load devtools/shared/frame-script-utils.js
     getFrameScript();
 
     // Load frame script with helpers for JSON View tests.
     let rootDir = getRootDirectory(gTestPath);
     let frameScriptUrl = rootDir + "doc_frame_script.js";
     browser.messageManager.loadFrameScript(frameScriptUrl, false);
 
+    // Check if there is a JSONView object.
+    if (!content.window.wrappedJSObject.JSONView) {
+      deferred.reject("JSON Viewer did not load.");
+      return;
+    }
+
     // Resolve if the JSONView is fully loaded or wait
     // for an initialization event.
     if (content.window.wrappedJSObject.JSONView.initialized) {
       deferred.resolve(tab);
     } else {
       waitForContentMessage("Test:JsonView:JSONViewInitialized").then(() => {
         deferred.resolve(tab);
       });
@@ -166,8 +172,13 @@ function waitForTime(delay) {
 
 function waitForFilter() {
   return executeInContent("Test:JsonView:WaitForFilter");
 }
 
 function normalizeNewLines(value) {
   return value.replace("(\r\n|\n)", "\n");
 }
+
+function evalInContent(code) {
+  return executeInContent("Test:JsonView:Eval", {code})
+  .then(result => result.result);
+}
--- a/devtools/client/netmonitor/configs/development.json
+++ b/devtools/client/netmonitor/configs/development.json
@@ -19,17 +19,18 @@
   },
   "node": {
     "debug": true,
     "host": "localhost",
     "port": 9229
   },
   "firefox": {
     "webSocketConnection": false,
-    "proxyHost": "localhost:9000",
-    "webSocketHost": "localhost:6080",
+    "host": "localhost",
+    "webSocketPort": 8116,
+    "tcpPort": 6080,
     "mcPath": "./firefox"
   },
   "development": {
     "serverPort": 8000,
     "examplesPort": 7999
   }
 }
--- a/devtools/client/netmonitor/package.json
+++ b/devtools/client/netmonitor/package.json
@@ -4,28 +4,27 @@
   "engines": {
     "node": ">=6.9.0"
   },
   "description": "Network monitor in developer tools",
   "dependencies": {
     "codemirror": "^5.24.2",
     "devtools-config": "=0.0.12",
     "devtools-contextmenu": "=0.0.3",
-    "devtools-launchpad": "=0.0.96",
-    "devtools-modules": "=0.0.31",
+    "devtools-launchpad": "=0.0.103",
+    "devtools-modules": "=0.0.32",
     "devtools-source-editor": "=0.0.3",
     "immutable": "^3.8.1",
     "jszip": "^3.1.3",
-    "react": "=15.3.2",
-    "react-dom": "=15.3.2",
+    "react": "=15.6.1",
+    "react-dom": "=15.6.1",
     "react-redux": "=5.0.3",
     "redux": "^3.6.0",
     "reselect": "^2.5.4"
   },
   "devDependencies": {
-    "babel-preset-es2015": "^6.6.0",
     "babel-register": "^6.24.0",
     "file-loader": "^0.10.1"
   },
   "scripts": {
     "start": "node bin/dev-server"
   }
 }
--- a/devtools/client/netmonitor/webpack.config.js
+++ b/devtools/client/netmonitor/webpack.config.js
@@ -19,58 +19,56 @@ let webpackConfig = {
 
   module: {
     rules: [
       {
         test: /\.(png|svg)$/,
         loader: "file-loader?name=[path][name].[ext]",
       },
       {
-        /*
-         * The version of webpack used in the launchpad seems to have trouble
-         * with the require("raw!${file}") that we use for the properties
-         * file in l10.js.
-         * This loader goes through the whole code and remove the "raw!" prefix
-         * so the raw-loader declared in devtools-launchpad config can load
-         * those files.
-         */
-        test: /\.js$/,
-        loader: "rewrite-raw",
-      },
-      {
         test: /\.js$/,
         loaders: [
+          /**
+            * The version of webpack used in the launchpad seems to have trouble
+            * with the require("raw!${file}") that we use for the properties
+            * file in l10.js.
+            * This loader goes through the whole code and remove the "raw!" prefix
+            * so the raw-loader declared in devtools-launchpad config can load
+            * those files.
+            */
+          "rewrite-raw",
           // Replace all references to this.browserRequire() by require()
           "rewrite-browser-require",
           // Replace all references to loader.lazyRequire() by require()
           "rewrite-lazy-require",
         ],
       }
     ]
   },
 
   resolveLoader: {
     modules: [
-      path.resolve("./node_modules"),
+      "node_modules",
       path.resolve("../shared/webpack"),
     ]
   },
 
   output: {
     path: path.join(__dirname, "assets/build"),
     filename: "[name].js",
     publicPath: "/assets/build",
     libraryTarget: "umd",
   },
 
   resolve: {
     modules: [
       // Make sure webpack is always looking for modules in
-      // `webconsole/node_modules` directory first.
-      path.resolve(__dirname, "node_modules"), "node_modules"
+      // `netmonitor/node_modules` directory first.
+      path.resolve(__dirname, "node_modules"),
+      "node_modules",
     ],
     alias: {
       "Services": "devtools-modules/src/Services",
       "react": path.join(__dirname, "node_modules/react"),
 
       "devtools/client/framework/devtools": path.join(__dirname, "../../client/shared/webpack/shims/framework-devtools-shim"),
       "devtools/client/framework/menu": "devtools-modules/src/menu",
       "devtools/client/netmonitor/src/utils/menu": "devtools-contextmenu",
@@ -133,14 +131,27 @@ const basePath = path.join(__dirname, ".
 const baseName = path.basename(__dirname);
 
 let config = toolboxConfig(webpackConfig, getConfig(), {
   // Exclude to transpile all scripts in devtools/ but not for this folder
   babelExcludes: new RegExp(`^${basePath}(.(?!${baseName}))*$`)
 });
 
 // Remove loaders from devtools-launchpad's webpack.config.js
-// * For svg-inline loader:
-//   Netmonitor uses file loader to bundle image assets instead of svg-inline-loader
-config.module.rules = config.module.rules
-  .filter((rule) => !["svg-inline-loader"].includes(rule.loader));
+// For svg-inline loader:
+// Using file loader to bundle image assets instead of svg-inline-loader
+config.module.rules = config.module.rules.filter((rule) => !["svg-inline-loader"].includes(rule.loader));
+
+// For PostCSS loader:
+// Disable PostCSS loader
+config.module.rules.forEach(rule => {
+  if (Array.isArray(rule.use)) {
+    rule.use.some((use, idx) => {
+      if (use.loader === "postcss-loader") {
+        rule.use = rule.use.slice(0, idx);
+        return true;
+      }
+      return false;
+    });
+  }
+});
 
 module.exports = config;
--- a/devtools/client/netmonitor/yarn.lock
+++ b/devtools/client/netmonitor/yarn.lock
@@ -24,39 +24,29 @@ acorn-dynamic-import@^2.0.0:
     acorn "^4.0.3"
 
 acorn-globals@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-3.1.0.tgz#fd8270f71fbb4996b004fa880ee5d46573a731bf"
   dependencies:
     acorn "^4.0.4"
 
-acorn-jsx@^3.0.0:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b"
-  dependencies:
-    acorn "^3.0.4"
-
-acorn@^3.0.4:
-  version "3.3.0"
-  resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a"
-
 acorn@^4.0.3, acorn@^4.0.4:
   version "4.0.11"
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.11.tgz#edcda3bd937e7556410d42ed5860f67399c794c0"
 
-acorn@^5.0.0, acorn@^5.0.1:
+acorn@^5.0.0:
   version "5.0.3"
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.0.3.tgz#c460df08491463f028ccb82eab3730bf01087b3d"
 
 adm-zip@0.4.7, adm-zip@^0.4.7:
   version "0.4.7"
   resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.7.tgz#8606c2cbf1c426ce8c8ec00174447fd49b6eafc1"
 
-ajv-keywords@^1.0.0, ajv-keywords@^1.1.1:
+ajv-keywords@^1.1.1:
   version "1.5.1"
   resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c"
 
 ajv-keywords@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.0.tgz#a296e17f7bfae7c1ce4f7e0de53d29cb32162df0"
 
 ajv@^4.7.0, ajv@^4.9.1:
@@ -90,17 +80,17 @@ alphanum-sort@^1.0.1, alphanum-sort@^1.0
 amd-loader@0.0.8:
   version "0.0.8"
   resolved "https://registry.yarnpkg.com/amd-loader/-/amd-loader-0.0.8.tgz#1022928040e567e8e6a6fb58bda052f819189206"
 
 amdefine@>=0.0.4:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
 
-ansi-escapes@^1.1.0, ansi-escapes@^1.4.0:
+ansi-escapes@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e"
 
 ansi-html@0.0.7:
   version "0.0.7"
   resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e"
 
 ansi-regex@^2.0.0:
@@ -166,37 +156,20 @@ arr-flatten@^1.0.1:
 array-equal@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93"
 
 array-flatten@1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
 
-array-union@^1.0.1:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39"
-  dependencies:
-    array-uniq "^1.0.1"
-
-array-uniq@^1.0.1:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6"
-
 array-unique@^0.2.1:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53"
 
-array.prototype.find@^2.0.1:
-  version "2.0.4"
-  resolved "https://registry.yarnpkg.com/array.prototype.find/-/array.prototype.find-2.0.4.tgz#556a5c5362c08648323ddaeb9de9d14bc1864c90"
-  dependencies:
-    define-properties "^1.1.2"
-    es-abstract "^1.7.0"
-
 arrify@^1.0.0, arrify@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
 
 asap@~2.0.3:
   version "2.0.5"
   resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.5.tgz#522765b50c3510490e52d7dcfe085ef9ba96958f"
 
@@ -300,17 +273,17 @@ babel-cli@^6.7.5:
     output-file-sync "^1.1.0"
     path-is-absolute "^1.0.0"
     slash "^1.0.0"
     source-map "^0.5.0"
     v8flags "^2.0.10"
   optionalDependencies:
     chokidar "^1.6.1"
 
-babel-code-frame@^6.11.0, babel-code-frame@^6.16.0, babel-code-frame@^6.22.0:
+babel-code-frame@^6.11.0, babel-code-frame@^6.22.0:
   version "6.22.0"
   resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.22.0.tgz#027620bee567a88c32561574e7fd0801d33118e4"
   dependencies:
     chalk "^1.1.0"
     esutils "^2.0.2"
     js-tokens "^3.0.0"
 
 babel-code-frame@^6.26.0:
@@ -416,25 +389,16 @@ babel-helper-call-delegate@^6.24.1:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz#ece6aacddc76e41c3461f88bfc575bd0daa2df8d"
   dependencies:
     babel-helper-hoist-variables "^6.24.1"
     babel-runtime "^6.22.0"
     babel-traverse "^6.24.1"
     babel-types "^6.24.1"
 
-babel-helper-define-map@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-define-map/-/babel-helper-define-map-6.24.1.tgz#7a9747f258d8947d32d515f6aa1c7bd02204a080"
-  dependencies:
-    babel-helper-function-name "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-types "^6.24.1"
-    lodash "^4.2.0"
-
 babel-helper-explode-assignable-expression@^6.24.1:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz#f25b82cf7dc10433c55f70592d5746400ac22caa"
   dependencies:
     babel-runtime "^6.22.0"
     babel-traverse "^6.24.1"
     babel-types "^6.24.1"
 
@@ -457,52 +421,26 @@ babel-helper-get-function-arity@^6.24.1:
 
 babel-helper-hoist-variables@^6.24.1:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz#1ecb27689c9d25513eadbc9914a73f5408be7a76"
   dependencies:
     babel-runtime "^6.22.0"
     babel-types "^6.24.1"
 
-babel-helper-optimise-call-expression@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz#f7a13427ba9f73f8f4fa993c54a97882d1244257"
-  dependencies:
-    babel-runtime "^6.22.0"
-    babel-types "^6.24.1"
-
-babel-helper-regex@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-regex/-/babel-helper-regex-6.24.1.tgz#d36e22fab1008d79d88648e32116868128456ce8"
-  dependencies:
-    babel-runtime "^6.22.0"
-    babel-types "^6.24.1"
-    lodash "^4.2.0"
-
 babel-helper-remap-async-to-generator@^6.24.1:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz#5ec581827ad723fecdd381f1c928390676e4551b"
   dependencies:
     babel-helper-function-name "^6.24.1"
     babel-runtime "^6.22.0"
     babel-template "^6.24.1"
     babel-traverse "^6.24.1"
     babel-types "^6.24.1"
 
-babel-helper-replace-supers@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz#bf6dbfe43938d17369a213ca8a8bf74b6a90ab1a"
-  dependencies:
-    babel-helper-optimise-call-expression "^6.24.1"
-    babel-messages "^6.23.0"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-    babel-traverse "^6.24.1"
-    babel-types "^6.24.1"
-
 babel-helpers@^6.24.1:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2"
   dependencies:
     babel-runtime "^6.22.0"
     babel-template "^6.24.1"
 
 babel-jest@^19.0.0:
@@ -522,22 +460,16 @@ babel-loader@^7.1.1:
     mkdirp "^0.5.1"
 
 babel-messages@^6.23.0:
   version "6.23.0"
   resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e"
   dependencies:
     babel-runtime "^6.22.0"
 
-babel-plugin-check-es2015-constants@^6.22.0:
-  version "6.22.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz#35157b101426fd2ffd3da3f75c7d1e91835bbf8a"
-  dependencies:
-    babel-runtime "^6.22.0"
-
 babel-plugin-istanbul@^4.0.0:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.1.tgz#c12de0fc6fe42adfb16be56f1ad11e4a9782eca9"
   dependencies:
     find-up "^2.1.0"
     istanbul-lib-instrument "^1.6.2"
     test-exclude "^4.0.3"
 
@@ -588,184 +520,58 @@ babel-plugin-transform-async-generator-f
 babel-plugin-transform-async-to-generator@^6.16.0, babel-plugin-transform-async-to-generator@^6.24.1:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz#6536e378aff6cb1d5517ac0e40eb3e9fc8d08761"
   dependencies:
     babel-helper-remap-async-to-generator "^6.24.1"
     babel-plugin-syntax-async-functions "^6.8.0"
     babel-runtime "^6.22.0"
 
-babel-plugin-transform-es2015-arrow-functions@^6.22.0:
-  version "6.22.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221"
-  dependencies:
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-es2015-block-scoped-functions@^6.22.0:
-  version "6.22.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz#bbc51b49f964d70cb8d8e0b94e820246ce3a6141"
-  dependencies:
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-es2015-block-scoping@^6.24.1, babel-plugin-transform-es2015-block-scoping@^6.7.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.24.1.tgz#76c295dc3a4741b1665adfd3167215dcff32a576"
-  dependencies:
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-    babel-traverse "^6.24.1"
-    babel-types "^6.24.1"
-    lodash "^4.2.0"
-
-babel-plugin-transform-es2015-classes@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db"
-  dependencies:
-    babel-helper-define-map "^6.24.1"
-    babel-helper-function-name "^6.24.1"
-    babel-helper-optimise-call-expression "^6.24.1"
-    babel-helper-replace-supers "^6.24.1"
-    babel-messages "^6.23.0"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-    babel-traverse "^6.24.1"
-    babel-types "^6.24.1"
-
-babel-plugin-transform-es2015-computed-properties@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz#6fe2a8d16895d5634f4cd999b6d3480a308159b3"
-  dependencies:
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-
-babel-plugin-transform-es2015-destructuring@^6.22.0, babel-plugin-transform-es2015-destructuring@^6.6.5:
+babel-plugin-transform-es2015-block-scoping@^6.7.1:
+  version "6.26.0"
+  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz#d70f5299c1308d05c12f463813b0a09e73b1895f"
+  dependencies:
+    babel-runtime "^6.26.0"
+    babel-template "^6.26.0"
+    babel-traverse "^6.26.0"
+    babel-types "^6.26.0"
+    lodash "^4.17.4"
+
+babel-plugin-transform-es2015-destructuring@^6.6.5:
   version "6.23.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d"
   dependencies:
     babel-runtime "^6.22.0"
 
-babel-plugin-transform-es2015-duplicate-keys@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz#73eb3d310ca969e3ef9ec91c53741a6f1576423e"
-  dependencies:
-    babel-runtime "^6.22.0"
-    babel-types "^6.24.1"
-
-babel-plugin-transform-es2015-for-of@^6.22.0:
-  version "6.23.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691"
-  dependencies:
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-es2015-function-name@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b"
-  dependencies:
-    babel-helper-function-name "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-types "^6.24.1"
-
-babel-plugin-transform-es2015-literals@^6.22.0:
-  version "6.22.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz#4f54a02d6cd66cf915280019a31d31925377ca2e"
-  dependencies:
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-es2015-modules-amd@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz#3b3e54017239842d6d19c3011c4bd2f00a00d154"
-  dependencies:
-    babel-plugin-transform-es2015-modules-commonjs "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-
-babel-plugin-transform-es2015-modules-commonjs@^6.22.0, babel-plugin-transform-es2015-modules-commonjs@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.24.1.tgz#d3e310b40ef664a36622200097c6d440298f2bfe"
+babel-plugin-transform-es2015-modules-commonjs@^6.22.0:
+  version "6.26.0"
+  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz#0d8394029b7dc6abe1a97ef181e00758dd2e5d8a"
   dependencies:
     babel-plugin-transform-strict-mode "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-    babel-types "^6.24.1"
-
-babel-plugin-transform-es2015-modules-systemjs@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23"
-  dependencies:
-    babel-helper-hoist-variables "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-
-babel-plugin-transform-es2015-modules-umd@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz#ac997e6285cd18ed6176adb607d602344ad38468"
-  dependencies:
-    babel-plugin-transform-es2015-modules-amd "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-
-babel-plugin-transform-es2015-object-super@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz#24cef69ae21cb83a7f8603dad021f572eb278f8d"
-  dependencies:
-    babel-helper-replace-supers "^6.24.1"
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-es2015-parameters@^6.24.1, babel-plugin-transform-es2015-parameters@^6.7.0:
+    babel-runtime "^6.26.0"
+    babel-template "^6.26.0"
+    babel-types "^6.26.0"
+
+babel-plugin-transform-es2015-parameters@^6.7.0:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b"
   dependencies:
     babel-helper-call-delegate "^6.24.1"
     babel-helper-get-function-arity "^6.24.1"
     babel-runtime "^6.22.0"
     babel-template "^6.24.1"
     babel-traverse "^6.24.1"
     babel-types "^6.24.1"
 
-babel-plugin-transform-es2015-shorthand-properties@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz#24f875d6721c87661bbd99a4622e51f14de38aa0"
-  dependencies:
-    babel-runtime "^6.22.0"
-    babel-types "^6.24.1"
-
-babel-plugin-transform-es2015-spread@^6.22.0, babel-plugin-transform-es2015-spread@^6.6.5:
+babel-plugin-transform-es2015-spread@^6.6.5:
   version "6.22.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz#d6d68a99f89aedc4536c81a542e8dd9f1746f8d1"
   dependencies:
     babel-runtime "^6.22.0"
 
-babel-plugin-transform-es2015-sticky-regex@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc"
-  dependencies:
-    babel-helper-regex "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-types "^6.24.1"
-
-babel-plugin-transform-es2015-template-literals@^6.22.0:
-  version "6.22.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz#a84b3450f7e9f8f1f6839d6d687da84bb1236d8d"
-  dependencies:
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-es2015-typeof-symbol@^6.22.0:
-  version "6.23.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz#dec09f1cddff94b52ac73d505c84df59dcceb372"
-  dependencies:
-    babel-runtime "^6.22.0"
-
-babel-plugin-transform-es2015-unicode-regex@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9"
-  dependencies:
-    babel-helper-regex "^6.24.1"
-    babel-runtime "^6.22.0"
-    regexpu-core "^2.0.0"
-
 babel-plugin-transform-exponentiation-operator@^6.24.1:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz#2ab0c9c7f3098fa48907772bb813fe41e8de3a0e"
   dependencies:
     babel-helper-builder-binary-assignment-operator-visitor "^6.24.1"
     babel-plugin-syntax-exponentiation-operator "^6.8.0"
     babel-runtime "^6.22.0"
 
@@ -778,22 +584,16 @@ babel-plugin-transform-flow-strip-types@
 
 babel-plugin-transform-object-rest-spread@^6.22.0:
   version "6.23.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.23.0.tgz#875d6bc9be761c58a2ae3feee5dc4895d8c7f921"
   dependencies:
     babel-plugin-syntax-object-rest-spread "^6.8.0"
     babel-runtime "^6.22.0"
 
-babel-plugin-transform-regenerator@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.24.1.tgz#b8da305ad43c3c99b4848e4fe4037b770d23c418"
-  dependencies:
-    regenerator-transform "0.9.11"
-
 babel-plugin-transform-runtime@^6.7.5:
   version "6.23.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-runtime/-/babel-plugin-transform-runtime-6.23.0.tgz#88490d446502ea9b8e7efb0fe09ec4d99479b1ee"
   dependencies:
     babel-runtime "^6.22.0"
 
 babel-plugin-transform-strict-mode@^6.24.1:
   version "6.24.1"
@@ -814,86 +614,57 @@ babel-plugin-webpack-alias@^2.1.1:
 babel-polyfill@^6.23.0, babel-polyfill@^6.7.4:
   version "6.23.0"
   resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.23.0.tgz#8364ca62df8eafb830499f699177466c3b03499d"
   dependencies:
     babel-runtime "^6.22.0"
     core-js "^2.4.0"
     regenerator-runtime "^0.10.0"
 
-babel-preset-es2015@^6.6.0:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz#d44050d6bc2c9feea702aaf38d727a0210538939"
-  dependencies:
-    babel-plugin-check-es2015-constants "^6.22.0"
-    babel-plugin-transform-es2015-arrow-functions "^6.22.0"
-    babel-plugin-transform-es2015-block-scoped-functions "^6.22.0"
-    babel-plugin-transform-es2015-block-scoping "^6.24.1"
-    babel-plugin-transform-es2015-classes "^6.24.1"
-    babel-plugin-transform-es2015-computed-properties "^6.24.1"
-    babel-plugin-transform-es2015-destructuring "^6.22.0"
-    babel-plugin-transform-es2015-duplicate-keys "^6.24.1"
-    babel-plugin-transform-es2015-for-of "^6.22.0"
-    babel-plugin-transform-es2015-function-name "^6.24.1"
-    babel-plugin-transform-es2015-literals "^6.22.0"
-    babel-plugin-transform-es2015-modules-amd "^6.24.1"
-    babel-plugin-transform-es2015-modules-commonjs "^6.24.1"
-    babel-plugin-transform-es2015-modules-systemjs "^6.24.1"
-    babel-plugin-transform-es2015-modules-umd "^6.24.1"
-    babel-plugin-transform-es2015-object-super "^6.24.1"
-    babel-plugin-transform-es2015-parameters "^6.24.1"
-    babel-plugin-transform-es2015-shorthand-properties "^6.24.1"
-    babel-plugin-transform-es2015-spread "^6.22.0"
-    babel-plugin-transform-es2015-sticky-regex "^6.24.1"
-    babel-plugin-transform-es2015-template-literals "^6.22.0"
-    babel-plugin-transform-es2015-typeof-symbol "^6.22.0"
-    babel-plugin-transform-es2015-unicode-regex "^6.24.1"
-    babel-plugin-transform-regenerator "^6.24.1"
-
 babel-preset-jest@^19.0.0:
   version "19.0.0"
   resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-19.0.0.tgz#22d67201d02324a195811288eb38294bb3cac396"
   dependencies:
     babel-plugin-jest-hoist "^19.0.0"
 
 babel-preset-stage-3@^6.22.0:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-preset-stage-3/-/babel-preset-stage-3-6.24.1.tgz#836ada0a9e7a7fa37cb138fb9326f87934a48395"
   dependencies:
     babel-plugin-syntax-trailing-function-commas "^6.22.0"
     babel-plugin-transform-async-generator-functions "^6.24.1"
     babel-plugin-transform-async-to-generator "^6.24.1"
     babel-plugin-transform-exponentiation-operator "^6.24.1"
     babel-plugin-transform-object-rest-spread "^6.22.0"
 
-babel-register@^6.18.0, babel-register@^6.24.0, babel-register@^6.24.1:
+babel-register@^6.18.0, babel-register@^6.24.1:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.24.1.tgz#7e10e13a2f71065bdfad5a1787ba45bca6ded75f"
   dependencies:
     babel-core "^6.24.1"
     babel-runtime "^6.22.0"
     core-js "^2.4.0"
     home-or-tmp "^2.0.0"
     lodash "^4.2.0"
     mkdirp "^0.5.1"
     source-map-support "^0.4.2"
 
-babel-register@^6.26.0:
+babel-register@^6.24.0, babel-register@^6.26.0:
   version "6.26.0"
   resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071"
   dependencies:
     babel-core "^6.26.0"
     babel-runtime "^6.26.0"
     core-js "^2.5.0"
     home-or-tmp "^2.0.0"
     lodash "^4.17.4"
     mkdirp "^0.5.1"
     source-map-support "^0.4.15"
 
-babel-runtime@^6.18.0, babel-runtime@^6.22.0:
+babel-runtime@^6.22.0:
   version "6.23.0"
   resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.23.0.tgz#0a9489f144de70efb3ce4300accdb329e2fc543b"
   dependencies:
     core-js "^2.4.0"
     regenerator-runtime "^0.10.0"
 
 babel-runtime@^6.26.0:
   version "6.26.0"
@@ -945,17 +716,17 @@ babel-traverse@^6.26.0:
     babel-runtime "^6.26.0"
     babel-types "^6.26.0"
     babylon "^6.18.0"
     debug "^2.6.8"
     globals "^9.18.0"
     invariant "^2.2.2"
     lodash "^4.17.4"
 
-babel-types@^6.14.0, babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.23.0, babel-types@^6.24.1:
+babel-types@^6.14.0, babel-types@^6.18.0, babel-types@^6.23.0, babel-types@^6.24.1:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.24.1.tgz#a136879dc15b3606bda0d90c1fc74304c2ff0975"
   dependencies:
     babel-runtime "^6.22.0"
     esutils "^2.0.2"
     lodash "^4.2.0"
     to-fast-properties "^1.0.1"
 
@@ -1057,16 +828,20 @@ brorand@^1.0.1:
   resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
 
 browser-resolve@^1.11.2:
   version "1.11.2"
   resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.2.tgz#8ff09b0a2c421718a1051c260b32e48f442938ce"
   dependencies:
     resolve "1.1.7"
 
+browser-stdout@1.3.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f"
+
 browserify-aes@^1.0.0, browserify-aes@^1.0.4:
   version "1.0.8"
   resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.0.8.tgz#c8fa3b1b7585bb7ba77c5560b60996ddec6d5309"
   dependencies:
     buffer-xor "^1.0.3"
     cipher-base "^1.0.0"
     create-hash "^1.1.0"
     evp_bytestokey "^1.0.3"
@@ -1163,30 +938,24 @@ builtin-modules@^1.0.0:
 builtin-status-codes@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
 
 bytes@2.4.0:
   version "2.4.0"
   resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.4.0.tgz#7d97196f9d5baf7f6935e25985549edd2a6c2339"
 
-caller-path@^0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f"
-  dependencies:
-    callsites "^0.2.0"
-
-callsites@^0.2.0:
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca"
-
 callsites@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50"
 
+camelcase-css@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-1.0.1.tgz#157c4238265f5cf94a1dffde86446552cbf3f705"
+
 camelcase@^1.0.2:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39"
 
 camelcase@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a"
 
@@ -1221,17 +990,17 @@ caseless@~0.12.0:
 
 center-align@^0.1.1:
   version "0.1.3"
   resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad"
   dependencies:
     align-text "^0.1.3"
     lazy-cache "^1.0.3"
 
-chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3:
+chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
   dependencies:
     ansi-styles "^2.2.1"
     escape-string-regexp "^1.0.2"
     has-ansi "^2.0.0"
     strip-ansi "^3.0.0"
     supports-color "^2.0.0"
@@ -1295,40 +1064,26 @@ ci-info@^1.0.0:
 
 cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de"
   dependencies:
     inherits "^2.0.1"
     safe-buffer "^5.0.1"
 
-circular-json@^0.3.1:
-  version "0.3.1"
-  resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.1.tgz#be8b36aefccde8b3ca7aa2d6afc07a37242c0d2d"
-
 clap@^1.0.9:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/clap/-/clap-1.1.3.tgz#b3bd36e93dd4cbfb395a3c26896352445265c05b"
   dependencies:
     chalk "^1.1.3"
 
 classnames@^2.2.5:
   version "2.2.5"
   resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d"
 
-cli-cursor@^1.0.1:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987"
-  dependencies:
-    restore-cursor "^1.0.1"
-
-cli-width@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.1.0.tgz#b234ca209b29ef66fc518d9b98d5847b00edf00a"
-
 cliui@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1"
   dependencies:
     center-align "^0.1.1"
     right-align "^0.1.1"
     wordwrap "0.0.2"
 
@@ -1403,38 +1158,30 @@ combined-stream@^1.0.5, combined-stream@
   resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009"
   dependencies:
     delayed-stream "~1.0.0"
 
 commander@2.1.x:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.1.0.tgz#d121bbae860d9992a3d517ba96f56588e47c6781"
 
-commander@^2.8.1:
+commander@2.9.0, commander@^2.8.1:
   version "2.9.0"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4"
   dependencies:
     graceful-readlink ">= 1.0.0"
 
 commondir@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
 
 concat-map@0.0.1:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
 
-concat-stream@^1.5.2:
-  version "1.6.0"
-  resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7"
-  dependencies:
-    inherits "^2.0.3"
-    readable-stream "^2.2.2"
-    typedarray "^0.0.6"
-
 connected-domain@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/connected-domain/-/connected-domain-1.0.0.tgz#bfe77238c74be453a79f0cb6058deeb4f2358e93"
 
 console-browserify@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10"
   dependencies:
@@ -1532,16 +1279,24 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, 
   dependencies:
     cipher-base "^1.0.3"
     create-hash "^1.1.0"
     inherits "^2.0.1"
     ripemd160 "^2.0.0"
     safe-buffer "^5.0.1"
     sha.js "^2.4.8"
 
+create-react-class@^15.6.0:
+  version "15.6.2"
+  resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.2.tgz#cf1ed15f12aad7f14ef5f2dfe05e6c42f91ef02a"
+  dependencies:
+    fbjs "^0.8.9"
+    loose-envify "^1.3.1"
+    object-assign "^4.1.1"
+
 cross-spawn@^5.0.1:
   version "5.1.0"
   resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
   dependencies:
     lru-cache "^4.0.1"
     shebang-command "^1.2.0"
     which "^1.2.9"
 
@@ -1684,25 +1439,31 @@ debug@2.6.1:
     ms "0.7.2"
 
 debug@2.6.4:
   version "2.6.4"
   resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.4.tgz#7586a9b3c39741c0282ae33445c4e8ac74734fe0"
   dependencies:
     ms "0.7.3"
 
+debug@2.6.8, debug@^2.6.8:
+  version "2.6.8"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc"
+  dependencies:
+    ms "2.0.0"
+
 debug@^2.1.1, debug@^2.2.0:
   version "2.6.6"
   resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.6.tgz#a9fa6fbe9ca43cf1e79f73b75c0189cbb7d6db5a"
   dependencies:
     ms "0.7.3"
 
-debug@^2.6.8:
-  version "2.6.8"
-  resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc"
+debug@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
   dependencies:
     ms "2.0.0"
 
 decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
 
 deep-extend@~0.4.0:
@@ -1714,39 +1475,20 @@ deep-is@~0.1.3:
   resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
 
 default-require-extensions@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-1.0.0.tgz#f37ea15d3e13ffd9b437d33e1a75b5fb97874cb8"
   dependencies:
     strip-bom "^2.0.0"
 
-define-properties@^1.1.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94"
-  dependencies:
-    foreach "^2.0.5"
-    object-keys "^1.0.8"
-
 defined@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
 
-del@^2.0.2:
-  version "2.2.2"
-  resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8"
-  dependencies:
-    globby "^5.0.0"
-    is-path-cwd "^1.0.0"
-    is-path-in-cwd "^1.0.0"
-    object-assign "^4.0.1"
-    pify "^2.0.0"
-    pinkie-promise "^2.0.0"
-    rimraf "^2.2.8"
-
 delayed-stream@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
 
 delegates@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
 
@@ -1784,25 +1526,25 @@ devtools-connection@^0.0.7:
   resolved "https://registry.yarnpkg.com/devtools-connection/-/devtools-connection-0.0.7.tgz#1ec211018dc863079adb5422cf398f1201078c39"
 
 devtools-contextmenu@=0.0.3:
   version "0.0.3"
   resolved "https://registry.yarnpkg.com/devtools-contextmenu/-/devtools-contextmenu-0.0.3.tgz#be5183abcaeb728b6c5e2fe60fa9f0525fc959e0"
   dependencies:
     devtools-modules "^0.0.28"
 
-devtools-contextmenu@^0.0.6:
-  version "0.0.6"
-  resolved "https://registry.yarnpkg.com/devtools-contextmenu/-/devtools-contextmenu-0.0.6.tgz#03ebe541f283b0b7ccc00a4641e6eb1b7fff95f8"
-  dependencies:
-    devtools-modules "^0.0.31"
-
-devtools-launchpad@=0.0.96:
-  version "0.0.96"
-  resolved "https://registry.yarnpkg.com/devtools-launchpad/-/devtools-launchpad-0.0.96.tgz#5390ccccbbe06d25f488e66d4511bc94b4807ba7"
+devtools-contextmenu@^0.0.7:
+  version "0.0.7"
+  resolved "https://registry.yarnpkg.com/devtools-contextmenu/-/devtools-contextmenu-0.0.7.tgz#9c5561a1e0e2a91d3ac92f76c7ef3a972a0cb120"
+  dependencies:
+    devtools-modules "^0.0.32"
+
+devtools-launchpad@=0.0.103:
+  version "0.0.103"
+  resolved "https://registry.yarnpkg.com/devtools-launchpad/-/devtools-launchpad-0.0.103.tgz#4ef91204a17bbe333232d91ce4438c1a4aa9b92a"
   dependencies:
     amd-loader "0.0.8"
     autoprefixer "^7.1.2"
     babel-cli "^6.7.5"
     babel-core "^6.25.0"
     babel-eslint "^7.1.0"
     babel-loader "^7.1.1"
     babel-plugin-module-resolver "^2.2.0"
@@ -1818,61 +1560,65 @@ devtools-launchpad@=0.0.96:
     babel-polyfill "^6.7.4"
     babel-register "^6.18.0"
     body-parser "^1.15.2"
     check-node-version "^1.1.2"
     chrome-remote-interface "0.17.0"
     classnames "^2.2.5"
     co "=4.6.0"
     css-loader "^0.26.1"
+    debug "^3.1.0"
     devtools-config "^0.0.15"
     devtools-connection "^0.0.7"
-    devtools-contextmenu "^0.0.6"
-    devtools-modules "^0.0.31"
+    devtools-contextmenu "^0.0.7"
+    devtools-mc-assets "^0.0.2"
+    devtools-modules "^0.0.32"
     devtools-sprintf-js "^1.0.3"
     devtools-utils "^0.0.5"
-    eslint "^3.12.0"
-    eslint-plugin-babel "^3.3.0"
-    eslint-plugin-flowtype "^2.20.0"
-    eslint-plugin-mozilla "0.2.3"
-    eslint-plugin-react "^6.7.1"
     express "^4.13.4"
+    express-static "^1.2.4"
     extract-text-webpack-plugin "^3.0.0"
     fs-extra "^2.0.0"
     fuzzaldrin-plus "^0.4.0"
     geckodriver "=1.4.0"
     immutable "^3.7.6"
     json-loader "^0.5.4"
     minimist "^1.2.0"
     mustache "^2.2.1"
     node-static "^0.7.7"
     postcss "^6.0.6"
     postcss-bidirection "^2.4.0"
+    postcss-class-namespace "^0.1.0"
+    postcss-js "^1.0.1"
     postcss-loader "^2.0.6"
+    postcss-url-mapper "^1.2.0"
     properties-parser "^0.3.1"
     ps-node "^0.1.4"
     raw-loader "^0.5.1"
-    react "=15.3.2"
-    react-dom "=15.3.2"
-    react-hot-loader "^1.3.1"
     react-immutable-proptypes "^2.1.0"
-    react-redux "4.4.5"
-    redux "3.5.2"
+    react-redux "^5.0.6"
+    redux "^3.7.2"
     selenium-webdriver "=3.3.0"
     style-loader "^0.18.2"
+    svg-inline-loader "^0.8.0"
+    svg-inline-react "1.0.2"
     url-loader "^0.5.9"
     webpack "^3.3.0"
     webpack-dev-middleware "^1.11.0"
     webpack-env-loader-plugin "^1.0.0"
     webpack-hot-middleware "^2.18.2"
     ws "^1.0.1"
 
-devtools-modules@=0.0.31, devtools-modules@^0.0.31:
-  version "0.0.31"
-  resolved "https://registry.yarnpkg.com/devtools-modules/-/devtools-modules-0.0.31.tgz#06749ffa7b78cf2826467ffcce9d2edbbba3ced7"
+devtools-mc-assets@^0.0.2:
+  version "0.0.2"
+  resolved "https://registry.yarnpkg.com/devtools-mc-assets/-/devtools-mc-assets-0.0.2.tgz#082cf3fedc8c238d6731263f836bcc5c18490caa"
+
+devtools-modules@=0.0.32, devtools-modules@^0.0.32:
+  version "0.0.32"
+  resolved "https://registry.yarnpkg.com/devtools-modules/-/devtools-modules-0.0.32.tgz#7432d664dba16918b92bf8e4907f2396c15980d8"
   dependencies:
     jest "^19.0.2"
 
 devtools-modules@^0.0.28:
   version "0.0.28"
   resolved "https://registry.yarnpkg.com/devtools-modules/-/devtools-modules-0.0.28.tgz#5b3498a4844675daedc3c7128a95ba011431eb46"
 
 devtools-source-editor@=0.0.3:
@@ -1888,42 +1634,28 @@ devtools-sprintf-js@^1.0.3:
 devtools-utils@^0.0.5:
   version "0.0.5"
   resolved "https://registry.yarnpkg.com/devtools-utils/-/devtools-utils-0.0.5.tgz#e0d0e02dff3af4ef383543c4a8985fabf2dce4a1"
   dependencies:
     babel-plugin-transform-flow-strip-types "^6.22.0"
     babel-preset-stage-3 "^6.22.0"
     jest "^19.0.2"
 
-diff@^3.0.0:
+diff@3.2.0, diff@^3.0.0:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9"
 
 diffie-hellman@^5.0.0:
   version "5.0.2"
   resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.2.tgz#b5835739270cfe26acf632099fded2a07f209e5e"
   dependencies:
     bn.js "^4.1.0"
     miller-rabin "^4.0.0"
     randombytes "^2.0.0"
 
-doctrine@^1.2.2:
-  version "1.5.0"
-  resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa"
-  dependencies:
-    esutils "^2.0.2"
-    isarray "^1.0.0"
-
-doctrine@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.0.0.tgz#c73d8d2909d22291e1a007a395804da8b665fe63"
-  dependencies:
-    esutils "^2.0.2"
-    isarray "^1.0.0"
-
 domain-browser@^1.1.1:
   version "1.1.7"
   resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc"
 
 duplexer2@^0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1"
   dependencies:
@@ -1989,33 +1721,16 @@ enhanced-resolve@^3.3.0, enhanced-resolv
     prr "~0.0.0"
 
 error-ex@^1.2.0:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc"
   dependencies:
     is-arrayish "^0.2.1"
 
-es-abstract@^1.7.0:
-  version "1.7.0"
-  resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.7.0.tgz#dfade774e01bfcd97f96180298c449c8623fb94c"
-  dependencies:
-    es-to-primitive "^1.1.1"
-    function-bind "^1.1.0"
-    is-callable "^1.1.3"
-    is-regex "^1.0.3"
-
-es-to-primitive@^1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.1.1.tgz#45355248a88979034b6792e19bb81f2b7975dd0d"
-  dependencies:
-    is-callable "^1.1.1"
-    is-date-object "^1.0.1"
-    is-symbol "^1.0.1"
-
 es5-ext@^0.10.14, es5-ext@^0.10.9, es5-ext@~0.10.14:
   version "0.10.15"
   resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.15.tgz#c330a5934c1ee21284a7c081a86e5fd937c91ea6"
   dependencies:
     es6-iterator "2"
     es6-symbol "~3.1"
 
 es6-iterator@2, es6-iterator@^2.0.1, es6-iterator@~2.0.1:
@@ -2066,17 +1781,17 @@ es6-weak-map@^2.0.1:
     es5-ext "^0.10.14"
     es6-iterator "^2.0.1"
     es6-symbol "^3.1.1"
 
 escape-html@~1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
 
-escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
+escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
 
 escodegen@^1.6.1:
   version "1.8.1"
   resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.8.1.tgz#5a5b53af4693110bebb0867aa3430dd3b70a1018"
   dependencies:
     esprima "^2.7.1"
@@ -2090,118 +1805,36 @@ escope@^3.6.0:
   version "3.6.0"
   resolved "https://registry.yarnpkg.com/escope/-/escope-3.6.0.tgz#e01975e812781a163a6dadfdd80398dc64c889c3"
   dependencies:
     es6-map "^0.1.3"
     es6-weak-map "^2.0.1"
     esrecurse "^4.1.0"
     estraverse "^4.1.1"
 
-eslint-plugin-babel@^3.3.0:
-  version "3.3.0"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-babel/-/eslint-plugin-babel-3.3.0.tgz#2f494aedcf6f4aa4e75b9155980837bc1fbde193"
-
-eslint-plugin-flowtype@^2.20.0:
-  version "2.32.1"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-2.32.1.tgz#bbee185dedf97e5f63ec975cdcddd199bd2a2501"
-  dependencies:
-    lodash "^4.15.0"
-
-eslint-plugin-mozilla@0.2.3:
-  version "0.2.3"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-mozilla/-/eslint-plugin-mozilla-0.2.3.tgz#4deb85afb52f3622444c59420e005dc3ef44851b"
-  dependencies:
-    escope "^3.6.0"
-    espree "^3.2.0"
-    estraverse "^4.2.0"
-    sax "^1.1.4"
-
-eslint-plugin-react@^6.7.1:
-  version "6.10.3"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-6.10.3.tgz#c5435beb06774e12c7db2f6abaddcbf900cd3f78"
-  dependencies:
-    array.prototype.find "^2.0.1"
-    doctrine "^1.2.2"
-    has "^1.0.1"
-    jsx-ast-utils "^1.3.4"
-    object.assign "^4.0.4"
-
-eslint@^3.12.0:
-  version "3.19.0"
-  resolved "https://registry.yarnpkg.com/eslint/-/eslint-3.19.0.tgz#c8fc6201c7f40dd08941b87c085767386a679acc"
-  dependencies:
-    babel-code-frame "^6.16.0"
-    chalk "^1.1.3"
-    concat-stream "^1.5.2"
-    debug "^2.1.1"
-    doctrine "^2.0.0"
-    escope "^3.6.0"
-    espree "^3.4.0"
-    esquery "^1.0.0"
-    estraverse "^4.2.0"
-    esutils "^2.0.2"
-    file-entry-cache "^2.0.0"
-    glob "^7.0.3"
-    globals "^9.14.0"
-    ignore "^3.2.0"
-    imurmurhash "^0.1.4"
-    inquirer "^0.12.0"
-    is-my-json-valid "^2.10.0"
-    is-resolvable "^1.0.0"
-    js-yaml "^3.5.1"
-    json-stable-stringify "^1.0.0"
-    levn "^0.3.0"
-    lodash "^4.0.0"
-    mkdirp "^0.5.0"
-    natural-compare "^1.4.0"
-    optionator "^0.8.2"
-    path-is-inside "^1.0.1"
-    pluralize "^1.2.1"
-    progress "^1.1.8"
-    require-uncached "^1.0.2"
-    shelljs "^0.7.5"
-    strip-bom "^3.0.0"
-    strip-json-comments "~2.0.1"
-    table "^3.7.8"
-    text-table "~0.2.0"
-    user-home "^2.0.0"
-
-espree@^3.2.0, espree@^3.4.0:
-  version "3.4.2"
-  resolved "https://registry.yarnpkg.com/espree/-/espree-3.4.2.tgz#38dbdedbedc95b8961a1fbf04734a8f6a9c8c592"
-  dependencies:
-    acorn "^5.0.1"
-    acorn-jsx "^3.0.0"
-
 esprima@^2.6.0, esprima@^2.7.1:
   version "2.7.3"
   resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581"
 
 esprima@^3.1.1:
   version "3.1.3"
   resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
 
-esquery@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.0.tgz#cfba8b57d7fba93f17298a8a006a04cda13d80fa"
-  dependencies:
-    estraverse "^4.0.0"
-
 esrecurse@^4.1.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.1.0.tgz#4713b6536adf7f2ac4f327d559e7756bff648220"
   dependencies:
     estraverse "~4.1.0"
     object-assign "^4.0.1"
 
 estraverse@^1.9.1:
   version "1.9.3"
   resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.9.3.tgz#af67f2dc922582415950926091a4005d29c9bb44"
 
-estraverse@^4.0.0, estraverse@^4.1.1, estraverse@^4.2.0:
+estraverse@^4.1.1:
   version "4.2.0"
   resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13"
 
 estraverse@~4.1.0:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.1.1.tgz#f6caca728933a850ef90661d0e17982ba47111a2"
 
 esutils@^2.0.2:
@@ -2243,32 +1876,34 @@ execa@^0.7.0:
     cross-spawn "^5.0.1"
     get-stream "^3.0.0"
     is-stream "^1.1.0"
     npm-run-path "^2.0.0"
     p-finally "^1.0.0"
     signal-exit "^3.0.0"
     strip-eof "^1.0.0"
 
-exit-hook@^1.0.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8"
-
 expand-brackets@^0.1.4:
   version "0.1.5"
   resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b"
   dependencies:
     is-posix-bracket "^0.1.0"
 
 expand-range@^1.8.1:
   version "1.8.2"
   resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337"
   dependencies:
     fill-range "^2.1.0"
 
+express-static@^1.2.4:
+  version "1.2.4"
+  resolved "https://registry.yarnpkg.com/express-static/-/express-static-1.2.4.tgz#effe53be83b1372e2c0e81e68aa8ae55af76f208"
+  dependencies:
+    mail2 latest
+
 express@^4.13.4:
   version "4.15.2"
   resolved "https://registry.yarnpkg.com/express/-/express-4.15.2.tgz#af107fc148504457f2dca9a6f2571d7129b97b35"
   dependencies:
     accepts "~1.3.3"
     array-flatten "1.1.1"
     content-disposition "0.5.2"
     content-type "~1.0.2"
@@ -2339,42 +1974,28 @@ fb-watchman@^1.8.0:
     bser "1.0.2"
 
 fb-watchman@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.0.tgz#54e9abf7dfa2f26cd9b1636c588c1afc05de5d58"
   dependencies:
     bser "^2.0.0"
 
-fbjs@^0.8.4:
-  version "0.8.12"
-  resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.12.tgz#10b5d92f76d45575fd63a217d4ea02bea2f8ed04"
+fbjs@^0.8.16, fbjs@^0.8.9:
+  version "0.8.16"
+  resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db"
   dependencies:
     core-js "^1.0.0"
     isomorphic-fetch "^2.1.1"
     loose-envify "^1.0.0"
     object-assign "^4.1.0"
     promise "^7.1.1"
     setimmediate "^1.0.5"
     ua-parser-js "^0.7.9"
 
-figures@^1.3.5:
-  version "1.7.0"
-  resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e"
-  dependencies:
-    escape-string-regexp "^1.0.5"
-    object-assign "^4.1.0"
-
-file-entry-cache@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361"
-  dependencies:
-    flat-cache "^1.2.1"
-    object-assign "^4.0.1"
-
 file-loader@^0.10.1:
   version "0.10.1"
   resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-0.10.1.tgz#815034119891fc6441fb5a64c11bc93c22ddd842"
   dependencies:
     loader-utils "^1.0.2"
 
 filename-regex@^2.0.0:
   version "2.0.0"
@@ -2432,43 +2053,30 @@ find-up@^1.0.0:
     pinkie-promise "^2.0.0"
 
 find-up@^2.0.0, find-up@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7"
   dependencies:
     locate-path "^2.0.0"
 
-flat-cache@^1.2.1:
-  version "1.2.2"
-  resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.2.2.tgz#fa86714e72c21db88601761ecf2f555d1abc6b96"
-  dependencies:
-    circular-json "^0.3.1"
-    del "^2.0.2"
-    graceful-fs "^4.1.2"
-    write "^0.2.1"
-
 flatten@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782"
 
 for-in@^1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
 
 for-own@^0.1.4:
   version "0.1.5"
   resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce"
   dependencies:
     for-in "^1.0.1"
 
-foreach@^2.0.5:
-  version "2.0.5"
-  resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99"
-
 forever-agent@~0.6.1:
   version "0.6.1"
   resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
 
 form-data@~2.1.1:
   version "2.1.4"
   resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1"
   dependencies:
@@ -2518,17 +2126,17 @@ fstream@^1.0.0, fstream@^1.0.10, fstream
   version "1.0.11"
   resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171"
   dependencies:
     graceful-fs "^4.1.2"
     inherits "~2.0.0"
     mkdirp ">=0.5 0"
     rimraf "2"
 
-function-bind@^1.0.2, function-bind@^1.1.0:
+function-bind@^1.0.2:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.0.tgz#16176714c801798e4e8f2cf7f7529467bb4a5771"
 
 fuzzaldrin-plus@^0.4.0:
   version "0.4.1"
   resolved "https://registry.yarnpkg.com/fuzzaldrin-plus/-/fuzzaldrin-plus-0.4.1.tgz#979595024aab74184942307d631d7aa441eee379"
 
 gauge@~2.7.1:
@@ -2548,26 +2156,16 @@ geckodriver@=1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/geckodriver/-/geckodriver-1.4.0.tgz#f3bc1e4e6139e9e09b44e0777225c628e41d129c"
   dependencies:
     adm-zip "0.4.7"
     bluebird "3.4.6"
     got "5.6.0"
     tar.gz "1.0.5"
 
-generate-function@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.0.0.tgz#6858fe7c0969b7d4e9093337647ac79f60dfbe74"
-
-generate-object-property@^1.1.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0"
-  dependencies:
-    is-property "^1.0.0"
-
 get-caller-file@^1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5"
 
 get-stream@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
 
@@ -2585,46 +2183,35 @@ glob-base@^0.3.0:
     is-glob "^2.0.0"
 
 glob-parent@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28"
   dependencies:
     is-glob "^2.0.0"
 
-glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1:
+glob@7.1.1, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1:
   version "7.1.1"
   resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8"
   dependencies:
     fs.realpath "^1.0.0"
     inflight "^1.0.4"
     inherits "2"
     minimatch "^3.0.2"
     once "^1.3.0"
     path-is-absolute "^1.0.0"
 
-globals@^9.0.0, globals@^9.14.0:
+globals@^9.0.0:
   version "9.17.0"
   resolved "https://registry.yarnpkg.com/globals/-/globals-9.17.0.tgz#0c0ca696d9b9bb694d2e5470bd37777caad50286"
 
 globals@^9.18.0:
   version "9.18.0"
   resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
 
-globby@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d"
-  dependencies:
-    array-union "^1.0.1"
-    arrify "^1.0.0"
-    glob "^7.0.3"
-    object-assign "^4.0.1"
-    pify "^2.0.0"
-    pinkie-promise "^2.0.0"
-
 got@5.6.0:
   version "5.6.0"
   resolved "https://registry.yarnpkg.com/got/-/got-5.6.0.tgz#bb1d7ee163b78082bbc8eb836f3f395004ea6fbf"
   dependencies:
     create-error-class "^3.0.1"
     duplexer2 "^0.1.4"
     is-plain-obj "^1.0.0"
     is-redirect "^1.0.0"
@@ -2644,16 +2231,20 @@ got@5.6.0:
 graceful-fs@^4.1.2, graceful-fs@^4.1.4, graceful-fs@^4.1.6:
   version "4.1.11"
   resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
 
 "graceful-readlink@>= 1.0.0":
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
 
+growl@1.9.2:
+  version "1.9.2"
+  resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f"
+
 growly@^1.3.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
 
 handlebars@^4.0.3:
   version "4.0.6"
   resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.6.tgz#2ce4484850537f9c97a8026d5399b935c4ed4ed7"
   dependencies:
@@ -2722,32 +2313,40 @@ hawk@~3.1.3:
   version "3.1.3"
   resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4"
   dependencies:
     boom "2.x.x"
     cryptiles "2.x.x"
     hoek "2.x.x"
     sntp "1.x.x"
 
+he@1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
+
 hmac-drbg@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
   dependencies:
     hash.js "^1.0.3"
     minimalistic-assert "^1.0.0"
     minimalistic-crypto-utils "^1.0.1"
 
 hoek@2.x.x:
   version "2.16.3"
   resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
 
 hoist-non-react-statics@^1.0.3:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz#aa448cf0986d55cc40773b17174b7dd066cb7cfb"
 
+hoist-non-react-statics@^2.2.1:
+  version "2.3.1"
+  resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.3.1.tgz#343db84c6018c650778898240135a1420ee22ce0"
+
 home-or-tmp@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"
   dependencies:
     os-homedir "^1.0.0"
     os-tmpdir "^1.0.1"
 
 hosted-git-info@^2.1.4:
@@ -2800,32 +2399,24 @@ iconv-lite@0.4.15, iconv-lite@~0.4.13:
 icss-replace-symbols@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.0.2.tgz#cb0b6054eb3af6edc9ab1d62d01933e2d4c8bfa5"
 
 ieee754@^1.1.4:
   version "1.1.8"
   resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4"
 
-ignore@^3.2.0:
-  version "3.2.7"
-  resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.2.7.tgz#4810ca5f1d8eca5595213a34b94f2eb4ed926bbd"
-
 immediate@~3.0.5:
   version "3.0.6"
   resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
 
 immutable@^3.7.6, immutable@^3.8.1:
   version "3.8.1"
   resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.1.tgz#200807f11ab0f72710ea485542de088075f68cd2"
 
-imurmurhash@^0.1.4:
-  version "0.1.4"
-  resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
-
 indexes-of@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607"
 
 indexof@0.0.1:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d"
 
@@ -2843,34 +2434,16 @@ inherits@2, inherits@2.0.3, inherits@^2.
 inherits@2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
 
 ini@~1.3.0:
   version "1.3.4"
   resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e"
 
-inquirer@^0.12.0:
-  version "0.12.0"
-  resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e"
-  dependencies:
-    ansi-escapes "^1.1.0"
-    ansi-regex "^2.0.0"
-    chalk "^1.0.0"
-    cli-cursor "^1.0.1"
-    cli-width "^2.0.0"
-    figures "^1.3.5"
-    lodash "^4.3.0"
-    readline2 "^1.0.1"
-    run-async "^0.1.0"
-    rx-lite "^3.1.2"
-    string-width "^1.0.1"
-    strip-ansi "^3.0.0"
-    through "^2.3.6"
-
 interpret@^1.0.0:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.3.tgz#cbc35c62eeee73f19ab7b10a801511401afc0f90"
 
 invariant@^2.0.0, invariant@^2.2.0, invariant@^2.2.2:
   version "2.2.2"
   resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360"
   dependencies:
@@ -2903,30 +2476,22 @@ is-buffer@^1.1.5:
   resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.5.tgz#1f3b26ef613b214b88cbca23cc6c01d87961eecc"
 
 is-builtin-module@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe"
   dependencies:
     builtin-modules "^1.0.0"
 
-is-callable@^1.1.1, is-callable@^1.1.3:
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.3.tgz#86eb75392805ddc33af71c92a0eedf74ee7604b2"
-
 is-ci@^1.0.9:
   version "1.0.10"
   resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.0.10.tgz#f739336b2632365061a9d48270cd56ae3369318e"
   dependencies:
     ci-info "^1.0.0"
 
-is-date-object@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16"
-
 is-directory@^0.3.1:
   version "0.3.1"
   resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1"
 
 is-dotfile@^1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.2.tgz#2c132383f39199f8edc268ca01b9b007d205cc4d"
 
@@ -2961,97 +2526,52 @@ is-fullwidth-code-point@^2.0.0:
   resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
 
 is-glob@^2.0.0, is-glob@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863"
   dependencies:
     is-extglob "^1.0.0"
 
-is-my-json-valid@^2.10.0:
-  version "2.16.0"
-  resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.16.0.tgz#f079dd9bfdae65ee2038aae8acbc86ab109e3693"
-  dependencies:
-    generate-function "^2.0.0"
-    generate-object-property "^1.1.0"
-    jsonpointer "^4.0.0"
-    xtend "^4.0.0"
-
 is-number@^2.0.2, is-number@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f"
   dependencies:
     kind-of "^3.0.2"
 
-is-path-cwd@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d"
-
-is-path-in-cwd@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz#6477582b8214d602346094567003be8a9eac04dc"
-  dependencies:
-    is-path-inside "^1.0.0"
-
-is-path-inside@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.0.tgz#fc06e5a1683fbda13de667aff717bbc10a48f37f"
-  dependencies:
-    path-is-inside "^1.0.1"
-
 is-plain-obj@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
 
 is-posix-bracket@^0.1.0:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4"
 
 is-primitive@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575"
 
-is-property@^1.0.0:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84"
-
 is-redirect@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24"
 
-is-regex@^1.0.3:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491"
-  dependencies:
-    has "^1.0.1"
-
-is-resolvable@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.0.0.tgz#8df57c61ea2e3c501408d100fb013cf8d6e0cc62"
-  dependencies:
-    tryit "^1.0.1"
-
 is-retry-allowed@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34"
 
 is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
 
 is-svg@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-2.1.0.tgz#cf61090da0d9efbcab8722deba6f032208dbb0e9"
   dependencies:
     html-comment-regex "^1.1.0"
 
-is-symbol@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572"
-
 is-typedarray@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
 
 is-utf8@^0.2.0:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
 
@@ -3361,17 +2881,17 @@ js-base64@^2.1.9:
 js-tokens@^3.0.0:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7"
 
 js-tokens@^3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
 
-js-yaml@^3.4.3, js-yaml@^3.5.1, js-yaml@^3.7.0:
+js-yaml@^3.4.3, js-yaml@^3.7.0:
   version "3.8.3"
   resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.8.3.tgz#33a05ec481c850c8875929166fe1beb61c728766"
   dependencies:
     argparse "^1.0.7"
     esprima "^3.1.1"
 
 js-yaml@~3.7.0:
   version "3.7.0"
@@ -3429,57 +2949,53 @@ json-parse-helpfulerror@^1.0.3:
 json-schema-traverse@^0.3.0:
   version "0.3.1"
   resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340"
 
 json-schema@0.2.3:
   version "0.2.3"
   resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
 
-json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1:
+json-stable-stringify@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af"
   dependencies:
     jsonify "~0.0.0"
 
 json-stringify-safe@~5.0.1:
   version "5.0.1"
   resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
 
+json3@3.3.2:
+  version "3.3.2"
+  resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1"
+
 json5@^0.5.0, json5@^0.5.1:
   version "0.5.1"
   resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
 
 jsonfile@^2.1.0:
   version "2.4.0"
   resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8"
   optionalDependencies:
     graceful-fs "^4.1.6"
 
 jsonify@~0.0.0:
   version "0.0.0"
   resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
 
-jsonpointer@^4.0.0:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9"
-
 jsprim@^1.2.2:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.0.tgz#a3b87e40298d8c380552d8cc7628a0bb95a22918"
   dependencies:
     assert-plus "1.0.0"
     extsprintf "1.0.2"
     json-schema "0.2.3"
     verror "1.3.6"
 
-jsx-ast-utils@^1.3.4:
-  version "1.4.1"
-  resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-1.4.1.tgz#3867213e8dd79bf1e8f2300c0cfc1efb182c0df1"
-
 jszip@^3.1.3:
   version "3.1.3"
   resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.1.3.tgz#8a920403b2b1651c0fc126be90192d9080957c37"
   dependencies:
     core-js "~2.3.0"
     es6-promise "~3.0.2"
     lie "~3.1.0"
     pako "~1.0.2"
@@ -3500,17 +3016,17 @@ lcid@^1.0.0:
   resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835"
   dependencies:
     invert-kv "^1.0.0"
 
 leven@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580"
 
-levn@^0.3.0, levn@~0.3.0:
+levn@~0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
   dependencies:
     prelude-ls "~1.1.2"
     type-check "~0.3.2"
 
 lie@~3.1.0:
   version "3.1.1"
@@ -3536,17 +3052,17 @@ load-json-file@^2.0.0:
     parse-json "^2.2.0"
     pify "^2.0.0"
     strip-bom "^3.0.0"
 
 loader-runner@^2.3.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2"
 
-loader-utils@^0.2.16:
+loader-utils@^0.2.11, loader-utils@^0.2.16:
   version "0.2.17"
   resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348"
   dependencies:
     big.js "^3.1.3"
     emojis-list "^2.0.0"
     json5 "^0.5.0"
     object-assign "^4.0.1"
 
@@ -3564,24 +3080,71 @@ locate-path@^2.0.0:
   dependencies:
     p-locate "^2.0.0"
     path-exists "^3.0.0"
 
 lodash-es@^4.2.0, lodash-es@^4.2.1:
   version "4.17.4"
   resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.4.tgz#dcc1d7552e150a0640073ba9cb31d70f032950e7"
 
+lodash._baseassign@^3.0.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz#8c38a099500f215ad09e59f1722fd0c52bfe0a4e"
+  dependencies:
+    lodash._basecopy "^3.0.0"
+    lodash.keys "^3.0.0"
+
+lodash._basecopy@^3.0.0:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36"
+
+lodash._basecreate@^3.0.0:
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz#1bc661614daa7fc311b7d03bf16806a0213cf821"
+
+lodash._getnative@^3.0.0:
+  version "3.9.1"
+  resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5"
+
+lodash._isiterateecall@^3.0.0:
+  version "3.0.9"
+  resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c"
+
 lodash._reinterpolate@~3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
 
 lodash.camelcase@^4.3.0:
   version "4.3.0"
   resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
 
+lodash.create@3.1.1:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/lodash.create/-/lodash.create-3.1.1.tgz#d7f2849f0dbda7e04682bb8cd72ab022461debe7"
+  dependencies:
+    lodash._baseassign "^3.0.0"
+    lodash._basecreate "^3.0.0"
+    lodash._isiterateecall "^3.0.0"
+
+lodash.isarguments@^3.0.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"
+
+lodash.isarray@^3.0.0:
+  version "3.0.4"
+  resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55"
+
+lodash.keys@^3.0.0:
+  version "3.1.2"
+  resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a"
+  dependencies:
+    lodash._getnative "^3.0.0"
+    lodash.isarguments "^3.0.0"
+    lodash.isarray "^3.0.0"
+
 lodash.memoize@^4.1.2:
   version "4.1.2"
   resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
 
 lodash.some@^4.5.1:
   version "4.6.0"
   resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d"
 
@@ -3597,25 +3160,25 @@ lodash.templatesettings@^4.0.0:
   resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.1.0.tgz#2b4d4e95ba440d915ff08bc899e4553666713316"
   dependencies:
     lodash._reinterpolate "~3.0.0"
 
 lodash.uniq@^4.5.0:
   version "4.5.0"
   resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
 
-lodash@^4.0.0, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0:
+lodash@^4.0.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1:
   version "4.17.4"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
 
 longest@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097"
 
-loose-envify@^1.0.0, loose-envify@^1.1.0:
+loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848"
   dependencies:
     js-tokens "^3.0.0"
 
 lowercase-keys@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306"
@@ -3626,16 +3189,22 @@ lru-cache@^4.0.1:
   dependencies:
     pseudomap "^1.0.2"
     yallist "^2.1.2"
 
 macaddress@^0.2.8:
   version "0.2.8"
   resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12"
 
+mail2@latest:
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/mail2/-/mail2-1.2.2.tgz#977c6940c6fabca4c353a0d84e584fe7cd7de06d"
+  dependencies:
+    mocha "^3.2.0"
+
 make-dir@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.0.0.tgz#97a011751e91dd87cfadef58832ebb04936de978"
   dependencies:
     pify "^2.3.0"
 
 makeerror@1.0.x:
   version "1.0.11"
@@ -3749,22 +3318,39 @@ minimatch@^3.0.4:
 minimist@0.0.8, minimist@~0.0.1:
   version "0.0.8"
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
 
 minimist@^1.1.1, minimist@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
 
-"mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
+mkdirp@0.5.1, "mkdirp@>=0.5 0", mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
   version "0.5.1"
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
   dependencies:
     minimist "0.0.8"
 
+mocha@^3.2.0:
+  version "3.5.3"
+  resolved "https://registry.yarnpkg.com/mocha/-/mocha-3.5.3.tgz#1e0480fe36d2da5858d1eb6acc38418b26eaa20d"
+  dependencies:
+    browser-stdout "1.3.0"
+    commander "2.9.0"
+    debug "2.6.8"
+    diff "3.2.0"
+    escape-string-regexp "1.0.5"
+    glob "7.1.1"
+    growl "1.9.2"
+    he "1.1.1"
+    json3 "3.3.2"
+    lodash.create "3.1.1"
+    mkdirp "0.5.1"
+    supports-color "3.1.2"
+
 mout@^0.11.0:
   version "0.11.1"
   resolved "https://registry.yarnpkg.com/mout/-/mout-0.11.1.tgz#ba3611df5f0e5b1ffbfd01166b8f02d1f5fa2b99"
 
 ms@0.7.2:
   version "0.7.2"
   resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765"
 
@@ -3775,20 +3361,16 @@ ms@0.7.3:
 ms@2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
 
 mustache@^2.2.1:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/mustache/-/mustache-2.3.0.tgz#4028f7778b17708a489930a6e52ac3bca0da41d0"
 
-mute-stream@0.0.5:
-  version "0.0.5"
-  resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0"
-
 nan@^2.3.0:
   version "2.6.2"
   resolved "https://registry.yarnpkg.com/nan/-/nan-2.6.2.tgz#e4ff34e6c95fdfb5aecc08de6596f43605a7db45"
 
 natural-compare@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
 
@@ -3931,32 +3513,20 @@ number-is-nan@^1.0.0:
 "nwmatcher@>= 1.3.9 < 2.0.0":
   version "1.3.9"
   resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.3.9.tgz#8bab486ff7fa3dfd086656bbe8b17116d3692d2a"
 
 oauth-sign@~0.8.1:
   version "0.8.2"
   resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
 
-object-assign@^4.0.1, object-assign@^4.1.0:
+object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
 
-object-keys@^1.0.10, object-keys@^1.0.8:
-  version "1.0.11"
-  resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d"
-
-object.assign@^4.0.4:
-  version "4.0.4"
-  resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.0.4.tgz#b1c9cc044ef1b9fe63606fc141abbb32e14730cc"
-  dependencies:
-    define-properties "^1.1.2"
-    function-bind "^1.1.0"
-    object-keys "^1.0.10"
-
 object.omit@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa"
   dependencies:
     for-own "^0.1.4"
     is-extendable "^0.1.1"
 
 on-finished@~2.3.0:
@@ -3966,28 +3536,24 @@ on-finished@~2.3.0:
     ee-first "1.1.1"
 
 once@^1.3.0, once@^1.3.3, once@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
   dependencies:
     wrappy "1"
 
-onetime@^1.0.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789"
-
 optimist@>=0.3.4, optimist@^0.6.1:
   version "0.6.1"
   resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
   dependencies:
     minimist "~0.0.1"
     wordwrap "~0.0.2"
 
-optionator@^0.8.1, optionator@^0.8.2:
+optionator@^0.8.1:
   version "0.8.2"
   resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64"
   dependencies:
     deep-is "~0.1.3"
     fast-levenshtein "~2.0.4"
     levn "~0.3.0"
     prelude-ls "~1.1.2"
     type-check "~0.3.2"
@@ -4106,20 +3672,16 @@ path-exists@^2.0.0:
 path-exists@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
 
 path-is-absolute@^1.0.0, path-is-absolute@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
 
-path-is-inside@^1.0.1:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
-
 path-key@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
 
 path-parse@^1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1"
 
@@ -4170,34 +3732,37 @@ pinkie@^2.0.0:
   resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
 
 pkg-dir@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b"
   dependencies:
     find-up "^2.1.0"
 
-pluralize@^1.2.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45"
-
 postcss-bidirection@^2.4.0:
   version "2.4.0"
   resolved "https://registry.yarnpkg.com/postcss-bidirection/-/postcss-bidirection-2.4.0.tgz#7cfa21448630e8f8f705b8342a198de4c104687b"
   dependencies:
     postcss "^5.0.10"
 
 postcss-calc@^5.2.0:
   version "5.3.1"
   resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-5.3.1.tgz#77bae7ca928ad85716e2fda42f261bf7c1d65b5e"
   dependencies:
     postcss "^5.0.2"
     postcss-message-helpers "^2.0.0"
     reduce-css-calc "^1.2.6"
 
+postcss-class-namespace@^0.1.0:
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/postcss-class-namespace/-/postcss-class-namespace-0.1.0.tgz#b5f7be4373c0d1458760b60cab838ca6212362ca"
+  dependencies:
+    lodash "^4.13.1"
+    postcss "^5.0.10"
+
 postcss-colormin@^2.1.8:
   version "2.2.2"
   resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-2.2.2.tgz#6631417d5f0e909a3d7ec26b24c8a8d1e4f96e4b"
   dependencies:
     colormin "^1.0.5"
     postcss "^5.0.13"
     postcss-value-parser "^3.2.3"
 
@@ -4241,16 +3806,23 @@ postcss-discard-unused@^2.2.1:
 
 postcss-filter-plugins@^2.0.0:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/postcss-filter-plugins/-/postcss-filter-plugins-2.0.2.tgz#6d85862534d735ac420e4a85806e1f5d4286d84c"
   dependencies:
     postcss "^5.0.4"
     uniqid "^4.0.0"
 
+postcss-js@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-1.0.1.tgz#ffaf29226e399ea74b5dce02cab1729d7addbc7b"
+  dependencies:
+    camelcase-css "^1.0.1"
+    postcss "^6.0.11"
+
 postcss-load-config@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-1.2.0.tgz#539e9afc9ddc8620121ebf9d8c3673e0ce50d28a"
   dependencies:
     cosmiconfig "^2.1.0"
     object-assign "^4.1.0"
     postcss-load-options "^1.2.0"
     postcss-load-plugins "^2.3.0"
@@ -4429,16 +4001,22 @@ postcss-svgo@^2.1.1:
 postcss-unique-selectors@^2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz#981d57d29ddcb33e7b1dfe1fd43b8649f933ca1d"
   dependencies:
     alphanum-sort "^1.0.1"
     postcss "^5.0.4"
     uniqs "^2.0.0"
 
+postcss-url-mapper@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/postcss-url-mapper/-/postcss-url-mapper-1.2.0.tgz#223f311ec52989d5074101f1576f66975697c27e"
+  dependencies:
+    postcss "^6.0.13"
+
 postcss-value-parser@^3.0.1, postcss-value-parser@^3.0.2, postcss-value-parser@^3.1.1, postcss-value-parser@^3.1.2, postcss-value-parser@^3.2.3, postcss-value-parser@^3.3.0:
   version "3.3.0"
   resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz#87f38f9f18f774a4ab4c8a232f5c5ce8872a9d15"
 
 postcss-zindex@^2.0.1:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-2.2.0.tgz#d2109ddc055b91af67fc4cb3b025946639d2af22"
   dependencies:
@@ -4458,16 +4036,24 @@ postcss@^5.0.10, postcss@^5.0.11, postcs
 postcss@^6.0.11, postcss@^6.0.2, postcss@^6.0.6:
   version "6.0.11"
   resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.11.tgz#f48db210b1d37a7f7ab6499b7a54982997ab6f72"
   dependencies:
     chalk "^2.1.0"
     source-map "^0.5.7"
     supports-color "^4.4.0"
 
+postcss@^6.0.13:
+  version "6.0.13"
+  resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.13.tgz#b9ecab4ee00c89db3ec931145bd9590bbf3f125f"
+  dependencies:
+    chalk "^2.1.0"
+    source-map "^0.6.1"
+    supports-color "^4.4.0"
+
 prelude-ls@~1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
 
 prepend-http@^1.0.0, prepend-http@^1.0.1:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
 
@@ -4488,26 +4074,30 @@ private@^0.1.6, private@^0.1.7:
 process-nextick-args@~1.0.6:
   version "1.0.7"
   resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3"
 
 process@^0.11.0:
   version "0.11.10"
   resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
 
-progress@^1.1.8:
-  version "1.1.8"
-  resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be"
-
 promise@^7.1.1:
   version "7.1.1"
   resolved "https://registry.yarnpkg.com/promise/-/promise-7.1.1.tgz#489654c692616b8aa55b0724fa809bb7db49c5bf"
   dependencies:
     asap "~2.0.3"
 
+prop-types@^15.5.10:
+  version "15.6.0"
+  resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856"
+  dependencies:
+    fbjs "^0.8.16"
+    loose-envify "^1.3.1"
+    object-assign "^4.1.1"
+
 properties-parser@^0.3.1:
   version "0.3.1"
   resolved "https://registry.yarnpkg.com/properties-parser/-/properties-parser-0.3.1.tgz#1316e9539ffbfd93845e369b211022abd478771a"
   dependencies:
     string.prototype.codepointat "^0.2.0"
 
 proxy-addr@~1.1.3:
   version "1.1.4"
@@ -4604,61 +4194,59 @@ rc@^1.1.7:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.1.tgz#2e03e8e42ee450b8cb3dce65be1bf8974e1dfd95"
   dependencies:
     deep-extend "~0.4.0"
     ini "~1.3.0"
     minimist "^1.2.0"
     strip-json-comments "~2.0.1"
 
-react-dom@=15.3.2:
-  version "15.3.2"
-  resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.3.2.tgz#c46b0aa5380d7b838e7a59c4a7beff2ed315531f"
-
-react-hot-api@^0.4.5:
-  version "0.4.7"
-  resolved "https://registry.yarnpkg.com/react-hot-api/-/react-hot-api-0.4.7.tgz#a7e22a56d252e11abd9366b61264cf4492c58171"
-
-react-hot-loader@^1.3.1:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-1.3.1.tgz#c95647ae78b73dfceff6ec71ffcb04182ff6daf9"
-  dependencies:
-    react-hot-api "^0.4.5"
-    source-map "^0.4.4"
+react-dom@=15.6.1:
+  version "15.6.1"
+  resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.6.1.tgz#2cb0ed4191038e53c209eb3a79a23e2a4cf99470"
+  dependencies:
+    fbjs "^0.8.9"
+    loose-envify "^1.1.0"
+    object-assign "^4.1.0"
+    prop-types "^15.5.10"
 
 react-immutable-proptypes@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/react-immutable-proptypes/-/react-immutable-proptypes-2.1.0.tgz#023d6f39bb15c97c071e9e60d00d136eac5fa0b4"
 
-react-redux@4.4.5:
-  version "4.4.5"
-  resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-4.4.5.tgz#f509a2981be2252d10c629ef7c559347a4aec457"
-  dependencies:
-    hoist-non-react-statics "^1.0.3"
-    invariant "^2.0.0"
-    lodash "^4.2.0"
-    loose-envify "^1.1.0"
-
 react-redux@=5.0.3:
   version "5.0.3"
   resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.0.3.tgz#86c3b68d56e74294a42e2a740ab66117ef6c019f"
   dependencies:
     hoist-non-react-statics "^1.0.3"
     invariant "^2.0.0"
     lodash "^4.2.0"
     lodash-es "^4.2.0"
     loose-envify "^1.1.0"
 
-react@=15.3.2:
-  version "15.3.2"
-  resolved "https://registry.yarnpkg.com/react/-/react-15.3.2.tgz#a7bccd2fee8af126b0317e222c28d1d54528d09e"
-  dependencies:
-    fbjs "^0.8.4"
+react-redux@^5.0.6:
+  version "5.0.6"
+  resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.0.6.tgz#23ed3a4f986359d68b5212eaaa681e60d6574946"
+  dependencies:
+    hoist-non-react-statics "^2.2.1"
+    invariant "^2.0.0"
+    lodash "^4.2.0"
+    lodash-es "^4.2.0"
+    loose-envify "^1.1.0"
+    prop-types "^15.5.10"
+
+react@=15.6.1:
+  version "15.6.1"
+  resolved "https://registry.yarnpkg.com/react/-/react-15.6.1.tgz#baa8434ec6780bde997cdc380b79cd33b96393df"
+  dependencies:
+    create-react-class "^15.6.0"
+    fbjs "^0.8.9"
     loose-envify "^1.1.0"
     object-assign "^4.1.0"
+    prop-types "^15.5.10"
 
 read-all-stream@^3.0.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/read-all-stream/-/read-all-stream-3.1.0.tgz#35c3e177f2078ef789ee4bfafa4373074eaef4fa"
   dependencies:
     pinkie-promise "^2.0.0"
     readable-stream "^2.0.0"
 
@@ -4687,17 +4275,17 @@ read-pkg@^1.0.0:
 read-pkg@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8"
   dependencies:
     load-json-file "^2.0.0"
     normalize-package-data "^2.3.2"
     path-type "^2.0.0"
 
-readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.2.2, readable-stream@^2.2.6:
+readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.2.6:
   version "2.2.9"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.9.tgz#cf78ec6f4a6d1eb43d26488cac97f042e74b7fc8"
   dependencies:
     buffer-shims "~1.0.0"
     core-util-is "~1.0.0"
     inherits "~2.0.1"
     isarray "~1.0.0"
     process-nextick-args "~1.0.6"
@@ -4719,105 +4307,75 @@ readdirp@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78"
   dependencies:
     graceful-fs "^4.1.2"
     minimatch "^3.0.2"
     readable-stream "^2.0.2"
     set-immediate-shim "^1.0.1"
 
-readline2@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/readline2/-/readline2-1.0.1.tgz#41059608ffc154757b715d9989d199ffbf372e35"
-  dependencies:
-    code-point-at "^1.0.0"
-    is-fullwidth-code-point "^1.0.0"
-    mute-stream "0.0.5"
-
-rechoir@^0.6.2:
-  version "0.6.2"
-  resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384"
-  dependencies:
-    resolve "^1.1.6"
-
 reduce-css-calc@^1.2.6:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz#747c914e049614a4c9cfbba629871ad1d2927716"
   dependencies:
     balanced-match "^0.4.2"
     math-expression-evaluator "^1.2.14"
     reduce-function-call "^1.0.1"
 
 reduce-function-call@^1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/reduce-function-call/-/reduce-function-call-1.0.2.tgz#5a200bf92e0e37751752fe45b0ab330fd4b6be99"
   dependencies:
     balanced-match "^0.4.2"
 
-redux@3.5.2:
-  version "3.5.2"
-  resolved "https://registry.yarnpkg.com/redux/-/redux-3.5.2.tgz#4533745e970b647ec26066a83aa30e9e26faf843"
-  dependencies:
-    lodash "^4.2.1"
-    lodash-es "^4.2.1"
-    loose-envify "^1.1.0"
-    symbol-observable "^0.2.3"
-
 redux@^3.6.0:
   version "3.6.0"
   resolved "https://registry.yarnpkg.com/redux/-/redux-3.6.0.tgz#887c2b3d0b9bd86eca2be70571c27654c19e188d"
   dependencies:
     lodash "^4.2.1"
     lodash-es "^4.2.1"
     loose-envify "^1.1.0"
     symbol-observable "^1.0.2"
 
+redux@^3.7.2:
+  version "3.7.2"
+  resolved "https://registry.yarnpkg.com/redux/-/redux-3.7.2.tgz#06b73123215901d25d065be342eb026bc1c8537b"
+  dependencies:
+    lodash "^4.2.1"
+    lodash-es "^4.2.1"
+    loose-envify "^1.1.0"
+    symbol-observable "^1.0.3"
+
 regenerate@^1.2.1:
   version "1.3.2"
   resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.2.tgz#d1941c67bad437e1be76433add5b385f95b19260"
 
 regenerator-runtime@^0.10.0:
   version "0.10.4"
   resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.4.tgz#74cb6598d3ba2eb18694e968a40e2b3b4df9cf93"
 
 regenerator-runtime@^0.11.0:
   version "0.11.0"
   resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz#7e54fe5b5ccd5d6624ea6255c3473be090b802e1"
 
-regenerator-transform@0.9.11:
-  version "0.9.11"
-  resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.9.11.tgz#3a7d067520cb7b7176769eb5ff868691befe1283"
-  dependencies:
-    babel-runtime "^6.18.0"
-    babel-types "^6.19.0"
-    private "^0.1.6"
-
 regex-cache@^0.4.2:
   version "0.4.3"
   resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.3.tgz#9b1a6c35d4d0dfcef5711ae651e8e9d3d7114145"
   dependencies:
     is-equal-shallow "^0.1.3"
     is-primitive "^2.0.0"
 
 regexpu-core@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-1.0.0.tgz#86a763f58ee4d7c2f6b102e4764050de7ed90c6b"
   dependencies:
     regenerate "^1.2.1"
     regjsgen "^0.2.0"
     regjsparser "^0.1.4"
 
-regexpu-core@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240"
-  dependencies:
-    regenerate "^1.2.1"
-    regjsgen "^0.2.0"
-    regjsparser "^0.1.4"
-
 regjsgen@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7"
 
 regjsparser@^0.1.4:
   version "0.1.5"
   resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c"
   dependencies:
@@ -4875,81 +4433,53 @@ require-directory@^2.1.1:
 require-from-string@^1.1.0:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-1.2.1.tgz#529c9ccef27380adfec9a2f965b649bbee636418"
 
 require-main-filename@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
 
-require-uncached@^1.0.2:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3"
-  dependencies:
-    caller-path "^0.1.0"
-    resolve-from "^1.0.0"
-
 reselect@^2.5.4:
   version "2.5.4"
   resolved "https://registry.yarnpkg.com/reselect/-/reselect-2.5.4.tgz#b7d23fdf00b83fa7ad0279546f8dbbbd765c7047"
 
-resolve-from@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226"
-
 resolve@1.1.7:
   version "1.1.7"
   resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
 
-resolve@^1.1.6, resolve@^1.2.0:
+resolve@^1.2.0:
   version "1.3.3"
   resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.3.3.tgz#655907c3469a8680dc2de3a275a8fdd69691f0e5"
   dependencies:
     path-parse "^1.0.5"
 
-restore-cursor@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541"
-  dependencies:
-    exit-hook "^1.0.0"
-    onetime "^1.0.0"
-
 right-align@^0.1.1:
   version "0.1.3"
   resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef"
   dependencies:
     align-text "^0.1.1"
 
-rimraf@2, rimraf@^2.2.8, rimraf@^2.4.4, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6.1:
+rimraf@2, rimraf@^2.4.4, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6.1:
   version "2.6.1"
   resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.1.tgz#c2338ec643df7a1b7fe5c54fa86f57428a55f33d"
   dependencies:
     glob "^7.0.5"
 
 ripemd160@^2.0.0, ripemd160@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.1.tgz#0f4584295c53a3628af7e6d79aca21ce57d1c6e7"
   dependencies:
     hash-base "^2.0.0"
     inherits "^2.0.1"
 
-run-async@^0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/run-async/-/run-async-0.1.0.tgz#c8ad4a5e110661e402a7d21b530e009f25f8e389"
-  dependencies:
-    once "^1.3.0"
-
 run-parallel@^1.1.4:
   version "1.1.6"
   resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.6.tgz#29003c9a2163e01e2d2dfc90575f2c6c1d61a039"
 
-rx-lite@^3.1.2:
-  version "3.1.2"
-  resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102"
-
 safe-buffer@^5.0.1:
   version "5.0.1"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7"
 
 safe-buffer@^5.1.0, safe-buffer@^5.1.1:
   version "5.1.1"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
 
@@ -4960,17 +4490,17 @@ sane@~1.5.0:
     anymatch "^1.3.0"
     exec-sh "^0.2.0"
     fb-watchman "^1.8.0"
     minimatch "^3.0.2"
     minimist "^1.1.1"
     walker "~1.0.5"
     watch "~0.10.0"
 
-sax@>=0.6.0, sax@^1.1.4, sax@^1.2.1, sax@~1.2.1:
+sax@>=0.6.0, sax@^1.2.1, sax@~1.2.1:
   version "1.2.2"
   resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.2.tgz#fd8631a23bc7826bef5d871bdb87378c95647828"
 
 schema-utils@^0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.3.0.tgz#f5877222ce3e931edae039f17eb3716e7137f8cf"
   dependencies:
     ajv "^5.0.0"
@@ -5042,40 +4572,32 @@ shebang-command@^1.2.0:
   resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
   dependencies:
     shebang-regex "^1.0.0"
 
 shebang-regex@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
 
-shelljs@^0.7.5:
-  version "0.7.7"
-  resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.7.tgz#b2f5c77ef97148f4b4f6e22682e10bba8667cff1"
-  dependencies:
-    glob "^7.0.0"
-    interpret "^1.0.0"
-    rechoir "^0.6.2"
-
 shellwords@^0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.0.tgz#66afd47b6a12932d9071cbfd98a52e785cd0ba14"
 
 signal-exit@^3.0.0:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
 
+simple-html-tokenizer@^0.1.1:
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/simple-html-tokenizer/-/simple-html-tokenizer-0.1.1.tgz#05c2eec579ffffe145a030ac26cfea61b980fabe"
+
 slash@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
 
-slice-ansi@0.0.4:
-  version "0.0.4"
-  resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35"
-
 sntp@1.x.x:
   version "1.0.9"
   resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198"
   dependencies:
     hoek "2.x.x"
 
 sort-keys@^1.0.0:
   version "1.1.2"
@@ -5112,16 +4634,20 @@ source-map@^0.4.4:
 source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1, source-map@~0.5.3:
   version "0.5.6"
   resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
 
 source-map@^0.5.7:
   version "0.5.7"
   resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
 
+source-map@^0.6.1:
+  version "0.6.1"
+  resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
+
 source-map@~0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.2.0.tgz#dab73fbcfc2ba819b4de03bd6f6eaa48164b3f9d"
   dependencies:
     amdefine ">=0.0.4"
 
 spdx-correct@~1.0.0:
   version "1.0.2"
@@ -5246,73 +4772,76 @@ strip-json-comments@^2.0.1, strip-json-c
 
 style-loader@^0.18.2:
   version "0.18.2"
   resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.18.2.tgz#cc31459afbcd6d80b7220ee54b291a9fd66ff5eb"
   dependencies:
     loader-utils "^1.0.2"
     schema-utils "^0.3.0"
 
+supports-color@3.1.2:
+  version "3.1.2"
+  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5"
+  dependencies:
+    has-flag "^1.0.0"
+
 supports-color@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
 
 supports-color@^3.1.0, supports-color@^3.1.2, supports-color@^3.2.3:
   version "3.2.3"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6"
   dependencies:
     has-flag "^1.0.0"
 
 supports-color@^4.0.0, supports-color@^4.2.1, supports-color@^4.4.0:
   version "4.4.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.4.0.tgz#883f7ddabc165142b2a61427f3352ded195d1a3e"
   dependencies:
     has-flag "^2.0.0"
 
+svg-inline-loader@^0.8.0:
+  version "0.8.0"
+  resolved "https://registry.yarnpkg.com/svg-inline-loader/-/svg-inline-loader-0.8.0.tgz#7e9d905d80d0b4e68d2df21afcd08ee9e9a3ea6e"
+  dependencies:
+    loader-utils "^0.2.11"
+    object-assign "^4.0.1"
+    simple-html-tokenizer "^0.1.1"
+
+svg-inline-react@1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/svg-inline-react/-/svg-inline-react-1.0.2.tgz#138dfd0eb7ac52d689c3aa0a10d1d0b134a7d081"
+
 svgo@^0.7.0:
   version "0.7.2"
   resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5"
   dependencies:
     coa "~1.0.1"
     colors "~1.1.2"
     csso "~2.3.1"
     js-yaml "~3.7.0"
     mkdirp "~0.5.1"
     sax "~1.2.1"
     whet.extend "~0.9.9"
 
-symbol-observable@^0.2.3:
-  version "0.2.4"
-  resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-0.2.4.tgz#95a83db26186d6af7e7a18dbd9760a2f86d08f40"
-
-symbol-observable@^1.0.2:
+symbol-observable@^1.0.2, symbol-observable@^1.0.3:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.4.tgz#29bf615d4aa7121bdd898b22d4b3f9bc4e2aa03d"
 
 symbol-tree@^3.2.1:
   version "3.2.2"
   resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6"
 
 table-parser@^0.1.3:
   version "0.1.3"
   resolved "https://registry.yarnpkg.com/table-parser/-/table-parser-0.1.3.tgz#0441cfce16a59481684c27d1b5a67ff15a43c7b0"
   dependencies:
     connected-domain "^1.0.0"
 
-table@^3.7.8:
-  version "3.8.3"
-  resolved "https://registry.yarnpkg.com/table/-/table-3.8.3.tgz#2bbc542f0fda9861a755d3947fefd8b3f513855f"
-  dependencies:
-    ajv "^4.7.0"
-    ajv-keywords "^1.0.0"
-    chalk "^1.1.1"
-    lodash "^4.0.0"
-    slice-ansi "0.0.4"
-    string-width "^2.0.0"
-
 tapable@^0.2.7, tapable@~0.2.5:
   version "0.2.8"
   resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.8.tgz#99372a5c999bf2df160afc0d74bed4f47948cd22"
 
 tar-pack@^3.4.0:
   version "3.4.0"
   resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.0.tgz#23be2d7f671a8339376cbdb0b8fe3fdebf317984"
   dependencies:
@@ -5348,28 +4877,20 @@ test-exclude@^4.0.3:
   resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.0.3.tgz#86a13ce3effcc60e6c90403cf31a27a60ac6c4e7"
   dependencies:
     arrify "^1.0.1"
     micromatch "^2.3.11"
     object-assign "^4.1.0"
     read-pkg-up "^1.0.1"
     require-main-filename "^1.0.1"
 
-text-table@~0.2.0:
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
-
 throat@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/throat/-/throat-3.0.0.tgz#e7c64c867cbb3845f10877642f7b60055b8ec0d6"
 
-through@^2.3.6:
-  version "2.3.8"
-  resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
-
 time-stamp@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-2.0.0.tgz#95c6a44530e15ba8d6f4a3ecb8c3a3fac46da357"
 
 timed-out@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-2.0.0.tgz#f38b0ae81d3747d628001f41dafc652ace671c0a"
 
@@ -5410,20 +4931,16 @@ tough-cookie@^2.3.2, tough-cookie@~2.3.0
 tr46@~0.0.3:
   version "0.0.3"
   resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
 
 trim-right@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
 
-tryit@^1.0.1:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/tryit/-/tryit-1.0.3.tgz#393be730a9446fd1ead6da59a014308f36c289cb"
-
 tty-browserify@0.0.0:
   version "0.0.0"
   resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"
 
 tunnel-agent@^0.6.0:
   version "0.6.0"
   resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
   dependencies:
@@ -5441,20 +4958,16 @@ type-check@~0.3.2:
 
 type-is@~1.6.14:
   version "1.6.15"
   resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410"
   dependencies:
     media-typer "0.3.0"
     mime-types "~2.1.15"
 
-typedarray@^0.0.6:
-  version "0.0.6"
-  resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
-
 ua-parser-js@^0.7.9:
   version "0.7.12"
   resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.12.tgz#04c81a99bdd5dc52263ea29d24c6bf8d4818a4bb"
 
 uglify-js@^2.6:
   version "2.7.5"
   resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.7.5.tgz#4612c0c7baaee2ba7c487de4904ae122079f2ca8"
   dependencies:
@@ -5533,22 +5046,16 @@ url@^0.11.0:
   dependencies:
     punycode "1.3.2"
     querystring "0.2.0"
 
 user-home@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/user-home/-/user-home-1.1.1.tgz#2b5be23a32b63a7c9deb8d0f28d485724a3df190"
 
-user-home@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/user-home/-/user-home-2.0.0.tgz#9c70bfd8169bc1dcbf48604e0f04b8b49cde9e9f"
-  dependencies:
-    os-homedir "^1.0.0"
-
 util-deprecate@~1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
 
 util@0.10.3, util@^0.10.3:
   version "0.10.3"
   resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9"
   dependencies:
@@ -5678,17 +5185,17 @@ webpack@^2.2.1:
     source-map "^0.5.3"
     supports-color "^3.1.0"
     tapable "~0.2.5"
     uglify-js "^2.8.27"
     watchpack "^1.3.1"
     webpack-sources "^1.0.1"
     yargs "^6.0.0"
 
-webpack@^3.3.0, webpack@^3.5.6:
+webpack@^3.3.0:
   version "3.5.6"
   resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.5.6.tgz#a492fb6c1ed7f573816f90e00c8fbb5a20cc5c36"
   dependencies:
     acorn "^5.0.0"
     acorn-dynamic-import "^2.0.0"
     ajv "^5.1.5"
     ajv-keywords "^2.0.0"
     async "^2.1.2"
@@ -5780,22 +5287,16 @@ wrap-ansi@^2.0.0:
   dependencies:
     string-width "^1.0.1"
     strip-ansi "^3.0.1"
 
 wrappy@1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
 
-write@^0.2.1:
-  version "0.2.1"
-  resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757"
-  dependencies:
-    mkdirp "^0.5.1"
-
 ws@1.1.x, ws@^1.0.1:
   version "1.1.4"
   resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.4.tgz#57f40d036832e5f5055662a397c4de76ed66bf61"
   dependencies:
     options ">=0.0.5"
     ultron "1.0.x"
 
 xml-name-validator@^2.0.1:
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
@@ -191,17 +191,23 @@ skip-if = true # Bug 1406060
 skip-if = true # Bug 1406060
 [browser_console_webconsole_iframe_messages.js]
 skip-if = true # Bug 1406060
 [browser_console_webconsole_private_browsing.js]
 skip-if = true #	Bug 1403188
 # old console skip-if = e10s # Bug 1042253 - webconsole e10s tests
 [browser_jsterm_accessibility.js]
 [browser_jsterm_add_edited_input_to_history.js]
+[browser_jsterm_autocomplete_array_no_index.js]
+[browser_jsterm_autocomplete_escape_key.js]
 [browser_jsterm_autocomplete_helpers.js]
+[browser_jsterm_autocomplete_inside_text.js]
+[browser_jsterm_autocomplete_nav_and_tab_key.js]
+[browser_jsterm_autocomplete_return_key_no_selection.js]
+[browser_jsterm_autocomplete_return_key.js]
 [browser_jsterm_autocomplete-properties-with-non-alphanumeric-names.js]
 [browser_jsterm_copy_command.js]
 [browser_jsterm_dollar.js]
 [browser_jsterm_history_persist.js]
 [browser_jsterm_inspect.js]
 [browser_jsterm_no_autocompletion_on_defined_variables.js]
 [browser_jsterm_no_input_and_tab_key_pressed.js]
 [browser_jsterm_no_input_change_and_tab_key_pressed.js]
@@ -213,18 +219,16 @@ skip-if = true #	Bug 1403452
 [browser_webconsole_autocomplete_and_selfxss.js]
 subsuite = clipboard
 skip-if = true #	Bug 1404850
 # old console skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_webconsole_autocomplete_crossdomain_iframe.js]
 skip-if = true # Bug 1408919
 [browser_webconsole_autocomplete_in_debugger_stackframe.js]
 skip-if = true # Bug 1408920
-[browser_webconsole_autocomplete_keys.js]
-skip-if = true # Bug 1408921
 [browser_webconsole_autocomplete_popup.js]
 skip-if = true # Bug 1408922
 [browser_webconsole_autocomplete_popup_close_on_tab_switch.js]
 skip-if = true # Bug 1408923
 [browser_webconsole_batching.js]
 [browser_webconsole_block_mixedcontent_securityerrors.js]
 tags = mcb
 skip-if = true #	Bug 1403899
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_add_edited_input_to_history.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_add_edited_input_to_history.js
@@ -6,22 +6,19 @@
 // Test that user input that is not submitted in the command line input is not
 // lost after navigating in history.
 // See https://bugzilla.mozilla.org/show_bug.cgi?id=817834
 
 "use strict";
 
 const TEST_URI = "data:text/html;charset=utf-8,Web Console test for bug 817834";
 
-add_task(function* () {
-  let hud = yield openNewTabAndConsole(TEST_URI);
-  // Clearing history that might have been set in previous tests.
-  yield hud.jsterm.clearHistory();
+add_task(async function () {
+  let hud = await openNewTabAndConsole(TEST_URI);
   testEditedInputHistory(hud);
-  yield hud.jsterm.clearHistory();
 });
 
 function testEditedInputHistory(hud) {
   let jsterm = hud.jsterm;
   let inputNode = jsterm.inputNode;
 
   ok(!jsterm.getInputValue(), "jsterm.getInputValue() is empty");
   is(inputNode.selectionStart, 0);
rename from devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_autocomplete_keys.js
rename to devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_autocomplete_array_no_index.js
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_autocomplete_keys.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_autocomplete_array_no_index.js
@@ -2,368 +2,42 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // See Bug 585991.
 
-const TEST_URI = "data:text/html;charset=utf-8,<p>bug 585991 - autocomplete " +
-                 "popup keyboard usage test";
-
-// We should turn off auto-multiline editing during these tests
-const PREF_AUTO_MULTILINE = "devtools.webconsole.autoMultiline";
-var HUD, popup, jsterm, inputNode, completeNode;
-
-add_task(function* () {
-  Services.prefs.setBoolPref(PREF_AUTO_MULTILINE, false);
-  yield loadTab(TEST_URI);
-  let hud = yield openConsole();
-
-  yield consoleOpened(hud);
-  yield popupHideAfterTab();
-  yield testReturnKey();
-  yield dontShowArrayNumbers();
-  yield testReturnWithNoSelection();
-  yield popupHideAfterReturnWithNoSelection();
-  yield testCompletionInText();
-  yield popupHideAfterCompletionInText();
-
-  HUD = popup = jsterm = inputNode = completeNode = null;
-  Services.prefs.setBoolPref(PREF_AUTO_MULTILINE, true);
-});
+const TEST_URI = `data:text/html;charset=utf-8,
+<head>
+  <script>
+    window.foo = [1,2,3];
+  </script>
+</head>
+<body>bug 585991 - Autocomplete popup on array</body>`;
 
-var consoleOpened = Task.async(function* (hud) {
-  let deferred = defer();
-  HUD = hud;
-  info("web console opened");
-
-  jsterm = HUD.jsterm;
-
-  yield jsterm.execute("window.foobarBug585991={" +
-    "'item0': 'value0'," +
-    "'item1': 'value1'," +
-    "'item2': 'value2'," +
-    "'item3': 'value3'" +
-  "}");
-  yield jsterm.execute("window.testBug873250a = 'hello world';"
-    + "window.testBug873250b = 'hello world 2';");
-  popup = jsterm.autocompletePopup;
-  completeNode = jsterm.completeNode;
-  inputNode = jsterm.inputNode;
-
-  ok(!popup.isOpen, "popup is not open");
-
-  popup.once("popup-opened", () => {
-    ok(popup.isOpen, "popup is open");
-
-    // 4 values, and the following properties:
-    // __defineGetter__  __defineSetter__ __lookupGetter__ __lookupSetter__
-    // __proto__ hasOwnProperty isPrototypeOf propertyIsEnumerable
-    // toLocaleString toString toSource unwatch valueOf watch constructor.
-    is(popup.itemCount, 19, "popup.itemCount is correct");
-
-    let sameItems = popup.getItems().reverse().map(function (e) {
-      return e.label;
-    });
+add_task(async function () {
+  let { jsterm } = await openNewTabAndConsole(TEST_URI);
 
-    ok(sameItems.every(function (prop, index) {
-      return [
-        "__defineGetter__",
-        "__defineSetter__",
-        "__lookupGetter__",
-        "__lookupSetter__",
-        "__proto__",
-        "constructor",
-        "hasOwnProperty",
-        "isPrototypeOf",
-        "item0",
-        "item1",
-        "item2",
-        "item3",
-        "propertyIsEnumerable",
-        "toLocaleString",
-        "toSource",
-        "toString",
-        "unwatch",
-        "valueOf",
-        "watch",
-      ][index] === prop;
-    }), "getItems returns the items we expect");
-
-    is(popup.selectedIndex, 18,
-       "Index of the first item from bottom is selected.");
-    EventUtils.synthesizeKey("VK_DOWN", {});
-
-    let prefix = jsterm.getInputValue().replace(/[\S]/g, " ");
-
-    is(popup.selectedIndex, 0, "index 0 is selected");
-    is(popup.selectedItem.label, "watch", "watch is selected");
-    is(completeNode.value, prefix + "watch",
-        "completeNode.value holds watch");
-
-    EventUtils.synthesizeKey("VK_DOWN", {});
+  const {
+    autocompletePopup: popup,
+    completeNode,
+    inputNode,
+  } = jsterm;
 
-    is(popup.selectedIndex, 1, "index 1 is selected");
-    is(popup.selectedItem.label, "valueOf", "valueOf is selected");
-    is(completeNode.value, prefix + "valueOf",
-        "completeNode.value holds valueOf");
-
-    EventUtils.synthesizeKey("VK_UP", {});
-
-    is(popup.selectedIndex, 0, "index 0 is selected");
-    is(popup.selectedItem.label, "watch", "watch is selected");
-    is(completeNode.value, prefix + "watch",
-        "completeNode.value holds watch");
-
-    let currentSelectionIndex = popup.selectedIndex;
-
-    EventUtils.synthesizeKey("VK_PAGE_DOWN", {});
-
-    ok(popup.selectedIndex > currentSelectionIndex,
-      "Index is greater after PGDN");
+  let onPopUpOpen = popup.once("popup-opened");
 
-    currentSelectionIndex = popup.selectedIndex;
-    EventUtils.synthesizeKey("VK_PAGE_UP", {});
-
-    ok(popup.selectedIndex < currentSelectionIndex,
-       "Index is less after Page UP");
-
-    EventUtils.synthesizeKey("VK_END", {});
-    is(popup.selectedIndex, 18, "index is last after End");
-
-    EventUtils.synthesizeKey("VK_HOME", {});
-    is(popup.selectedIndex, 0, "index is first after Home");
-
-    info("press Tab and wait for popup to hide");
-    popup.once("popup-closed", () => {
-      deferred.resolve();
-    });
-    EventUtils.synthesizeKey("VK_TAB", {});
-  });
-
-  jsterm.setInputValue("window.foobarBug585991");
+  info("wait for popup to show");
+  jsterm.setInputValue("foo");
   EventUtils.synthesizeKey(".", {});
 
-  return deferred.promise;
-});
-
-function popupHideAfterTab() {
-  let deferred = defer();
-
-  // At this point the completion suggestion should be accepted.
-  ok(!popup.isOpen, "popup is not open");
-
-  is(jsterm.getInputValue(), "window.foobarBug585991.watch",
-     "completion was successful after VK_TAB");
-
-  ok(!completeNode.value, "completeNode is empty");
-
-  popup.once("popup-opened", function onShown() {
-    ok(popup.isOpen, "popup is open");
-
-    is(popup.itemCount, 19, "popup.itemCount is correct");
-
-    is(popup.selectedIndex, 18, "First index from bottom is selected");
-    EventUtils.synthesizeKey("VK_DOWN", {});
-
-    let prefix = jsterm.getInputValue().replace(/[\S]/g, " ");
-
-    is(popup.selectedIndex, 0, "index 0 is selected");
-    is(popup.selectedItem.label, "watch", "watch is selected");
-    is(completeNode.value, prefix + "watch",
-        "completeNode.value holds watch");
-
-    popup.once("popup-closed", function onHidden() {
-      ok(!popup.isOpen, "popup is not open after VK_ESCAPE");
-
-      is(jsterm.getInputValue(), "window.foobarBug585991.",
-         "completion was cancelled");
-
-      ok(!completeNode.value, "completeNode is empty");
-
-      deferred.resolve();
-    }, false);
-
-    info("press Escape to close the popup");
-    executeSoon(function () {
-      EventUtils.synthesizeKey("VK_ESCAPE", {});
-    });
-  }, false);
-
-  info("wait for completion: window.foobarBug585991.");
-  executeSoon(function () {
-    jsterm.setInputValue("window.foobarBug585991");
-    EventUtils.synthesizeKey(".", {});
-  });
-
-  return deferred.promise;
-}
+  await onPopUpOpen;
 
-function testReturnKey() {
-  let deferred = defer();
-
-  popup.once("popup-opened", function onShown() {
-    ok(popup.isOpen, "popup is open");
-
-    is(popup.itemCount, 19, "popup.itemCount is correct");
-
-    is(popup.selectedIndex, 18, "First index from bottom is selected");
-    EventUtils.synthesizeKey("VK_DOWN", {});
-
-    let prefix = jsterm.getInputValue().replace(/[\S]/g, " ");
-
-    is(popup.selectedIndex, 0, "index 0 is selected");
-    is(popup.selectedItem.label, "watch", "watch is selected");
-    is(completeNode.value, prefix + "watch",
-        "completeNode.value holds watch");
-
-    EventUtils.synthesizeKey("VK_DOWN", {});
-
-    is(popup.selectedIndex, 1, "index 1 is selected");
-    is(popup.selectedItem.label, "valueOf", "valueOf is selected");
-    is(completeNode.value, prefix + "valueOf",
-       "completeNode.value holds valueOf");
-
-    popup.once("popup-closed", function onHidden() {
-      ok(!popup.isOpen, "popup is not open after VK_RETURN");
-
-      is(jsterm.getInputValue(), "window.foobarBug585991.valueOf",
-         "completion was successful after VK_RETURN");
-
-      ok(!completeNode.value, "completeNode is empty");
-
-      deferred.resolve();
-    }, false);
-
-    info("press Return to accept suggestion. wait for popup to hide");
-
-    executeSoon(() => EventUtils.synthesizeKey("VK_RETURN", {}));
-  }, false);
-
-  info("wait for completion suggestions: window.foobarBug585991.");
-
-  executeSoon(function () {
-    jsterm.setInputValue("window.foobarBug58599");
-    EventUtils.synthesizeKey("1", {});
-    EventUtils.synthesizeKey(".", {});
-  });
-
-  return deferred.promise;
-}
-
-function* dontShowArrayNumbers() {
-  let deferred = defer();
+  let popupItems = popup.getItems().map(e => e.label);
+  is(popupItems.includes("0"), false, "Completing on an array doesn't show numbers.");
 
-  info("dontShowArrayNumbers");
-  yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
-    content.wrappedJSObject.foobarBug585991 = ["Sherlock Holmes"];
-  });
-
-  jsterm = HUD.jsterm;
-  popup = jsterm.autocompletePopup;
-
-  popup.once("popup-opened", function onShown() {
-    let sameItems = popup.getItems().map(function (e) {
-      return e.label;
-    });
-    ok(!sameItems.some(function (prop) {
-      prop === "0";
-    }), "Completing on an array doesn't show numbers.");
-
-    popup.once("popup-closed", function popupHidden() {
-      deferred.resolve();
-    }, false);
-
-    info("wait for popup to hide");
-    executeSoon(() => EventUtils.synthesizeKey("VK_ESCAPE", {}));
-  }, false);
-
-  info("wait for popup to show");
-  executeSoon(() => {
-    jsterm.setInputValue("window.foobarBug585991");
-    EventUtils.synthesizeKey(".", {});
-  });
-
-  return deferred.promise;
-}
-
-function testReturnWithNoSelection() {
-  let deferred = defer();
-
-  info("test pressing return with open popup, but no selection, see bug 873250");
-
-  popup.once("popup-opened", function onShown() {
-    ok(popup.isOpen, "popup is open");
-    is(popup.itemCount, 2, "popup.itemCount is correct");
-    isnot(popup.selectedIndex, -1, "popup.selectedIndex is correct");
-
-    info("press Return and wait for popup to hide");
-    popup.once("popup-closed", function popupHidden() {
-      deferred.resolve();
-    });
-    executeSoon(() => EventUtils.synthesizeKey("VK_RETURN", {}));
-  });
-
-  executeSoon(() => {
-    info("wait for popup to show");
-    jsterm.setInputValue("window.testBu");
-    EventUtils.synthesizeKey("g", {});
-  });
+  info("press Escape to close the popup");
+  const onPopupClose = popup.once("popup-closed");
+  EventUtils.synthesizeKey("VK_ESCAPE", {});
 
-  return deferred.promise;
-}
-
-function popupHideAfterReturnWithNoSelection() {
-  ok(!popup.isOpen, "popup is not open after VK_RETURN");
-
-  is(jsterm.getInputValue(), "", "inputNode is empty after VK_RETURN");
-  is(completeNode.value, "", "completeNode is empty");
-  is(jsterm.history[jsterm.history.length - 1], "window.testBug",
-     "jsterm history is correct");
-
-  return promise.resolve();
-}
-
-function testCompletionInText() {
-  info("test that completion works inside text, see bug 812618");
-
-  let deferred = defer();
-
-  popup.once("popup-opened", function onShown() {
-    ok(popup.isOpen, "popup is open");
-    is(popup.itemCount, 2, "popup.itemCount is correct");
-
-    EventUtils.synthesizeKey("VK_DOWN", {});
-    is(popup.selectedIndex, 0, "popup.selectedIndex is correct");
-    ok(!completeNode.value, "completeNode.value is empty");
-
-    let items = popup.getItems().reverse().map(e => e.label);
-    let sameItems = items.every((prop, index) =>
-      ["testBug873250a", "testBug873250b"][index] === prop);
-    ok(sameItems, "getItems returns the items we expect");
-
-    info("press Tab and wait for popup to hide");
-    popup.once("popup-closed", function popupHidden() {
-      deferred.resolve();
-    });
-    EventUtils.synthesizeKey("VK_TAB", {});
-  });
-
-  jsterm.setInputValue("dump(window.testBu)");
-  inputNode.selectionStart = inputNode.selectionEnd = 18;
-  EventUtils.synthesizeKey("g", {});
-  return deferred.promise;
-}
-
-function popupHideAfterCompletionInText() {
-  // At this point the completion suggestion should be accepted.
-  ok(!popup.isOpen, "popup is not open");
-  is(jsterm.getInputValue(), "dump(window.testBug873250b)",
-     "completion was successful after VK_TAB");
-  is(inputNode.selectionStart, 26, "cursor location is correct");
-  is(inputNode.selectionStart, inputNode.selectionEnd,
-     "cursor location (confirmed)");
-  ok(!completeNode.value, "completeNode is empty");
-
-  return promise.resolve();
-}
+  await onPopupClose;
+});
copy from devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_autocomplete_keys.js
copy to devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_autocomplete_escape_key.js
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_autocomplete_keys.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_autocomplete_escape_key.js
@@ -2,368 +2,54 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // See Bug 585991.
 
-const TEST_URI = "data:text/html;charset=utf-8,<p>bug 585991 - autocomplete " +
-                 "popup keyboard usage test";
-
-// We should turn off auto-multiline editing during these tests
-const PREF_AUTO_MULTILINE = "devtools.webconsole.autoMultiline";
-var HUD, popup, jsterm, inputNode, completeNode;
-
-add_task(function* () {
-  Services.prefs.setBoolPref(PREF_AUTO_MULTILINE, false);
-  yield loadTab(TEST_URI);
-  let hud = yield openConsole();
+const TEST_URI = `data:text/html;charset=utf-8,
+<head>
+  <script>
+    /* Create a prototype-less object so popup does not contain native
+     * Object prototype properties.
+     */
+    window.foo = Object.create(null);
+    Object.assign(window.foo, {
+      item0: "value0",
+      item1: "value1",
+    });
+  </script>
+</head>
+<body>bug 585991 - autocomplete popup escape key usage test</body>`;
 
-  yield consoleOpened(hud);
-  yield popupHideAfterTab();
-  yield testReturnKey();
-  yield dontShowArrayNumbers();
-  yield testReturnWithNoSelection();
-  yield popupHideAfterReturnWithNoSelection();
-  yield testCompletionInText();
-  yield popupHideAfterCompletionInText();
-
-  HUD = popup = jsterm = inputNode = completeNode = null;
-  Services.prefs.setBoolPref(PREF_AUTO_MULTILINE, true);
-});
-
-var consoleOpened = Task.async(function* (hud) {
-  let deferred = defer();
-  HUD = hud;
+add_task(async function () {
+  let { jsterm } = await openNewTabAndConsole(TEST_URI);
   info("web console opened");
 
-  jsterm = HUD.jsterm;
-
-  yield jsterm.execute("window.foobarBug585991={" +
-    "'item0': 'value0'," +
-    "'item1': 'value1'," +
-    "'item2': 'value2'," +
-    "'item3': 'value3'" +
-  "}");
-  yield jsterm.execute("window.testBug873250a = 'hello world';"
-    + "window.testBug873250b = 'hello world 2';");
-  popup = jsterm.autocompletePopup;
-  completeNode = jsterm.completeNode;
-  inputNode = jsterm.inputNode;
-
-  ok(!popup.isOpen, "popup is not open");
-
-  popup.once("popup-opened", () => {
-    ok(popup.isOpen, "popup is open");
-
-    // 4 values, and the following properties:
-    // __defineGetter__  __defineSetter__ __lookupGetter__ __lookupSetter__
-    // __proto__ hasOwnProperty isPrototypeOf propertyIsEnumerable
-    // toLocaleString toString toSource unwatch valueOf watch constructor.
-    is(popup.itemCount, 19, "popup.itemCount is correct");
-
-    let sameItems = popup.getItems().reverse().map(function (e) {
-      return e.label;
-    });
-
-    ok(sameItems.every(function (prop, index) {
-      return [
-        "__defineGetter__",
-        "__defineSetter__",
-        "__lookupGetter__",
-        "__lookupSetter__",
-        "__proto__",
-        "constructor",
-        "hasOwnProperty",
-        "isPrototypeOf",
-        "item0",
-        "item1",
-        "item2",
-        "item3",
-        "propertyIsEnumerable",
-        "toLocaleString",
-        "toSource",
-        "toString",
-        "unwatch",
-        "valueOf",
-        "watch",
-      ][index] === prop;
-    }), "getItems returns the items we expect");
+  const {
+    autocompletePopup: popup,
+    completeNode,
+    inputNode,
+  } = jsterm;
 
-    is(popup.selectedIndex, 18,
-       "Index of the first item from bottom is selected.");
-    EventUtils.synthesizeKey("VK_DOWN", {});
-
-    let prefix = jsterm.getInputValue().replace(/[\S]/g, " ");
-
-    is(popup.selectedIndex, 0, "index 0 is selected");
-    is(popup.selectedItem.label, "watch", "watch is selected");
-    is(completeNode.value, prefix + "watch",
-        "completeNode.value holds watch");
-
-    EventUtils.synthesizeKey("VK_DOWN", {});
-
-    is(popup.selectedIndex, 1, "index 1 is selected");
-    is(popup.selectedItem.label, "valueOf", "valueOf is selected");
-    is(completeNode.value, prefix + "valueOf",
-        "completeNode.value holds valueOf");
-
-    EventUtils.synthesizeKey("VK_UP", {});
-
-    is(popup.selectedIndex, 0, "index 0 is selected");
-    is(popup.selectedItem.label, "watch", "watch is selected");
-    is(completeNode.value, prefix + "watch",
-        "completeNode.value holds watch");
+  let onPopUpOpen = popup.once("popup-opened");
 
-    let currentSelectionIndex = popup.selectedIndex;
-
-    EventUtils.synthesizeKey("VK_PAGE_DOWN", {});
-
-    ok(popup.selectedIndex > currentSelectionIndex,
-      "Index is greater after PGDN");
-
-    currentSelectionIndex = popup.selectedIndex;
-    EventUtils.synthesizeKey("VK_PAGE_UP", {});
-
-    ok(popup.selectedIndex < currentSelectionIndex,
-       "Index is less after Page UP");
-
-    EventUtils.synthesizeKey("VK_END", {});
-    is(popup.selectedIndex, 18, "index is last after End");
-
-    EventUtils.synthesizeKey("VK_HOME", {});
-    is(popup.selectedIndex, 0, "index is first after Home");
-
-    info("press Tab and wait for popup to hide");
-    popup.once("popup-closed", () => {
-      deferred.resolve();
-    });
-    EventUtils.synthesizeKey("VK_TAB", {});
-  });
-
-  jsterm.setInputValue("window.foobarBug585991");
+  info("wait for completion: window.foo.");
+  jsterm.setInputValue("window.foo");
   EventUtils.synthesizeKey(".", {});
 
-  return deferred.promise;
-});
-
-function popupHideAfterTab() {
-  let deferred = defer();
-
-  // At this point the completion suggestion should be accepted.
-  ok(!popup.isOpen, "popup is not open");
-
-  is(jsterm.getInputValue(), "window.foobarBug585991.watch",
-     "completion was successful after VK_TAB");
-
-  ok(!completeNode.value, "completeNode is empty");
-
-  popup.once("popup-opened", function onShown() {
-    ok(popup.isOpen, "popup is open");
-
-    is(popup.itemCount, 19, "popup.itemCount is correct");
-
-    is(popup.selectedIndex, 18, "First index from bottom is selected");
-    EventUtils.synthesizeKey("VK_DOWN", {});
-
-    let prefix = jsterm.getInputValue().replace(/[\S]/g, " ");
-
-    is(popup.selectedIndex, 0, "index 0 is selected");
-    is(popup.selectedItem.label, "watch", "watch is selected");
-    is(completeNode.value, prefix + "watch",
-        "completeNode.value holds watch");
-
-    popup.once("popup-closed", function onHidden() {
-      ok(!popup.isOpen, "popup is not open after VK_ESCAPE");
-
-      is(jsterm.getInputValue(), "window.foobarBug585991.",
-         "completion was cancelled");
-
-      ok(!completeNode.value, "completeNode is empty");
-
-      deferred.resolve();
-    }, false);
-
-    info("press Escape to close the popup");
-    executeSoon(function () {
-      EventUtils.synthesizeKey("VK_ESCAPE", {});
-    });
-  }, false);
-
-  info("wait for completion: window.foobarBug585991.");
-  executeSoon(function () {
-    jsterm.setInputValue("window.foobarBug585991");
-    EventUtils.synthesizeKey(".", {});
-  });
-
-  return deferred.promise;
-}
+  await onPopUpOpen;
 
-function testReturnKey() {
-  let deferred = defer();
-
-  popup.once("popup-opened", function onShown() {
-    ok(popup.isOpen, "popup is open");
-
-    is(popup.itemCount, 19, "popup.itemCount is correct");
-
-    is(popup.selectedIndex, 18, "First index from bottom is selected");
-    EventUtils.synthesizeKey("VK_DOWN", {});
-
-    let prefix = jsterm.getInputValue().replace(/[\S]/g, " ");
-
-    is(popup.selectedIndex, 0, "index 0 is selected");
-    is(popup.selectedItem.label, "watch", "watch is selected");
-    is(completeNode.value, prefix + "watch",
-        "completeNode.value holds watch");
-
-    EventUtils.synthesizeKey("VK_DOWN", {});
-
-    is(popup.selectedIndex, 1, "index 1 is selected");
-    is(popup.selectedItem.label, "valueOf", "valueOf is selected");
-    is(completeNode.value, prefix + "valueOf",
-       "completeNode.value holds valueOf");
-
-    popup.once("popup-closed", function onHidden() {
-      ok(!popup.isOpen, "popup is not open after VK_RETURN");
-
-      is(jsterm.getInputValue(), "window.foobarBug585991.valueOf",
-         "completion was successful after VK_RETURN");
-
-      ok(!completeNode.value, "completeNode is empty");
-
-      deferred.resolve();
-    }, false);
-
-    info("press Return to accept suggestion. wait for popup to hide");
-
-    executeSoon(() => EventUtils.synthesizeKey("VK_RETURN", {}));
-  }, false);
-
-  info("wait for completion suggestions: window.foobarBug585991.");
-
-  executeSoon(function () {
-    jsterm.setInputValue("window.foobarBug58599");
-    EventUtils.synthesizeKey("1", {});
-    EventUtils.synthesizeKey(".", {});
-  });
-
-  return deferred.promise;
-}
-
-function* dontShowArrayNumbers() {
-  let deferred = defer();
+  ok(popup.isOpen, "popup is open");
+  ok(popup.itemCount, "popup has items");
 
-  info("dontShowArrayNumbers");
-  yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
-    content.wrappedJSObject.foobarBug585991 = ["Sherlock Holmes"];
-  });
-
-  jsterm = HUD.jsterm;
-  popup = jsterm.autocompletePopup;
-
-  popup.once("popup-opened", function onShown() {
-    let sameItems = popup.getItems().map(function (e) {
-      return e.label;
-    });
-    ok(!sameItems.some(function (prop) {
-      prop === "0";
-    }), "Completing on an array doesn't show numbers.");
-
-    popup.once("popup-closed", function popupHidden() {
-      deferred.resolve();
-    }, false);
-
-    info("wait for popup to hide");
-    executeSoon(() => EventUtils.synthesizeKey("VK_ESCAPE", {}));
-  }, false);
-
-  info("wait for popup to show");
-  executeSoon(() => {
-    jsterm.setInputValue("window.foobarBug585991");
-    EventUtils.synthesizeKey(".", {});
-  });
-
-  return deferred.promise;
-}
-
-function testReturnWithNoSelection() {
-  let deferred = defer();
-
-  info("test pressing return with open popup, but no selection, see bug 873250");
-
-  popup.once("popup-opened", function onShown() {
-    ok(popup.isOpen, "popup is open");
-    is(popup.itemCount, 2, "popup.itemCount is correct");
-    isnot(popup.selectedIndex, -1, "popup.selectedIndex is correct");
-
-    info("press Return and wait for popup to hide");
-    popup.once("popup-closed", function popupHidden() {
-      deferred.resolve();
-    });
-    executeSoon(() => EventUtils.synthesizeKey("VK_RETURN", {}));
-  });
-
-  executeSoon(() => {
-    info("wait for popup to show");
-    jsterm.setInputValue("window.testBu");
-    EventUtils.synthesizeKey("g", {});
-  });
+  info("press Escape to close the popup");
+  const onPopupClose = popup.once("popup-closed");
+  EventUtils.synthesizeKey("VK_ESCAPE", {});
 
-  return deferred.promise;
-}
-
-function popupHideAfterReturnWithNoSelection() {
-  ok(!popup.isOpen, "popup is not open after VK_RETURN");
-
-  is(jsterm.getInputValue(), "", "inputNode is empty after VK_RETURN");
-  is(completeNode.value, "", "completeNode is empty");
-  is(jsterm.history[jsterm.history.length - 1], "window.testBug",
-     "jsterm history is correct");
-
-  return promise.resolve();
-}
-
-function testCompletionInText() {
-  info("test that completion works inside text, see bug 812618");
-
-  let deferred = defer();
-
-  popup.once("popup-opened", function onShown() {
-    ok(popup.isOpen, "popup is open");
-    is(popup.itemCount, 2, "popup.itemCount is correct");
-
-    EventUtils.synthesizeKey("VK_DOWN", {});
-    is(popup.selectedIndex, 0, "popup.selectedIndex is correct");
-    ok(!completeNode.value, "completeNode.value is empty");
+  await onPopupClose;
 
-    let items = popup.getItems().reverse().map(e => e.label);
-    let sameItems = items.every((prop, index) =>
-      ["testBug873250a", "testBug873250b"][index] === prop);
-    ok(sameItems, "getItems returns the items we expect");
-
-    info("press Tab and wait for popup to hide");
-    popup.once("popup-closed", function popupHidden() {
-      deferred.resolve();
-    });
-    EventUtils.synthesizeKey("VK_TAB", {});
-  });
-
-  jsterm.setInputValue("dump(window.testBu)");
-  inputNode.selectionStart = inputNode.selectionEnd = 18;
-  EventUtils.synthesizeKey("g", {});
-  return deferred.promise;
-}
-
-function popupHideAfterCompletionInText() {
-  // At this point the completion suggestion should be accepted.
-  ok(!popup.isOpen, "popup is not open");
-  is(jsterm.getInputValue(), "dump(window.testBug873250b)",
-     "completion was successful after VK_TAB");
-  is(inputNode.selectionStart, 26, "cursor location is correct");
-  is(inputNode.selectionStart, inputNode.selectionEnd,
-     "cursor location (confirmed)");
+  ok(!popup.isOpen, "popup is not open after VK_ESCAPE");
+  is(jsterm.getInputValue(), "window.foo.", "completion was cancelled");
   ok(!completeNode.value, "completeNode is empty");
-
-  return promise.resolve();
-}
+});
copy from devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_autocomplete_keys.js
copy to devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_autocomplete_inside_text.js
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_autocomplete_keys.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_autocomplete_inside_text.js
@@ -1,369 +1,62 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-// See Bug 585991.
-
-const TEST_URI = "data:text/html;charset=utf-8,<p>bug 585991 - autocomplete " +
-                 "popup keyboard usage test";
-
-// We should turn off auto-multiline editing during these tests
-const PREF_AUTO_MULTILINE = "devtools.webconsole.autoMultiline";
-var HUD, popup, jsterm, inputNode, completeNode;
-
-add_task(function* () {
-  Services.prefs.setBoolPref(PREF_AUTO_MULTILINE, false);
-  yield loadTab(TEST_URI);
-  let hud = yield openConsole();
+// See Bug 812618.
 
-  yield consoleOpened(hud);
-  yield popupHideAfterTab();
-  yield testReturnKey();
-  yield dontShowArrayNumbers();
-  yield testReturnWithNoSelection();
-  yield popupHideAfterReturnWithNoSelection();
-  yield testCompletionInText();
-  yield popupHideAfterCompletionInText();
+const TEST_URI = `data:text/html;charset=utf-8,
+<head>
+  <script>
+    window.testBugA = "hello world";
+    window.testBugB = "hello world 2";
+  </script>
+</head>
+<body>bug 812618 - test completion inside text</body>`;
 
-  HUD = popup = jsterm = inputNode = completeNode = null;
-  Services.prefs.setBoolPref(PREF_AUTO_MULTILINE, true);
-});
-
-var consoleOpened = Task.async(function* (hud) {
-  let deferred = defer();
-  HUD = hud;
+add_task(async function () {
+  let { jsterm } = await openNewTabAndConsole(TEST_URI);
   info("web console opened");
 
-  jsterm = HUD.jsterm;
-
-  yield jsterm.execute("window.foobarBug585991={" +
-    "'item0': 'value0'," +
-    "'item1': 'value1'," +
-    "'item2': 'value2'," +
-    "'item3': 'value3'" +
-  "}");
-  yield jsterm.execute("window.testBug873250a = 'hello world';"
-    + "window.testBug873250b = 'hello world 2';");
-  popup = jsterm.autocompletePopup;
-  completeNode = jsterm.completeNode;
-  inputNode = jsterm.inputNode;
-
-  ok(!popup.isOpen, "popup is not open");
-
-  popup.once("popup-opened", () => {
-    ok(popup.isOpen, "popup is open");
-
-    // 4 values, and the following properties:
-    // __defineGetter__  __defineSetter__ __lookupGetter__ __lookupSetter__
-    // __proto__ hasOwnProperty isPrototypeOf propertyIsEnumerable
-    // toLocaleString toString toSource unwatch valueOf watch constructor.
-    is(popup.itemCount, 19, "popup.itemCount is correct");
+  const {
+    autocompletePopup: popup,
+    completeNode,
+    inputNode,
+  } = jsterm;
 
-    let sameItems = popup.getItems().reverse().map(function (e) {
-      return e.label;
-    });
+  const onPopUpOpen = popup.once("popup-opened");
 
-    ok(sameItems.every(function (prop, index) {
-      return [
-        "__defineGetter__",
-        "__defineSetter__",
-        "__lookupGetter__",
-        "__lookupSetter__",
-        "__proto__",
-        "constructor",
-        "hasOwnProperty",
-        "isPrototypeOf",
-        "item0",
-        "item1",
-        "item2",
-        "item3",
-        "propertyIsEnumerable",
-        "toLocaleString",
-        "toSource",
-        "toString",
-        "unwatch",
-        "valueOf",
-        "watch",
-      ][index] === prop;
-    }), "getItems returns the items we expect");
+  const dumpString = "dump(window.testBu)";
+  jsterm.setInputValue(dumpString);
+  inputNode.selectionStart = inputNode.selectionEnd = dumpString.indexOf(")");
+  EventUtils.synthesizeKey("g", {});
+
+  await onPopUpOpen;
 
-    is(popup.selectedIndex, 18,
-       "Index of the first item from bottom is selected.");
-    EventUtils.synthesizeKey("VK_DOWN", {});
-
-    let prefix = jsterm.getInputValue().replace(/[\S]/g, " ");
-
-    is(popup.selectedIndex, 0, "index 0 is selected");
-    is(popup.selectedItem.label, "watch", "watch is selected");
-    is(completeNode.value, prefix + "watch",
-        "completeNode.value holds watch");
-
-    EventUtils.synthesizeKey("VK_DOWN", {});
+  ok(popup.isOpen, "popup is open");
+  is(popup.itemCount, 2, "popup.itemCount is correct");
 
-    is(popup.selectedIndex, 1, "index 1 is selected");
-    is(popup.selectedItem.label, "valueOf", "valueOf is selected");
-    is(completeNode.value, prefix + "valueOf",
-        "completeNode.value holds valueOf");
-
-    EventUtils.synthesizeKey("VK_UP", {});
-
-    is(popup.selectedIndex, 0, "index 0 is selected");
-    is(popup.selectedItem.label, "watch", "watch is selected");
-    is(completeNode.value, prefix + "watch",
-        "completeNode.value holds watch");
-
-    let currentSelectionIndex = popup.selectedIndex;
-
-    EventUtils.synthesizeKey("VK_PAGE_DOWN", {});
+  EventUtils.synthesizeKey("VK_DOWN", {});
+  is(popup.selectedIndex, 0, "popup.selectedIndex is correct");
+  ok(!completeNode.value, "completeNode.value is empty");
 
-    ok(popup.selectedIndex > currentSelectionIndex,
-      "Index is greater after PGDN");
-
-    currentSelectionIndex = popup.selectedIndex;
-    EventUtils.synthesizeKey("VK_PAGE_UP", {});
-
-    ok(popup.selectedIndex < currentSelectionIndex,
-       "Index is less after Page UP");
-
-    EventUtils.synthesizeKey("VK_END", {});
-    is(popup.selectedIndex, 18, "index is last after End");
-
-    EventUtils.synthesizeKey("VK_HOME", {});
-    is(popup.selectedIndex, 0, "index is first after Home");
+  let items = popup.getItems().map(e => e.label);
+  let expectedItems = ["testBugB", "testBugA"];
+  is(items.join("-"), expectedItems.join("-"), "getItems returns the items we expect");
 
-    info("press Tab and wait for popup to hide");
-    popup.once("popup-closed", () => {
-      deferred.resolve();
-    });
-    EventUtils.synthesizeKey("VK_TAB", {});
-  });
+  info("press Tab and wait for popup to hide");
+  const onPopupClose = popup.once("popup-closed");
+  EventUtils.synthesizeKey("VK_TAB", {});
 
-  jsterm.setInputValue("window.foobarBug585991");
-  EventUtils.synthesizeKey(".", {});
-
-  return deferred.promise;
-});
-
-function popupHideAfterTab() {
-  let deferred = defer();
+  await onPopupClose;
 
   // At this point the completion suggestion should be accepted.
   ok(!popup.isOpen, "popup is not open");
-
-  is(jsterm.getInputValue(), "window.foobarBug585991.watch",
-     "completion was successful after VK_TAB");
-
+  const expectedInput = "dump(window.testBugB)";
+  is(jsterm.getInputValue(), expectedInput, "completion was successful after VK_TAB");
+  is(inputNode.selectionStart, expectedInput.length - 1, "cursor location is correct");
+  is(inputNode.selectionStart, inputNode.selectionEnd, "cursor location (confirmed)");
   ok(!completeNode.value, "completeNode is empty");
-
-  popup.once("popup-opened", function onShown() {
-    ok(popup.isOpen, "popup is open");
-
-    is(popup.itemCount, 19, "popup.itemCount is correct");
-
-    is(popup.selectedIndex, 18, "First index from bottom is selected");
-    EventUtils.synthesizeKey("VK_DOWN", {});
-
-    let prefix = jsterm.getInputValue().replace(/[\S]/g, " ");
-
-    is(popup.selectedIndex, 0, "index 0 is selected");
-    is(popup.selectedItem.label, "watch", "watch is selected");
-    is(completeNode.value, prefix + "watch",
-        "completeNode.value holds watch");
-
-    popup.once("popup-closed", function onHidden() {
-      ok(!popup.isOpen, "popup is not open after VK_ESCAPE");
-
-      is(jsterm.getInputValue(), "window.foobarBug585991.",
-         "completion was cancelled");
-
-      ok(!completeNode.value, "completeNode is empty");
-
-      deferred.resolve();
-    }, false);
-
-    info("press Escape to close the popup");
-    executeSoon(function () {
-      EventUtils.synthesizeKey("VK_ESCAPE", {});
-    });
-  }, false);
-
-  info("wait for completion: window.foobarBug585991.");
-  executeSoon(function () {
-    jsterm.setInputValue("window.foobarBug585991");
-    EventUtils.synthesizeKey(".", {});
-  });
-
-  return deferred.promise;
-}
-
-function testReturnKey() {
-  let deferred = defer();
-
-  popup.once("popup-opened", function onShown() {
-    ok(popup.isOpen, "popup is open");
-
-    is(popup.itemCount, 19, "popup.itemCount is correct");
-
-    is(popup.selectedIndex, 18, "First index from bottom is selected");
-    EventUtils.synthesizeKey("VK_DOWN", {});
-
-    let prefix = jsterm.getInputValue().replace(/[\S]/g, " ");
-
-    is(popup.selectedIndex, 0, "index 0 is selected");
-    is(popup.selectedItem.label, "watch", "watch is selected");
-    is(completeNode.value, prefix + "watch",
-        "completeNode.value holds watch");
-
-    EventUtils.synthesizeKey("VK_DOWN", {});
-
-    is(popup.selectedIndex, 1, "index 1 is selected");
-    is(popup.selectedItem.label, "valueOf", "valueOf is selected");
-    is(completeNode.value, prefix + "valueOf",
-       "completeNode.value holds valueOf");
-
-    popup.once("popup-closed", function onHidden() {
-      ok(!popup.isOpen, "popup is not open after VK_RETURN");
-
-      is(jsterm.getInputValue(), "window.foobarBug585991.valueOf",
-         "completion was successful after VK_RETURN");
-
-      ok(!completeNode.value, "completeNode is empty");
-
-      deferred.resolve();
-    }, false);
-
-    info("press Return to accept suggestion. wait for popup to hide");
-
-    executeSoon(() => EventUtils.synthesizeKey("VK_RETURN", {}));
-  }, false);
-
-  info("wait for completion suggestions: window.foobarBug585991.");
-
-  executeSoon(function () {
-    jsterm.setInputValue("window.foobarBug58599");
-    EventUtils.synthesizeKey("1", {});
-    EventUtils.synthesizeKey(".", {});
-  });
-
-  return deferred.promise;
-}
-
-function* dontShowArrayNumbers() {
-  let deferred = defer();
-
-  info("dontShowArrayNumbers");
-  yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
-    content.wrappedJSObject.foobarBug585991 = ["Sherlock Holmes"];
-  });
-
-  jsterm = HUD.jsterm;
-  popup = jsterm.autocompletePopup;
-
-  popup.once("popup-opened", function onShown() {
-    let sameItems = popup.getItems().map(function (e) {
-      return e.label;
-    });
-    ok(!sameItems.some(function (prop) {
-      prop === "0";
-    }), "Completing on an array doesn't show numbers.");
-
-    popup.once("popup-closed", function popupHidden() {
-      deferred.resolve();
-    }, false);
-
-    info("wait for popup to hide");
-    executeSoon(() => EventUtils.synthesizeKey("VK_ESCAPE", {}));
-  }, false);
-
-  info("wait for popup to show");
-  executeSoon(() => {
-    jsterm.setInputValue("window.foobarBug585991");
-    EventUtils.synthesizeKey(".", {});
-  });
-
-  return deferred.promise;
-}
-
-function testReturnWithNoSelection() {
-  let deferred = defer();
-
-  info("test pressing return with open popup, but no selection, see bug 873250");
-
-  popup.once("popup-opened", function onShown() {
-    ok(popup.isOpen, "popup is open");
-    is(popup.itemCount, 2, "popup.itemCount is correct");
-    isnot(popup.selectedIndex, -1, "popup.selectedIndex is correct");
-
-    info("press Return and wait for popup to hide");
-    popup.once("popup-closed", function popupHidden() {
-      deferred.resolve();
-    });
-    executeSoon(() => EventUtils.synthesizeKey("VK_RETURN", {}));
-  });
-
-  executeSoon(() => {
-    info("wait for popup to show");
-    jsterm.setInputValue("window.testBu");
-    EventUtils.synthesizeKey("g", {});
-  });
-
-  return deferred.promise;
-}
-
-function popupHideAfterReturnWithNoSelection() {
-  ok(!popup.isOpen, "popup is not open after VK_RETURN");
-
-  is(jsterm.getInputValue(), "", "inputNode is empty after VK_RETURN");
-  is(completeNode.value, "", "completeNode is empty");
-  is(jsterm.history[jsterm.history.length - 1], "window.testBug",
-     "jsterm history is correct");
-
-  return promise.resolve();
-}
-
-function testCompletionInText() {
-  info("test that completion works inside text, see bug 812618");
-
-  let deferred = defer();
-
-  popup.once("popup-opened", function onShown() {
-    ok(popup.isOpen, "popup is open");
-    is(popup.itemCount, 2, "popup.itemCount is correct");
-
-    EventUtils.synthesizeKey("VK_DOWN", {});
-    is(popup.selectedIndex, 0, "popup.selectedIndex is correct");
-    ok(!completeNode.value, "completeNode.value is empty");
-
-    let items = popup.getItems().reverse().map(e => e.label);
-    let sameItems = items.every((prop, index) =>
-      ["testBug873250a", "testBug873250b"][index] === prop);
-    ok(sameItems, "getItems returns the items we expect");
-
-    info("press Tab and wait for popup to hide");
-    popup.once("popup-closed", function popupHidden() {
-      deferred.resolve();
-    });
-    EventUtils.synthesizeKey("VK_TAB", {});
-  });
-
-  jsterm.setInputValue("dump(window.testBu)");
-  inputNode.selectionStart = inputNode.selectionEnd = 18;
-  EventUtils.synthesizeKey("g", {});
-  return deferred.promise;
-}
-
-function popupHideAfterCompletionInText() {
-  // At this point the completion suggestion should be accepted.
-  ok(!popup.isOpen, "popup is not open");
-  is(jsterm.getInputValue(), "dump(window.testBug873250b)",
-     "completion was successful after VK_TAB");
-  is(inputNode.selectionStart, 26, "cursor location is correct");
-  is(inputNode.selectionStart, inputNode.selectionEnd,
-     "cursor location (confirmed)");
-  ok(!completeNode.value, "completeNode is empty");
-
-  return promise.resolve();
-}
+});
copy from devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_autocomplete_keys.js
copy to devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_autocomplete_nav_and_tab_key.js
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_autocomplete_keys.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_autocomplete_nav_and_tab_key.js
@@ -2,368 +2,108 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // See Bug 585991.
 
-const TEST_URI = "data:text/html;charset=utf-8,<p>bug 585991 - autocomplete " +
-                 "popup keyboard usage test";
-
-// We should turn off auto-multiline editing during these tests
-const PREF_AUTO_MULTILINE = "devtools.webconsole.autoMultiline";
-var HUD, popup, jsterm, inputNode, completeNode;
-
-add_task(function* () {
-  Services.prefs.setBoolPref(PREF_AUTO_MULTILINE, false);
-  yield loadTab(TEST_URI);
-  let hud = yield openConsole();
+const TEST_URI = `data:text/html;charset=utf-8,
+<head>
+  <script>
+    /* Create a prototype-less object so popup does not contain native
+     * Object prototype properties.
+     */
+    window.foo = Object.create(null);
+    Object.assign(window.foo, {
+      item0: "value0",
+      item1: "value1",
+      item2: "value2",
+      item3: "value3",
+    });
+  </script>
+</head>
+<body>bug 585991 - autocomplete popup navigation and tab key usage test</body>`;
 
-  yield consoleOpened(hud);
-  yield popupHideAfterTab();
-  yield testReturnKey();
-  yield dontShowArrayNumbers();
-  yield testReturnWithNoSelection();
-  yield popupHideAfterReturnWithNoSelection();
-  yield testCompletionInText();
-  yield popupHideAfterCompletionInText();
-
-  HUD = popup = jsterm = inputNode = completeNode = null;
-  Services.prefs.setBoolPref(PREF_AUTO_MULTILINE, true);
-});
-
-var consoleOpened = Task.async(function* (hud) {
-  let deferred = defer();
-  HUD = hud;
+add_task(async function () {
+  let { jsterm } = await openNewTabAndConsole(TEST_URI);
   info("web console opened");
 
-  jsterm = HUD.jsterm;
-
-  yield jsterm.execute("window.foobarBug585991={" +
-    "'item0': 'value0'," +
-    "'item1': 'value1'," +
-    "'item2': 'value2'," +
-    "'item3': 'value3'" +
-  "}");
-  yield jsterm.execute("window.testBug873250a = 'hello world';"
-    + "window.testBug873250b = 'hello world 2';");
-  popup = jsterm.autocompletePopup;
-  completeNode = jsterm.completeNode;
-  inputNode = jsterm.inputNode;
+  const {
+    autocompletePopup: popup,
+    completeNode,
+    inputNode,
+  } = jsterm;
 
   ok(!popup.isOpen, "popup is not open");
 
-  popup.once("popup-opened", () => {
-    ok(popup.isOpen, "popup is open");
+  const onPopUpOpen = popup.once("popup-opened");
+  jsterm.setInputValue("window.foo");
 
-    // 4 values, and the following properties:
-    // __defineGetter__  __defineSetter__ __lookupGetter__ __lookupSetter__
-    // __proto__ hasOwnProperty isPrototypeOf propertyIsEnumerable
-    // toLocaleString toString toSource unwatch valueOf watch constructor.
-    is(popup.itemCount, 19, "popup.itemCount is correct");
+  // Shows the popup
+  EventUtils.synthesizeKey(".", {});
+  await onPopUpOpen;
 
-    let sameItems = popup.getItems().reverse().map(function (e) {
-      return e.label;
-    });
+  ok(popup.isOpen, "popup is open");
 
-    ok(sameItems.every(function (prop, index) {
-      return [
-        "__defineGetter__",
-        "__defineSetter__",
-        "__lookupGetter__",
-        "__lookupSetter__",
-        "__proto__",
-        "constructor",
-        "hasOwnProperty",
-        "isPrototypeOf",
-        "item0",
-        "item1",
-        "item2",
-        "item3",
-        "propertyIsEnumerable",
-        "toLocaleString",
-        "toSource",
-        "toString",
-        "unwatch",
-        "valueOf",
-        "watch",
-      ][index] === prop;
-    }), "getItems returns the items we expect");
+  const popupItems = popup.getItems().map(e => e.label);
+  const expectedPopupItems = [
+    "item3",
+    "item2",
+    "item1",
+    "item0",
+  ];
 
-    is(popup.selectedIndex, 18,
-       "Index of the first item from bottom is selected.");
-    EventUtils.synthesizeKey("VK_DOWN", {});
+  is(popup.itemCount, expectedPopupItems.length, "popup.itemCount is correct");
+  is(popupItems.join("-"), expectedPopupItems.join("-"),
+    "getItems returns the items we expect");
+  is(popup.selectedIndex, expectedPopupItems.length - 1,
+      "Index of the first item from bottom is selected.");
 
-    let prefix = jsterm.getInputValue().replace(/[\S]/g, " ");
+  EventUtils.synthesizeKey("VK_DOWN", {});
 
-    is(popup.selectedIndex, 0, "index 0 is selected");
-    is(popup.selectedItem.label, "watch", "watch is selected");
-    is(completeNode.value, prefix + "watch",
-        "completeNode.value holds watch");
+  let prefix = jsterm.getInputValue().replace(/[\S]/g, " ");
+  is(popup.selectedIndex, 0, "index 0 is selected");
+  is(popup.selectedItem.label, "item3", "item3 is selected");
+  is(completeNode.value, prefix + "item3", "completeNode.value holds item3");
+
+  EventUtils.synthesizeKey("VK_DOWN", {});
 
-    EventUtils.synthesizeKey("VK_DOWN", {});
+  is(popup.selectedIndex, 1, "index 1 is selected");
+  is(popup.selectedItem.label, "item2", "item2 is selected");
+  is(completeNode.value, prefix + "item2", "completeNode.value holds item2");
 
-    is(popup.selectedIndex, 1, "index 1 is selected");
-    is(popup.selectedItem.label, "valueOf", "valueOf is selected");
-    is(completeNode.value, prefix + "valueOf",
-        "completeNode.value holds valueOf");
-
-    EventUtils.synthesizeKey("VK_UP", {});
+  EventUtils.synthesizeKey("VK_UP", {});
 
-    is(popup.selectedIndex, 0, "index 0 is selected");
-    is(popup.selectedItem.label, "watch", "watch is selected");
-    is(completeNode.value, prefix + "watch",
-        "completeNode.value holds watch");
-
-    let currentSelectionIndex = popup.selectedIndex;
+  is(popup.selectedIndex, 0, "index 0 is selected");
+  is(popup.selectedItem.label, "item3", "item3 is selected");
+  is(completeNode.value, prefix + "item3", "completeNode.value holds item3");
 
-    EventUtils.synthesizeKey("VK_PAGE_DOWN", {});
+  let currentSelectionIndex = popup.selectedIndex;
 
-    ok(popup.selectedIndex > currentSelectionIndex,
-      "Index is greater after PGDN");
+  EventUtils.synthesizeKey("VK_PAGE_DOWN", {});
 
-    currentSelectionIndex = popup.selectedIndex;
-    EventUtils.synthesizeKey("VK_PAGE_UP", {});
+  ok(popup.selectedIndex > currentSelectionIndex, "Index is greater after PGDN");
 
-    ok(popup.selectedIndex < currentSelectionIndex,
-       "Index is less after Page UP");
+  currentSelectionIndex = popup.selectedIndex;
+  EventUtils.synthesizeKey("VK_PAGE_UP", {});
 
-    EventUtils.synthesizeKey("VK_END", {});
-    is(popup.selectedIndex, 18, "index is last after End");
+  ok(popup.selectedIndex < currentSelectionIndex, "Index is less after Page UP");
 
-    EventUtils.synthesizeKey("VK_HOME", {});
-    is(popup.selectedIndex, 0, "index is first after Home");
+  EventUtils.synthesizeKey("VK_END", {});
+  is(popup.selectedIndex, expectedPopupItems.length - 1, "index is last after End");
 
-    info("press Tab and wait for popup to hide");
-    popup.once("popup-closed", () => {
-      deferred.resolve();
-    });
-    EventUtils.synthesizeKey("VK_TAB", {});
-  });
+  EventUtils.synthesizeKey("VK_HOME", {});
+  is(popup.selectedIndex, 0, "index is first after Home");
 
-  jsterm.setInputValue("window.foobarBug585991");
-  EventUtils.synthesizeKey(".", {});
+  info("press Tab and wait for popup to hide");
+  const onPopupClose = popup.once("popup-closed");
+  EventUtils.synthesizeKey("VK_TAB", {});
 
-  return deferred.promise;
-});
-
-function popupHideAfterTab() {
-  let deferred = defer();
+  await onPopupClose;
 
   // At this point the completion suggestion should be accepted.
   ok(!popup.isOpen, "popup is not open");
-
-  is(jsterm.getInputValue(), "window.foobarBug585991.watch",
+  is(jsterm.getInputValue(), "window.foo.item3",
      "completion was successful after VK_TAB");
-
   ok(!completeNode.value, "completeNode is empty");
-
-  popup.once("popup-opened", function onShown() {
-    ok(popup.isOpen, "popup is open");
-
-    is(popup.itemCount, 19, "popup.itemCount is correct");
-
-    is(popup.selectedIndex, 18, "First index from bottom is selected");
-    EventUtils.synthesizeKey("VK_DOWN", {});
-
-    let prefix = jsterm.getInputValue().replace(/[\S]/g, " ");
-
-    is(popup.selectedIndex, 0, "index 0 is selected");
-    is(popup.selectedItem.label, "watch", "watch is selected");
-    is(completeNode.value, prefix + "watch",
-        "completeNode.value holds watch");
-
-    popup.once("popup-closed", function onHidden() {
-      ok(!popup.isOpen, "popup is not open after VK_ESCAPE");
-
-      is(jsterm.getInputValue(), "window.foobarBug585991.",
-         "completion was cancelled");
-
-      ok(!completeNode.value, "completeNode is empty");
-
-      deferred.resolve();
-    }, false);
-
-    info("press Escape to close the popup");
-    executeSoon(function () {
-      EventUtils.synthesizeKey("VK_ESCAPE", {});
-    });
-  }, false);
-
-  info("wait for completion: window.foobarBug585991.");
-  executeSoon(function () {
-    jsterm.setInputValue("window.foobarBug585991");
-    EventUtils.synthesizeKey(".", {});
-  });
-
-  return deferred.promise;
-}
-
-function testReturnKey() {
-  let deferred = defer();
-
-  popup.once("popup-opened", function onShown() {
-    ok(popup.isOpen, "popup is open");
-
-    is(popup.itemCount, 19, "popup.itemCount is correct");
-
-    is(popup.selectedIndex, 18, "First index from bottom is selected");
-    EventUtils.synthesizeKey("VK_DOWN", {});
-
-    let prefix = jsterm.getInputValue().replace(/[\S]/g, " ");
-
-    is(popup.selectedIndex, 0, "index 0 is selected");
-    is(popup.selectedItem.label, "watch", "watch is selected");
-    is(completeNode.value, prefix + "watch",
-        "completeNode.value holds watch");
-
-    EventUtils.synthesizeKey("VK_DOWN", {});
-
-    is(popup.selectedIndex, 1, "index 1 is selected");
-    is(popup.selectedItem.label, "valueOf", "valueOf is selected");
-    is(completeNode.value, prefix + "valueOf",
-       "completeNode.value holds valueOf");
-
-    popup.once("popup-closed", function onHidden() {
-      ok(!popup.isOpen, "popup is not open after VK_RETURN");
-
-      is(jsterm.getInputValue(), "window.foobarBug585991.valueOf",
-         "completion was successful after VK_RETURN");
-
-      ok(!completeNode.value, "completeNode is empty");
-
-      deferred.resolve();
-    }, false);
-
-    info("press Return to accept suggestion. wait for popup to hide");
-
-    executeSoon(() => EventUtils.synthesizeKey("VK_RETURN", {}));
-  }, false);
-
-  info("wait for completion suggestions: window.foobarBug585991.");
-
-  executeSoon(function () {
-    jsterm.setInputValue("window.foobarBug58599");
-    EventUtils.synthesizeKey("1", {});
-    EventUtils.synthesizeKey(".", {});
-  });
-
-  return deferred.promise;
-}
-
-function* dontShowArrayNumbers() {
-  let deferred = defer();
-
-  info("dontShowArrayNumbers");
-  yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
-    content.wrappedJSObject.foobarBug585991 = ["Sherlock Holmes"];
-  });
-
-  jsterm = HUD.jsterm;
-  popup = jsterm.autocompletePopup;
-
-  popup.once("popup-opened", function onShown() {
-    let sameItems = popup.getItems().map(function (e) {
-      return e.label;
-    });
-    ok(!sameItems.some(function (prop) {
-      prop === "0";
-    }), "Completing on an array doesn't show numbers.");
-
-    popup.once("popup-closed", function popupHidden() {
-      deferred.resolve();
-    }, false);
-
-    info("wait for popup to hide");
-    executeSoon(() => EventUtils.synthesizeKey("VK_ESCAPE", {}));
-  }, false);
-
-  info("wait for popup to show");
-  executeSoon(() => {
-    jsterm.setInputValue("window.foobarBug585991");
-    EventUtils.synthesizeKey(".", {});
-  });
-
-  return deferred.promise;
-}
-
-function testReturnWithNoSelection() {
-  let deferred = defer();
-
-  info("test pressing return with open popup, but no selection, see bug 873250");
-
-  popup.once("popup-opened", function onShown() {
-    ok(popup.isOpen, "popup is open");
-    is(popup.itemCount, 2, "popup.itemCount is correct");
-    isnot(popup.selectedIndex, -1, "popup.selectedIndex is correct");
-
-    info("press Return and wait for popup to hide");
-    popup.once("popup-closed", function popupHidden() {
-      deferred.resolve();
-    });
-    executeSoon(() => EventUtils.synthesizeKey("VK_RETURN", {}));
-  });
-
-  executeSoon(() => {
-    info("wait for popup to show");
-    jsterm.setInputValue("window.testBu");
-    EventUtils.synthesizeKey("g", {});
-  });
-
-  return deferred.promise;
-}
-
-function popupHideAfterReturnWithNoSelection() {
-  ok(!popup.isOpen, "popup is not open after VK_RETURN");
-
-  is(jsterm.getInputValue(), "", "inputNode is empty after VK_RETURN");
-  is(completeNode.value, "", "completeNode is empty");
-  is(jsterm.history[jsterm.history.length - 1], "window.testBug",
-     "jsterm history is correct");
-
-  return promise.resolve();
-}
-
-function testCompletionInText() {
-  info("test that completion works inside text, see bug 812618");
-
-  let deferred = defer();
-
-  popup.once("popup-opened", function onShown() {
-    ok(popup.isOpen, "popup is open");
-    is(popup.itemCount, 2, "popup.itemCount is correct");
-
-    EventUtils.synthesizeKey("VK_DOWN", {});
-    is(popup.selectedIndex, 0, "popup.selectedIndex is correct");
-    ok(!completeNode.value, "completeNode.value is empty");
-
-    let items = popup.getItems().reverse().map(e => e.label);
-    let sameItems = items.every((prop, index) =>
-      ["testBug873250a", "testBug873250b"][index] === prop);
-    ok(sameItems, "getItems returns the items we expect");
-
-    info("press Tab and wait for popup to hide");
-    popup.once("popup-closed", function popupHidden() {
-      deferred.resolve();
-    });
-    EventUtils.synthesizeKey("VK_TAB", {});
-  });
-
-  jsterm.setInputValue("dump(window.testBu)");
-  inputNode.selectionStart = inputNode.selectionEnd = 18;
-  EventUtils.synthesizeKey("g", {});
-  return deferred.promise;
-}
-
-function popupHideAfterCompletionInText() {
-  // At this point the completion suggestion should be accepted.
-  ok(!popup.isOpen, "popup is not open");
-  is(jsterm.getInputValue(), "dump(window.testBug873250b)",
-     "completion was successful after VK_TAB");
-  is(inputNode.selectionStart, 26, "cursor location is correct");
-  is(inputNode.selectionStart, inputNode.selectionEnd,
-     "cursor location (confirmed)");
-  ok(!completeNode.value, "completeNode is empty");
-
-  return promise.resolve();
-}
+});
copy from devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_autocomplete_keys.js
copy to devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_autocomplete_return_key.js
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_autocomplete_keys.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_autocomplete_return_key.js
@@ -2,368 +2,80 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // See Bug 585991.
 
-const TEST_URI = "data:text/html;charset=utf-8,<p>bug 585991 - autocomplete " +
-                 "popup keyboard usage test";
+const TEST_URI = `data:text/html;charset=utf-8,
+<head>
+  <script>
+    /* Create a prototype-less object so popup does not contain native
+     * Object prototype properties.
+     */
+    window.foobar = Object.create(null);
+    Object.assign(window.foobar, {
+      item0: "value0",
+      item1: "value1",
+      item2: "value2",
+      item3: "value3",
+    });
+  </script>
+</head>
+<body>bug 585991 - test pressing return with open popup</body>`;
 
 // We should turn off auto-multiline editing during these tests
 const PREF_AUTO_MULTILINE = "devtools.webconsole.autoMultiline";
-var HUD, popup, jsterm, inputNode, completeNode;
 
-add_task(function* () {
+add_task(async function () {
   Services.prefs.setBoolPref(PREF_AUTO_MULTILINE, false);
-  yield loadTab(TEST_URI);
-  let hud = yield openConsole();
-
-  yield consoleOpened(hud);
-  yield popupHideAfterTab();
-  yield testReturnKey();
-  yield dontShowArrayNumbers();
-  yield testReturnWithNoSelection();
-  yield popupHideAfterReturnWithNoSelection();
-  yield testCompletionInText();
-  yield popupHideAfterCompletionInText();
-
-  HUD = popup = jsterm = inputNode = completeNode = null;
-  Services.prefs.setBoolPref(PREF_AUTO_MULTILINE, true);
-});
-
-var consoleOpened = Task.async(function* (hud) {
-  let deferred = defer();
-  HUD = hud;
-  info("web console opened");
-
-  jsterm = HUD.jsterm;
-
-  yield jsterm.execute("window.foobarBug585991={" +
-    "'item0': 'value0'," +
-    "'item1': 'value1'," +
-    "'item2': 'value2'," +
-    "'item3': 'value3'" +
-  "}");
-  yield jsterm.execute("window.testBug873250a = 'hello world';"
-    + "window.testBug873250b = 'hello world 2';");
-  popup = jsterm.autocompletePopup;
-  completeNode = jsterm.completeNode;
-  inputNode = jsterm.inputNode;
-
-  ok(!popup.isOpen, "popup is not open");
-
-  popup.once("popup-opened", () => {
-    ok(popup.isOpen, "popup is open");
-
-    // 4 values, and the following properties:
-    // __defineGetter__  __defineSetter__ __lookupGetter__ __lookupSetter__
-    // __proto__ hasOwnProperty isPrototypeOf propertyIsEnumerable
-    // toLocaleString toString toSource unwatch valueOf watch constructor.
-    is(popup.itemCount, 19, "popup.itemCount is correct");
-
-    let sameItems = popup.getItems().reverse().map(function (e) {
-      return e.label;
-    });
 
-    ok(sameItems.every(function (prop, index) {
-      return [
-        "__defineGetter__",
-        "__defineSetter__",
-        "__lookupGetter__",
-        "__lookupSetter__",
-        "__proto__",
-        "constructor",
-        "hasOwnProperty",
-        "isPrototypeOf",
-        "item0",
-        "item1",
-        "item2",
-        "item3",
-        "propertyIsEnumerable",
-        "toLocaleString",
-        "toSource",
-        "toString",
-        "unwatch",
-        "valueOf",
-        "watch",
-      ][index] === prop;
-    }), "getItems returns the items we expect");
-
-    is(popup.selectedIndex, 18,
-       "Index of the first item from bottom is selected.");
-    EventUtils.synthesizeKey("VK_DOWN", {});
-
-    let prefix = jsterm.getInputValue().replace(/[\S]/g, " ");
-
-    is(popup.selectedIndex, 0, "index 0 is selected");
-    is(popup.selectedItem.label, "watch", "watch is selected");
-    is(completeNode.value, prefix + "watch",
-        "completeNode.value holds watch");
-
-    EventUtils.synthesizeKey("VK_DOWN", {});
+  let { jsterm } = await openNewTabAndConsole(TEST_URI);
+  const {
+    autocompletePopup: popup,
+    completeNode,
+    inputNode,
+  } = jsterm;
 
-    is(popup.selectedIndex, 1, "index 1 is selected");
-    is(popup.selectedItem.label, "valueOf", "valueOf is selected");
-    is(completeNode.value, prefix + "valueOf",
-        "completeNode.value holds valueOf");
-
-    EventUtils.synthesizeKey("VK_UP", {});
-
-    is(popup.selectedIndex, 0, "index 0 is selected");
-    is(popup.selectedItem.label, "watch", "watch is selected");
-    is(completeNode.value, prefix + "watch",
-        "completeNode.value holds watch");
-
-    let currentSelectionIndex = popup.selectedIndex;
-
-    EventUtils.synthesizeKey("VK_PAGE_DOWN", {});
-
-    ok(popup.selectedIndex > currentSelectionIndex,
-      "Index is greater after PGDN");
+  let onPopUpOpen = popup.once("popup-opened");
 
-    currentSelectionIndex = popup.selectedIndex;
-    EventUtils.synthesizeKey("VK_PAGE_UP", {});
-
-    ok(popup.selectedIndex < currentSelectionIndex,
-       "Index is less after Page UP");
-
-    EventUtils.synthesizeKey("VK_END", {});
-    is(popup.selectedIndex, 18, "index is last after End");
+  info("wait for completion suggestions: window.foobar.");
 
-    EventUtils.synthesizeKey("VK_HOME", {});
-    is(popup.selectedIndex, 0, "index is first after Home");
-
-    info("press Tab and wait for popup to hide");
-    popup.once("popup-closed", () => {
-      deferred.resolve();
-    });
-    EventUtils.synthesizeKey("VK_TAB", {});
-  });
-
-  jsterm.setInputValue("window.foobarBug585991");
+  jsterm.setInputValue("window.fooba");
+  EventUtils.synthesizeKey("r", {});
   EventUtils.synthesizeKey(".", {});
 
-  return deferred.promise;
-});
+  await onPopUpOpen;
+
+  ok(popup.isOpen, "popup is open");
 
-function popupHideAfterTab() {
-  let deferred = defer();
+  const expectedPopupItems = [
+    "item3",
+    "item2",
+    "item1",
+    "item0",
+  ];
+  is(popup.itemCount, expectedPopupItems.length, "popup.itemCount is correct");
+  is(popup.selectedIndex, expectedPopupItems.length - 1,
+    "First index from bottom is selected");
 
-  // At this point the completion suggestion should be accepted.
-  ok(!popup.isOpen, "popup is not open");
+  EventUtils.synthesizeKey("VK_DOWN", {});
+
+  is(popup.selectedIndex, 0, "index 0 is selected");
+  is(popup.selectedItem.label, "item3", "item3 is selected");
+  let prefix = jsterm.getInputValue().replace(/[\S]/g, " ");
+  is(completeNode.value, prefix + "item3", "completeNode.value holds item3");
 
-  is(jsterm.getInputValue(), "window.foobarBug585991.watch",
-     "completion was successful after VK_TAB");
+  info("press Return to accept suggestion. wait for popup to hide");
+  const onPopupClose = popup.once("popup-closed");
+  EventUtils.synthesizeKey("VK_RETURN", {});
 
+  await onPopupClose;
+
+  ok(!popup.isOpen, "popup is not open after VK_RETURN");
+  is(jsterm.getInputValue(), "window.foobar.item3",
+    "completion was successful after VK_RETURN");
   ok(!completeNode.value, "completeNode is empty");
 
-  popup.once("popup-opened", function onShown() {
-    ok(popup.isOpen, "popup is open");
-
-    is(popup.itemCount, 19, "popup.itemCount is correct");
-
-    is(popup.selectedIndex, 18, "First index from bottom is selected");
-    EventUtils.synthesizeKey("VK_DOWN", {});
-
-    let prefix = jsterm.getInputValue().replace(/[\S]/g, " ");
-
-    is(popup.selectedIndex, 0, "index 0 is selected");
-    is(popup.selectedItem.label, "watch", "watch is selected");
-    is(completeNode.value, prefix + "watch",
-        "completeNode.value holds watch");
-
-    popup.once("popup-closed", function onHidden() {
-      ok(!popup.isOpen, "popup is not open after VK_ESCAPE");
-
-      is(jsterm.getInputValue(), "window.foobarBug585991.",
-         "completion was cancelled");
-
-      ok(!completeNode.value, "completeNode is empty");
-
-      deferred.resolve();
-    }, false);
-
-    info("press Escape to close the popup");
-    executeSoon(function () {
-      EventUtils.synthesizeKey("VK_ESCAPE", {});
-    });
-  }, false);
-
-  info("wait for completion: window.foobarBug585991.");
-  executeSoon(function () {
-    jsterm.setInputValue("window.foobarBug585991");
-    EventUtils.synthesizeKey(".", {});
-  });
-
-  return deferred.promise;
-}
-
-function testReturnKey() {
-  let deferred = defer();
-
-  popup.once("popup-opened", function onShown() {
-    ok(popup.isOpen, "popup is open");
-
-    is(popup.itemCount, 19, "popup.itemCount is correct");
-
-    is(popup.selectedIndex, 18, "First index from bottom is selected");
-    EventUtils.synthesizeKey("VK_DOWN", {});
-
-    let prefix = jsterm.getInputValue().replace(/[\S]/g, " ");
-
-    is(popup.selectedIndex, 0, "index 0 is selected");
-    is(popup.selectedItem.label, "watch", "watch is selected");
-    is(completeNode.value, prefix + "watch",
-        "completeNode.value holds watch");
-
-    EventUtils.synthesizeKey("VK_DOWN", {});
-
-    is(popup.selectedIndex, 1, "index 1 is selected");
-    is(popup.selectedItem.label, "valueOf", "valueOf is selected");
-    is(completeNode.value, prefix + "valueOf",
-       "completeNode.value holds valueOf");
-
-    popup.once("popup-closed", function onHidden() {
-      ok(!popup.isOpen, "popup is not open after VK_RETURN");
-
-      is(jsterm.getInputValue(), "window.foobarBug585991.valueOf",
-         "completion was successful after VK_RETURN");
-
-      ok(!completeNode.value, "completeNode is empty");
-
-      deferred.resolve();
-    }, false);
-
-    info("press Return to accept suggestion. wait for popup to hide");
-
-    executeSoon(() => EventUtils.synthesizeKey("VK_RETURN", {}));
-  }, false);
-
-  info("wait for completion suggestions: window.foobarBug585991.");
-
-  executeSoon(function () {
-    jsterm.setInputValue("window.foobarBug58599");
-    EventUtils.synthesizeKey("1", {});
-    EventUtils.synthesizeKey(".", {});
-  });
-
-  return deferred.promise;
-}
-
-function* dontShowArrayNumbers() {
-  let deferred = defer();
-
-  info("dontShowArrayNumbers");
-  yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
-    content.wrappedJSObject.foobarBug585991 = ["Sherlock Holmes"];
-  });
-
-  jsterm = HUD.jsterm;
-  popup = jsterm.autocompletePopup;
-
-  popup.once("popup-opened", function onShown() {
-    let sameItems = popup.getItems().map(function (e) {
-      return e.label;
-    });
-    ok(!sameItems.some(function (prop) {
-      prop === "0";
-    }), "Completing on an array doesn't show numbers.");
-
-    popup.once("popup-closed", function popupHidden() {
-      deferred.resolve();
-    }, false);
-
-    info("wait for popup to hide");
-    executeSoon(() => EventUtils.synthesizeKey("VK_ESCAPE", {}));
-  }, false);
-
-  info("wait for popup to show");
-  executeSoon(() => {
-    jsterm.setInputValue("window.foobarBug585991");
-    EventUtils.synthesizeKey(".", {});
-  });
-
-  return deferred.promise;
-}
-
-function testReturnWithNoSelection() {
-  let deferred = defer();
-
-  info("test pressing return with open popup, but no selection, see bug 873250");
-
-  popup.once("popup-opened", function onShown() {
-    ok(popup.isOpen, "popup is open");
-    is(popup.itemCount, 2, "popup.itemCount is correct");
-    isnot(popup.selectedIndex, -1, "popup.selectedIndex is correct");
-
-    info("press Return and wait for popup to hide");
-    popup.once("popup-closed", function popupHidden() {
-      deferred.resolve();
-    });
-    executeSoon(() => EventUtils.synthesizeKey("VK_RETURN", {}));
-  });
-
-  executeSoon(() => {
-    info("wait for popup to show");
-    jsterm.setInputValue("window.testBu");
-    EventUtils.synthesizeKey("g", {});
-  });
-
-  return deferred.promise;
-}
-
-function popupHideAfterReturnWithNoSelection() {
-  ok(!popup.isOpen, "popup is not open after VK_RETURN");
-
-  is(jsterm.getInputValue(), "", "inputNode is empty after VK_RETURN");
-  is(completeNode.value, "", "completeNode is empty");
-  is(jsterm.history[jsterm.history.length - 1], "window.testBug",
-     "jsterm history is correct");
-
-  return promise.resolve();
-}
-
-function testCompletionInText() {
-  info("test that completion works inside text, see bug 812618");
-
-  let deferred = defer();
-
-  popup.once("popup-opened", function onShown() {
-    ok(popup.isOpen, "popup is open");
-    is(popup.itemCount, 2, "popup.itemCount is correct");
-
-    EventUtils.synthesizeKey("VK_DOWN", {});
-    is(popup.selectedIndex, 0, "popup.selectedIndex is correct");
-    ok(!completeNode.value, "completeNode.value is empty");
-
-    let items = popup.getItems().reverse().map(e => e.label);
-    let sameItems = items.every((prop, index) =>
-      ["testBug873250a", "testBug873250b"][index] === prop);
-    ok(sameItems, "getItems returns the items we expect");
-
-    info("press Tab and wait for popup to hide");
-    popup.once("popup-closed", function popupHidden() {
-      deferred.resolve();
-    });
-    EventUtils.synthesizeKey("VK_TAB", {});
-  });
-
-  jsterm.setInputValue("dump(window.testBu)");
-  inputNode.selectionStart = inputNode.selectionEnd = 18;
-  EventUtils.synthesizeKey("g", {});
-  return deferred.promise;
-}
-
-function popupHideAfterCompletionInText() {
-  // At this point the completion suggestion should be accepted.
-  ok(!popup.isOpen, "popup is not open");
-  is(jsterm.getInputValue(), "dump(window.testBug873250b)",
-     "completion was successful after VK_TAB");
-  is(inputNode.selectionStart, 26, "cursor location is correct");
-  is(inputNode.selectionStart, inputNode.selectionEnd,
-     "cursor location (confirmed)");
-  ok(!completeNode.value, "completeNode is empty");
-
-  return promise.resolve();
-}
+  Services.prefs.clearUserPref(PREF_AUTO_MULTILINE);
+});
copy from devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_autocomplete_keys.js
copy to devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_autocomplete_return_key_no_selection.js
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_autocomplete_keys.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_autocomplete_return_key_no_selection.js
@@ -1,369 +1,49 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-// See Bug 585991.
-
-const TEST_URI = "data:text/html;charset=utf-8,<p>bug 585991 - autocomplete " +
-                 "popup keyboard usage test";
-
-// We should turn off auto-multiline editing during these tests
-const PREF_AUTO_MULTILINE = "devtools.webconsole.autoMultiline";
-var HUD, popup, jsterm, inputNode, completeNode;
-
-add_task(function* () {
-  Services.prefs.setBoolPref(PREF_AUTO_MULTILINE, false);
-  yield loadTab(TEST_URI);
-  let hud = yield openConsole();
-
-  yield consoleOpened(hud);
-  yield popupHideAfterTab();
-  yield testReturnKey();
-  yield dontShowArrayNumbers();
-  yield testReturnWithNoSelection();
-  yield popupHideAfterReturnWithNoSelection();
-  yield testCompletionInText();
-  yield popupHideAfterCompletionInText();
-
-  HUD = popup = jsterm = inputNode = completeNode = null;
-  Services.prefs.setBoolPref(PREF_AUTO_MULTILINE, true);
-});
-
-var consoleOpened = Task.async(function* (hud) {
-  let deferred = defer();
-  HUD = hud;
-  info("web console opened");
-
-  jsterm = HUD.jsterm;
-
-  yield jsterm.execute("window.foobarBug585991={" +
-    "'item0': 'value0'," +
-    "'item1': 'value1'," +
-    "'item2': 'value2'," +
-    "'item3': 'value3'" +
-  "}");
-  yield jsterm.execute("window.testBug873250a = 'hello world';"
-    + "window.testBug873250b = 'hello world 2';");
-  popup = jsterm.autocompletePopup;
-  completeNode = jsterm.completeNode;
-  inputNode = jsterm.inputNode;
-
-  ok(!popup.isOpen, "popup is not open");
-
-  popup.once("popup-opened", () => {
-    ok(popup.isOpen, "popup is open");
-
-    // 4 values, and the following properties:
-    // __defineGetter__  __defineSetter__ __lookupGetter__ __lookupSetter__
-    // __proto__ hasOwnProperty isPrototypeOf propertyIsEnumerable
-    // toLocaleString toString toSource unwatch valueOf watch constructor.
-    is(popup.itemCount, 19, "popup.itemCount is correct");
-
-    let sameItems = popup.getItems().reverse().map(function (e) {
-      return e.label;
-    });
-
-    ok(sameItems.every(function (prop, index) {
-      return [
-        "__defineGetter__",
-        "__defineSetter__",
-        "__lookupGetter__",
-        "__lookupSetter__",
-        "__proto__",
-        "constructor",
-        "hasOwnProperty",
-        "isPrototypeOf",
-        "item0",
-        "item1",
-        "item2",
-        "item3",
-        "propertyIsEnumerable",
-        "toLocaleString",
-        "toSource",
-        "toString",
-        "unwatch",
-        "valueOf",
-        "watch",
-      ][index] === prop;
-    }), "getItems returns the items we expect");
-
-    is(popup.selectedIndex, 18,
-       "Index of the first item from bottom is selected.");
-    EventUtils.synthesizeKey("VK_DOWN", {});
-
-    let prefix = jsterm.getInputValue().replace(/[\S]/g, " ");
-
-    is(popup.selectedIndex, 0, "index 0 is selected");
-    is(popup.selectedItem.label, "watch", "watch is selected");
-    is(completeNode.value, prefix + "watch",
-        "completeNode.value holds watch");
-
-    EventUtils.synthesizeKey("VK_DOWN", {});
-
-    is(popup.selectedIndex, 1, "index 1 is selected");
-    is(popup.selectedItem.label, "valueOf", "valueOf is selected");
-    is(completeNode.value, prefix + "valueOf",
-        "completeNode.value holds valueOf");
-
-    EventUtils.synthesizeKey("VK_UP", {});
-
-    is(popup.selectedIndex, 0, "index 0 is selected");
-    is(popup.selectedItem.label, "watch", "watch is selected");
-    is(completeNode.value, prefix + "watch",
-        "completeNode.value holds watch");
-
-    let currentSelectionIndex = popup.selectedIndex;
-
-    EventUtils.synthesizeKey("VK_PAGE_DOWN", {});
-
-    ok(popup.selectedIndex > currentSelectionIndex,
-      "Index is greater after PGDN");
-
-    currentSelectionIndex = popup.selectedIndex;
-    EventUtils.synthesizeKey("VK_PAGE_UP", {});
-
-    ok(popup.selectedIndex < currentSelectionIndex,
-       "Index is less after Page UP");
-
-    EventUtils.synthesizeKey("VK_END", {});
-    is(popup.selectedIndex, 18, "index is last after End");
-
-    EventUtils.synthesizeKey("VK_HOME", {});
-    is(popup.selectedIndex, 0, "index is first after Home");
-
-    info("press Tab and wait for popup to hide");
-    popup.once("popup-closed", () => {
-      deferred.resolve();
-    });
-    EventUtils.synthesizeKey("VK_TAB", {});
-  });
+// See Bug 873250.
 
-  jsterm.setInputValue("window.foobarBug585991");
-  EventUtils.synthesizeKey(".", {});
-
-  return deferred.promise;
-});
-
-function popupHideAfterTab() {
-  let deferred = defer();
-
-  // At this point the completion suggestion should be accepted.
-  ok(!popup.isOpen, "popup is not open");
-
-  is(jsterm.getInputValue(), "window.foobarBug585991.watch",
-     "completion was successful after VK_TAB");
-
-  ok(!completeNode.value, "completeNode is empty");
-
-  popup.once("popup-opened", function onShown() {
-    ok(popup.isOpen, "popup is open");
-
-    is(popup.itemCount, 19, "popup.itemCount is correct");
-
-    is(popup.selectedIndex, 18, "First index from bottom is selected");
-    EventUtils.synthesizeKey("VK_DOWN", {});
-
-    let prefix = jsterm.getInputValue().replace(/[\S]/g, " ");
-
-    is(popup.selectedIndex, 0, "index 0 is selected");
-    is(popup.selectedItem.label, "watch", "watch is selected");
-    is(completeNode.value, prefix + "watch",
-        "completeNode.value holds watch");
-
-    popup.once("popup-closed", function onHidden() {
-      ok(!popup.isOpen, "popup is not open after VK_ESCAPE");
-
-      is(jsterm.getInputValue(), "window.foobarBug585991.",
-         "completion was cancelled");
-
-      ok(!completeNode.value, "completeNode is empty");
-
-      deferred.resolve();
-    }, false);
-
-    info("press Escape to close the popup");
-    executeSoon(function () {
-      EventUtils.synthesizeKey("VK_ESCAPE", {});
-    });
-  }, false);
-
-  info("wait for completion: window.foobarBug585991.");
-  executeSoon(function () {
-    jsterm.setInputValue("window.foobarBug585991");
-    EventUtils.synthesizeKey(".", {});
-  });
-
-  return deferred.promise;
-}
-
-function testReturnKey() {
-  let deferred = defer();
-
-  popup.once("popup-opened", function onShown() {
-    ok(popup.isOpen, "popup is open");
-
-    is(popup.itemCount, 19, "popup.itemCount is correct");
+const TEST_URI = `data:text/html;charset=utf-8,
+<head>
+  <script>
+    window.testBugA = "hello world";
+    window.testBugB = "hello world 2";
+  </script>
+</head>
+<body>bug 873250 - test pressing return with open popup, but no selection</body>`;
 
-    is(popup.selectedIndex, 18, "First index from bottom is selected");
-    EventUtils.synthesizeKey("VK_DOWN", {});
-
-    let prefix = jsterm.getInputValue().replace(/[\S]/g, " ");
-
-    is(popup.selectedIndex, 0, "index 0 is selected");
-    is(popup.selectedItem.label, "watch", "watch is selected");
-    is(completeNode.value, prefix + "watch",
-        "completeNode.value holds watch");
-
-    EventUtils.synthesizeKey("VK_DOWN", {});
-
-    is(popup.selectedIndex, 1, "index 1 is selected");
-    is(popup.selectedItem.label, "valueOf", "valueOf is selected");
-    is(completeNode.value, prefix + "valueOf",
-       "completeNode.value holds valueOf");
-
-    popup.once("popup-closed", function onHidden() {
-      ok(!popup.isOpen, "popup is not open after VK_RETURN");
-
-      is(jsterm.getInputValue(), "window.foobarBug585991.valueOf",
-         "completion was successful after VK_RETURN");
-
-      ok(!completeNode.value, "completeNode is empty");
-
-      deferred.resolve();
-    }, false);
-
-    info("press Return to accept suggestion. wait for popup to hide");
-
-    executeSoon(() => EventUtils.synthesizeKey("VK_RETURN", {}));
-  }, false);
-
-  info("wait for completion suggestions: window.foobarBug585991.");
+add_task(async function () {
+  let { jsterm } = await openNewTabAndConsole(TEST_URI);
+  const {
+    autocompletePopup: popup,
+    completeNode,
+    inputNode,
+  } = jsterm;
 
-  executeSoon(function () {
-    jsterm.setInputValue("window.foobarBug58599");
-    EventUtils.synthesizeKey("1", {});
-    EventUtils.synthesizeKey(".", {});
-  });
-
-  return deferred.promise;
-}
-
-function* dontShowArrayNumbers() {
-  let deferred = defer();
-
-  info("dontShowArrayNumbers");
-  yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
-    content.wrappedJSObject.foobarBug585991 = ["Sherlock Holmes"];
-  });
-
-  jsterm = HUD.jsterm;
-  popup = jsterm.autocompletePopup;
-
-  popup.once("popup-opened", function onShown() {
-    let sameItems = popup.getItems().map(function (e) {
-      return e.label;
-    });
-    ok(!sameItems.some(function (prop) {
-      prop === "0";
-    }), "Completing on an array doesn't show numbers.");
-
-    popup.once("popup-closed", function popupHidden() {
-      deferred.resolve();
-    }, false);
-
-    info("wait for popup to hide");
-    executeSoon(() => EventUtils.synthesizeKey("VK_ESCAPE", {}));
-  }, false);
+  const onPopUpOpen = popup.once("popup-opened");
 
   info("wait for popup to show");
-  executeSoon(() => {
-    jsterm.setInputValue("window.foobarBug585991");
-    EventUtils.synthesizeKey(".", {});
-  });
-
-  return deferred.promise;
-}
+  jsterm.setInputValue("window.testBu");
+  EventUtils.synthesizeKey("g", {});
 
-function testReturnWithNoSelection() {
-  let deferred = defer();
-
-  info("test pressing return with open popup, but no selection, see bug 873250");
-
-  popup.once("popup-opened", function onShown() {
-    ok(popup.isOpen, "popup is open");
-    is(popup.itemCount, 2, "popup.itemCount is correct");
-    isnot(popup.selectedIndex, -1, "popup.selectedIndex is correct");
+  await onPopUpOpen;
 
-    info("press Return and wait for popup to hide");
-    popup.once("popup-closed", function popupHidden() {
-      deferred.resolve();
-    });
-    executeSoon(() => EventUtils.synthesizeKey("VK_RETURN", {}));
-  });
+  ok(popup.isOpen, "popup is open");
+  is(popup.itemCount, 2, "popup.itemCount is correct");
+  isnot(popup.selectedIndex, -1, "popup.selectedIndex is correct");
 
-  executeSoon(() => {
-    info("wait for popup to show");
-    jsterm.setInputValue("window.testBu");
-    EventUtils.synthesizeKey("g", {});
-  });
+  info("press Return and wait for popup to hide");
+  const onPopUpClose = popup.once("popup-closed");
+  executeSoon(() => EventUtils.synthesizeKey("VK_RETURN", {}));
+  await onPopUpClose;
 
-  return deferred.promise;
-}
-
-function popupHideAfterReturnWithNoSelection() {
   ok(!popup.isOpen, "popup is not open after VK_RETURN");
-
   is(jsterm.getInputValue(), "", "inputNode is empty after VK_RETURN");
   is(completeNode.value, "", "completeNode is empty");
   is(jsterm.history[jsterm.history.length - 1], "window.testBug",
      "jsterm history is correct");
-
-  return promise.resolve();
-}
-
-function testCompletionInText() {
-  info("test that completion works inside text, see bug 812618");
-
-  let deferred = defer();
-
-  popup.once("popup-opened", function onShown() {
-    ok(popup.isOpen, "popup is open");
-    is(popup.itemCount, 2, "popup.itemCount is correct");
-
-    EventUtils.synthesizeKey("VK_DOWN", {});
-    is(popup.selectedIndex, 0, "popup.selectedIndex is correct");
-    ok(!completeNode.value, "completeNode.value is empty");
-
-    let items = popup.getItems().reverse().map(e => e.label);
-    let sameItems = items.every((prop, index) =>
-      ["testBug873250a", "testBug873250b"][index] === prop);
-    ok(sameItems, "getItems returns the items we expect");
-
-    info("press Tab and wait for popup to hide");
-    popup.once("popup-closed", function popupHidden() {
-      deferred.resolve();
-    });
-    EventUtils.synthesizeKey("VK_TAB", {});
-  });
-
-  jsterm.setInputValue("dump(window.testBu)");
-  inputNode.selectionStart = inputNode.selectionEnd = 18;
-  EventUtils.synthesizeKey("g", {});
-  return deferred.promise;
-}
-
-function popupHideAfterCompletionInText() {
-  // At this point the completion suggestion should be accepted.
-  ok(!popup.isOpen, "popup is not open");
-  is(jsterm.getInputValue(), "dump(window.testBug873250b)",
-     "completion was successful after VK_TAB");
-  is(inputNode.selectionStart, 26, "cursor location is correct");
-  is(inputNode.selectionStart, inputNode.selectionEnd,
-     "cursor location (confirmed)");
-  ok(!completeNode.value, "completeNode is empty");
-
-  return promise.resolve();
-}
+});
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_dollar.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_dollar.js
@@ -6,23 +6,20 @@
 "use strict";
 
 // Test that using `$` and `$$` in jsterm call the global content functions
 // if they are defined. See Bug 621644.
 
 const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
                  "new-console-output/test/mochitest/test-jsterm-dollar.html";
 
-add_task(function* () {
-  let hud = yield openNewTabAndConsole(TEST_URI);
-  yield test$(hud);
-  yield test$$(hud);
-
-  // Clear history to not affect next tests.
-  yield hud.jsterm.clearHistory();
+add_task(async function () {
+  let hud = await openNewTabAndConsole(TEST_URI);
+  await test$(hud);
+  await test$$(hud);
 });
 
 async function test$(hud) {
   hud.jsterm.clearOutput();
   const msg = await hud.jsterm.execute("$(document.body)");
   ok(msg.textContent.includes("<p>"), "jsterm output is correct for $()");
 }
 
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_history_persist.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_history_persist.js
@@ -9,47 +9,44 @@
 "use strict";
 
 requestLongerTimeout(2);
 
 const TEST_URI = "data:text/html;charset=utf-8,Web Console test for " +
                  "persisting history - bug 943306";
 const INPUT_HISTORY_COUNT = 10;
 
-add_task(function* () {
+add_task(async function() {
   info("Setting custom input history pref to " + INPUT_HISTORY_COUNT);
   Services.prefs.setIntPref("devtools.webconsole.inputHistoryCount", INPUT_HISTORY_COUNT);
 
   // First tab: run a bunch of commands and then make sure that you can
   // navigate through their history.
-  let hud1 = yield openNewTabAndConsole(TEST_URI);
-
-  // Clearing history that might have been set in previous tests.
-  yield hud1.jsterm.clearHistory();
+  let hud1 = await openNewTabAndConsole(TEST_URI);
 
   is(JSON.stringify(hud1.jsterm.history), "[]", "No history on first tab initially");
-  yield populateInputHistory(hud1);
+  await populateInputHistory(hud1);
   is(JSON.stringify(hud1.jsterm.history),
      '["0","1","2","3","4","5","6","7","8","9"]',
      "First tab has populated history");
 
   // Second tab: Just make sure that you can navigate through the history
   // generated by the first tab.
-  let hud2 = yield openNewTabAndConsole(TEST_URI);
+  let hud2 = await openNewTabAndConsole(TEST_URI, false);
   is(JSON.stringify(hud2.jsterm.history),
      '["0","1","2","3","4","5","6","7","8","9"]',
      "Second tab has populated history");
-  yield testNavigatingHistoryInUI(hud2);
+  await testNavigatingHistoryInUI(hud2);
   is(JSON.stringify(hud2.jsterm.history),
      '["0","1","2","3","4","5","6","7","8","9",""]',
      "An empty entry has been added in the second tab due to history perusal");
 
   // Third tab: Should have the same history as first tab, but if we run a
   // command, then the history of the first and second shouldn't be affected
-  let hud3 = yield openNewTabAndConsole(TEST_URI);
+  let hud3 = await openNewTabAndConsole(TEST_URI, false);
   is(JSON.stringify(hud3.jsterm.history),
      '["0","1","2","3","4","5","6","7","8","9"]',
      "Third tab has populated history");
 
   // Set input value separately from execute so UP arrow accurately navigates
   // history.
   hud3.jsterm.setInputValue('"hello from third tab"');
   hud3.jsterm.execute();
@@ -62,44 +59,44 @@ add_task(function* () {
      "Second tab history hasn't changed due to command in third tab");
   is(JSON.stringify(hud3.jsterm.history),
      '["1","2","3","4","5","6","7","8","9","\\"hello from third tab\\""]',
      "Third tab has updated history (and purged the first result) after " +
      "running a command");
 
   // Fourth tab: Should have the latest command from the third tab, followed
   // by the rest of the history from the first tab.
-  let hud4 = yield openNewTabAndConsole(TEST_URI);
+  let hud4 = await openNewTabAndConsole(TEST_URI, false);
   is(JSON.stringify(hud4.jsterm.history),
      '["1","2","3","4","5","6","7","8","9","\\"hello from third tab\\""]',
      "Fourth tab has most recent history");
 
-  yield hud4.jsterm.clearHistory();
+  await hud4.jsterm.clearHistory();
   is(JSON.stringify(hud4.jsterm.history), "[]", "Clearing history for a tab works");
 
-  let hud5 = yield openNewTabAndConsole(TEST_URI);
+  let hud5 = await openNewTabAndConsole(TEST_URI, false);
   is(JSON.stringify(hud5.jsterm.history), "[]",
      "Clearing history carries over to a new tab");
 
   info("Clearing custom input history pref");
   Services.prefs.clearUserPref("devtools.webconsole.inputHistoryCount");
 });
 
 /**
  * Populate the history by running the following commands:
  *  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  */
-function* populateInputHistory(hud) {
+async function populateInputHistory(hud) {
   let jsterm = hud.jsterm;
 
   for (let i = 0; i < INPUT_HISTORY_COUNT; i++) {
     // Set input value separately from execute so UP arrow accurately navigates
     // history.
     jsterm.setInputValue(i);
-    yield jsterm.execute();
+    await jsterm.execute();
   }
 }
 
 /**
  * Check pressing up results in history traversal like:
  *  [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
  */
 function testNavigatingHistoryInUI(hud) {
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_no_autocompletion_on_defined_variables.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_no_autocompletion_on_defined_variables.js
@@ -5,18 +5,18 @@
 
 // Tests for bug 704295
 
 "use strict";
 
 const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
                  "test/test-console.html";
 
-add_task(function* () {
-  let hud = yield openNewTabAndConsole(TEST_URI);
+add_task(async function () {
+  let hud = await openNewTabAndConsole(TEST_URI);
   testCompletion(hud);
 });
 
 function testCompletion(hud) {
   let jsterm = hud.jsterm;
   let input = jsterm.inputNode;
 
   // Test typing 'var d = 5;' and press RETURN
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_no_input_and_tab_key_pressed.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_no_input_and_tab_key_pressed.js
@@ -4,18 +4,18 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // See Bug 583816.
 
 const TEST_URI = "data:text/html,Testing jsterm with no input";
 
-add_task(function* () {
-  let hud = yield openNewTabAndConsole(TEST_URI);
+add_task(async function() {
+  let hud = await openNewTabAndConsole(TEST_URI);
   testCompletion(hud);
 });
 
 function testCompletion(hud) {
   let jsterm = hud.jsterm;
   let input = jsterm.inputNode;
 
   jsterm.setInputValue("");
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_no_input_change_and_tab_key_pressed.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_no_input_change_and_tab_key_pressed.js
@@ -4,18 +4,18 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // See Bug 734061.
 
 const TEST_URI = "data:text/html,Testing jsterm focus";
 
-add_task(function* () {
-  let hud = yield openNewTabAndConsole(TEST_URI);
+add_task(async function() {
+  let hud = await openNewTabAndConsole(TEST_URI);
   let jsterm = hud.jsterm;
   let input = jsterm.inputNode;
 
   is(hasFocus(input), true, "input has focus");
   EventUtils.synthesizeKey("VK_TAB", {});
   is(hasFocus(input), false, "focus moved away");
 
   // Test user changed something
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_batching.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_batching.js
@@ -5,43 +5,43 @@
 
 "use strict";
 
 // Check adding console calls as batch keep the order of the message.
 
 const TEST_URI = "http://example.com/browser/devtools/client/webconsole/new-console-output/test/mochitest/test-batching.html";
 const { l10n } = require("devtools/client/webconsole/new-console-output/utils/messages");
 
-add_task(function* () {
-  let hud = yield openNewTabAndConsole(TEST_URI);
+add_task(async function() {
+  let hud = await openNewTabAndConsole(TEST_URI);
   const messageNumber = 100;
-  yield testSimpleBatchLogging(hud, messageNumber);
-  yield testBatchLoggingAndClear(hud, messageNumber);
+  await testSimpleBatchLogging(hud, messageNumber);
+  await testBatchLoggingAndClear(hud, messageNumber);
 });
 
-function* testSimpleBatchLogging(hud, messageNumber) {
-  yield ContentTask.spawn(gBrowser.selectedBrowser, messageNumber,
+async function testSimpleBatchLogging(hud, messageNumber) {
+  await ContentTask.spawn(gBrowser.selectedBrowser, messageNumber,
     function (numMessages) {
       content.wrappedJSObject.batchLog(numMessages);
     }
   );
 
   for (let i = 0; i < messageNumber; i++) {
-    let node = yield waitFor(() => findMessageAtIndex(hud, i, i));
+    let node = await waitFor(() => findMessageAtIndex(hud, i, i));
     is(node.textContent, i.toString(), `message at index "${i}" is the expected one`);
   }
 }
 
-function* testBatchLoggingAndClear(hud, messageNumber) {
-  yield ContentTask.spawn(gBrowser.selectedBrowser, messageNumber,
+async function testBatchLoggingAndClear(hud, messageNumber) {
+  await ContentTask.spawn(gBrowser.selectedBrowser, messageNumber,
     function (numMessages) {
       content.wrappedJSObject.batchLogAndClear(numMessages);
     }
   );
-  yield waitFor(() => findMessage(hud, l10n.getStr("consoleCleared")));
+  await waitFor(() => findMessage(hud, l10n.getStr("consoleCleared")));
   ok(true, "console cleared message is displayed");
 
   // Passing the text argument as an empty string will returns all the message,
   // whatever their content is.
   const messages = findMessages(hud, "");
   is(messages.length, 1, "console was cleared as expected");
 }
 
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_context_menu_copy_entire_message.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_context_menu_copy_entire_message.js
@@ -23,77 +23,77 @@ const TEST_URI = `data:text/html;charset
     }
     wrapper();
   };
 </script>`;
 
 // Test the Copy menu item of the webconsole copies the expected clipboard text for
 // different log messages.
 
-add_task(function* () {
+add_task(async function() {
   let observer = new PrefObserver("");
   let onPrefUpdated = observer.once(PREF_MESSAGE_TIMESTAMP, () => {});
   Services.prefs.setBoolPref(PREF_MESSAGE_TIMESTAMP, true);
-  yield onPrefUpdated;
+  await onPrefUpdated;
 
-  let hud = yield openNewTabAndConsole(TEST_URI);
+  let hud = await openNewTabAndConsole(TEST_URI);
   hud.jsterm.clearOutput();
 
   info("Call the log function defined in the test page");
-  yield ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
     content.wrappedJSObject.logStuff();
   });
 
   info("Test copy menu item for the simple log");
-  let message = yield waitFor(() => findMessage(hud, "simple text message"));
-  let clipboardText = yield copyMessageContent(hud, message);
+  let message = await waitFor(() => findMessage(hud, "simple text message"));
+  let clipboardText = await copyMessageContent(hud, message);
   ok(true, "Clipboard text was found and saved");
 
   info("Check copied text for simple log message");
   let lines = clipboardText.split("\n");
   ok(lines.length, 2, "There are 2 lines in the copied text");
   is(lines[1], "", "The last line is an empty new line");
   ok(LOG_FORMAT_WITH_TIMESTAMP.test(lines[0]),
     "Log line has the right format:\n" + lines[0]);
 
   info("Test copy menu item for the stack trace message");
-  message = yield waitFor(() => findMessage(hud, "console.trace"));
-  clipboardText = yield copyMessageContent(hud, message);
+  message = await waitFor(() => findMessage(hud, "console.trace"));
+  clipboardText = await copyMessageContent(hud, message);
   ok(true, "Clipboard text was found and saved");
 
   info("Check copied text for stack trace message");
   lines = clipboardText.split("\n");
   ok(lines.length, 4, "There are 4 lines in the copied text");
   is(lines[3], "", "The last line is an empty new line");
   ok(LOG_FORMAT_WITH_TIMESTAMP.test(lines[0]),
     "Log line has the right format:\n" + lines[0]);
   ok(TRACE_FORMAT.test(lines[1]), "Stacktrace line has the right format:\n" + lines[1]);
   ok(TRACE_FORMAT.test(lines[2]), "Stacktrace line has the right format:\n" + lines[2]);
 
   info("Test copy menu item without timestamp");
 
   onPrefUpdated = observer.once(PREF_MESSAGE_TIMESTAMP, () => {});
   Services.prefs.setBoolPref(PREF_MESSAGE_TIMESTAMP, false);
-  yield onPrefUpdated;
+  await onPrefUpdated;
 
   info("Test copy menu item for the simple log");
-  message = yield waitFor(() => findMessage(hud, "simple text message"));
-  clipboardText = yield copyMessageContent(hud, message);
+  message = await waitFor(() => findMessage(hud, "simple text message"));
+  clipboardText = await copyMessageContent(hud, message);
   ok(true, "Clipboard text was found and saved");
 
   info("Check copied text for simple log message");
   lines = clipboardText.split("\n");
   ok(lines.length, 2, "There are 2 lines in the copied text");
   is(lines[1], "", "The last line is an empty new line");
   ok(LOG_FORMAT_WITHOUT_TIMESTAMP.test(lines[0]),
     "Log line has the right format:\n" + lines[0]);
 
   info("Test copy menu item for the stack trace message");
-  message = yield waitFor(() => findMessage(hud, "console.trace"));
-  clipboardText = yield copyMessageContent(hud, message);
+  message = await waitFor(() => findMessage(hud, "console.trace"));
+  clipboardText = await copyMessageContent(hud, message);
   ok(true, "Clipboard text was found and saved");
 
   info("Check copied text for stack trace message");
   lines = clipboardText.split("\n");
   ok(lines.length, 4, "There are 4 lines in the copied text");
   is(lines[3], "", "The last line is an empty new line");
   ok(LOG_FORMAT_WITHOUT_TIMESTAMP.test(lines[0]),
     "Log line has the right format:\n" + lines[0]);
@@ -103,23 +103,23 @@ add_task(function* () {
   observer.destroy();
   Services.prefs.clearUserPref(PREF_MESSAGE_TIMESTAMP);
 });
 
 /**
  * Simple helper method to open the context menu on a given message, and click on the copy
  * menu item.
  */
-function* copyMessageContent(hud, message) {
-  let menuPopup = yield openContextMenu(hud, message);
+async function copyMessageContent(hud, message) {
+  let menuPopup = await openContextMenu(hud, message);
   let copyMenuItem = menuPopup.querySelector("#console-menu-copy");
   ok(copyMenuItem, "copy menu item is enabled");
 
   let clipboardText;
-  yield waitForClipboardPromise(
+  await waitForClipboardPromise(
     () => copyMenuItem.click(),
     data => {
       clipboardText = data;
       return data === message.textContent;
     }
   );
   return clipboardText;
 }
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_context_menu_copy_link_location.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_context_menu_copy_link_location.js
@@ -7,53 +7,53 @@
 // messages and copies the expected URL.
 
 "use strict";
 
 const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
   "new-console-output/test/mochitest/test-console.html?_date=" + Date.now();
 const CONTEXT_MENU_ID = "#console-menu-copy-url";
 
-add_task(function* () {
+add_task(async function() {
   // Enable net messages in the console for this test.
-  yield pushPref("devtools.webconsole.filter.net", true);
+  await pushPref("devtools.webconsole.filter.net", true);
 
-  let hud = yield openNewTabAndConsole(TEST_URI);
+  let hud = await openNewTabAndConsole(TEST_URI);
   hud.jsterm.clearOutput();
 
   info("Test Copy URL menu item for text log");
 
   info("Logging a text message in the content window");
-  yield ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
     content.wrappedJSObject.console.log("simple text message");
   });
-  let message = yield waitFor(() => findMessage(hud, "simple text message"));
+  let message = await waitFor(() => findMessage(hud, "simple text message"));
   ok(message, "Text log found in the console");
 
   info("Open and check the context menu for the logged text message");
-  let menuPopup = yield openContextMenu(hud, message);
+  let menuPopup = await openContextMenu(hud, message);
   let copyURLItem = menuPopup.querySelector(CONTEXT_MENU_ID);
   ok(!copyURLItem, "Copy URL menu item is hidden for a simple text message");
 
-  yield hideContextMenu(hud);
+  await hideContextMenu(hud);
   hud.jsterm.clearOutput();
 
   info("Test Copy URL menu item for network log");
 
   info("Reload the content window to produce a network log");
-  yield ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
     content.wrappedJSObject.location.reload();
   });
 
-  message = yield waitFor(() => findMessage(hud, "test-console.html"));
+  message = await waitFor(() => findMessage(hud, "test-console.html"));
   ok(message, "Network log found in the console");
 
   info("Open and check the context menu for the logged network message");
-  menuPopup = yield openContextMenu(hud, message);
+  menuPopup = await openContextMenu(hud, message);
   copyURLItem = menuPopup.querySelector(CONTEXT_MENU_ID);
   ok(copyURLItem, "Copy url menu item is available in context menu");
 
   info("Click on Copy URL menu item and wait for clipboard to be updated");
-  yield waitForClipboardPromise(() => copyURLItem.click(), TEST_URI);
+  await waitForClipboardPromise(() => copyURLItem.click(), TEST_URI);
   ok(true, "Expected text was copied to the clipboard.");
 
-  yield hideContextMenu(hud);
+  await hideContextMenu(hud);
 });
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_context_menu_copy_object.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_context_menu_copy_object.js
@@ -17,107 +17,107 @@ const TEST_URI = `data:text/html;charset
   console.log(532);
   console.log(true);
   console.log(false);
   console.log(undefined);
   console.log(null);
 </script>`;
 const copyObjectMenuItemId = "#console-menu-copy-object";
 
-add_task(function* () {
-  let hud = yield openNewTabAndConsole(TEST_URI);
+add_task(async function() {
+  let hud = await openNewTabAndConsole(TEST_URI);
 
   let [msgWithText, msgWithObj, msgNested] =
-    yield waitFor(() => findMessages(hud, "foo"));
+    await waitFor(() => findMessages(hud, "foo"));
   ok(msgWithText && msgWithObj && msgNested, "Three messages should have appeared");
 
-  let [groupMsgObj] = yield waitFor(() => findMessages(hud, "group", ".message-body"));
-  let [collapsedGroupMsgObj] = yield waitFor(() =>
+  let [groupMsgObj] = await waitFor(() => findMessages(hud, "group", ".message-body"));
+  let [collapsedGroupMsgObj] = await waitFor(() =>
     findMessages(hud, "collapsed", ".message-body"));
-  let [numberMsgObj] = yield waitFor(() => findMessages(hud, `532`, ".message-body"));
-  let [trueMsgObj] = yield waitFor(() => findMessages(hud, `true`, ".message-body"));
-  let [falseMsgObj] = yield waitFor(() => findMessages(hud, `false`, ".message-body"));
-  let [undefinedMsgObj] = yield waitFor(() => findMessages(hud, `undefined`,
+  let [numberMsgObj] = await waitFor(() => findMessages(hud, `532`, ".message-body"));
+  let [trueMsgObj] = await waitFor(() => findMessages(hud, `true`, ".message-body"));
+  let [falseMsgObj] = await waitFor(() => findMessages(hud, `false`, ".message-body"));
+  let [undefinedMsgObj] = await waitFor(() => findMessages(hud, `undefined`,
     ".message-body"));
-  let [nullMsgObj] = yield waitFor(() => findMessages(hud, `null`, ".message-body"));
+  let [nullMsgObj] = await waitFor(() => findMessages(hud, `null`, ".message-body"));
   ok(nullMsgObj, "One message with null value should have appeared");
 
   let text = msgWithText.querySelector(".objectBox-string");
   let objInMsgWithObj = msgWithObj.querySelector(".objectBox-object");
   let textInMsgWithObj = msgWithObj.querySelector(".objectBox-string");
 
   // The third message has an object nested in an array, the array is therefore the top
   // object, the object is the nested object.
   let topObjInMsg = msgNested.querySelector(".objectBox-array");
   let nestedObjInMsg = msgNested.querySelector(".objectBox-object");
 
-  let consoleMessages = yield waitFor(() => findMessages(hud, "console.log(\"foo\");",
+  let consoleMessages = await waitFor(() => findMessages(hud, "console.log(\"foo\");",
     ".message-location"));
-  yield testCopyObjectMenuItemDisabled(hud, consoleMessages[0]);
+  await testCopyObjectMenuItemDisabled(hud, consoleMessages[0]);
 
   info(`Check "Copy object" is enabled for text only messages
     thus copying the text`);
-  yield testCopyObject(hud, text, `foo`, false);
+  await testCopyObject(hud, text, `foo`, false);
 
   info(`Check "Copy object" is enabled for text in complex messages
    thus copying the text`);
-  yield testCopyObject(hud, textInMsgWithObj, `foo`, false);
+  await testCopyObject(hud, textInMsgWithObj, `foo`, false);
 
   info("Check `Copy object` is enabled for objects in complex messages");
-  yield testCopyObject(hud, objInMsgWithObj, `{"baz":1}`, true);
+  await testCopyObject(hud, objInMsgWithObj, `{"baz":1}`, true);
 
   info("Check `Copy object` is enabled for top object in nested messages");
-  yield testCopyObject(hud, topObjInMsg, `["foo",{"baz":1},2]`, true);
+  await testCopyObject(hud, topObjInMsg, `["foo",{"baz":1},2]`, true);
 
   info("Check `Copy object` is enabled for nested object in nested messages");
-  yield testCopyObject(hud, nestedObjInMsg, `{"baz":1}`, true);
+  await testCopyObject(hud, nestedObjInMsg, `{"baz":1}`, true);
 
   info("Check `Copy object` is disabled on `console.group('group')` messages");
-  yield testCopyObjectMenuItemDisabled(hud, groupMsgObj);
+  await testCopyObjectMenuItemDisabled(hud, groupMsgObj);
 
   info(`Check "Copy object" is disabled in "console.groupCollapsed('collapsed')"
     messages`);
-  yield testCopyObjectMenuItemDisabled(hud, collapsedGroupMsgObj);
+  await testCopyObjectMenuItemDisabled(hud, collapsedGroupMsgObj);
 
   // Check for primitive objects
   info("Check `Copy object` is enabled for numbers");
-  yield testCopyObject(hud, numberMsgObj, `532`, false);
+  await testCopyObject(hud, numberMsgObj, `532`, false);
 
   info("Check `Copy object` is enabled for booleans");
-  yield testCopyObject(hud, trueMsgObj, `true`, false);
-  yield testCopyObject(hud, falseMsgObj, `false`, false);
+  await testCopyObject(hud, trueMsgObj, `true`, false);
+  await testCopyObject(hud, falseMsgObj, `false`, false);
 
   info("Check `Copy object` is enabled for undefined and null");
-  yield testCopyObject(hud, undefinedMsgObj, `undefined`, false);
-  yield testCopyObject(hud, nullMsgObj, `null`, false);
+  await testCopyObject(hud, undefinedMsgObj, `undefined`, false);
+  await testCopyObject(hud, nullMsgObj, `null`, false);
 });
 
-function* testCopyObject(hud, element, expectedMessage, objectInput) {
+async function testCopyObject(hud, element, expectedMessage, objectInput) {
   info("Check `Copy object` is enabled");
-  let menuPopup = yield openContextMenu(hud, element);
+  let menuPopup = await openContextMenu(hud, element);
   let copyObjectMenuItem = menuPopup.querySelector(copyObjectMenuItemId);
   ok(!copyObjectMenuItem.disabled,
     "`Copy object` is enabled for object in complex message");
 
   const validatorFn = data => {
     let prettifiedMessage = prettyPrintMessage(expectedMessage, objectInput);
     return data === prettifiedMessage;
   };
 
   info("Click on `Copy object`");
-  yield waitForClipboardPromise(() => copyObjectMenuItem.click(), validatorFn);
+  await waitForClipboardPromise(() => copyObjectMenuItem.click(), validatorFn);
 
   info("`Copy object` by using the access-key O");
-  menuPopup = yield openContextMenu(hud, element);
-  yield waitForClipboardPromise(() => synthesizeKeyShortcut("O"), validatorFn);
+  menuPopup = await openContextMenu(hud, element);
+  await waitForClipboardPromise(() => synthesizeKeyShortcut("O"), validatorFn);
 }
 
-function* testCopyObjectMenuItemDisabled(hud, element) {
-  let menuPopup = yield openContextMenu(hud, element);
+async function testCopyObjectMenuItemDisabled(hud, element) {
+  let menuPopup = await openContextMenu(hud, element);
   let copyObjectMenuItem = menuPopup.querySelector(copyObjectMenuItemId);
   ok(copyObjectMenuItem.disabled, `"Copy object" is disabled for messages
     with no variables/objects`);
-  yield hideContextMenu(hud);
+  await hideContextMenu(hud);
 }
 
 function prettyPrintMessage(message, isObject) {
   return isObject ? JSON.stringify(JSON.parse(message), null, 2) : message;
 }
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_context_menu_open_url.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_context_menu_open_url.js
@@ -6,60 +6,60 @@
 // Test that the Open URL in new Tab menu item is displayed for network logs and works as
 // expected.
 
 "use strict";
 
 const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
   "new-console-output/test/mochitest/test-console.html";
 
-add_task(function* () {
+add_task(async function() {
   // Enable net messages in the console for this test.
-  yield pushPref("devtools.webconsole.filter.net", true);
+  await pushPref("devtools.webconsole.filter.net", true);
 
-  let hud = yield openNewTabAndConsole(TEST_URI);
+  let hud = await openNewTabAndConsole(TEST_URI);
   hud.jsterm.clearOutput();
 
   info("Test Open URL menu item for text log");
 
   info("Logging a text message in the content window");
-  yield ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
     content.wrappedJSObject.console.log("simple text message");
   });
-  let message = yield waitFor(() => findMessage(hud, "simple text message"));
+  let message = await waitFor(() => findMessage(hud, "simple text message"));
   ok(message, "Text log found in the console");
 
   info("Open and check the context menu for the logged text message");
-  let menuPopup = yield openContextMenu(hud, message);
+  let menuPopup = await openContextMenu(hud, message);
   let openUrlItem = menuPopup.querySelector("#console-menu-open-url");
   ok(!openUrlItem, "Open URL menu item is not available");
 
-  yield hideContextMenu(hud);
+  await hideContextMenu(hud);
   hud.jsterm.clearOutput();
 
   info("Test Open URL menu item for network log");
 
   info("Reload the content window to produce a network log");
-  yield ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
     content.wrappedJSObject.location.reload();
   });
-  message = yield waitFor(() => findMessage(hud, "test-console.html"));
+  message = await waitFor(() => findMessage(hud, "test-console.html"));
   ok(message, "Network log found in the console");
 
   info("Open and check the context menu for the logged network message");
-  menuPopup = yield openContextMenu(hud, message);
+  menuPopup = await openContextMenu(hud, message);
   openUrlItem = menuPopup.querySelector("#console-menu-open-url");
   ok(openUrlItem, "Open URL menu item is available");
 
   let currentTab = gBrowser.selectedTab;
   let tabLoaded = listenToTabLoad();
   info("Click on Open URL menu item and wait for new tab to open");
   openUrlItem.click();
-  yield hideContextMenu(hud);
-  let newTab = yield tabLoaded;
+  await hideContextMenu(hud);
+  let newTab = await tabLoaded;
   let newTabHref = newTab.linkedBrowser._contentWindow.location.href;
   is(newTabHref, TEST_URI, "Tab was opened with the expected URL");
 
   info("Remove the new tab and select the previous tab back");
   gBrowser.removeTab(newTab);
   gBrowser.selectedTab = currentTab;
 });
 
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_context_menu_store_as_global.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_context_menu_store_as_global.js
@@ -10,82 +10,82 @@
 
 const TEST_URI = `data:text/html;charset=utf-8,<script>
   window.bar = { baz: 1 };
   console.log("foo");
   console.log("foo", window.bar);
   console.log(["foo", window.bar, 2]);
 </script>`;
 
-add_task(function* () {
-  let hud = yield openNewTabAndConsole(TEST_URI);
+add_task(async function() {
+  let hud = await openNewTabAndConsole(TEST_URI);
 
   let [msgWithText, msgWithObj, msgNested] =
-    yield waitFor(() => findMessages(hud, "foo"));
+    await waitFor(() => findMessages(hud, "foo"));
   ok(msgWithText && msgWithObj && msgNested, "Three messages should have appeared");
 
   let text = msgWithText.querySelector(".objectBox-string");
   let objInMsgWithObj = msgWithObj.querySelector(".objectBox-object");
   let textInMsgWithObj = msgWithObj.querySelector(".objectBox-string");
 
   // The third message has an object nested in an array, the array is therefore the top
   // object, the object is the nested object.
   let topObjInMsg = msgNested.querySelector(".objectBox-array");
   let nestedObjInMsg = msgNested.querySelector(".objectBox-object");
 
   info("Check store as global variable is disabled for text only messages");
-  let menuPopup = yield openContextMenu(hud, text);
+  let menuPopup = await openContextMenu(hud, text);
   let storeMenuItem = menuPopup.querySelector("#console-menu-store");
   ok(storeMenuItem.disabled, "store as global variable is disabled for text message");
-  yield hideContextMenu(hud);
+  await hideContextMenu(hud);
 
   info("Check store as global variable is disabled for text in complex messages");
-  menuPopup = yield openContextMenu(hud, textInMsgWithObj);
+  menuPopup = await openContextMenu(hud, textInMsgWithObj);
   storeMenuItem = menuPopup.querySelector("#console-menu-store");
   ok(storeMenuItem.disabled,
     "store as global variable is disabled for text in complex message");
-  yield hideContextMenu(hud);
+  await hideContextMenu(hud);
 
   info("Check store as global variable is enabled for objects in complex messages");
-  yield storeAsVariable(hud, objInMsgWithObj);
+  await storeAsVariable(hud, objInMsgWithObj);
 
   is(hud.jsterm.getInputValue(), "temp0", "Input was set");
 
-  let executedResult = yield hud.jsterm.execute();
+  let executedResult = await hud.jsterm.execute();
   ok(executedResult.textContent.includes("{ baz: 1 }"),
      "Correct variable assigned into console");
 
   info("Check store as global variable is enabled for top object in nested messages");
-  yield storeAsVariable(hud, topObjInMsg);
+  await storeAsVariable(hud, topObjInMsg);
 
   is(hud.jsterm.getInputValue(), "temp1", "Input was set");
 
-  executedResult = yield hud.jsterm.execute();
+  executedResult = await hud.jsterm.execute();
   ok(executedResult.textContent.includes(`[ "foo", {\u2026}, 2 ]`),
      "Correct variable assigned into console " + executedResult.textContent);
 
   info("Check store as global variable is enabled for nested object in nested messages");
-  yield storeAsVariable(hud, nestedObjInMsg);
+  await storeAsVariable(hud, nestedObjInMsg);
 
   is(hud.jsterm.getInputValue(), "temp2", "Input was set");
 
-  executedResult = yield hud.jsterm.execute();
+  executedResult = await hud.jsterm.execute();
   ok(executedResult.textContent.includes("{ baz: 1 }"),
      "Correct variable assigned into console " + executedResult.textContent);
 });
 
-function* storeAsVariable(hud, element) {
+async function storeAsVariable(hud, element) {
   info("Check store as global variable is enabled");
-  let menuPopup = yield openContextMenu(hud, element);
+  let menuPopup = await openContextMenu(hud, element);
   let storeMenuItem = menuPopup.querySelector("#console-menu-store");
   ok(!storeMenuItem.disabled,
     "store as global variable is enabled for object in complex message");
 
   info("Click on store as global variable");
   let onceInputSet = hud.jsterm.once("set-input-value");
   storeMenuItem.click();
 
   info("Wait for console input to be updated with the temp variable");
-  yield onceInputSet;
+  await onceInputSet;
 
   info("Wait for context menu to be hidden");
-  yield hideContextMenu(hud);
+  await hideContextMenu(hud);
 }
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_filters.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_filters.js
@@ -3,26 +3,26 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests filters.
 
 "use strict";
 const { MESSAGE_LEVEL } = require("devtools/client/webconsole/new-console-output/constants");
 const TEST_URI = "http://example.com/browser/devtools/client/webconsole/new-console-output/test/mochitest/test-console-filters.html";
-add_task(function* () {
-  let hud = yield openNewTabAndConsole(TEST_URI);
+add_task(async function () {
+  let hud = await openNewTabAndConsole(TEST_URI);
   const outputNode = hud.ui.outputNode;
-  const toolbar = yield waitFor(() => {
+  const toolbar = await waitFor(() => {
     return outputNode.querySelector(".webconsole-filterbar-primary");
   });
   ok(toolbar, "Toolbar found");
   // Show the filter bar
   toolbar.querySelector(".devtools-filter-icon").click();
-  const filterBar = yield waitFor(() => {
+  const filterBar = await waitFor(() => {
     return outputNode.querySelector(".webconsole-filterbar-secondary");
   });
   ok(filterBar, "Filter bar is shown when filter icon is clicked.");
 
   // Check defaults.
   Object.values(MESSAGE_LEVEL).forEach(level => {
     ok(filterIsEnabled(filterBar.querySelector(`.${level}`)),
       `Filter button for ${level} is on by default`);
@@ -34,30 +34,32 @@ add_task(function* () {
 
   // Check that messages are shown as expected. This depends on cached messages being
   // shown.
   ok(findMessages(hud, "").length == 5,
     "Messages of all levels shown when filters are on.");
 
   // Check that messages are not shown when their filter is turned off.
   filterBar.querySelector(".error").click();
-  yield waitFor(() => findMessages(hud, "").length == 4);
+  await waitFor(() => findMessages(hud, "").length == 4);
   ok(true, "When a filter is turned off, its messages are not shown.");
 
   // Check that the ui settings were persisted.
-  yield closeTabAndToolbox();
-  yield testFilterPersistence();
+  await closeTabAndToolbox();
+  await testFilterPersistence();
 });
+
 function filterIsEnabled(button) {
   return button.classList.contains("checked");
 }
-function* testFilterPersistence() {
-  let hud = yield openNewTabAndConsole(TEST_URI);
+
+async function testFilterPersistence() {
+  let hud = await openNewTabAndConsole(TEST_URI);
   const outputNode = hud.ui.outputNode;
-  const filterBar = yield waitFor(() => {
+  const filterBar = await waitFor(() => {
     return outputNode.querySelector(".webconsole-filterbar-secondary");
   });
   ok(filterBar, "Filter bar ui setting is persisted.");
   // Check that the filter settings were persisted.
   ok(!filterIsEnabled(filterBar.querySelector(".error")),
     "Filter button setting is persisted");
   ok(findMessages(hud, "").length == 4,
     "Messages of all levels shown when filters are on.");
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_filters_persist.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_filters_persist.js
@@ -4,63 +4,64 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests all filters persist.
 
 "use strict";
 
 const TEST_URI = "http://example.com/browser/devtools/client/webconsole/new-console-output/test/mochitest/test-console-filters.html";
 
-add_task(function* () {
-  let hud = yield openNewTabAndConsole(TEST_URI);
+add_task(async function() {
+  let hud = await openNewTabAndConsole(TEST_URI);
 
-  let filterButtons = yield getFilterButtons(hud);
+  let filterButtons = await getFilterButtons(hud);
   info("Disable all filters");
   filterButtons.forEach(filterButton => {
     if (filterIsEnabled(filterButton)) {
       filterButton.click();
     }
   });
 
   info("Close and re-open the console");
-  yield closeTabAndToolbox();
-  hud = yield openNewTabAndConsole(TEST_URI);
+  await closeTabAndToolbox();
+  hud = await openNewTabAndConsole(TEST_URI);
 
   info("Check that all filters are disabled, and enable them");
-  filterButtons = yield getFilterButtons(hud);
+  filterButtons = await getFilterButtons(hud);
   filterButtons.forEach(filterButton => {
     ok(!filterIsEnabled(filterButton), "filter is disabled");
     filterButton.click();
   });
 
   info("Close and re-open the console");
-  yield closeTabAndToolbox();
-  hud = yield openNewTabAndConsole(TEST_URI);
+  await closeTabAndToolbox();
+  hud = await openNewTabAndConsole(TEST_URI);
 
   info("Check that all filters are enabled");
-  filterButtons = yield getFilterButtons(hud);
+  filterButtons = await getFilterButtons(hud);
   filterButtons.forEach(filterButton => {
     ok(filterIsEnabled(filterButton), "filter is enabled");
   });
   // Check that the ui settings were persisted.
-  yield closeTabAndToolbox();
+  await closeTabAndToolbox();
 });
-function* getFilterButtons(hud) {
+
+async function getFilterButtons(hud) {
   const outputNode = hud.ui.outputNode;
   info("Wait for console toolbar to appear");
-  const toolbar = yield waitFor(() => {
+  const toolbar = await waitFor(() => {
     return outputNode.querySelector(".webconsole-filterbar-primary");
   });
   // Show the filter bar if it is hidden
   if (!outputNode.querySelector(".webconsole-filterbar-secondary")) {
     toolbar.querySelector(".devtools-filter-icon").click();
   }
 
   info("Wait for console filterbar to appear");
-  const filterBar = yield waitFor(() => {
+  const filterBar = await waitFor(() => {
     return outputNode.querySelector(".webconsole-filterbar-secondary");
   });
   ok(filterBar, "Filter bar is shown when filter icon is clicked.");
 
   return filterBar.querySelectorAll(".devtools-button");
 }
 
 function filterIsEnabled(button) {
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_input_focus.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_input_focus.js
@@ -8,40 +8,40 @@
 "use strict";
 
 const TEST_URI =
   `data:text/html;charset=utf-8,Test input focused
   <script>
     console.log("console message 1");
   </script>`;
 
-add_task(function* () {
-  let hud = yield openNewTabAndConsole(TEST_URI);
+add_task(async function() {
+  let hud = await openNewTabAndConsole(TEST_URI);
 
   let inputNode = hud.jsterm.inputNode;
   info("Focus after console is opened");
   ok(hasFocus(inputNode), "input node is focused after console is opened");
 
   hud.jsterm.clearOutput();
   ok(hasFocus(inputNode), "input node is focused after output is cleared");
 
   info("Focus during message logging");
-  ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+  ContentTask.spawn(gBrowser.selectedBrowser, {}, function() {
     content.wrappedJSObject.console.log("console message 2");
   });
-  let msg = yield waitFor(() => findMessage(hud, "console message 2"));
+  let msg = await waitFor(() => findMessage(hud, "console message 2"));
   ok(hasFocus(inputNode, "input node is focused, first time"));
 
   info("Focus after clicking in the output area");
-  yield waitForBlurredInput(hud);
+  await waitForBlurredInput(hud);
   EventUtils.sendMouseEvent({type: "click"}, msg);
   ok(hasFocus(inputNode), "input node is focused, second time");
 
   info("Setting a text selection and making sure a click does not re-focus");
-  yield waitForBlurredInput(hud);
+  await waitForBlurredInput(hud);
   let selection = hud.iframeWindow.getSelection();
   selection.selectAllChildren(msg.querySelector(".message-body"));
   EventUtils.sendMouseEvent({type: "click"}, msg);
   ok(!hasFocus(inputNode), "input node not focused after text is selected");
 });
 
 function waitForBlurredInput(hud) {
   let inputNode = hud.jsterm.inputNode;
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_keyboard_accessibility.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_keyboard_accessibility.js
@@ -11,21 +11,21 @@ const TEST_URI =
   `data:text/html;charset=utf-8,<p>Test keyboard accessibility</p>
   <script>
     for (let i = 1; i <= 100; i++) {
       console.log("console message " + i);
     }
   </script>
   `;
 
-add_task(function* () {
-  let hud = yield openNewTabAndConsole(TEST_URI);
+add_task(async function () {
+  let hud = await openNewTabAndConsole(TEST_URI);
   info("Web Console opened");
   const outputScroller = hud.ui.outputScroller;
-  yield waitFor(() => findMessages(hud, "").length == 100);
+  await waitFor(() => findMessages(hud, "").length == 100);
   let currentPosition = outputScroller.scrollTop;
   const bottom = currentPosition;
   hud.jsterm.inputNode.focus();
   // Page up.
   EventUtils.synthesizeKey("VK_PAGE_UP", {});
   isnot(outputScroller.scrollTop, currentPosition,
     "scroll position changed after page up");
   // Page down.
@@ -48,17 +48,17 @@ add_task(function* () {
   info("try ctrl-l to clear output");
   let clearShortcut;
   if (Services.appinfo.OS === "Darwin") {
     clearShortcut = WCUL10n.getStr("webconsole.clear.keyOSX");
   } else {
     clearShortcut = WCUL10n.getStr("webconsole.clear.key");
   }
   synthesizeKeyShortcut(clearShortcut);
-  yield waitFor(() => findMessages(hud, "").length == 0);
+  await waitFor(() => findMessages(hud, "").length == 0);
   ok(hasFocus(hud.jsterm.inputNode), "jsterm input is focused");
 
   // Focus filter
   info("try ctrl-f to focus filter");
   synthesizeKeyShortcut(WCUL10n.getStr("webconsole.find.key"));
   ok(!hasFocus(hud.jsterm.inputNode), "jsterm input