Merge mozilla-central to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 28 Apr 2017 10:45:09 +0200
changeset 403712 a77813ea2be4f9d70bd83e2ec14acdfd8276bfa2
parent 403711 658b857c35f848ad8d9c428c586cce875f9434a3 (current diff)
parent 403628 84762dbeb5380461fe27f0afa0e27e8ba9dd3b01 (diff)
child 403713 259ac1fb5cf2ce6098257fd51d217db5224f2c1c
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to mozilla-inbound
browser/extensions/shield-recipe-client/test/unit/test_server.sjs
testing/mozharness/configs/multi_locale/b2g_linux32.py
testing/mozharness/configs/multi_locale/b2g_linux64.py
testing/mozharness/configs/multi_locale/b2g_macosx64.py
testing/mozharness/configs/multi_locale/b2g_win32.py
testing/mozharness/configs/users/aki/gaia_json.py
testing/mozharness/docs/bump_gaia_json.rst
testing/mozharness/docs/gaia_build_integration.rst
testing/mozharness/docs/gaia_integration.rst
testing/mozharness/docs/gaia_unit.rst
testing/mozharness/scripts/gaia_build_integration.py
testing/mozharness/scripts/gaia_build_unit.py
testing/mozharness/scripts/gaia_integration.py
testing/mozharness/scripts/gaia_linter.py
testing/mozharness/scripts/gaia_unit.py
testing/mozharness/scripts/spidermonkey/build.b2g
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1599,16 +1599,18 @@ var gBrowserInit = {
     gBrowser.tabContainer.addEventListener("TabSelect", function() {
       for (let panel of document.querySelectorAll("panel[tabspecific='true']")) {
         if (panel.state == "open") {
           panel.hidePopup();
         }
       }
     });
 
+    gPageActionButton.init();
+
     this.delayedStartupFinished = true;
 
     Services.obs.notifyObservers(window, "browser-delayed-startup-finished");
     TelemetryTimestamps.add("delayedStartupFinished");
   },
 
   // Returns the URI(s) to load at startup.
   _getUriToLoad() {
@@ -7751,16 +7753,48 @@ var gIdentityHandler = {
     container.appendChild(nameLabel);
     container.appendChild(stateLabel);
     container.appendChild(button);
 
     return container;
   }
 };
 
+
+var gPageActionButton = {
+  get button() {
+    delete this.button;
+    return this.button = document.getElementById("urlbar-page-action-button");
+  },
+
+  get panel() {
+    delete this.panel;
+    return this.panel = document.getElementById("page-action-panel");
+  },
+
+  init() {
+    if (getBoolPref("browser.photon.structure.enabled")) {
+      this.button.hidden = false;
+    }
+  },
+
+  onEvent(event) {
+    event.stopPropagation();
+
+    if ((event.type == "click" && event.button != 0) ||
+        (event.type == "keypress" && event.charCode != KeyEvent.DOM_VK_SPACE &&
+         event.keyCode != KeyEvent.DOM_VK_RETURN)) {
+      return; // Left click, space or enter only
+    }
+
+    this.panel.hidden = false;
+    this.panel.openPopup(this.button, "bottomcenter topright");
+  },
+};
+
 function getNotificationBox(aWindow) {
   var foundBrowser = gBrowser.getBrowserForDocument(aWindow.document);
   if (foundBrowser)
     return gBrowser.getNotificationBox(foundBrowser)
   return null;
 }
 
 function getTabModalPromptBox(aWindow) {
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -400,16 +400,26 @@
         <button class="ctrlTab-preview" flex="1"/>
         <button class="ctrlTab-preview" flex="1"/>
       </hbox>
       <hbox pack="center">
         <button id="ctrlTab-showAll" class="ctrlTab-preview" noicon="true"/>
       </hbox>
     </panel>
 
+    <panel id="page-action-panel"
+           role="group"
+           type="arrow"
+           hidden="true"
+           flip="slide"
+           position="bottomcenter topright"
+           noautofocus="true">
+      This space intentionally left blank.
+    </panel>
+
     <!-- Bookmarks and history tooltip -->
     <tooltip id="bhTooltip"/>
 
     <tooltip id="tabbrowser-tab-tooltip" onpopupshowing="gBrowser.createTooltip(event);"/>
 
     <tooltip id="back-button-tooltip">
       <label class="tooltip-label" value="&backButton.tooltip;"/>
 #ifdef XP_MACOSX
@@ -797,16 +807,21 @@
                              class="chromeclass-toolbar-additional"
                              command="Browser:ReloadOrDuplicate"
                              onclick="checkForMiddleClick(this, event);"
                              tooltip="dynamic-shortcut-tooltip"/>
               <toolbarbutton id="urlbar-stop-button"
                              class="chromeclass-toolbar-additional"
                              command="Browser:Stop"
                              tooltip="dynamic-shortcut-tooltip"/>
+              <toolbarbutton id="urlbar-page-action-button"
+                             hidden="true"
+                             class="chromeclass-toolbar-additional"
+                             tooltiptext="&pageActionButton.tooltip;"
+                             onclick="gPageActionButton.onEvent(event);"/>
             </textbox>
           </hbox>
         </toolbaritem>
 
         <toolbaritem id="search-container" title="&searchItem.title;"
                      align="center" class="chromeclass-toolbar-additional panel-wide-item"
                      cui-areatype="toolbar"
                      flex="100" persist="width" removable="true">
--- a/browser/components/extensions/ext-browser.js
+++ b/browser/components/extensions/ext-browser.js
@@ -1,12 +1,12 @@
 "use strict";
 
 XPCOMUtils.defineLazyModuleGetter(global, "EventEmitter",
-                                  "resource://devtools/shared/event-emitter.js");
+                                  "resource://gre/modules/EventEmitter.jsm");
 
 // This function is pretty tightly tied to Extension.jsm.
 // Its job is to fill in the |tab| property of the sender.
 function getSender(extension, target, sender) {
   let tabId;
   if ("tabId" in sender) {
     // The message came from a privileged extension page running in a tab. In
     // that case, it should include a tabId property (which is filled in by the
--- a/browser/components/extensions/ext-browserAction.js
+++ b/browser/components/extensions/ext-browserAction.js
@@ -10,17 +10,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/Timer.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ViewPopup",
                                   "resource:///modules/ExtensionPopups.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "DOMUtils",
                                    "@mozilla.org/inspector/dom-utils;1",
                                    "inIDOMUtils");
 
-Cu.import("resource://devtools/shared/event-emitter.js");
+Cu.import("resource://gre/modules/EventEmitter.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 
 var {
   IconDetails,
 } = ExtensionUtils;
 
 const POPUP_PRELOAD_TIMEOUT_MS = 200;
 
--- a/browser/components/extensions/ext-c-devtools-panels.js
+++ b/browser/components/extensions/ext-c-devtools-panels.js
@@ -1,14 +1,14 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
-                                  "resource://devtools/shared/event-emitter.js");
+                                  "resource://gre/modules/EventEmitter.jsm");
 
 var {
   promiseDocumentLoaded,
 } = ExtensionUtils;
 
 /**
  * Represents an addon devtools panel in the child process.
  *
--- a/browser/components/extensions/ext-contextMenus.js
+++ b/browser/components/extensions/ext-contextMenus.js
@@ -323,23 +323,26 @@ function getContexts(contextData) {
   if (contextData.onPageAction) {
     contexts.add("page_action");
   }
 
   if (contextData.onBrowserAction) {
     contexts.add("browser_action");
   }
 
+  if (contextData.onTab) {
+    contexts.add("tab");
+  }
+
   if (contexts.size === 0) {
     contexts.add("page");
   }
 
-  if (contextData.onTab) {
-    contexts.add("tab");
-  } else {
+  // New non-content contexts supported in Firefox are not part of "all".
+  if (!contextData.onTab) {
     contexts.add("all");
   }
 
   return contexts;
 }
 
 function MenuItem(extension, createProperties, isRoot = false) {
   this.extension = extension;
--- a/browser/components/extensions/test/browser/browser_ext_contextMenus_chrome.js
+++ b/browser/components/extensions/test/browser/browser_ext_contextMenus_chrome.js
@@ -71,19 +71,22 @@ add_task(function* test_tabContextMenu()
   const first = ExtensionTestUtils.loadExtension({
     manifest: {
       permissions: ["contextMenus"],
     },
     async background() {
       await browser.contextMenus.create({
         id: "alpha-beta-parent", title: "alpha-beta parent", contexts: ["tab"],
       });
+
       await browser.contextMenus.create({parentId: "alpha-beta-parent", title: "alpha"});
       await browser.contextMenus.create({parentId: "alpha-beta-parent", title: "beta"});
 
+      await browser.contextMenus.create({title: "dummy", contexts: ["page"]});
+
       browser.contextMenus.onClicked.addListener((info, tab) => {
         browser.test.sendMessage("click", {info, tab});
       });
 
       const [tab] = await browser.tabs.query({active: true});
       browser.test.sendMessage("ready", tab.id);
     },
   });
@@ -107,16 +110,18 @@ add_task(function* test_tabContextMenu()
 
   const menu = yield openTabContextMenu();
   const [separator, submenu, gamma] = Array.from(menu.children).slice(-3);
   is(separator.tagName, "menuseparator", "Separator before first extension item");
 
   is(submenu.tagName, "menu", "Correct submenu type");
   is(submenu.label, "alpha-beta parent", "Correct submenu title");
 
+  isnot(gamma.label, "dummy", "`page` context menu item should not appear here");
+
   is(gamma.tagName, "menuitem", "Third menu item type is correct");
   is(gamma.label, "gamma", "Third menu item label is correct");
 
   const popup = yield openSubmenu(submenu);
   is(popup, submenu.firstChild, "Correct submenu opened");
   is(popup.children.length, 2, "Correct number of submenu items");
 
   const [alpha, beta] = popup.children;
--- a/browser/components/newtab/NewTabPrefsProvider.jsm
+++ b/browser/components/newtab/NewTabPrefsProvider.jsm
@@ -6,17 +6,17 @@
 this.EXPORTED_SYMBOLS = ["NewTabPrefsProvider"];
 
 const {interfaces: Ci, utils: Cu} = Components;
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "EventEmitter", function() {
-  const {EventEmitter} = Cu.import("resource://devtools/shared/event-emitter.js", {});
+  const {EventEmitter} = Cu.import("resource://gre/modules/EventEmitter.jsm", {});
   return EventEmitter;
 });
 
 // Supported prefs and data type
 const gPrefsMap = new Map([
   ["browser.newtabpage.activity-stream.enabled", "bool"],
   ["browser.newtabpage.enabled", "bool"],
   ["browser.newtabpage.enhanced", "bool"],
--- a/browser/components/newtab/NewTabSearchProvider.jsm
+++ b/browser/components/newtab/NewTabSearchProvider.jsm
@@ -11,17 +11,17 @@ const CURRENT_ENGINE = "browser-search-e
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "ContentSearch",
                                   "resource:///modules/ContentSearch.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "EventEmitter", function() {
-  const {EventEmitter} = Cu.import("resource://devtools/shared/event-emitter.js", {});
+  const {EventEmitter} = Cu.import("resource://gre/modules/EventEmitter.jsm", {});
   return EventEmitter;
 });
 
 function SearchProvider() {
   EventEmitter.decorate(this);
 }
 
 SearchProvider.prototype = {
--- a/browser/components/newtab/NewTabWebChannel.jsm
+++ b/browser/components/newtab/NewTabWebChannel.jsm
@@ -20,17 +20,17 @@ Cu.import("resource://gre/modules/Prefer
 
 XPCOMUtils.defineLazyModuleGetter(this, "NewTabPrefsProvider",
                                   "resource:///modules/NewTabPrefsProvider.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NewTabRemoteResources",
                                   "resource:///modules/NewTabRemoteResources.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "WebChannel",
                                   "resource://gre/modules/WebChannel.jsm");
 XPCOMUtils.defineLazyGetter(this, "EventEmitter", function() {
-  const {EventEmitter} = Cu.import("resource://devtools/shared/event-emitter.js", {});
+  const {EventEmitter} = Cu.import("resource://gre/modules/EventEmitter.jsm", {});
   return EventEmitter;
 });
 
 const CHAN_ID = "newtab";
 const PREF_ENABLED = "browser.newtabpage.remote";
 const PREF_MODE = "browser.newtabpage.remote.mode";
 
 /**
--- a/browser/components/newtab/PlacesProvider.jsm
+++ b/browser/components/newtab/PlacesProvider.jsm
@@ -13,17 +13,17 @@ this.EXPORTED_SYMBOLS = ["PlacesProvider
 const {interfaces: Ci, utils: Cu} = Components;
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
   "resource://gre/modules/PlacesUtils.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "EventEmitter", function() {
-  const {EventEmitter} = Cu.import("resource://devtools/shared/event-emitter.js", {});
+  const {EventEmitter} = Cu.import("resource://gre/modules/EventEmitter.jsm", {});
   return EventEmitter;
 });
 
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
                                   "resource://gre/modules/NewTabUtils.jsm");
 
--- a/browser/components/preferences/in-content/sync.xul
+++ b/browser/components/preferences/in-content/sync.xul
@@ -90,17 +90,17 @@
                     onclick="gSyncPane.openChangeProfileImage(event);" hidden="true"
                     onkeypress="gSyncPane.openChangeProfileImage(event);"
                     tooltiptext="&profilePicture.tooltip;"/>
               </vbox>
               <vbox flex="1" pack="center">
                 <label id="fxaDisplayName" hidden="true"/>
                 <label id="fxaEmailAddress1"/>
                 <hbox class="fxaAccountBoxButtons">
-                  <button id="fxaUnlinkButton" label="&disconnect2.label;" accesskey="&disconnect2.accesskey;"/>
+                  <button id="fxaUnlinkButton" label="&disconnect3.label;" accesskey="&disconnect3.accesskey;"/>
                   <html:a id="verifiedManage" target="_blank"
                          accesskey="&verifiedManage.accesskey;"
                          onkeypress="gSyncPane.openManageFirefoxAccount(event);"><!--
                   -->&verifiedManage.label;</html:a>
                 </hbox>
               </vbox>
             </hbox>
 
@@ -186,18 +186,18 @@
         <label control="fxaSyncComputerName">
           &fxaSyncDeviceName.label;
         </label>
       </caption>
       <hbox id="fxaDeviceName">
         <textbox id="fxaSyncComputerName" disabled="true"/>
         <hbox>
           <button id="fxaChangeDeviceName"
-                  label="&changeSyncDeviceName1.label;"
-                  accesskey="&changeSyncDeviceName1.accesskey;"/>
+                  label="&changeSyncDeviceName2.label;"
+                  accesskey="&changeSyncDeviceName2.accesskey;"/>
           <button id="fxaCancelChangeDeviceName"
                   label="&cancelChangeSyncDeviceName.label;"
                   accesskey="&cancelChangeSyncDeviceName.accesskey;"
                   hidden="true"/>
           <button id="fxaSaveChangeDeviceName"
                   label="&saveChangeSyncDeviceName.label;"
                   accesskey="&saveChangeSyncDeviceName.accesskey;"
                   hidden="true"/>
--- a/browser/installer/windows/nsis/shared.nsh
+++ b/browser/installer/windows/nsis/shared.nsh
@@ -102,16 +102,21 @@
   ; Fix the distribution.ini file if applicable
   ${FixDistributionsINI}
 
   RmDir /r /REBOOTOK "$INSTDIR\${TO_BE_DELETED}"
 
   ; Register AccessibleHandler.dll with COM (this writes to HKLM)
   ${RegisterAccessibleHandler}
 
+!ifndef HAVE_64BIT_BUILD
+  ; Clean up any IAccessible registry corruption
+  ${FixCorruptOleAccRegistration}
+!endif
+
 !ifdef MOZ_MAINTENANCE_SERVICE
   Call IsUserAdmin
   Pop $R0
   ${If} $R0 == "true"
   ; Only proceed if we have HKLM write access
   ${AndIf} $TmpVal == "HKLM"
     ; We check to see if the maintenance service install was already attempted.
     ; Since the Maintenance service can be installed either x86 or x64,
@@ -917,16 +922,59 @@
 !define AddMaintCertKeys "!insertmacro AddMaintCertKeys"
 !endif
 
 !macro RegisterAccessibleHandler
   ${RegisterDLL} "$INSTDIR\AccessibleHandler.dll"
 !macroend
 !define RegisterAccessibleHandler "!insertmacro RegisterAccessibleHandler"
 
+!ifndef HAVE_64BIT_BUILD
+!define IID_IAccessible "{618736E0-3C3D-11CF-810C-00AA00389B71}"
+!define CLSID_UniversalMarshaler "{00020404-0000-0000-C000-000000000046}"
+!define OleAccTypeLib "{1EA4DBF0-3C3B-11CF-810C-00AA00389B71}"
+!define OleAccTypeLibVersion "1.1"
+Function FixCorruptOleAccRegistration
+  Push $0
+
+  ; Read IAccessible's ProxyStubClsid32. If it is not CLSID_UniversalMarshaler
+  ; then we must be running Windows 10 Creators Update which does not use a
+  ; type library.
+  ReadRegStr $0 HKCR "Interface\${IID_IAccessible}\ProxyStubClsid32" ""
+  ${Unless} "$0" == "${CLSID_UniversalMarshaler}"
+    Pop $0
+    Return
+  ${EndIf}
+
+  Push $1
+
+  ; IAccessible is using the universal marshler, therefore we expect a valid
+  ; TypeLib key to exist
+  ClearErrors
+  ReadRegStr $0 HKCR "Interface\${IID_IAccessible}\TypeLib" ""
+  ReadRegStr $1 HKCR "Interface\${IID_IAccessible}\TypeLib" "Version"
+  ReadRegStr $0 HKCR "TypeLib\$0\$1\0\win32" ""
+  Pop $1
+  ${IfNot} ${Errors}
+  ${AndIf} ${FileExists} "$0"
+    Pop $0
+    Return
+  ${EndIf}
+
+  Pop $0
+
+  ; Some third-party code has previously overridden system typelibs
+  ; with their own but did not clean itself up during uninstall.
+  ; Revert to the system typelib.
+  WriteRegStr HKCR "Interface\${IID_IAccessible}\TypeLib" "" "${OleAccTypeLib}"
+  WriteRegStr HKCR "Interface\${IID_IAccessible}\TypeLib" "Version" "${OleAccTypeLibVersion}"
+FunctionEnd
+!define FixCorruptOleAccRegistration "Call FixCorruptOleAccRegistration"
+!endif
+
 ; Removes various registry entries for reasons noted below (does not use SHCTX).
 !macro RemoveDeprecatedKeys
   StrCpy $0 "SOFTWARE\Classes"
   ; Remove support for launching chrome urls from the shell during install or
   ; update if the DefaultIcon is from firefox.exe (Bug 301073).
   ${RegCleanAppHandler} "chrome"
 
   ; Remove protocol handler registry keys added by the MS shim
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -921,8 +921,10 @@ you can use these alternative items. Oth
 
 <!ENTITY updateRestart.message "After a quick restart, &brandShorterName; will restore all your open tabs and windows.">
 <!ENTITY updateRestart.header.message2 "Restart to update &brandShorterName;.">
 <!ENTITY updateRestart.acceptButton.label "Restart and Restore">
 <!ENTITY updateRestart.acceptButton.accesskey "R">
 <!ENTITY updateRestart.cancelButton.label "Not Now">
 <!ENTITY updateRestart.cancelButton.accesskey "N">
 <!ENTITY updateRestart.panelUI.label "Restart &brandShorterName; to apply the update">
+
+<!ENTITY pageActionButton.tooltip "Page actions">
--- a/browser/locales/en-US/chrome/browser/preferences/sync.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/sync.dtd
@@ -14,18 +14,18 @@
 <!ENTITY engine.passwords.accesskey "P">
 <!ENTITY engine.prefs.label         "Preferences">
 <!ENTITY engine.prefs.accesskey     "S">
 <!ENTITY engine.addons.label        "Add-ons">
 <!ENTITY engine.addons.accesskey    "A">
 
 <!-- Device Settings -->
 <!ENTITY fxaSyncDeviceName.label       "Device Name">
-<!ENTITY changeSyncDeviceName1.label "Change Device Name">
-<!ENTITY changeSyncDeviceName1.accesskey "h">
+<!ENTITY changeSyncDeviceName2.label "Change Device Name…">
+<!ENTITY changeSyncDeviceName2.accesskey "h">
 <!ENTITY cancelChangeSyncDeviceName.label "Cancel">
 <!ENTITY cancelChangeSyncDeviceName.accesskey "n">
 <!ENTITY saveChangeSyncDeviceName.label "Save">
 <!ENTITY saveChangeSyncDeviceName.accesskey "v">
 
 <!-- Footer stuff -->
 <!ENTITY prefs.tosLink.label        "Terms of Service">
 <!ENTITY fxaPrivacyNotice.link.label "Privacy Notice">
@@ -47,18 +47,18 @@ both, to better adapt this sentence to t
 <!ENTITY signedInLoginFailure.aftername.label "">
 
 <!ENTITY notSignedIn.label           "You are not signed in.">
 <!ENTITY signIn.label                "Sign in">
 <!ENTITY signIn.accesskey            "g">
 <!ENTITY profilePicture.tooltip      "Change profile picture">
 <!ENTITY verifiedManage.label        "Manage Account">
 <!ENTITY verifiedManage.accesskey    "o">
-<!ENTITY disconnect2.label           "Disconnect">
-<!ENTITY disconnect2.accesskey       "D">
+<!ENTITY disconnect3.label           "Disconnect…">
+<!ENTITY disconnect3.accesskey       "D">
 <!ENTITY verify.label                "Verify Email">
 <!ENTITY verify.accesskey            "V">
 <!ENTITY forget.label                "Forget this Email">
 <!ENTITY forget.accesskey            "F">
 
 <!ENTITY signedOut.caption            "Take Your Web With You">
 <!ENTITY signedOut.description        "Synchronize your bookmarks, history, tabs, passwords, add-ons, and preferences across all your devices.">
 <!ENTITY signedOut.accountBox.title   "Connect with a &syncBrand.fxAccount.label;">
--- a/browser/modules/ExtensionsUI.jsm
+++ b/browser/modules/ExtensionsUI.jsm
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
 
 this.EXPORTED_SYMBOLS = ["ExtensionsUI"];
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://devtools/shared/event-emitter.js");
+Cu.import("resource://gre/modules/EventEmitter.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                   "resource://gre/modules/AddonManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
                                   "resource://gre/modules/PluralForm.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
                                   "resource:///modules/RecentWindow.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
--- a/browser/themes/shared/browser.inc.css
+++ b/browser/themes/shared/browser.inc.css
@@ -26,9 +26,23 @@
 }
 
 #urlbar-zoom-button > .toolbarbutton-text {
   display: -moz-box;
 }
 
 #urlbar-zoom-button > .toolbarbutton-icon {
   display: none;
-}
\ No newline at end of file
+}
+
+/* Page action button */
+#urlbar-page-action-button {
+  -moz-appearance: none;
+  border-style: none;
+  list-style-image: url("chrome://browser/skin/page-action.svg");
+  margin: 0;
+  padding: 0 6px;
+  fill: currentColor;
+}
+
+#urlbar-page-action-button > .toolbarbutton-icon {
+  width: 16px;
+}
--- a/browser/themes/shared/incontentprefs/icons.svg
+++ b/browser/themes/shared/incontentprefs/icons.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" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24">
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
   <style>
     use:not(:target) {
       display: none;
     }
     use {
       fill: #fbfbfb;
       stroke: rgba(0,0,0,0.4);
       stroke-width: .5px;
@@ -23,61 +23,60 @@
     </g>
     <g id="content-shape">
       <path d="M16.286,2H5.571C4.388,2,3.429,2.96,3.429,4.143v15.714 C3.429,21.04,4.388,22,5.571,22h12.857c1.185,0,2.143-0.96,2.143-2.143V6.286L16.286,2z M18.945,19.223c0,0.22-0.18,0.4-0.4,0.4 h-13.2c-0.22,0-0.4-0.18-0.4-0.4v-0.846c0-0.22,0.18-0.4,0.4-0.4h13.2c0.22,0,0.4,0.18,0.4,0.4V19.223z M18.945,15.223 c0,0.22-0.18,0.4-0.4,0.4h-13.2c-0.22,0-0.4-0.18-0.4-0.4v-0.846c0-0.22,0.18-0.4,0.4-0.4h13.2c0.22,0,0.4,0.18,0.4,0.4V15.223z M18.945,11.229c0,0.22-0.18,0.4-0.4,0.4h-13.2c-0.22,0-0.4-0.18-0.4-0.4v-0.846c0-0.22,0.18-0.4,0.4-0.4h13.2 c0.22,0,0.4,0.18,0.4,0.4V11.229z M14.833,7.707v-4.65l4.65,4.65H14.833z"/>
     </g>
     <g id="security-shape">
       <path d="M18.909,9.783h-0.863V8.086C18.046,4.725,15.339,2,12,2 C8.661,2,5.954,4.725,5.954,8.086v1.697H5.091c-0.955,0-1.728,0.779-1.728,1.739v8.738c0,0.961,0.773,1.74,1.728,1.74h13.818 c0.954,0,1.728-0.779,1.728-1.74v-8.738C20.637,10.562,19.863,9.783,18.909,9.783z M8.545,8.086c0-1.92,1.547-3.478,3.455-3.478 c1.908,0,3.455,1.557,3.455,3.478v1.697h-6.91V8.086z M5.181,16.092l-0.909-1.2v-2.284l2.728,3.483H5.181z M8.818,16.092 l-2.773-3.657h1.727l2.864,3.657H8.818z M12,16.092l-2.773-3.657h1.727l2.864,3.657H12z M15.637,16.092l-2.773-3.657h1.727 l2.864,3.657H15.637z M19.728,16.092h-0.455l-2.773-3.657h1.727l1.501,1.916V16.092z"/>
     </g>
     <g id="sync-shape">
-      <path style="fill:#F1F1F1;" d="M0.9,19.9h3.3H15h3.3c0.5,0,0.9-0.4,0.9-0.9v-1.1c0-1-0.5-1.9-1.2-2.5c-2.3-1.8-4.6-2.9-5.1-3.1
-      c-0.1,0-0.1-0.1-0.1-0.2v-1.6c0.3-0.5,0.4-1,0.5-1.5c0.2,0.1,0.6,0.1,1-1.3c0.3-1.1,0.1-1.5-0.2-1.6C15,1.7,13,1.6,13,1.6
-      S12.7,1,11.8,0.5C11.3,0.2,10.5-0.1,9.5,0C9.1,0,8.8,0.1,8.5,0.2c-0.4,0.1-0.7,0.3-1,0.5C7.1,1,6.8,1.2,6.4,1.6
-      c-0.5,0.5-1,1.2-1.1,2C5.1,4.3,5.1,5,5.4,5.8C5,5.7,4.6,5.9,5,7.4c0.3,1.1,0.6,1.4,0.8,1.4c0.1,0.6,0.3,1.3,0.7,1.9v1.4
-      c0,0.1,0,0.1-0.1,0.2c-0.5,0.2-2.8,1.4-5.1,3.1C0.5,16,0,16.9,0,17.9V19C0,19.5,0.4,19.9,0.9,19.9"/>
+      <path style="fill:#F1F1F1;" d="M3.2,22h3.3h10.8h3.3c0.5,0,0.9-0.4,0.9-0.9V20c0-1-0.5-1.9-1.2-2.5c-2.3-1.8-4.6-2.9-5.1-3.1
+        c-0.1,0-0.1-0.1-0.1-0.2v-1.6c0.3-0.5,0.4-1,0.5-1.5c0.2,0.1,0.6,0.1,1-1.3c0.3-1.1,0.1-1.5-0.2-1.6c0.9-4.4-1.1-4.5-1.1-4.5
+        S15,3.1,14.1,2.6c-0.5-0.3-1.3-0.6-2.3-0.5c-0.4,0-0.7,0.1-1,0.2c-0.4,0.1-0.7,0.3-1,0.5C9.4,3.1,9.1,3.3,8.7,3.7
+        c-0.5,0.5-1,1.2-1.1,2C7.4,6.4,7.4,7.1,7.7,7.9C7.3,7.8,6.9,8,7.3,9.5c0.3,1.1,0.6,1.4,0.8,1.4c0.1,0.6,0.3,1.3,0.7,1.9v1.4
+        c0,0.1,0,0.1-0.1,0.2c-0.5,0.2-2.8,1.4-5.1,3.1C2.8,18.1,2.3,19,2.3,20v1.1C2.3,21.6,2.7,22,3.2,22"/>
     </g>
     <g id="advanced-shape">
-      <path style="fill:#F1F1F1;" d="M19.3,13.4C19.3,13.4,19.3,13.4,19.3,13.4L19.3,13.4C19.3,13.4,19.3,13.4,19.3,13.4L19.3,13.4z
-      M19.3,13.4c-0.1,1-0.5,1.8-1.2,2.4c-0.5,0.5-1.1,0.9-1.7,1.2c-0.2,0.4-0.4,0.7-0.8,1c-0.5,0.4-1.3,0.8-2.1,1
-      c-0.9,0.2-1.6,0.3-2.3,0.3h-0.9c0.1,0.2,0.2,0.2,0.4,0.2c-1.1-0.1-2.1-0.3-2.9-0.5c0.2,0.2,0.5,0.4,0.8,0.4c-1.5-0.3-2.9-1-4.1-1.9
-      c-0.3-0.2-0.4-0.3-0.5-0.4c-1-0.8-1.7-1.6-2.3-2.6c-0.7-1.1-1.1-2.5-1.3-4.1c-0.1,0.5-0.1,0.9-0.1,1.1C0.1,10.2,0.1,9,0.4,7.9
-      C0.3,8.2,0.1,8.6,0,9c0.1-0.8,0.4-1.8,0.9-2.8C1,5.8,1.2,5.5,1.3,5.4V4.3c0-0.1,0-0.3,0.1-0.6c0-0.2,0.1-0.4,0.2-0.6v0.1
-      c0,0,0-0.1,0-0.1c0-0.1,0-0.1,0-0.1c0-0.1,0-0.2,0.1-0.2v0.1c0,0,0-0.1,0-0.1c0,0,0-0.1,0-0.1s0-0.1,0-0.2C2,2.2,2,2.1,2,2.1
-      C2.2,2,2.2,1.9,2.3,1.9V2c0,0.2,0.1,0.5,0.3,1l0-0.1C2.8,3.1,3,3.4,3.4,3.7c0.8-0.2,1.5-0.3,2.4-0.1C5.9,3.5,5.9,3.4,6,3.3v0.1
-      C6.1,3.2,6.3,3.1,6.6,3v0c0.2-0.1,0.5-0.3,1-0.4L7.5,2.6c0.1-0.1,0.2-0.1,0.3-0.1c0.1,0,0.2,0,0.3,0c0.1,0,0.3,0,0.4,0
-      c-0.1,0-0.1,0.1-0.1,0.1c0,0-0.1,0.1-0.1,0.1s-0.1,0-0.1,0h0.1C7.8,3.2,7.4,3.7,7.2,4.4c-0.1,0-0.1,0.1-0.1,0.2l0.1,0.1
-      c0.2,0.3,0.5,0.5,0.9,0.5h1.4c0.2,0.1,0.3,0.2,0.3,0.2c0,0.2-0.1,0.4-0.3,0.6c0,0.1-0.1,0.2-0.3,0.4C8.5,7,8,7.3,7.7,7.6v0.1
-      c0,0,0,0,0,0.1c0,0,0,0,0,0.1c-0.1,0-0.1,0-0.1,0.1C7.7,7.9,7.8,8,7.8,8.2c0,0.2,0,0.4-0.1,0.6c0,0-0.1-0.1-0.1-0.1
-      c-0.1,0-0.1,0-0.1,0c0.1,0.1,0.2,0.1,0.2,0.2V9c0,0-0.1,0-0.1,0c0,0-0.1-0.1-0.1-0.1l-0.1,0c-0.1,0-0.1,0-0.2-0.1
-      c-0.1,0-0.1,0-0.1,0L7,8.7c-0.1,0-0.1,0-0.1,0L7,8.7c-0.1,0-0.1,0-0.2,0l-0.1,0C6.5,8.8,6.4,9,6.4,9.3c0,0.6,0.4,1.1,1.1,1.5
-      C7.7,11,8,11.1,8.3,11.2c0.3,0,0.5,0,0.7,0c0.2-0.1,0.4-0.1,0.6-0.2s0.4-0.2,0.5-0.2c0.7-0.2,1.4,0,1.9,0.5c0.2,0.1,0.2,0.2,0.1,0.4
-      c-0.1,0.2-0.2,0.3-0.4,0.2c-0.1,0-0.2,0-0.2,0c-0.1,0-0.1,0-0.3,0.1S11.1,12,11,12s-0.2,0.1-0.3,0.2c-0.1,0.1-0.3,0.2-0.4,0.3
-      c-0.9,0.5-1.9,0.6-3,0.4c0.4,0.4,0.7,0.7,1.1,0.9c0.1,0,0.2,0.1,0.6,0.1c0.3,0,0.5,0.1,0.6,0.2C9.4,14,9.1,14,8.9,14.1
-      c0.8,0.5,1.7,0.7,2.8,0.5c0.4-0.1,0.7-0.2,1-0.4c-0.1,0.1-0.1,0.3-0.2,0.4c0.1,0.1,0.4-0.1,0.6-0.5c0.1-0.1,0.3-0.3,0.6-0.4
-      c0,0,0,0.1,0,0.2c0,0.1,0,0.1,0,0.2s0,0.1,0.1,0.1c0.4,0,0.7-0.4,1.1-1.3c0.3-0.6,0.5-1.3,0.6-2.2c0.1,0.2,0.2,0.5,0.2,0.8
-      c0.2-0.5,0.3-1,0.3-1.3c0-0.3,0-1.3-0.1-2.9c0.3,0.4,0.5,0.7,0.7,1.1c0.1-1.2-0.1-2.2-0.5-3.1c-0.4-0.8-0.9-1.5-1.4-1.9
-      c0.5,0.1,1,0.3,1.4,0.6l-0.3-0.2c-1.4-1.4-3.1-2.2-5.1-2.4c-2-0.2-3.8,0.3-5.4,1.4c-0.6,0-1.1,0-1.5,0.1C3.6,2.7,3.5,2.6,3.5,2.5
-      C5.3,0.8,7.4,0,9.9,0s4.6,0.8,6.5,2.4L16.2,2c0.6,0.3,1,0.7,1.4,1.2l-0.1-0.3l0,0.1l0-0.1c0.9,0.7,1.4,1.6,1.7,2.7
-      c0.2,0.9,0.2,1.6,0.1,2.3c0.1,0.1,0.1,0.1,0.1,0.3l0.3-1.1c0.1,0.3,0.2,0.7,0.2,1C20,8.5,20,8.9,20,9.2c0,0.4-0.1,0.7-0.1,1
-      c-0.1,0.3-0.1,0.7-0.2,1s-0.2,0.6-0.3,0.8c-0.1,0.2-0.2,0.5-0.3,0.7C19.1,12.8,19.2,13,19.3,13.4L19.3,13.4z"/>
+      <path style="fill:#F1F1F1;" d="M21.3,15.6L21.3,15.6L21.3,15.6L21.3,15.6L21.3,15.6z M21.3,15.6c-0.1,1-0.5,1.8-1.2,2.4
+        c-0.5,0.5-1.1,0.9-1.7,1.2c-0.2,0.4-0.4,0.7-0.8,1c-0.5,0.4-1.3,0.8-2.1,1c-0.9,0.2-1.6,0.3-2.3,0.3h-0.9c0.1,0.2,0.2,0.2,0.4,0.2
+        c-1.1-0.1-2.1-0.3-2.9-0.5c0.2,0.2,0.5,0.4,0.8,0.4c-1.5-0.3-2.9-1-4.1-1.9c-0.3-0.2-0.4-0.3-0.5-0.4c-1-0.8-1.7-1.6-2.3-2.6
+        c-0.7-1.1-1.1-2.5-1.3-4.1c-0.1,0.5-0.1,0.9-0.1,1.1c-0.2-1.3-0.2-2.5,0.1-3.6c-0.1,0.3-0.3,0.7-0.4,1.1c0.1-0.8,0.4-1.8,0.9-2.8
+        C3,8.1,3.2,7.8,3.3,7.7V6.6c0-0.1,0-0.3,0.1-0.6c0-0.2,0.1-0.4,0.2-0.6v0.1V5.4c0-0.1,0-0.1,0-0.1c0-0.1,0-0.2,0.1-0.2v0.1V5.1V5
+        c0,0,0-0.1,0-0.2C4,4.4,4,4.3,4,4.3c0.2-0.1,0.2-0.2,0.3-0.2v0.1c0,0.2,0.1,0.5,0.3,1l0,0C4.8,5.3,5,5.7,5.4,5.9
+        c0.8-0.2,1.5-0.3,2.4-0.1c0.1,0,0.1-0.1,0.2-0.2v0.1c0.1-0.2,0.3-0.3,0.6-0.4l0,0c0.2-0.1,0.5-0.3,1-0.4H9.5
+        c0.1-0.1,0.2-0.1,0.3-0.1s0.2,0,0.3,0s0.3,0,0.4,0c-0.1,0-0.1,0.1-0.1,0.1L10.3,5h-0.1h0.1C9.8,5.5,9.4,6,9.2,6.7
+        c-0.1,0-0.1,0.1-0.1,0.2L9.2,7c0.2,0.3,0.5,0.5,0.9,0.5h1.4c0.2,0.1,0.3,0.2,0.3,0.2c0,0.2-0.1,0.4-0.3,0.6c0,0.1-0.1,0.2-0.3,0.4
+        C10.5,9.2,10,9.6,9.7,9.9V10c0,0,0,0,0,0.1c0,0,0,0,0,0.1c-0.1,0-0.1,0-0.1,0.1c0.1-0.1,0.2,0,0.2,0.2s0,0.4-0.1,0.6L9.6,11
+        c-0.1,0-0.1,0-0.1,0c0.1,0.1,0.2,0.1,0.2,0.2v0.1H9.6l-0.1-0.1H9.4c-0.1,0-0.1,0-0.2-0.1c-0.1,0-0.1,0-0.1,0L9,10.9
+        c-0.1,0-0.1,0-0.1,0H9c-0.1,0-0.1,0-0.2,0H8.7c-0.2,0.1-0.3,0.3-0.3,0.6c0,0.6,0.4,1.1,1.1,1.5c0.2,0.2,0.5,0.3,0.8,0.4
+        c0.3,0,0.5,0,0.7,0c0.2-0.1,0.4-0.1,0.6-0.2S12,13,12.1,13c0.7-0.2,1.4,0,1.9,0.5c0.2,0.1,0.2,0.2,0.1,0.4
+        c-0.1,0.2-0.2,0.3-0.4,0.2c-0.1,0-0.2,0-0.2,0c-0.1,0-0.1,0-0.3,0.1s-0.1,0-0.2,0s-0.2,0.1-0.3,0.2c-0.1,0.1-0.3,0.2-0.4,0.3
+        c-0.9,0.5-1.9,0.6-3,0.4c0.4,0.4,0.7,0.7,1.1,0.9c0.1,0,0.2,0.1,0.6,0.1c0.3,0,0.5,0.1,0.6,0.2c-0.2-0.1-0.5-0.1-0.7,0
+        c0.8,0.5,1.7,0.7,2.8,0.5c0.4-0.1,0.7-0.2,1-0.4c-0.1,0.1-0.1,0.3-0.2,0.4c0.1,0.1,0.4-0.1,0.6-0.5c0.1-0.1,0.3-0.3,0.6-0.4
+        c0,0,0,0.1,0,0.2s0,0.1,0,0.2s0,0.1,0.1,0.1c0.4,0,0.7-0.4,1.1-1.3c0.3-0.6,0.5-1.3,0.6-2.2c0.1,0.2,0.2,0.5,0.2,0.8
+        c0.2-0.5,0.3-1,0.3-1.3s0-1.3-0.1-2.9c0.3,0.4,0.5,0.7,0.7,1.1c0.1-1.2-0.1-2.2-0.5-3.1c-0.4-0.8-0.9-1.5-1.4-1.9
+        c0.5,0.1,1,0.3,1.4,0.6L17.8,6c-1.4-1.4-3.1-2.2-5.1-2.4S8.9,4,7.3,5.1c-0.6,0-1.1,0-1.5,0.1C5.6,4.9,5.5,4.8,5.5,4.8
+        C7.3,3,9.4,2.2,11.9,2.2s4.6,0.8,6.5,2.4l-0.2-0.4c0.6,0.3,1,0.7,1.4,1.2l-0.1-0.3v0.1l0,0c0.9,0.7,1.4,1.6,1.7,2.7
+        c0.2,0.9,0.2,1.6,0.1,2.3c0.1,0.1,0.1,0.1,0.1,0.3l0.3-1.1c0.1,0.3,0.2,0.7,0.2,1c0.1,0.4,0.1,0.8,0.1,1.1c0,0.4-0.1,0.7-0.1,1
+        c-0.1,0.3-0.1,0.7-0.2,1s-0.2,0.6-0.3,0.8c-0.1,0.2-0.2,0.5-0.3,0.7C21.1,15.1,21.2,15.2,21.3,15.6L21.3,15.6z"/>
     </g>
     <g id="searchResults-shape">
       <path d="M8,16.3c1.5,0,3-0.4,4.3-1.3l4.6,4.6c0.3,0.3,0.8,0.4,1.2,0.3s0.8-0.5,0.9-0.9s0-0.9-0.3-1.2l-4.5-4.5
 			c2.4-2.9,2.5-7.2,0.2-10.2S8-0.8,4.6,0.8s-5.2,5.4-4.4,9.1S4.2,16.3,8,16.3z M8,1.9c3.4,0,6.1,2.8,6.1,6.2s-2.7,6.2-6.1,6.2
 			S1.9,11.5,1.9,8C1.9,4.6,4.6,1.9,8,1.9L8,1.9z"/>
 			<path d="M8,12.9c2.6,0,4.7-2.1,4.7-4.8S10.6,3.4,8,3.4c-2.6,0-4.7,2.1-4.7,4.7c0,1.3,0.5,2.5,1.4,3.4
 			C5.6,12.4,6.7,12.9,8,12.9L8,12.9z M7.8,4.5c0.4,0,0.8,0.4,0.8,0.8S8.3,6.1,7.8,6.1C6.8,6.1,6,6.9,6,7.9c0,0.4-0.4,0.8-0.8,0.8
 			S4.5,8.3,4.5,7.9C4.5,6,6,4.5,7.8,4.5L7.8,4.5z"/>
     </g>
   </defs>
-  <use id="general" xlink:href="#general-shape"/>
-  <use id="general-native" xlink:href="#general-shape"/>
-  <use id="content" xlink:href="#content-shape"/>
-  <use id="content-native" xlink:href="#content-shape"/>
-  <use id="security" xlink:href="#security-shape"/>
-  <use id="security-native" xlink:href="#security-shape"/>
-  <use id="sync" xlink:href="#sync-shape"/>
-  <use id="sync-native" xlink:href="#sync-shape"/>
-  <use id="advanced" xlink:href="#advanced-shape"/>
-  <use id="advanced-native" xlink:href="#advanced-shape"/>
-  <use id="searchResults" xlink:href="#searchResults-shape"/>
-  <use id="searchResults-native" xlink:href="#searchResults-shape"/>
+  <use id="general" href="#general-shape"/>
+  <use id="general-native" href="#general-shape"/>
+  <use id="content" href="#content-shape"/>
+  <use id="content-native" href="#content-shape"/>
+  <use id="security" href="#security-shape"/>
+  <use id="security-native" href="#security-shape"/>
+  <use id="sync" href="#sync-shape"/>
+  <use id="sync-native" href="#sync-shape"/>
+  <use id="advanced" href="#advanced-shape"/>
+  <use id="advanced-native" href="#advanced-shape"/>
+  <use id="searchResults" href="#searchResults-shape"/>
+  <use id="searchResults-native" href="#searchResults-shape"/>
 </svg>
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -138,11 +138,12 @@
   skin/classic/browser/privatebrowsing/private-browsing.svg    (../shared/privatebrowsing/private-browsing.svg)
   skin/classic/browser/privatebrowsing/tracking-protection-off.svg (../shared/privatebrowsing/tracking-protection-off.svg)
   skin/classic/browser/privatebrowsing/tracking-protection.svg (../shared/privatebrowsing/tracking-protection.svg)
   skin/classic/browser/compacttheme/loading-inverted.png (../shared/compacttheme/loading-inverted.png)
   skin/classic/browser/compacttheme/loading-inverted@2x.png (../shared/compacttheme/loading-inverted@2x.png)
   skin/classic/browser/compacttheme/urlbar-history-dropmarker.svg (../shared/compacttheme/urlbar-history-dropmarker.svg)
   skin/classic/browser/urlbar-star.svg                         (../shared/urlbar-star.svg)
   skin/classic/browser/urlbar-tab.svg                          (../shared/urlbar-tab.svg)
+  skin/classic/browser/page-action.svg                         (../shared/page-action.svg)
   skin/classic/browser/menu-icons/new-window.svg               (../shared/menu-icons/new-window.svg)
   skin/classic/browser/menu-icons/print.svg                    (../shared/menu-icons/print.svg)
   skin/classic/browser/menu-icons/private-window.svg           (../shared/menu-icons/private-window.svg)
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/page-action.svg
@@ -0,0 +1,7 @@
+<?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" width="16" height="16">
+  <path fill="context-fill" d="M2 6a2 2 0 1 0 2 2 2 2 0 0 0-2-2zm6 0a2 2 0 1 0 2 2 2 2 0 0 0-2-2zm6 0a2 2 0 1 0 2 2 2 2 0 0 0-2-2z"/>
+</svg>
--- a/devtools/client/locales/en-US/netmonitor.properties
+++ b/devtools/client/locales/en-US/netmonitor.properties
@@ -429,16 +429,24 @@ netmonitor.toolbar.remoteip=Remote IP
 # LOCALIZATION NOTE (netmonitor.toolbar.cause): This is the label displayed
 # in the network table toolbar, above the "cause" column.
 netmonitor.toolbar.cause=Cause
 
 # LOCALIZATION NOTE (netmonitor.toolbar.type): This is the label displayed
 # in the network table toolbar, above the "type" column.
 netmonitor.toolbar.type=Type
 
+# LOCALIZATION NOTE (netmonitor.toolbar.cookies): This is the label displayed
+# in the network table toolbar, above the "cookies" column.
+netmonitor.toolbar.cookies=Cookies
+
+# LOCALIZATION NOTE (netmonitor.toolbar.setCookies): This is the label displayed
+# in the network table toolbar, above the "set cookies" column.
+netmonitor.toolbar.setCookies=Set Cookies
+
 # LOCALIZATION NOTE (netmonitor.toolbar.transferred): This is the label displayed
 # in the network table toolbar, above the "transferred" column, which is the
 # compressed / encoded size.
 netmonitor.toolbar.transferred=Transferred
 
 # LOCALIZATION NOTE (netmonitor.toolbar.contentSize): This is the label displayed
 # in the network table toolbar, above the "size" column, which is the
 # uncompressed / decoded size.
--- a/devtools/client/netmonitor/index.js
+++ b/devtools/client/netmonitor/index.js
@@ -8,27 +8,28 @@
  * This script is the entry point of devtools-launchpad. Make netmonitor possible
  * to run on standalone browser tab without chrome privilege.
  * See README.md for more information.
  */
 const React = require("react");
 const ReactDOM = require("react-dom");
 const { bindActionCreators } = require("redux");
 const { bootstrap, renderRoot } = require("devtools-launchpad");
-const { EventEmitter } = require("devtools-modules");
+const EventEmitter = require("devtools-modules/src/utils/event-emitter");
 const { Services: { appinfo, pref }} = require("devtools-modules");
 const { configureStore } = require("./src/utils/create-store");
 
 require("./src/assets/styles/netmonitor.css");
 
 EventEmitter.decorate(window);
 
 pref("devtools.netmonitor.enabled", true);
 pref("devtools.netmonitor.filters", "[\"all\"]");
-pref("devtools.netmonitor.hiddenColumns", "[]");
+pref("devtools.netmonitor.hiddenColumns",
+     "[\"cookies\",\"protocol\",\"remoteip\",\"setCookies\"]");
 pref("devtools.netmonitor.panes-network-details-width", 550);
 pref("devtools.netmonitor.panes-network-details-height", 450);
 pref("devtools.netmonitor.har.defaultLogDir", "");
 pref("devtools.netmonitor.har.defaultFileName", "Archive %date");
 pref("devtools.netmonitor.har.jsonp", false);
 pref("devtools.netmonitor.har.jsonpCallback", "");
 pref("devtools.netmonitor.har.includeResponseBodies", true);
 pref("devtools.netmonitor.har.compress", false);
--- a/devtools/client/netmonitor/package.json
+++ b/devtools/client/netmonitor/package.json
@@ -3,18 +3,18 @@
   "version": "0.0.1",
   "engines": {
     "node": ">=6.9.0"
   },
   "description": "Network monitor in developer tools",
   "dependencies": {
     "codemirror": "^5.24.2",
     "devtools-config": "=0.0.12",
-    "devtools-launchpad": "=0.0.67",
-    "devtools-modules": "=0.0.24",
+    "devtools-launchpad": "=0.0.75",
+    "devtools-modules": "=0.0.27",
     "devtools-source-editor": "=0.0.3",
     "immutable": "^3.8.1",
     "jszip": "^3.1.3",
     "react": "=15.3.2",
     "react-dom": "=15.3.2",
     "react-redux": "=5.0.3",
     "redux": "^3.6.0",
     "reselect": "^2.5.4"
--- a/devtools/client/netmonitor/src/assets/styles/netmonitor.css
+++ b/devtools/client/netmonitor/src/assets/styles/netmonitor.css
@@ -430,16 +430,28 @@ body,
 }
 
 /* Protocol column */
 
 .requests-list-protocol {
   width: 8%;
 }
 
+/* Cookies column */
+
+.requests-list-cookies {
+  width: 6%;
+}
+
+/* Set Cookies column */
+
+.requests-list-set-cookies {
+  width: 8%;
+}
+
 /* Domain column */
 
 .requests-list-domain {
   width: 13%;
 }
 
 .requests-list-domain.requests-list-column {
   text-align: start;
--- a/devtools/client/netmonitor/src/components/moz.build
+++ b/devtools/client/netmonitor/src/components/moz.build
@@ -9,21 +9,23 @@ DevToolsModules(
     'headers-panel.js',
     'mdn-link.js',
     'monitor-panel.js',
     'network-details-panel.js',
     'params-panel.js',
     'properties-view.js',
     'request-list-column-cause.js',
     'request-list-column-content-size.js',
+    'request-list-column-cookies.js',
     'request-list-column-domain.js',
     'request-list-column-file.js',
     'request-list-column-method.js',
     'request-list-column-protocol.js',
     'request-list-column-remote-ip.js',
+    'request-list-column-set-cookies.js',
     'request-list-column-status.js',
     'request-list-column-transferred-size.js',
     'request-list-column-type.js',
     'request-list-column-waterfall.js',
     'request-list-content.js',
     'request-list-empty-notice.js',
     'request-list-header.js',
     'request-list-item.js',
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/components/request-list-column-cookies.js
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {
+  createClass,
+  DOM,
+  PropTypes,
+} = require("devtools/client/shared/vendor/react");
+
+const { div } = DOM;
+
+const RequestListColumnCookies = createClass({
+  displayName: "RequestListColumnCookies",
+
+  propTypes: {
+    item: PropTypes.object.isRequired,
+  },
+
+  shouldComponentUpdate(nextProps) {
+    let { requestCookies: currRequestCookies = { cookies: [] } } = this.props.item;
+    let { requestCookies: nextRequestCookies = { cookies: [] } } = nextProps.item;
+    currRequestCookies = currRequestCookies.cookies || currRequestCookies;
+    nextRequestCookies = nextRequestCookies.cookies || nextRequestCookies;
+    return currRequestCookies !== nextRequestCookies;
+  },
+
+  render() {
+    let { requestCookies = { cookies: [] } } = this.props.item;
+    requestCookies = requestCookies.cookies || requestCookies;
+    let requestCookiesLength = requestCookies.length > 0 ? requestCookies.length : "";
+    return (
+      div({
+        className: "requests-list-column requests-list-cookies",
+        title: requestCookiesLength
+      }, requestCookiesLength)
+    );
+  }
+});
+
+module.exports = RequestListColumnCookies;
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/components/request-list-column-set-cookies.js
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {
+  createClass,
+  DOM,
+  PropTypes,
+} = require("devtools/client/shared/vendor/react");
+
+const { div } = DOM;
+
+const RequestListColumnSetCookies = createClass({
+  displayName: "RequestListColumnSetCookies",
+
+  propTypes: {
+    item: PropTypes.object.isRequired,
+  },
+
+  shouldComponentUpdate(nextProps) {
+    let { responseCookies: currResponseCookies = { cookies: [] } } = this.props.item;
+    let { responseCookies: nextResponseCookies = { cookies: [] } } = nextProps.item;
+    currResponseCookies = currResponseCookies.cookies || currResponseCookies;
+    nextResponseCookies = nextResponseCookies.cookies || nextResponseCookies;
+    return currResponseCookies !== nextResponseCookies;
+  },
+
+  render() {
+    let { responseCookies = { cookies: [] } } = this.props.item;
+    responseCookies = responseCookies.cookies || responseCookies;
+    let responseCookiesLength = responseCookies.length > 0 ? responseCookies.length : "";
+    return (
+      div({
+        className: "requests-list-column requests-list-set-cookies",
+        title: responseCookiesLength
+      }, responseCookiesLength)
+    );
+  }
+});
+
+module.exports = RequestListColumnSetCookies;
--- a/devtools/client/netmonitor/src/components/request-list-item.js
+++ b/devtools/client/netmonitor/src/components/request-list-item.js
@@ -11,21 +11,23 @@ const {
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 const I = require("devtools/client/shared/vendor/immutable");
 const { propertiesEqual } = require("../utils/request-utils");
 
 // Components
 const RequestListColumnCause = createFactory(require("./request-list-column-cause"));
 const RequestListColumnContentSize = createFactory(require("./request-list-column-content-size"));
+const RequestListColumnCookies = createFactory(require("./request-list-column-cookies"));
 const RequestListColumnDomain = createFactory(require("./request-list-column-domain"));
 const RequestListColumnFile = createFactory(require("./request-list-column-file"));
 const RequestListColumnMethod = createFactory(require("./request-list-column-method"));
 const RequestListColumnProtocol = createFactory(require("./request-list-column-protocol"));
 const RequestListColumnRemoteIP = createFactory(require("./request-list-column-remote-ip"));
+const RequestListColumnSetCookies = createFactory(require("./request-list-column-set-cookies"));
 const RequestListColumnStatus = createFactory(require("./request-list-column-status"));
 const RequestListColumnTransferredSize = createFactory(require("./request-list-column-transferred-size"));
 const RequestListColumnType = createFactory(require("./request-list-column-type"));
 const RequestListColumnWaterfall = createFactory(require("./request-list-column-waterfall"));
 
 const { div } = DOM;
 
 /**
@@ -132,16 +134,18 @@ const RequestListItem = createClass({
         columns.get("status") && RequestListColumnStatus({ item }),
         columns.get("method") && RequestListColumnMethod({ item }),
         columns.get("file") && RequestListColumnFile({ item }),
         columns.get("protocol") && RequestListColumnProtocol({ item }),
         columns.get("domain") && RequestListColumnDomain({ item, onSecurityIconClick }),
         columns.get("remoteip") && RequestListColumnRemoteIP({ item }),
         columns.get("cause") && RequestListColumnCause({ item, onCauseBadgeClick }),
         columns.get("type") && RequestListColumnType({ item }),
+        columns.get("cookies") && RequestListColumnCookies({ item }),
+        columns.get("setCookies") && RequestListColumnSetCookies({ item }),
         columns.get("transferred") && RequestListColumnTransferredSize({ item }),
         columns.get("contentSize") && RequestListColumnContentSize({ item }),
         columns.get("waterfall") &&
           RequestListColumnWaterfall({ item, firstRequestStartedMillis }),
       )
     );
   }
 });
--- a/devtools/client/netmonitor/src/constants.js
+++ b/devtools/client/netmonitor/src/constants.js
@@ -130,16 +130,25 @@ const HEADERS = [
     name: "cause",
     canFilter: true,
   },
   {
     name: "type",
     canFilter: false,
   },
   {
+    name: "cookies",
+    canFilter: false,
+  },
+  {
+    name: "setCookies",
+    boxName: "set-cookies",
+    canFilter: false,
+  },
+  {
     name: "transferred",
     canFilter: true,
   },
   {
     name: "contentSize",
     boxName: "size",
     filterKey: "size",
     canFilter: true,
--- a/devtools/client/netmonitor/src/reducers/ui.js
+++ b/devtools/client/netmonitor/src/reducers/ui.js
@@ -22,16 +22,18 @@ const Columns = I.Record({
   status: true,
   method: true,
   file: true,
   protocol: false,
   domain: true,
   remoteip: false,
   cause: true,
   type: true,
+  cookies: false,
+  setCookies: false,
   transferred: true,
   contentSize: true,
   waterfall: true,
 });
 
 const UI = I.Record({
   columns: new Columns(),
   detailsPanelSelectedTab: "headers",
--- a/devtools/client/netmonitor/src/utils/sort-predicates.js
+++ b/devtools/client/netmonitor/src/utils/sort-predicates.js
@@ -72,16 +72,36 @@ function remoteip(first, second) {
 
 function cause(first, second) {
   const firstCause = first.cause.type;
   const secondCause = second.cause.type;
   const result = compareValues(firstCause, secondCause);
   return result || waterfall(first, second);
 }
 
+function setCookies(first, second) {
+  let { responseCookies: firstResponseCookies = { cookies: [] } } = first;
+  let { responseCookies: secondResponseCookies = { cookies: [] } } = second;
+  firstResponseCookies = firstResponseCookies.cookies || firstResponseCookies;
+  secondResponseCookies = secondResponseCookies.cookies || secondResponseCookies;
+  const result =
+    compareValues(firstResponseCookies.length, secondResponseCookies.length);
+  return result || waterfall(first, second);
+}
+
+function cookies(first, second) {
+  let { requestCookies: firstRequestCookies = { cookies: [] } } = first;
+  let { requestCookies: secondRequestCookies = { cookies: [] } } = second;
+  firstRequestCookies = firstRequestCookies.cookies || firstRequestCookies;
+  secondRequestCookies = secondRequestCookies.cookies || secondRequestCookies;
+  const result =
+    compareValues(firstRequestCookies.length, secondRequestCookies.length);
+  return result || waterfall(first, second);
+}
+
 function type(first, second) {
   const firstType = getAbbreviatedMimeType(first.mimeType).toLowerCase();
   const secondType = getAbbreviatedMimeType(second.mimeType).toLowerCase();
   const result = compareValues(firstType, secondType);
   return result || waterfall(first, second);
 }
 
 function transferred(first, second) {
@@ -94,16 +114,18 @@ function contentSize(first, second) {
   return result || waterfall(first, second);
 }
 
 exports.Sorters = {
   status,
   method,
   file,
   protocol,
+  cookies,
+  setCookies,
   domain,
   remoteip,
   cause,
   type,
   transferred,
   contentSize,
   waterfall,
 };
--- a/devtools/client/netmonitor/webpack.config.js
+++ b/devtools/client/netmonitor/webpack.config.js
@@ -34,17 +34,17 @@ let webpackConfig = {
   },
 
   // Fallback compatibility for npm link
   resolve: {
     fallback: path.join(__dirname, "node_modules"),
     alias: {
       "react": path.join(__dirname, "node_modules/react"),
       "devtools/client/framework/devtools": path.join(__dirname, "../../client/shims/devtools"),
-      "devtools/client/framework/menu": "devtools-modules/client/framework/menu",
+      "devtools/client/framework/menu": "devtools-modules/src/menu",
       "devtools/client/framework/menu-item": path.join(__dirname, "../../client/framework/menu-item"),
       "devtools/client/locales": path.join(__dirname, "../../client/locales/en-US"),
       "devtools/client/shared/components/reps/reps": path.join(__dirname, "../../client/shared/components/reps/reps"),
       "devtools/client/shared/components/search-box": path.join(__dirname, "../../client/shared/components/search-box"),
       "devtools/client/shared/components/splitter/draggable": path.join(__dirname, "../../client/shared/components/splitter/draggable"),
       "devtools/client/shared/components/splitter/split-box": path.join(__dirname, "../../client/shared/components/splitter/split-box"),
       "devtools/client/shared/components/stack-trace": path.join(__dirname, "../../client/shared/components/stack-trace"),
       "devtools/client/shared/components/tabs/tabbar": path.join(__dirname, "../../client/shared/components/tabs/tabbar"),
@@ -67,25 +67,25 @@ let webpackConfig = {
       "devtools/client/shared/vendor/jszip": "jszip",
       "devtools/client/shared/widgets/tooltip/HTMLTooltip": path.join(__dirname, "../../client/shared/widgets/tooltip/HTMLTooltip"),
       "devtools/client/shared/widgets/tooltip/ImageTooltipHelper": path.join(__dirname, "../../client/shared/widgets/tooltip/ImageTooltipHelper"),
       "devtools/client/shared/widgets/tooltip/TooltipToggle": path.join(__dirname, "../../client/shared/widgets/tooltip/TooltipToggle"),
       "devtools/client/shared/widgets/Chart": path.join(__dirname, "../../client/shared/widgets/Chart"),
       "devtools/client/sourceeditor/editor": "devtools-source-editor/src/source-editor",
       "devtools/shared/async-utils": path.join(__dirname, "../../shared/async-utils"),
       "devtools/shared/defer": path.join(__dirname, "../../shared/defer"),
-      "devtools/shared/event-emitter": "devtools-modules/shared/event-emitter",
+      "devtools/shared/event-emitter": "devtools-modules/src/utils/event-emitter",
       "devtools/shared/fronts/timeline": path.join(__dirname, "../../shared/shims/fronts/timeline"),
       "devtools/shared/l10n": path.join(__dirname, "../../shared/l10n"),
       "devtools/shared/locales": path.join(__dirname, "../../shared/locales/en-US"),
       "devtools/shared/platform/clipboard": path.join(__dirname, "../../shared/platform/content/clipboard"),
       "devtools/shared/plural-form": path.join(__dirname, "../../shared/plural-form"),
       "devtools/shared/task": path.join(__dirname, "../../shared/task"),
       "toolkit/locales": path.join(__dirname, "../../../toolkit/locales/en-US"),
-      "Services": "devtools-modules/client/shared/shim/Services",
+      "Services": "devtools-modules/src/Services",
     },
   },
 };
 
 const mappings = [
   [
     /utils\/menu/, "devtools-launchpad/src/components/shared/menu"
   ],
--- a/devtools/client/netmonitor/yarn.lock
+++ b/devtools/client/netmonitor/yarn.lock
@@ -1,33 +1,47 @@
 # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
 # yarn lockfile v1
 
 
+abab@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.3.tgz#b81de5f7274ec4e756d797cd834f303642724e5d"
+
 abbrev@1:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.0.tgz#d0554c2256636e2f56e7c2e5ad183f859428d81f"
 
 accepts@~1.3.3:
   version "1.3.3"
   resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.3.tgz#c3ca7434938648c3e0d9c1e328dd68b622c284ca"
   dependencies:
     mime-types "~2.1.11"
     negotiator "0.6.1"
 
+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.0, acorn@^3.0.4:
   version "3.3.0"
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a"
 
+acorn@^4.0.4:
+  version "4.0.11"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.11.tgz#edcda3bd937e7556410d42ed5860f67399c794c0"
+
 acorn@^5.0.1:
   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"
 
@@ -57,49 +71,61 @@ alphanum-sort@^1.0.1, alphanum-sort@^1.0
 amd-loader@0.0.5:
   version "0.0.5"
   resolved "https://registry.yarnpkg.com/amd-loader/-/amd-loader-0.0.5.tgz#9b4c1c26b70015e4ddaec7d6dcd21265090819a0"
 
 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.1.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:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
 
 ansi-styles@^2.2.1:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
 
+ansi-styles@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.0.0.tgz#5404e93a544c4fec7f048262977bebfe3155e0c1"
+  dependencies:
+    color-convert "^1.0.0"
+
 anymatch@^1.3.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.0.tgz#a3e52fa39168c825ff57b0248126ce5a8ff95507"
   dependencies:
     arrify "^1.0.0"
     micromatch "^2.1.5"
 
+append-transform@^0.4.0:
+  version "0.4.0"
+  resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-0.4.0.tgz#d76ebf8ca94d276e247a36bad44a4b74ab611991"
+  dependencies:
+    default-require-extensions "^1.0.0"
+
 aproba@^1.0.3:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.1.1.tgz#95d3600f07710aa0e9298c726ad5ecf2eacbabab"
 
 are-we-there-yet@~1.1.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.2.tgz#80e470e95a084794fe1899262c5667c6e88de1b3"
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz#bb5dca382bb94f05e15194373d16fd3ba1ca110d"
   dependencies:
     delegates "^1.0.0"
-    readable-stream "^2.0.0 || ^1.1.13"
+    readable-stream "^2.0.6"
 
 argparse@^1.0.7:
   version "1.0.9"
   resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86"
   dependencies:
     sprintf-js "~1.0.2"
 
 arr-diff@^2.0.0:
@@ -107,16 +133,20 @@ arr-diff@^2.0.0:
   resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf"
   dependencies:
     arr-flatten "^1.0.1"
 
 arr-flatten@^1.0.1:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.0.3.tgz#a274ed85ac08849b6bd7847c4580745dc51adfb1"
 
+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:
@@ -132,17 +162,17 @@ array-unique@^0.2.1:
 
 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.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"
 
 asn1@~0.2.3:
@@ -166,20 +196,26 @@ assert@^1.1.1:
 async-each@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
 
 async@^0.9.0:
   version "0.9.2"
   resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d"
 
-async@^1.3.0, async@^1.5.0:
+async@^1.3.0, async@^1.4.0, async@^1.5.0:
   version "1.5.2"
   resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
 
+async@^2.1.4:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/async/-/async-2.3.0.tgz#1013d1051047dd320fe24e494d5c66ecaf6147d9"
+  dependencies:
+    lodash "^4.14.0"
+
 async@~0.2.6:
   version "0.2.10"
   resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
 
 asynckit@^0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
 
@@ -226,17 +262,17 @@ babel-cli@^6.7.5:
 babel-code-frame@^6.11.0, babel-code-frame@^6.16.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-core@^6.17.0, babel-core@^6.24.1:
+babel-core@^6.0.0, babel-core@^6.17.0, babel-core@^6.24.1:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.24.1.tgz#8c428564dce1e1f41fb337ec34f4c3b022b5ad83"
   dependencies:
     babel-code-frame "^6.22.0"
     babel-generator "^6.24.1"
     babel-helpers "^6.24.1"
     babel-messages "^6.23.0"
     babel-register "^6.24.1"
@@ -251,37 +287,45 @@ babel-core@^6.17.0, babel-core@^6.24.1:
     lodash "^4.2.0"
     minimatch "^3.0.2"
     path-is-absolute "^1.0.0"
     private "^0.1.6"
     slash "^1.0.0"
     source-map "^0.5.0"
 
 babel-eslint@^7.1.0:
-  version "7.2.2"
-  resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-7.2.2.tgz#0da2cbe6554fd0fb069f19674f2db2f9c59270ff"
+  version "7.2.3"
+  resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-7.2.3.tgz#b2fe2d80126470f5c19442dc757253a897710827"
   dependencies:
     babel-code-frame "^6.22.0"
     babel-traverse "^6.23.1"
     babel-types "^6.23.0"
-    babylon "^6.16.1"
-
-babel-generator@^6.24.1:
+    babylon "^6.17.0"
+
+babel-generator@^6.18.0, babel-generator@^6.24.1:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.24.1.tgz#e715f486c58ded25649d888944d52aa07c5d9497"
   dependencies:
     babel-messages "^6.23.0"
     babel-runtime "^6.22.0"
     babel-types "^6.24.1"
     detect-indent "^4.0.0"
     jsesc "^1.3.0"
     lodash "^4.2.0"
     source-map "^0.5.0"
     trim-right "^1.0.1"
 
+babel-helper-builder-binary-assignment-operator-visitor@^6.24.1:
+  version "6.24.1"
+  resolved "https://registry.yarnpkg.com/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz#cce4517ada356f4220bcae8a02c2b346f9a56664"
+  dependencies:
+    babel-helper-explode-assignable-expression "^6.24.1"
+    babel-runtime "^6.22.0"
+    babel-types "^6.24.1"
+
 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"
@@ -290,16 +334,24 @@ 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"
+
 babel-helper-function-name@^6.24.1:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9"
   dependencies:
     babel-helper-get-function-arity "^6.24.1"
     babel-runtime "^6.22.0"
     babel-template "^6.24.1"
     babel-traverse "^6.24.1"
@@ -357,16 +409,24 @@ babel-helper-replace-supers@^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:
+  version "19.0.0"
+  resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-19.0.0.tgz#59323ced99a3a84d359da219ca881074ffc6ce3f"
+  dependencies:
+    babel-core "^6.0.0"
+    babel-plugin-istanbul "^4.0.0"
+    babel-preset-jest "^19.0.0"
+
 babel-loader@^6.2.4:
   version "6.4.1"
   resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-6.4.1.tgz#0b34112d5b0748a8dcdbf51acf6f9bd42d50b8ca"
   dependencies:
     find-cache-dir "^0.1.1"
     loader-utils "^0.2.16"
     mkdirp "^0.5.1"
     object-assign "^4.0.1"
@@ -378,33 +438,69 @@ babel-messages@^6.23.0:
     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"
+
+babel-plugin-jest-hoist@^19.0.0:
+  version "19.0.0"
+  resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-19.0.0.tgz#4ae2a04ea612a6e73651f3fde52c178991304bea"
+
 babel-plugin-module-resolver@^2.2.0:
   version "2.7.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-module-resolver/-/babel-plugin-module-resolver-2.7.0.tgz#9c1cb2fcf2a1bdb45e91c6c985b96311123797f9"
   dependencies:
     find-babel-config "^1.0.1"
     glob "^7.1.1"
     resolve "^1.2.0"
 
 babel-plugin-syntax-async-functions@^6.8.0:
   version "6.13.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95"
 
+babel-plugin-syntax-async-generators@^6.5.0:
+  version "6.13.0"
+  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz#6bc963ebb16eccbae6b92b596eb7f35c342a8b9a"
+
+babel-plugin-syntax-exponentiation-operator@^6.8.0:
+  version "6.13.0"
+  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de"
+
 babel-plugin-syntax-flow@^6.18.0:
   version "6.18.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz#4c3ab20a2af26aa20cd25995c398c4eb70310c8d"
 
-babel-plugin-transform-async-to-generator@^6.16.0:
+babel-plugin-syntax-object-rest-spread@^6.8.0:
+  version "6.13.0"
+  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5"
+
+babel-plugin-syntax-trailing-function-commas@^6.22.0:
+  version "6.22.0"
+  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3"
+
+babel-plugin-transform-async-generator-functions@^6.24.1:
+  version "6.24.1"
+  resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-generator-functions/-/babel-plugin-transform-async-generator-functions-6.24.1.tgz#f058900145fd3e9907a6ddf28da59f215258a5db"
+  dependencies:
+    babel-helper-remap-async-to-generator "^6.24.1"
+    babel-plugin-syntax-async-generators "^6.5.0"
+    babel-runtime "^6.22.0"
+
+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:
@@ -570,23 +666,38 @@ babel-plugin-transform-es2015-typeof-sym
 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-flow-strip-types@^6.14.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"
+
+babel-plugin-transform-flow-strip-types@^6.14.0, babel-plugin-transform-flow-strip-types@^6.22.0:
   version "6.22.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz#84cb672935d43714fdc32bce84568d87441cf7cf"
   dependencies:
     babel-plugin-syntax-flow "^6.18.0"
     babel-runtime "^6.22.0"
 
+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"
@@ -642,16 +753,32 @@ babel-preset-es2015@^6.6.0:
     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:
   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"
@@ -661,52 +788,52 @@ babel-register@^6.18.0, babel-register@^
 
 babel-runtime@^6.18.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-template@^6.24.1:
+babel-template@^6.16.0, babel-template@^6.24.1:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.24.1.tgz#04ae514f1f93b3a2537f2a0f60a5a45fb8308333"
   dependencies:
     babel-runtime "^6.22.0"
     babel-traverse "^6.24.1"
     babel-types "^6.24.1"
     babylon "^6.11.0"
     lodash "^4.2.0"
 
-babel-traverse@^6.23.1, babel-traverse@^6.24.1:
+babel-traverse@^6.18.0, babel-traverse@^6.23.1, babel-traverse@^6.24.1:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.24.1.tgz#ab36673fd356f9a0948659e7b338d5feadb31695"
   dependencies:
     babel-code-frame "^6.22.0"
     babel-messages "^6.23.0"
     babel-runtime "^6.22.0"
     babel-types "^6.24.1"
     babylon "^6.15.0"
     debug "^2.2.0"
     globals "^9.0.0"
     invariant "^2.2.0"
     lodash "^4.2.0"
 
-babel-types@^6.14.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.19.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"
 
-babylon@^6.11.0, babylon@^6.15.0, babylon@^6.16.1:
-  version "6.16.1"
-  resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.16.1.tgz#30c5a22f481978a9e7f8cdfdf496b11d94b404d3"
+babylon@^6.11.0, babylon@^6.13.0, babylon@^6.15.0, babylon@^6.17.0:
+  version "6.17.0"
+  resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.17.0.tgz#37da948878488b9c4e3c4038893fa3314b3fc932"
 
 balanced-match@^0.4.1, balanced-match@^0.4.2:
   version "0.4.2"
   resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838"
 
 base64-js@^1.0.2:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.0.tgz#a39992d723584811982be5e290bb6a53d86700f1"
@@ -770,16 +897,22 @@ brace-expansion@^1.0.0:
 braces@^1.8.2:
   version "1.8.5"
   resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7"
   dependencies:
     expand-range "^1.8.1"
     preserve "^0.2.0"
     repeat-element "^1.1.2"
 
+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"
+
 browserify-aes@0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-0.4.0.tgz#067149b668df31c4b58533e02d01e806d8608e2c"
   dependencies:
     inherits "^2.0.1"
 
 browserify-zlib@^0.1.4:
   version "0.1.4"
@@ -789,28 +922,44 @@ browserify-zlib@^0.1.4:
 
 browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6:
   version "1.7.7"
   resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-1.7.7.tgz#0bd76704258be829b2398bb50e4b62d1a166b0b9"
   dependencies:
     caniuse-db "^1.0.30000639"
     electron-to-chromium "^1.2.7"
 
+bser@1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/bser/-/bser-1.0.2.tgz#381116970b2a6deea5646dd15dd7278444b56169"
+  dependencies:
+    node-int64 "^0.4.0"
+
+bser@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/bser/-/bser-2.0.0.tgz#9ac78d3ed5d915804fd87acb158bc797147a1719"
+  dependencies:
+    node-int64 "^0.4.0"
+
 buffer-shims@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51"
 
 buffer@^4.9.0:
   version "4.9.1"
   resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298"
   dependencies:
     base64-js "^1.0.2"
     ieee754 "^1.1.4"
     isarray "^1.0.0"
 
+builtin-modules@^1.0.0:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
+
 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"
 
@@ -819,32 +968,40 @@ caller-path@^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@^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"
+
 caniuse-api@^1.5.2:
   version "1.6.1"
   resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-1.6.1.tgz#b534e7c734c4f81ec5fbe8aca2ad24354b962c6c"
   dependencies:
     browserslist "^1.3.6"
     caniuse-db "^1.0.30000529"
     lodash.memoize "^4.1.2"
     lodash.uniq "^4.5.0"
 
 caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639:
-  version "1.0.30000655"
-  resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000655.tgz#e40b6287adc938848d6708ef83d65b5f54ac1874"
+  version "1.0.30000662"
+  resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000662.tgz#616b17a525b52fec14611f88af3d5a9b5438c050"
 
 capture-stack-trace@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz#4a6fa07399c26bba47f0b2496b4d0fb408c5550d"
 
 caseless@~0.12.0:
   version "0.12.0"
   resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
@@ -891,16 +1048,20 @@ chokidar@^1.0.0, chokidar@^1.6.1:
 
 chrome-remote-interface@0.17.0:
   version "0.17.0"
   resolved "https://registry.yarnpkg.com/chrome-remote-interface/-/chrome-remote-interface-0.17.0.tgz#79b8f0da97b6c37c7b4e9c6370c33f332998bb37"
   dependencies:
     commander "2.1.x"
     ws "1.1.x"
 
+ci-info@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.0.0.tgz#dc5285f2b4e251821683681c381c3388f46ec534"
+
 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:
@@ -923,16 +1084,24 @@ cli-width@^2.0.0:
 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"
 
+cliui@^3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d"
+  dependencies:
+    string-width "^1.0.1"
+    strip-ansi "^3.0.1"
+    wrap-ansi "^2.0.0"
+
 clone@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.2.tgz#260b7a99ebb1edfe247538175f783243cb19d149"
 
 co@=4.6.0, co@^4.6.0:
   version "4.6.0"
   resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
 
@@ -942,20 +1111,20 @@ coa@~1.0.1:
   dependencies:
     q "^1.1.2"
 
 code-point-at@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
 
 codemirror@^5.24.2, codemirror@^5.25.0:
-  version "5.25.0"
-  resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.25.0.tgz#78e06939c7bb41f65707b8aa9c5328111948b756"
-
-color-convert@^1.3.0:
+  version "5.25.2"
+  resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.25.2.tgz#8c77677ca9c9248d757d3a07ed1e89a8404850b7"
+
+color-convert@^1.0.0, color-convert@^1.3.0:
   version "1.9.0"
   resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.0.tgz#1accf97dd739b983bf994d56fec8f95853641b7a"
   dependencies:
     color-name "^1.1.1"
 
 color-name@^1.0.0, color-name@^1.1.1:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.2.tgz#5c8ab72b64bd2215d617ae9559ebb148475cf98d"
@@ -1035,16 +1204,20 @@ console-control-strings@^1.0.0, console-
 constants-browserify@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
 
 content-disposition@0.5.2:
   version "0.5.2"
   resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4"
 
+content-type-parser@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/content-type-parser/-/content-type-parser-1.0.1.tgz#c3e56988c53c65127fb46d4032a3a900246fdc94"
+
 content-type@~1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.2.tgz#b7d113aee7a8dd27bd21133c4dc2529df1721eed"
 
 convert-source-map@^1.1.0:
   version "1.5.0"
   resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5"
 
@@ -1068,24 +1241,25 @@ core-js@~2.3.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.3.0.tgz#fab83fbb0b2d8dc85fa636c4b9d34c75420c6d65"
 
 core-util-is@~1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
 
 cosmiconfig@^2.1.0, cosmiconfig@^2.1.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-2.1.1.tgz#817f2c2039347a1e9bf7d090c0923e53f749ca82"
-  dependencies:
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-2.1.2.tgz#c43ae86d238f08f1728a345ed60ceb0aef63c060"
+  dependencies:
+    is-directory "^0.3.1"
     js-yaml "^3.4.3"
+    json-parse-helpfulerror "^1.0.3"
     minimist "^1.2.0"
     object-assign "^4.1.0"
     os-homedir "^1.0.1"
-    parse-json "^2.2.0"
     require-from-string "^1.1.0"
 
 create-error-class@^3.0.1:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6"
   dependencies:
     capture-stack-trace "^1.0.0"
 
@@ -1184,16 +1358,26 @@ cssesc@^0.1.0:
 
 csso@~2.3.1:
   version "2.3.2"
   resolved "https://registry.yarnpkg.com/csso/-/csso-2.3.2.tgz#ddd52c587033f49e94b71fc55569f252e8ff5f85"
   dependencies:
     clap "^1.0.9"
     source-map "^0.5.3"
 
+cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0":
+  version "0.3.2"
+  resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.2.tgz#b8036170c79f07a90ff2f16e22284027a243848b"
+
+"cssstyle@>= 0.2.37 < 0.3.0":
+  version "0.2.37"
+  resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-0.2.37.tgz#541097234cb2513c83ceed3acddc27ff27987d54"
+  dependencies:
+    cssom "0.3.x"
+
 d@1:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f"
   dependencies:
     es5-ext "^0.10.9"
 
 dashdash@^1.12.0:
   version "1.14.1"
@@ -1206,34 +1390,46 @@ date-now@^0.1.4:
   resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
 
 debug@2.6.1:
   version "2.6.1"
   resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.1.tgz#79855090ba2c4e3115cc7d8769491d58f0491351"
   dependencies:
     ms "0.7.2"
 
-debug@2.6.3, debug@^2.1.1, debug@^2.2.0:
-  version "2.6.3"
-  resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.3.tgz#0f7eb8c30965ec08c72accfa0130c8b79984141d"
-  dependencies:
-    ms "0.7.2"
-
-decamelize@^1.0.0, decamelize@^1.1.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.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"
+
+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:
   version "0.4.1"
   resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.1.tgz#efe4113d08085f4e6f9687759810f807469e2253"
 
 deep-is@~0.1.3:
   version "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:
@@ -1273,19 +1469,23 @@ detect-indent@^4.0.0:
   resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208"
   dependencies:
     repeating "^2.0.0"
 
 devtools-config@=0.0.12, devtools-config@^0.0.12:
   version "0.0.12"
   resolved "https://registry.yarnpkg.com/devtools-config/-/devtools-config-0.0.12.tgz#4094e62efec23cdc31bd0e6d89e15f7c51d6a7e6"
 
-devtools-launchpad@=0.0.67:
-  version "0.0.67"
-  resolved "https://registry.yarnpkg.com/devtools-launchpad/-/devtools-launchpad-0.0.67.tgz#87f03be9003eb9cd0724252e84cea7b5b42852be"
+devtools-connection@^0.0.4:
+  version "0.0.4"
+  resolved "https://registry.yarnpkg.com/devtools-connection/-/devtools-connection-0.0.4.tgz#96174ad89dcfd34604829eee4cb4cb2673242ebf"
+
+devtools-launchpad@=0.0.75:
+  version "0.0.75"
+  resolved "https://registry.yarnpkg.com/devtools-launchpad/-/devtools-launchpad-0.0.75.tgz#1ce79b4ad65ceee14cfb8e7b0e8b98710c4016bc"
   dependencies:
     amd-loader "0.0.5"
     autoprefixer "^6.7.6"
     babel-cli "^6.7.5"
     babel-core "^6.17.0"
     babel-eslint "^7.1.0"
     babel-loader "^6.2.4"
     babel-plugin-module-resolver "^2.2.0"
@@ -1302,17 +1502,20 @@ devtools-launchpad@=0.0.67:
     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"
     devtools-config "^0.0.12"
-    devtools-modules "^0.0.24"
+    devtools-connection "^0.0.4"
+    devtools-modules "^0.0.27"
+    devtools-sprintf-js "^1.0.3"
+    devtools-utils "^0.0.2"
     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"
     extract-text-webpack-plugin "^1.0.1"
     fs-extra "^2.0.0"
@@ -1332,37 +1535,51 @@ devtools-launchpad@=0.0.67:
     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-inlinesvg "^0.5.3"
     react-redux "4.4.5"
     redux "3.5.2"
-    selenium-webdriver "^3.0.1"
+    selenium-webdriver "=3.3.0"
     style-loader "^0.13.1"
     svg-inline-loader "^0.7.1"
     webpack "1.14.0"
     webpack-dev-middleware "^1.6.1"
     webpack-env-loader-plugin "^0.1.4"
     webpack-hot-middleware "^2.12.0"
     ws "^1.0.1"
 
-devtools-modules@=0.0.24, devtools-modules@^0.0.24:
-  version "0.0.24"
-  resolved "https://registry.yarnpkg.com/devtools-modules/-/devtools-modules-0.0.24.tgz#95495f8a9aae8a21ab901d8998ce5ed704fe92e5"
-  dependencies:
-    properties-parser "^0.3.1"
+devtools-modules@=0.0.27, devtools-modules@^0.0.27:
+  version "0.0.27"
+  resolved "https://registry.yarnpkg.com/devtools-modules/-/devtools-modules-0.0.27.tgz#55ba9cc4fab4eb37335b343a94f26ded9b360e81"
 
 devtools-source-editor@=0.0.3:
   version "0.0.3"
   resolved "https://registry.yarnpkg.com/devtools-source-editor/-/devtools-source-editor-0.0.3.tgz#691a8376667907809597c1dbe310965208b346a7"
   dependencies:
     codemirror "^5.25.0"
 
+devtools-sprintf-js@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/devtools-sprintf-js/-/devtools-sprintf-js-1.0.3.tgz#c2aa3c39d4047525e60410325c86770620281b87"
+
+devtools-utils@^0.0.2:
+  version "0.0.2"
+  resolved "https://registry.yarnpkg.com/devtools-utils/-/devtools-utils-0.0.2.tgz#8e711f276920f75785cdd9ae061d7d45752c6d44"
+  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:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9"
+
 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:
@@ -1388,18 +1605,18 @@ ecc-jsbn@~0.1.1:
   dependencies:
     jsbn "~0.1.0"
 
 ee-first@1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
 
 electron-to-chromium@^1.2.7:
-  version "1.3.3"
-  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.3.tgz#651eb63fe89f39db70ffc8dbd5d9b66958bc6a0e"
+  version "1.3.8"
+  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.8.tgz#b2c8a2c79bb89fbbfd3724d9555e15095b5f5fb6"
 
 emojis-list@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389"
 
 encodeurl@~1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20"
@@ -1413,17 +1630,17 @@ encoding@^0.1.11:
 enhanced-resolve@~0.9.0:
   version "0.9.1"
   resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz#4d6e689b3725f86090927ccc86cd9f1635b89e2e"
   dependencies:
     graceful-fs "^4.1.2"
     memory-fs "^0.2.0"
     tapable "^0.1.8"
 
-errno@^0.1.3:
+"errno@>=0.1.1 <0.2.0-0", errno@^0.1.3:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.4.tgz#b896e23a9e5e8ba33871fc996abd3635fc9a1c7d"
   dependencies:
     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"
@@ -1506,32 +1723,43 @@ es6-weak-map@^2.0.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:
   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"
+    estraverse "^1.9.1"
+    esutils "^2.0.2"
+    optionator "^0.8.1"
+  optionalDependencies:
+    source-map "~0.2.0"
+
 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.30.4"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-2.30.4.tgz#771d6bb4578ab8598e9c58018fea2e1a22946249"
+  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"
@@ -1585,23 +1813,23 @@ eslint@^3.12.0:
     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.1"
-  resolved "https://registry.yarnpkg.com/espree/-/espree-3.4.1.tgz#28a83ab4aaed71ed8fe0f5efe61b76a05c13c4d2"
+  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.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:
@@ -1612,16 +1840,20 @@ esquery@^1.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:
   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"
 
@@ -1639,16 +1871,22 @@ event-emitter@~0.3.5:
   dependencies:
     d "1"
     es5-ext "~0.10.14"
 
 events@^1.0.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
 
+exec-sh@^0.2.0:
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.2.0.tgz#14f75de3f20d286ef933099b2ce50a90359cef10"
+  dependencies:
+    merge "^1.1.3"
+
 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:
@@ -1718,16 +1956,28 @@ extsprintf@1.0.2:
 fast-levenshtein@~2.0.4:
   version "2.0.6"
   resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
 
 fastparse@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8"
 
+fb-watchman@^1.8.0:
+  version "1.9.2"
+  resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-1.9.2.tgz#a24cf47827f82d38fb59a69ad70b76e3b6ae7383"
+  dependencies:
+    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, fbjs@^0.8.4:
   version "0.8.12"
   resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.12.tgz#10b5d92f76d45575fd63a217d4ea02bea2f8ed04"
   dependencies:
     core-js "^1.0.0"
     isomorphic-fetch "^2.1.1"
     loose-envify "^1.0.0"
     object-assign "^4.1.0"
@@ -1754,31 +2004,38 @@ file-loader@^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"
   resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.0.tgz#996e3e80479b98b9897f15a8a58b3d084e926775"
 
+fileset@^2.0.2:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/fileset/-/fileset-2.0.3.tgz#8e7548a96d3cc2327ee5e674168723a333bba2a0"
+  dependencies:
+    glob "^7.0.3"
+    minimatch "^3.0.3"
+
 fill-range@^2.1.0:
   version "2.2.3"
   resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723"
   dependencies:
     is-number "^2.1.0"
     isobject "^2.0.0"
     randomatic "^1.1.3"
     repeat-element "^1.1.2"
     repeat-string "^1.5.2"
 
 finalhandler@~1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.0.1.tgz#bcd15d1689c0e5ed729b6f7f541a6df984117db8"
-  dependencies:
-    debug "2.6.3"
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.0.2.tgz#d0e36f9dbc557f2de14423df6261889e9d60c93a"
+  dependencies:
+    debug "2.6.4"
     encodeurl "~1.0.1"
     escape-html "~1.0.3"
     on-finished "~2.3.0"
     parseurl "~1.3.1"
     statuses "~1.3.1"
     unpipe "~1.0.0"
 
 find-babel-config@^1.0.1:
@@ -1798,17 +2055,17 @@ find-cache-dir@^0.1.1:
 
 find-up@^1.0.0:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f"
   dependencies:
     path-exists "^2.0.0"
     pinkie-promise "^2.0.0"
 
-find-up@^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"
@@ -1899,18 +2156,18 @@ function-bind@^1.0.2, function-bind@^1.1
   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:
-  version "2.7.3"
-  resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.3.tgz#1c23855f962f17b3ad3d0dc7443f304542edfe09"
+  version "2.7.4"
+  resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7"
   dependencies:
     aproba "^1.0.3"
     console-control-strings "^1.0.0"
     has-unicode "^2.0.0"
     object-assign "^4.1.0"
     signal-exit "^3.0.0"
     string-width "^1.0.1"
     strip-ansi "^3.0.1"
@@ -1930,19 +2187,23 @@ generate-function@^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"
+
 getpass@^0.1.1:
-  version "0.1.6"
-  resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.6.tgz#283ffd9fc1256840875311c1b60e8c40187110e6"
+  version "0.1.7"
+  resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
   dependencies:
     assert-plus "^1.0.0"
 
 glob-base@^0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4"
   dependencies:
     glob-parent "^2.0.0"
@@ -2004,16 +2265,30 @@ 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"
 
+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:
+    async "^1.4.0"
+    optimist "^0.6.1"
+    source-map "^0.4.4"
+  optionalDependencies:
+    uglify-js "^2.6"
+
 har-schema@^1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e"
 
 har-validator@~4.2.1:
   version "4.2.1"
   resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a"
   dependencies:
@@ -2059,23 +2334,33 @@ hoist-non-react-statics@^1.0.3:
 
 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:
+  version "2.4.2"
+  resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.4.2.tgz#0076b9f46a270506ddbaaea56496897460612a67"
+
 html-comment-regex@^1.1.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e"
 
+html-encoding-sniffer@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.1.tgz#79bf7a785ea495fe66165e734153f363ff5437da"
+  dependencies:
+    whatwg-encoding "^1.0.1"
+
 html-entities@^1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.0.tgz#41948caf85ce82fed36e4e6a0ed371a6664379e2"
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f"
 
 http-errors@~1.6.1:
   version "1.6.1"
   resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.1.tgz#5f8b8ed98aca545656bf572997387f904a722257"
   dependencies:
     depd "1.1.0"
     inherits "2.0.3"
     setprototypeof "1.0.3"
@@ -2096,16 +2381,20 @@ httpplease@^0.16:
     urllite "~0.5.0"
     xmlhttprequest "*"
     xtend "~3.0.0"
 
 https-browserify@0.0.1:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-0.0.1.tgz#3f91365cabe60b77ed0ebba24b454e3e09d95a82"
 
+iconv-lite@0.4.13:
+  version "0.4.13"
+  resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2"
+
 iconv-lite@0.4.15, iconv-lite@~0.4.13:
   version "0.4.15"
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb"
 
 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"
 
@@ -2174,25 +2463,29 @@ inquirer@^0.12.0:
     strip-ansi "^3.0.0"
     through "^2.3.6"
 
 interpret@^0.6.4:
   version "0.6.6"
   resolved "https://registry.yarnpkg.com/interpret/-/interpret-0.6.6.tgz#fecd7a18e7ce5ca6abfb953e1f86213a49f1625b"
 
 interpret@^1.0.0:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.2.tgz#f4f623f0bb7122f15f5717c8e254b8161b5c5b2d"
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.3.tgz#cbc35c62eeee73f19ab7b10a801511401afc0f90"
 
 invariant@^2.0.0, invariant@^2.2.0:
   version "2.2.2"
   resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360"
   dependencies:
     loose-envify "^1.0.0"
 
+invert-kv@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
+
 ipaddr.js@1.3.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.3.0.tgz#1e03a52fdad83a8bbb2b25cbf4998b4cffcd3dec"
 
 is-absolute-url@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6"
 
@@ -2201,28 +2494,44 @@ is-arrayish@^0.2.1:
   resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
 
 is-binary-path@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898"
   dependencies:
     binary-extensions "^1.0.0"
 
-is-buffer@^1.0.2:
+is-buffer@^1.1.5:
   version "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"
 
 is-equal-shallow@^0.1.3:
   version "0.1.3"
   resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534"
   dependencies:
@@ -2338,16 +2647,20 @@ is-svg@^2.0.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"
+
 isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
 
 isexe@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
 
@@ -2363,31 +2676,299 @@ isomorphic-fetch@^2.1.1:
   dependencies:
     node-fetch "^1.0.1"
     whatwg-fetch ">=0.10.0"
 
 isstream@~0.1.2:
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
 
+istanbul-api@^1.1.0-alpha.1:
+  version "1.1.7"
+  resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.1.7.tgz#f6f37f09f8002b130f891c646b70ee4a8e7345ae"
+  dependencies:
+    async "^2.1.4"
+    fileset "^2.0.2"
+    istanbul-lib-coverage "^1.0.2"
+    istanbul-lib-hook "^1.0.5"
+    istanbul-lib-instrument "^1.7.0"
+    istanbul-lib-report "^1.0.0"
+    istanbul-lib-source-maps "^1.1.1"
+    istanbul-reports "^1.0.2"
+    js-yaml "^3.7.0"
+    mkdirp "^0.5.1"
+    once "^1.4.0"
+
+istanbul-lib-coverage@^1.0.0, istanbul-lib-coverage@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.0.2.tgz#87a0c015b6910651cb3b184814dfb339337e25e1"
+
+istanbul-lib-hook@^1.0.5:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.0.5.tgz#6ca3d16d60c5f4082da39f7c5cd38ea8a772b88e"
+  dependencies:
+    append-transform "^0.4.0"
+
+istanbul-lib-instrument@^1.1.1, istanbul-lib-instrument@^1.6.2, istanbul-lib-instrument@^1.7.0:
+  version "1.7.0"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.7.0.tgz#b8e0dc25709bb44e17336ab47b7bb5c97c23f659"
+  dependencies:
+    babel-generator "^6.18.0"
+    babel-template "^6.16.0"
+    babel-traverse "^6.18.0"
+    babel-types "^6.18.0"
+    babylon "^6.13.0"
+    istanbul-lib-coverage "^1.0.2"
+    semver "^5.3.0"
+
+istanbul-lib-report@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.0.0.tgz#d83dac7f26566b521585569367fe84ccfc7aaecb"
+  dependencies:
+    istanbul-lib-coverage "^1.0.2"
+    mkdirp "^0.5.1"
+    path-parse "^1.0.5"
+    supports-color "^3.1.2"
+
+istanbul-lib-source-maps@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.1.1.tgz#f8c8c2e8f2160d1d91526d97e5bd63b2079af71c"
+  dependencies:
+    istanbul-lib-coverage "^1.0.2"
+    mkdirp "^0.5.1"
+    rimraf "^2.4.4"
+    source-map "^0.5.3"
+
+istanbul-reports@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.0.2.tgz#4e8366abe6fa746cc1cd6633f108de12cc6ac6fa"
+  dependencies:
+    handlebars "^4.0.3"
+
+jest-changed-files@^19.0.2:
+  version "19.0.2"
+  resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-19.0.2.tgz#16c54c84c3270be408e06d2e8af3f3e37a885824"
+
+jest-cli@^19.0.2:
+  version "19.0.2"
+  resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-19.0.2.tgz#cc3620b62acac5f2d93a548cb6ef697d4ec85443"
+  dependencies:
+    ansi-escapes "^1.4.0"
+    callsites "^2.0.0"
+    chalk "^1.1.1"
+    graceful-fs "^4.1.6"
+    is-ci "^1.0.9"
+    istanbul-api "^1.1.0-alpha.1"
+    istanbul-lib-coverage "^1.0.0"
+    istanbul-lib-instrument "^1.1.1"
+    jest-changed-files "^19.0.2"
+    jest-config "^19.0.2"
+    jest-environment-jsdom "^19.0.2"
+    jest-haste-map "^19.0.0"
+    jest-jasmine2 "^19.0.2"
+    jest-message-util "^19.0.0"
+    jest-regex-util "^19.0.0"
+    jest-resolve-dependencies "^19.0.0"
+    jest-runtime "^19.0.2"
+    jest-snapshot "^19.0.2"
+    jest-util "^19.0.2"
+    micromatch "^2.3.11"
+    node-notifier "^5.0.1"
+    slash "^1.0.0"
+    string-length "^1.0.1"
+    throat "^3.0.0"
+    which "^1.1.1"
+    worker-farm "^1.3.1"
+    yargs "^6.3.0"
+
+jest-config@^19.0.2:
+  version "19.0.4"
+  resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-19.0.4.tgz#42980211d46417e91ca7abffd086c270234f73fd"
+  dependencies:
+    chalk "^1.1.1"
+    jest-environment-jsdom "^19.0.2"
+    jest-environment-node "^19.0.2"
+    jest-jasmine2 "^19.0.2"
+    jest-regex-util "^19.0.0"
+    jest-resolve "^19.0.2"
+    jest-validate "^19.0.2"
+    pretty-format "^19.0.0"
+
+jest-diff@^19.0.0:
+  version "19.0.0"
+  resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-19.0.0.tgz#d1563cfc56c8b60232988fbc05d4d16ed90f063c"
+  dependencies:
+    chalk "^1.1.3"
+    diff "^3.0.0"
+    jest-matcher-utils "^19.0.0"
+    pretty-format "^19.0.0"
+
+jest-environment-jsdom@^19.0.2:
+  version "19.0.2"
+  resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-19.0.2.tgz#ceda859c4a4b94ab35e4de7dab54b926f293e4a3"
+  dependencies:
+    jest-mock "^19.0.0"
+    jest-util "^19.0.2"
+    jsdom "^9.11.0"
+
+jest-environment-node@^19.0.2:
+  version "19.0.2"
+  resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-19.0.2.tgz#6e84079db87ed21d0c05e1f9669f207b116fe99b"
+  dependencies:
+    jest-mock "^19.0.0"
+    jest-util "^19.0.2"
+
+jest-file-exists@^19.0.0:
+  version "19.0.0"
+  resolved "https://registry.yarnpkg.com/jest-file-exists/-/jest-file-exists-19.0.0.tgz#cca2e587a11ec92e24cfeab3f8a94d657f3fceb8"
+
+jest-haste-map@^19.0.0:
+  version "19.0.2"
+  resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-19.0.2.tgz#286484c3a16e86da7872b0877c35dce30c3d6f07"
+  dependencies:
+    fb-watchman "^2.0.0"
+    graceful-fs "^4.1.6"
+    micromatch "^2.3.11"
+    sane "~1.5.0"
+    worker-farm "^1.3.1"
+
+jest-jasmine2@^19.0.2:
+  version "19.0.2"
+  resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-19.0.2.tgz#167991ac825981fb1a800af126e83afcca832c73"
+  dependencies:
+    graceful-fs "^4.1.6"
+    jest-matcher-utils "^19.0.0"
+    jest-matchers "^19.0.0"
+    jest-message-util "^19.0.0"
+    jest-snapshot "^19.0.2"
+
+jest-matcher-utils@^19.0.0:
+  version "19.0.0"
+  resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-19.0.0.tgz#5ecd9b63565d2b001f61fbf7ec4c7f537964564d"
+  dependencies:
+    chalk "^1.1.3"
+    pretty-format "^19.0.0"
+
+jest-matchers@^19.0.0:
+  version "19.0.0"
+  resolved "https://registry.yarnpkg.com/jest-matchers/-/jest-matchers-19.0.0.tgz#c74ecc6ebfec06f384767ba4d6fa4a42d6755754"
+  dependencies:
+    jest-diff "^19.0.0"
+    jest-matcher-utils "^19.0.0"
+    jest-message-util "^19.0.0"
+    jest-regex-util "^19.0.0"
+
+jest-message-util@^19.0.0:
+  version "19.0.0"
+  resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-19.0.0.tgz#721796b89c0e4d761606f9ba8cb828a3b6246416"
+  dependencies:
+    chalk "^1.1.1"
+    micromatch "^2.3.11"
+
+jest-mock@^19.0.0:
+  version "19.0.0"
+  resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-19.0.0.tgz#67038641e9607ab2ce08ec4a8cb83aabbc899d01"
+
+jest-regex-util@^19.0.0:
+  version "19.0.0"
+  resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-19.0.0.tgz#b7754587112aede1456510bb1f6afe74ef598691"
+
+jest-resolve-dependencies@^19.0.0:
+  version "19.0.0"
+  resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-19.0.0.tgz#a741ad1fa094140e64ecf2642a504f834ece22ee"
+  dependencies:
+    jest-file-exists "^19.0.0"
+
+jest-resolve@^19.0.2:
+  version "19.0.2"
+  resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-19.0.2.tgz#5793575de4f07aec32f7d7ff0c6c181963eefb3c"
+  dependencies:
+    browser-resolve "^1.11.2"
+    jest-haste-map "^19.0.0"
+    resolve "^1.2.0"
+
+jest-runtime@^19.0.2:
+  version "19.0.3"
+  resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-19.0.3.tgz#a163354ace46910ee33f0282b6bff6b0b87d4330"
+  dependencies:
+    babel-core "^6.0.0"
+    babel-jest "^19.0.0"
+    babel-plugin-istanbul "^4.0.0"
+    chalk "^1.1.3"
+    graceful-fs "^4.1.6"
+    jest-config "^19.0.2"
+    jest-file-exists "^19.0.0"
+    jest-haste-map "^19.0.0"
+    jest-regex-util "^19.0.0"
+    jest-resolve "^19.0.2"
+    jest-util "^19.0.2"
+    json-stable-stringify "^1.0.1"
+    micromatch "^2.3.11"
+    strip-bom "3.0.0"
+    yargs "^6.3.0"
+
+jest-snapshot@^19.0.2:
+  version "19.0.2"
+  resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-19.0.2.tgz#9c1b216214f7187c38bfd5c70b1efab16b0ff50b"
+  dependencies:
+    chalk "^1.1.3"
+    jest-diff "^19.0.0"
+    jest-file-exists "^19.0.0"
+    jest-matcher-utils "^19.0.0"
+    jest-util "^19.0.2"
+    natural-compare "^1.4.0"
+    pretty-format "^19.0.0"
+
+jest-util@^19.0.2:
+  version "19.0.2"
+  resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-19.0.2.tgz#e0a0232a2ab9e6b2b53668bdb3534c2b5977ed41"
+  dependencies:
+    chalk "^1.1.1"
+    graceful-fs "^4.1.6"
+    jest-file-exists "^19.0.0"
+    jest-message-util "^19.0.0"
+    jest-mock "^19.0.0"
+    jest-validate "^19.0.2"
+    leven "^2.0.0"
+    mkdirp "^0.5.1"
+
+jest-validate@^19.0.2:
+  version "19.0.2"
+  resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-19.0.2.tgz#dc534df5f1278d5b63df32b14241d4dbf7244c0c"
+  dependencies:
+    chalk "^1.1.1"
+    jest-matcher-utils "^19.0.0"
+    leven "^2.0.0"
+    pretty-format "^19.0.0"
+
+jest@^19.0.2:
+  version "19.0.2"
+  resolved "https://registry.yarnpkg.com/jest/-/jest-19.0.2.tgz#b794faaf8ff461e7388f28beef559a54f20b2c10"
+  dependencies:
+    jest-cli "^19.0.2"
+
+jju@^1.1.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/jju/-/jju-1.3.0.tgz#dadd9ef01924bc728b03f2f7979bdbd62f7a2aaa"
+
 jodid25519@^1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/jodid25519/-/jodid25519-1.0.2.tgz#06d4912255093419477d425633606e0e90782967"
   dependencies:
     jsbn "~0.1.0"
 
 js-base64@^2.1.9:
   version "2.1.9"
   resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.1.9.tgz#f0e80ae039a4bd654b5f281fc93f04a914a7fcce"
 
 js-tokens@^3.0.0:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7"
 
-js-yaml@^3.4.3, js-yaml@^3.5.1:
+js-yaml@^3.4.3, js-yaml@^3.5.1, 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"
@@ -2395,28 +2976,58 @@ js-yaml@~3.7.0:
   dependencies:
     argparse "^1.0.7"
     esprima "^2.6.0"
 
 jsbn@~0.1.0:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
 
+jsdom@^9.11.0:
+  version "9.12.0"
+  resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-9.12.0.tgz#e8c546fffcb06c00d4833ca84410fed7f8a097d4"
+  dependencies:
+    abab "^1.0.3"
+    acorn "^4.0.4"
+    acorn-globals "^3.1.0"
+    array-equal "^1.0.0"
+    content-type-parser "^1.0.1"
+    cssom ">= 0.3.2 < 0.4.0"
+    cssstyle ">= 0.2.37 < 0.3.0"
+    escodegen "^1.6.1"
+    html-encoding-sniffer "^1.0.1"
+    nwmatcher ">= 1.3.9 < 2.0.0"
+    parse5 "^1.5.1"
+    request "^2.79.0"
+    sax "^1.2.1"
+    symbol-tree "^3.2.1"
+    tough-cookie "^2.3.2"
+    webidl-conversions "^4.0.0"
+    whatwg-encoding "^1.0.1"
+    whatwg-url "^4.3.0"
+    xml-name-validator "^2.0.1"
+
 jsesc@^1.3.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b"
 
 jsesc@~0.5.0:
   version "0.5.0"
   resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
 
 json-loader@^0.5.4:
   version "0.5.4"
   resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.4.tgz#8baa1365a632f58a3c46d20175fc6002c96e37de"
 
+json-parse-helpfulerror@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/json-parse-helpfulerror/-/json-parse-helpfulerror-1.0.3.tgz#13f14ce02eed4e981297b64eb9e3b932e2dd13dc"
+  dependencies:
+    jju "^1.1.0"
+
 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:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af"
   dependencies:
@@ -2449,54 +3060,72 @@ jsprim@^1.2.2:
   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.0"
-  resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-1.4.0.tgz#5afe38868f56bc8cc7aeaef0100ba8c75bd12591"
-  dependencies:
-    object-assign "^4.1.0"
+  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"
     readable-stream "~2.0.6"
 
 kind-of@^3.0.2:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.1.0.tgz#475d698a5e49ff5e53d14e3e732429dc8bf4cf47"
-  dependencies:
-    is-buffer "^1.0.2"
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.0.tgz#b58abe4d5c044ad33726a8c1525b48cf891bff07"
+  dependencies:
+    is-buffer "^1.1.5"
 
 lazy-cache@^1.0.3:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e"
 
+lcid@^1.0.0:
+  version "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:
   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"
   resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e"
   dependencies:
     immediate "~3.0.5"
 
+load-json-file@^1.0.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"
+  dependencies:
+    graceful-fs "^4.1.2"
+    parse-json "^2.2.0"
+    pify "^2.0.0"
+    pinkie-promise "^2.0.0"
+    strip-bom "^2.0.0"
+
 loader-utils@^0.2.11, loader-utils@^0.2.16, loader-utils@^0.2.3:
   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"
@@ -2548,17 +3177,17 @@ 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.15.0, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0:
+lodash@^4.0.0, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0:
   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:
@@ -2570,16 +3199,22 @@ loose-envify@^1.0.0, loose-envify@^1.1.0
 lowercase-keys@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306"
 
 macaddress@^0.2.8:
   version "0.2.8"
   resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12"
 
+makeerror@1.0.x:
+  version "1.0.11"
+  resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c"
+  dependencies:
+    tmpl "1.0.x"
+
 math-expression-evaluator@^1.2.14:
   version "1.2.16"
   resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.16.tgz#b357fa1ca9faefb8e48d10c14ef2bcb2d9f0a7c9"
 
 media-typer@0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
 
@@ -2600,21 +3235,25 @@ memory-fs@~0.4.1:
   dependencies:
     errno "^0.1.3"
     readable-stream "^2.0.1"
 
 merge-descriptors@1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
 
+merge@^1.1.3:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da"
+
 methods@~1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
 
-micromatch@^2.1.5:
+micromatch@^2.1.5, micromatch@^2.3.11:
   version "2.3.11"
   resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565"
   dependencies:
     arr-diff "^2.0.0"
     array-unique "^0.2.1"
     braces "^1.8.2"
     expand-brackets "^0.1.4"
     extglob "^0.3.1"
@@ -2636,27 +3275,27 @@ mime-types@^2.1.12, mime-types@~2.1.11, 
   resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.15.tgz#a4ebf5064094569237b8cf70046776d09fc92aed"
   dependencies:
     mime-db "~1.27.0"
 
 mime@1.3.4, mime@>=1.2.9, mime@^1.3.4:
   version "1.3.4"
   resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53"
 
-minimatch@^3.0.0, minimatch@^3.0.2:
+minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3:
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774"
   dependencies:
     brace-expansion "^1.0.0"
 
 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.2.0:
+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:
   version "0.5.1"
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
   dependencies:
     minimist "0.0.8"
@@ -2664,16 +3303,20 @@ minimist@^1.2.0:
 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"
 
+ms@0.7.3:
+  version "0.7.3"
+  resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.3.tgz#708155a5e44e33f5fd0fc53e81d0d40a91be1fff"
+
 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"
 
@@ -2691,16 +3334,20 @@ negotiator@0.6.1:
 
 node-fetch@^1.0.1:
   version "1.6.3"
   resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.6.3.tgz#dc234edd6489982d58e8f0db4f695029abcd8c04"
   dependencies:
     encoding "^0.1.11"
     is-stream "^1.0.1"
 
+node-int64@^0.4.0:
+  version "0.4.0"
+  resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
+
 node-libs-browser@^0.7.0:
   version "0.7.0"
   resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-0.7.0.tgz#3e272c0819e308935e26674408d7af0e1491b83b"
   dependencies:
     assert "^1.1.1"
     browserify-zlib "^0.1.4"
     buffer "^4.9.0"
     console-browserify "^1.1.0"
@@ -2719,16 +3366,25 @@ node-libs-browser@^0.7.0:
     stream-http "^2.3.1"
     string_decoder "^0.10.25"
     timers-browserify "^2.0.2"
     tty-browserify "0.0.0"
     url "^0.11.0"
     util "^0.10.3"
     vm-browserify "0.0.4"
 
+node-notifier@^5.0.1:
+  version "5.1.2"
+  resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.1.2.tgz#2fa9e12605fa10009d44549d6fcd8a63dde0e4ff"
+  dependencies:
+    growly "^1.3.0"
+    semver "^5.3.0"
+    shellwords "^0.1.0"
+    which "^1.2.12"
+
 node-pre-gyp@^0.6.29:
   version "0.6.34"
   resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.34.tgz#94ad1c798a11d7fc67381b50d47f8cc18d9799f7"
   dependencies:
     mkdirp "^0.5.1"
     nopt "^4.0.1"
     npmlog "^4.0.2"
     rc "^1.1.7"
@@ -2752,16 +3408,25 @@ node-status-codes@^1.0.0:
 
 nopt@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d"
   dependencies:
     abbrev "1"
     osenv "^0.1.4"
 
+normalize-package-data@^2.3.2:
+  version "2.3.8"
+  resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.3.8.tgz#d819eda2a9dedbd1ffa563ea4071d936782295bb"
+  dependencies:
+    hosted-git-info "^2.1.4"
+    is-builtin-module "^1.0.0"
+    semver "2 || 3 || 4 || 5"
+    validate-npm-package-license "^3.0.1"
+
 normalize-path@^2.0.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9"
   dependencies:
     remove-trailing-separator "^1.0.1"
 
 normalize-range@^0.1.2:
   version "0.1.2"
@@ -2788,16 +3453,20 @@ npmlog@^4.0.2:
 num2fraction@^1.2.2:
   version "1.2.2"
   resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede"
 
 number-is-nan@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
 
+"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.1.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
 
@@ -2821,34 +3490,34 @@ object.omit@^2.0.0:
     is-extendable "^0.1.1"
 
 on-finished@~2.3.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
   dependencies:
     ee-first "1.1.1"
 
-once@^1.3.0, once@^1.3.3, once@^1.4:
+once@^1.3.0, once@^1.3.3, once@^1.4, 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.0:
+optimist@>=0.3.4, optimist@^0.6.1, optimist@~0.6.0:
   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.2:
+optionator@^0.8.1, optionator@^0.8.2:
   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"
@@ -2861,16 +3530,22 @@ options@>=0.0.5:
 os-browserify@^0.2.0:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.2.1.tgz#63fc4ccee5d2d7763d26bbf8601078e6c2e0044f"
 
 os-homedir@^1.0.0, os-homedir@^1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
 
+os-locale@^1.4.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9"
+  dependencies:
+    lcid "^1.0.0"
+
 os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
 
 osenv@^0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.4.tgz#42fe6d5953df06c8064be6f176c3d05aaaa34644"
   dependencies:
@@ -2920,16 +3595,20 @@ parse-json@^2.1.0, parse-json@^2.2.0:
 
 parse-yarn-lock@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/parse-yarn-lock/-/parse-yarn-lock-1.0.2.tgz#39ef8b8f78bebdcad34bccd742d50ceed8cb25ec"
   dependencies:
     run-waterfall "^1.1.3"
     which "^1.2.12"
 
+parse5@^1.5.1:
+  version "1.5.1"
+  resolved "https://registry.yarnpkg.com/parse5/-/parse5-1.5.1.tgz#9b7f3b0de32be78dc2401b17573ccaf0f6f59d94"
+
 parseurl@~1.3.1:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.1.tgz#c8ab8c9223ba34888aa64a297b28853bec18da56"
 
 path-browserify@0.0.0:
   version "0.0.0"
   resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a"
 
@@ -2954,16 +3633,24 @@ path-is-inside@^1.0.1:
 path-parse@^1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1"
 
 path-to-regexp@0.1.7:
   version "0.1.7"
   resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
 
+path-type@^1.0.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
+  dependencies:
+    graceful-fs "^4.1.2"
+    pify "^2.0.0"
+    pinkie-promise "^2.0.0"
+
 pbkdf2-compat@2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/pbkdf2-compat/-/pbkdf2-compat-2.0.1.tgz#b6e0c8fa99494d94e0511575802a59a5c142f288"
 
 performance-now@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5"
 
@@ -3274,27 +3961,33 @@ prelude-ls@~1.1.2:
 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"
 
 preserve@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
 
+pretty-format@^19.0.0:
+  version "19.0.0"
+  resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-19.0.0.tgz#56530d32acb98a3fa4851c4e2b9d37b420684c84"
+  dependencies:
+    ansi-styles "^3.0.0"
+
 private@^0.1.6:
   version "0.1.7"
   resolved "https://registry.yarnpkg.com/private/-/private-0.1.7.tgz#68ce5e8a1ef0a23bb570cc28537b5332aba63ef1"
 
 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.9"
-  resolved "https://registry.yarnpkg.com/process/-/process-0.11.9.tgz#7bd5ad21aa6253e7da8682264f1e11d11c0318c1"
+  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"
@@ -3314,18 +4007,18 @@ proxy-addr@~1.1.3:
     forwarded "~0.1.0"
     ipaddr.js "1.3.0"
 
 prr@~0.0.0:
   version "0.0.0"
   resolved "https://registry.yarnpkg.com/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a"
 
 ps-node@^0.1.4:
-  version "0.1.5"
-  resolved "https://registry.yarnpkg.com/ps-node/-/ps-node-0.1.5.tgz#b51e5dd5650fe12ab4785d76ac4770dfbc56f986"
+  version "0.1.6"
+  resolved "https://registry.yarnpkg.com/ps-node/-/ps-node-0.1.6.tgz#9af67a99d7b1d0132e51a503099d38a8d2ace2c3"
   dependencies:
     table-parser "^0.1.3"
 
 punycode@1.3.2:
   version "1.3.2"
   resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
 
 punycode@^1.2.4, punycode@^1.4.1:
@@ -3336,18 +4029,18 @@ q@^1.1.2:
   version "1.5.0"
   resolved "https://registry.yarnpkg.com/q/-/q-1.5.0.tgz#dd01bac9d06d30e6f219aecb8253ee9ebdc308f1"
 
 qs@6.4.0, qs@~6.4.0:
   version "6.4.0"
   resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
 
 query-string@^4.1.0:
-  version "4.3.3"
-  resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.3.tgz#91c90ff7173d9acd9e088b3cc223f9b437865692"
+  version "4.3.4"
+  resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb"
   dependencies:
     object-assign "^4.1.0"
     strict-uri-encode "^1.0.0"
 
 querystring-es3@^0.2.0:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
 
@@ -3443,17 +4136,32 @@ react@=15.3.2:
 
 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"
 
-readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.1.4, readable-stream@^2.2.2, readable-stream@^2.2.6:
+read-pkg-up@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02"
+  dependencies:
+    find-up "^1.0.0"
+    read-pkg "^1.0.0"
+
+read-pkg@^1.0.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28"
+  dependencies:
+    load-json-file "^1.0.0"
+    normalize-package-data "^2.3.2"
+    path-type "^1.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:
   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"
@@ -3526,18 +4234,18 @@ redux@^3.6.0:
     loose-envify "^1.1.0"
     symbol-observable "^1.0.2"
 
 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.3"
-  resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.3.tgz#8c4367a904b51ea62a908ac310bf99ff90a82a3e"
+  version "0.10.4"
+  resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.4.tgz#74cb6598d3ba2eb18694e968a40e2b3b4df9cf93"
 
 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"
@@ -3588,17 +4296,17 @@ repeat-string@^1.5.2:
   resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
 
 repeating@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda"
   dependencies:
     is-finite "^1.0.0"
 
-request@^2.81.0:
+request@^2.79.0, request@^2.81.0:
   version "2.81.0"
   resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0"
   dependencies:
     aws-sign2 "~0.6.0"
     aws4 "^1.2.1"
     caseless "~0.12.0"
     combined-stream "~1.0.5"
     extend "~3.0.0"
@@ -3615,55 +4323,67 @@ request@^2.81.0:
     performance-now "^0.2.0"
     qs "~6.4.0"
     safe-buffer "^5.0.1"
     stringstream "~0.0.4"
     tough-cookie "~2.3.0"
     tunnel-agent "^0.6.0"
     uuid "^3.0.0"
 
+require-directory@^2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
+
 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:
-  version "1.3.2"
-  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.3.2.tgz#1f0442c9e0cbb8136e87b9305f932f46c7f28235"
+  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.5.1, rimraf@^2.5.4, rimraf@^2.6.1:
+rimraf@2, rimraf@^2.2.8, 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@0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-0.2.0.tgz#2bf198bde167cacfa51c0a928e84b68bbe171fce"
@@ -3685,30 +4405,42 @@ run-waterfall@^1.1.3:
 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"
 
-sax@>=0.6.0, sax@^1.1.4, sax@~1.2.1:
+sane@~1.5.0:
+  version "1.5.0"
+  resolved "https://registry.yarnpkg.com/sane/-/sane-1.5.0.tgz#a4adeae764d048621ecb27d5f9ecf513101939f3"
+  dependencies:
+    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:
   version "1.2.2"
   resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.2.tgz#fd8631a23bc7826bef5d871bdb87378c95647828"
 
-selenium-webdriver@^3.0.1:
+selenium-webdriver@=3.3.0:
   version "3.3.0"
   resolved "https://registry.yarnpkg.com/selenium-webdriver/-/selenium-webdriver-3.3.0.tgz#f14d9b04cee9495d4284d22105b189b8305ccca1"
   dependencies:
     adm-zip "^0.4.7"
     rimraf "^2.5.4"
     tmp "0.0.30"
     xml2js "^0.4.17"
 
-semver@^5.0.3, semver@^5.3.0:
+"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.3.0:
   version "5.3.0"
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
 
 send@0.15.1:
   version "0.15.1"
   resolved "https://registry.yarnpkg.com/send/-/send-0.15.1.tgz#8a02354c26e6f5cca700065f5f0cdeba90ec7b5f"
   dependencies:
     debug "2.6.1"
@@ -3729,17 +4461,17 @@ serve-static@1.12.1:
   version "1.12.1"
   resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.12.1.tgz#7443a965e3ced647aceb5639fa06bf4d1bbe0039"
   dependencies:
     encodeurl "~1.0.1"
     escape-html "~1.0.3"
     parseurl "~1.3.1"
     send "0.15.1"
 
-set-blocking@~2.0.0:
+set-blocking@^2.0.0, set-blocking@~2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
 
 set-immediate-shim@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61"
 
 setimmediate@^1.0.4, setimmediate@^1.0.5:
@@ -3757,16 +4489,20 @@ sha.js@2.2.6:
 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"
 
@@ -3805,16 +4541,36 @@ source-map@^0.4.4, source-map@~0.4.1:
   resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b"
   dependencies:
     amdefine ">=0.0.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.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"
+  resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40"
+  dependencies:
+    spdx-license-ids "^1.0.2"
+
+spdx-expression-parse@~1.0.0:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c"
+
+spdx-license-ids@^1.0.2:
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57"
+
 sprintf-js@~1.0.2:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
 
 sshpk@^1.7.0:
   version "1.13.0"
   resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.0.tgz#ff2a3e4fd04497555fed97b39a0fd82fafb3a33c"
   dependencies:
@@ -3849,17 +4605,23 @@ stream-http@^2.3.1:
     readable-stream "^2.2.6"
     to-arraybuffer "^1.0.0"
     xtend "^4.0.0"
 
 strict-uri-encode@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
 
-string-width@^1.0.1:
+string-length@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/string-length/-/string-length-1.0.1.tgz#56970fb1c38558e9e70b728bf3de269ac45adfac"
+  dependencies:
+    strip-ansi "^3.0.0"
+
+string-width@^1.0.1, string-width@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
   dependencies:
     code-point-at "^1.0.0"
     is-fullwidth-code-point "^1.0.0"
     strip-ansi "^3.0.0"
 
 string-width@^2.0.0:
@@ -3888,35 +4650,41 @@ stringstream@~0.0.4:
   resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878"
 
 strip-ansi@^3.0.0, strip-ansi@^3.0.1:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
   dependencies:
     ansi-regex "^2.0.0"
 
-strip-bom@^3.0.0:
+strip-bom@3.0.0, strip-bom@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
 
+strip-bom@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e"
+  dependencies:
+    is-utf8 "^0.2.0"
+
 strip-json-comments@^2.0.1, strip-json-comments@~2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
 
 style-loader@^0.13.1:
   version "0.13.2"
   resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.13.2.tgz#74533384cf698c7104c7951150b49717adc2f3bb"
   dependencies:
     loader-utils "^1.0.2"
 
 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.2.3:
+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"
 
 svg-inline-loader@^0.7.1:
   version "0.7.1"
   resolved "https://registry.yarnpkg.com/svg-inline-loader/-/svg-inline-loader-0.7.1.tgz#6d0e2728b7ec3414c2180b3f780bc3f7154ef226"
@@ -3940,16 +4708,20 @@ svgo@^0.7.0:
 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:
   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"
@@ -3992,20 +4764,34 @@ tar.gz@1.0.5:
 tar@^2.1.1, tar@^2.2.1:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1"
   dependencies:
     block-stream "*"
     fstream "^1.0.2"
     inherits "2"
 
+test-exclude@^4.0.3:
+  version "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"
 
 timed-out@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-2.0.0.tgz#f38b0ae81d3747d628001f41dafc652ace671c0a"
 
@@ -4016,30 +4802,38 @@ timers-browserify@^2.0.2:
     setimmediate "^1.0.4"
 
 tmp@0.0.30:
   version "0.0.30"
   resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.30.tgz#72419d4a8be7d6ce75148fd8b324e593a711c2ed"
   dependencies:
     os-tmpdir "~1.0.1"
 
+tmpl@1.0.x:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1"
+
 to-arraybuffer@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
 
 to-fast-properties@^1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.2.tgz#f3f5c0c3ba7299a7ef99427e44633257ade43320"
 
-tough-cookie@~2.3.0:
+tough-cookie@^2.3.2, tough-cookie@~2.3.0:
   version "2.3.2"
   resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.2.tgz#f081f76e4c85720e6c37a5faced737150d84072a"
   dependencies:
     punycode "^1.4.1"
 
+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"
 
@@ -4073,17 +4867,17 @@ type-is@~1.6.14:
 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.7.3:
+uglify-js@^2.6, uglify-js@~2.7.3:
   version "2.7.5"
   resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.7.5.tgz#4612c0c7baaee2ba7c487de4904ae122079f2ca8"
   dependencies:
     async "~0.2.6"
     source-map "~0.5.1"
     uglify-to-browserify "~1.0.0"
     yargs "~3.10.0"
 
@@ -4164,21 +4958,28 @@ utils-merge@1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.0.tgz#0294fb922bb9375153541c4f7096231f287c8af8"
 
 uuid@^3.0.0:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1"
 
 v8flags@^2.0.10:
-  version "2.0.12"
-  resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-2.0.12.tgz#73235d9f7176f8e8833fb286795445f7938d84e5"
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-2.1.1.tgz#aab1a1fa30d45f88dd321148875ac02c0b55e5b4"
   dependencies:
     user-home "^1.1.1"
 
+validate-npm-package-license@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc"
+  dependencies:
+    spdx-correct "~1.0.0"
+    spdx-expression-parse "~1.0.0"
+
 vary@~1.1.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.1.tgz#67535ebb694c1d52257457984665323f587e8d37"
 
 vendors@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.1.tgz#37ad73c8ee417fb3d580e785312307d274847f22"
 
@@ -4189,34 +4990,52 @@ verror@1.3.6:
     extsprintf "1.0.2"
 
 vm-browserify@0.0.4:
   version "0.0.4"
   resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-0.0.4.tgz#5d7ea45bbef9e4a6ff65f95438e0a87c357d5a73"
   dependencies:
     indexof "0.0.1"
 
+walker@~1.0.5:
+  version "1.0.7"
+  resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb"
+  dependencies:
+    makeerror "1.0.x"
+
+watch@~0.10.0:
+  version "0.10.0"
+  resolved "https://registry.yarnpkg.com/watch/-/watch-0.10.0.tgz#77798b2da0f9910d595f1ace5b0c2258521f21dc"
+
 watchpack@^0.2.1:
   version "0.2.9"
   resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-0.2.9.tgz#62eaa4ab5e5ba35fdfc018275626e3c0f5e3fb0b"
   dependencies:
     async "^0.9.0"
     chokidar "^1.0.0"
     graceful-fs "^4.1.2"
 
+webidl-conversions@^3.0.0:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
+
+webidl-conversions@^4.0.0:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.1.tgz#8015a17ab83e7e1b311638486ace81da6ce206a0"
+
 webpack-core@~0.6.9:
   version "0.6.9"
   resolved "https://registry.yarnpkg.com/webpack-core/-/webpack-core-0.6.9.tgz#fc571588c8558da77be9efb6debdc5a3b172bdc2"
   dependencies:
     source-list-map "~0.1.7"
     source-map "~0.4.1"
 
 webpack-dev-middleware@^1.6.1:
-  version "1.10.1"
-  resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.10.1.tgz#c6b4cf428139cf1aefbe06a0c00fdb4f8da2f893"
+  version "1.10.2"
+  resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.10.2.tgz#2e252ce1dfb020dbda1ccb37df26f30ab014dbd1"
   dependencies:
     memory-fs "~0.4.1"
     mime "^1.3.4"
     path-is-absolute "^1.0.0"
     range-parser "^1.0.3"
 
 webpack-env-loader-plugin@^0.1.4:
   version "0.1.4"
@@ -4259,25 +5078,42 @@ webpack@1.14.0, webpack@^1.12.14:
     node-libs-browser "^0.7.0"
     optimist "~0.6.0"
     supports-color "^3.1.0"
     tapable "~0.1.8"
     uglify-js "~2.7.3"
     watchpack "^0.2.1"
     webpack-core "~0.6.9"
 
+whatwg-encoding@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.1.tgz#3c6c451a198ee7aec55b1ec61d0920c67801a5f4"
+  dependencies:
+    iconv-lite "0.4.13"
+
 whatwg-fetch@>=0.10.0:
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84"
 
+whatwg-url@^4.3.0:
+  version "4.7.1"
+  resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-4.7.1.tgz#df4dc2e3f25a63b1fa5b32ed6d6c139577d690de"
+  dependencies:
+    tr46 "~0.0.3"
+    webidl-conversions "^3.0.0"
+
 whet.extend@~0.9.9:
   version "0.9.9"
   resolved "https://registry.yarnpkg.com/whet.extend/-/whet.extend-0.9.9.tgz#f877d5bf648c97e5aa542fadc16d6a259b9c11a1"
 
-which@^1.2.12:
+which-module@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f"
+
+which@^1.1.1, which@^1.2.12:
   version "1.2.14"
   resolved "https://registry.yarnpkg.com/which/-/which-1.2.14.tgz#9a87c4378f03e827cecaf1acdf56c736c01c14e5"
   dependencies:
     isexe "^2.0.0"
 
 wide-align@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.0.tgz#40edde802a71fea1f070da3e62dcda2e7add96ad"
@@ -4295,16 +5131,30 @@ wordwrap@0.0.2:
 wordwrap@~0.0.2:
   version "0.0.3"
   resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
 
 wordwrap@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
 
+worker-farm@^1.3.1:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.3.1.tgz#4333112bb49b17aa050b87895ca6b2cacf40e5ff"
+  dependencies:
+    errno ">=0.1.1 <0.2.0-0"
+    xtend ">=4.0.0 <4.1.0-0"
+
+wrap-ansi@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"
+  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:
@@ -4312,16 +5162,20 @@ write@^0.2.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:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635"
+
 xml2js@^0.4.17:
   version "0.4.17"
   resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.17.tgz#17be93eaae3f3b779359c795b419705a8817e868"
   dependencies:
     sax ">=0.6.0"
     xmlbuilder "^4.1.0"
 
 xmlbuilder@^4.1.0:
@@ -4329,35 +5183,63 @@ xmlbuilder@^4.1.0:
   resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-4.2.1.tgz#aa58a3041a066f90eaa16c2f5389ff19f3f461a5"
   dependencies:
     lodash "^4.0.0"
 
 xmlhttprequest@*:
   version "1.8.0"
   resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc"
 
-xtend@^4.0.0, xtend@~4.0.0:
+"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@~4.0.0:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
 
 xtend@~3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/xtend/-/xtend-3.0.0.tgz#5cce7407baf642cba7becda568111c493f59665a"
 
+y18n@^3.2.1:
+  version "3.2.1"
+  resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
+
 yaml@^0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/yaml/-/yaml-0.3.0.tgz#c31a616d07acdbc2012d73a6ba5b1b0bdd185a7f"
 
 yamljs@^0.2.6:
-  version "0.2.9"
-  resolved "https://registry.yarnpkg.com/yamljs/-/yamljs-0.2.9.tgz#bd3bdaa62ac09deb2a2e1ce803eeb4217b52a82f"
+  version "0.2.10"
+  resolved "https://registry.yarnpkg.com/yamljs/-/yamljs-0.2.10.tgz#481cc7c25ca73af59f591f0c96e3ce56c757a40f"
   dependencies:
     argparse "^1.0.7"
     glob "^7.0.5"
 
+yargs-parser@^4.2.0:
+  version "4.2.1"
+  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-4.2.1.tgz#29cceac0dc4f03c6c87b4a9f217dd18c9f74871c"
+  dependencies:
+    camelcase "^3.0.0"
+
+yargs@^6.3.0:
+  version "6.6.0"
+  resolved "https://registry.yarnpkg.com/yargs/-/yargs-6.6.0.tgz#782ec21ef403345f830a808ca3d513af56065208"
+  dependencies:
+    camelcase "^3.0.0"
+    cliui "^3.2.0"
+    decamelize "^1.1.1"
+    get-caller-file "^1.0.1"
+    os-locale "^1.4.0"
+    read-pkg-up "^1.0.1"
+    require-directory "^2.1.1"
+    require-main-filename "^1.0.1"
+    set-blocking "^2.0.0"
+    string-width "^1.0.2"
+    which-module "^1.0.0"
+    y18n "^3.2.1"
+    yargs-parser "^4.2.0"
+
 yargs@~3.10.0:
   version "3.10.0"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1"
   dependencies:
     camelcase "^1.0.2"
     cliui "^2.1.0"
     decamelize "^1.0.0"
     window-size "0.1.0"
--- a/devtools/client/preferences/devtools.js
+++ b/devtools/client/preferences/devtools.js
@@ -145,17 +145,17 @@ pref("devtools.serviceWorkers.testing.en
 
 // Enable the Network Monitor
 pref("devtools.netmonitor.enabled", true);
 
 // The default Network Monitor UI settings
 pref("devtools.netmonitor.panes-network-details-width", 550);
 pref("devtools.netmonitor.panes-network-details-height", 450);
 pref("devtools.netmonitor.filters", "[\"all\"]");
-pref("devtools.netmonitor.hiddenColumns", "[\"remoteip\",\"protocol\"]");
+pref("devtools.netmonitor.hiddenColumns", "[\"cookies\",\"protocol\",\"remoteip\",\"setCookies\"]");
 
 // The default Network monitor HAR export setting
 pref("devtools.netmonitor.har.defaultLogDir", "");
 pref("devtools.netmonitor.har.defaultFileName", "Archive %date");
 pref("devtools.netmonitor.har.jsonp", false);
 pref("devtools.netmonitor.har.jsonpCallback", "");
 pref("devtools.netmonitor.har.includeResponseBodies", true);
 pref("devtools.netmonitor.har.compress", false);
--- a/devtools/client/webconsole/new-console-output/components/message-types/evaluation-result.js
+++ b/devtools/client/webconsole/new-console-output/components/message-types/evaluation-result.js
@@ -36,17 +36,24 @@ function EvaluationResult(props) {
     frame,
     timeStamp,
     parameters,
     notes,
   } = message;
 
   let messageBody;
   if (message.messageText) {
-    messageBody = message.messageText;
+    if (typeof message.messageText === "string") {
+      messageBody = message.messageText;
+    } else if (
+      typeof message.messageText === "object"
+      && message.messageText.type === "longString"
+    ) {
+      messageBody = `${message.messageText.initial}…`;
+    }
   } else {
     messageBody = GripMessageBody({
       grip: parameters,
       serviceContainer,
       useQuotes: true,
       escapeWhitespace: false,
     });
   }
--- a/devtools/client/webconsole/new-console-output/components/message-types/page-error.js
+++ b/devtools/client/webconsole/new-console-output/components/message-types/page-error.js
@@ -34,25 +34,32 @@ function PageError(props) {
     serviceContainer,
     indent,
   } = props;
   const {
     id: messageId,
     source,
     type,
     level,
-    messageText: messageBody,
+    messageText,
     repeat,
     stacktrace,
     frame,
     exceptionDocURL,
     timeStamp,
     notes,
   } = message;
 
+  let messageBody;
+  if (typeof messageText === "string") {
+    messageBody = messageText;
+  } else if (typeof messageText === "object" && messageText.type === "longString") {
+    messageBody = `${message.messageText.initial}…`;
+  }
+
   const childProps = {
     dispatch,
     messageId,
     open,
     collapsible: Array.isArray(stacktrace),
     source,
     type,
     level,
--- a/devtools/client/webconsole/new-console-output/test/components/evaluation-result.test.js
+++ b/devtools/client/webconsole/new-console-output/test/components/evaluation-result.test.js
@@ -35,16 +35,25 @@ describe("EvaluationResult component:", 
     const wrapper = render(EvaluationResult({ message }));
 
     expect(wrapper.find(".message-body").text())
       .toBe("ReferenceError: asdf is not defined[Learn More]");
 
     expect(wrapper.find(".message.error").length).toBe(1);
   });
 
+  it("renders an error with a longString exception message", () => {
+    const message = stubPreparedMessages.get("longString message Error");
+    const wrapper = render(EvaluationResult({ message }));
+
+    const text = wrapper.find(".message-body").text();
+    expect(text.startsWith("Error: Long error Long error")).toBe(true);
+    expect(wrapper.find(".message.error").length).toBe(1);
+  });
+
   it("displays a [Learn more] link", () => {
     const store = setupStore([]);
 
     const message = stubPreparedMessages.get("asdf()");
 
     serviceContainer.openLink = sinon.spy();
     const wrapper = mount(Provider({store},
       EvaluationResult({message, serviceContainer})
--- a/devtools/client/webconsole/new-console-output/test/components/page-error.test.js
+++ b/devtools/client/webconsole/new-console-output/test/components/page-error.test.js
@@ -42,16 +42,24 @@ describe("PageError component:", () => {
 
     // There should be the location.
     const locationLink = wrapper.find(`.message-location`);
     expect(locationLink.length).toBe(1);
     // @TODO Will likely change. See bug 1307952
     expect(locationLink.text()).toBe("test-console-api.html:3:5");
   });
 
+  it("renders an error with a longString exception message", () => {
+    const message = stubPreparedMessages.get("TypeError longString message");
+    const wrapper = render(PageError({ message, serviceContainer }));
+
+    const text = wrapper.find(".message-body").text();
+    expect(text.startsWith("Error: Long error Long error")).toBe(true);
+  });
+
   it("displays a [Learn more] link", () => {
     const store = setupStore([]);
 
     const message = stubPreparedMessages.get("ReferenceError: asdf is not defined");
 
     serviceContainer.openLink = sinon.spy();
     const wrapper = mount(Provider({store},
       PageError({message, serviceContainer})
--- a/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/head.js
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/head.js
@@ -117,29 +117,52 @@ function getCleanedPacket(key, packet) {
     if (res.exception) {
       // Clean actor ids on exception messages.
       res.exception.actor = existingPacket.exception.actor;
       if (res.exception.preview) {
         if (res.exception.preview.timestamp) {
           // Clean timestamp there too.
           res.exception.preview.timestamp = existingPacket.exception.preview.timestamp;
         }
+
+        if (
+          typeof res.exception.preview.message === "object"
+          && res.exception.preview.message.type === "longString"
+        ) {
+          res.exception.preview.message.actor =
+            existingPacket.exception.preview.message.actor;
+        }
+      }
+
+      if (
+        typeof res.exceptionMessage === "object"
+        && res.exceptionMessage.type === "longString"
+      ) {
+        res.exceptionMessage.actor =
+          existingPacket.exceptionMessage.actor;
       }
     }
 
     if (res.eventActor) {
       // Clean actor ids, timeStamp and startedDateTime on network messages.
       res.eventActor.actor = existingPacket.eventActor.actor;
       res.eventActor.startedDateTime = existingPacket.eventActor.startedDateTime;
       res.eventActor.timeStamp = existingPacket.eventActor.timeStamp;
     }
 
     if (res.pageError) {
       // Clean timeStamp on pageError messages.
       res.pageError.timeStamp = existingPacket.pageError.timeStamp;
+
+      if (
+        typeof res.pageError.errorMessage === "object"
+        && res.pageError.errorMessage.type === "longString"
+      ) {
+        res.pageError.errorMessage.actor = existingPacket.pageError.errorMessage.actor;
+      }
     }
 
     if (res.packet) {
       if (res.packet.totalTime) {
         // res.packet.totalTime is read-only so we use assign to override it.
         res.packet = Object.assign({}, existingPacket.packet, {
           totalTime: existingPacket.packet.totalTime
         });
@@ -327,17 +350,17 @@ function* generateEvaluationResultStubs(
 
   let stubs = {
     preparedMessages: [],
     packets: [],
   };
 
   let toolbox = yield openNewTabAndToolbox(TEST_URI, "webconsole");
 
-  for (let [code, key] of evaluationResult) {
+  for (let [key, code] of evaluationResult) {
     const packet = yield new Promise(resolve => {
       toolbox.target.activeConsole.evaluateJS(code, resolve);
     });
     stubs.packets.push(formatPacket(key, packet));
     stubs.preparedMessages.push(formatStub(key, packet));
   }
 
   yield closeTabAndToolbox();
@@ -409,19 +432,18 @@ function* generatePageErrorStubs() {
   };
 
   let toolbox = yield openNewTabAndToolbox(TEST_URI, "webconsole");
 
   for (let [key, code] of pageError) {
     let received = new Promise(resolve => {
       toolbox.target.client.addListener("pageError", function onPacket(e, packet) {
         toolbox.target.client.removeListener("pageError", onPacket);
-        let message = prepareMessage(packet, {getNextId: () => 1});
-        stubs.packets.push(formatPacket(message.messageText, packet));
-        stubs.preparedMessages.push(formatStub(message.messageText, packet));
+        stubs.packets.push(formatPacket(key, packet));
+        stubs.preparedMessages.push(formatStub(key, packet));
         resolve();
       });
     });
 
     // On e10s, the exception is triggered in child process
     // and is ignored by test harness
     // expectUncaughtException should be called for each uncaught exception.
     if (!Services.appinfo.browserTabsRemoteAutostart) {
--- a/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/stub-snippets.js
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/stub-snippets.js
@@ -123,16 +123,18 @@ p {
 // Evaluation Result
 const evaluationResultCommands = [
   "new Date(0)",
   "asdf()",
   "1 + @"
 ];
 
 let evaluationResult = new Map(evaluationResultCommands.map(cmd => [cmd, cmd]));
+evaluationResult.set("longString message Error",
+  `throw new Error("Long error ".repeat(10000))`);
 
 // Network Event
 
 let networkEvent = new Map();
 
 networkEvent.set("GET request", {
   keys: ["GET request"],
   code: `
@@ -155,30 +157,33 @@ const xhr = new XMLHttpRequest();
 xhr.open("POST", "inexistent.html");
 xhr.send();
 `});
 
 // Page Error
 
 let pageError = new Map();
 
-pageError.set("Reference Error", `
+pageError.set("ReferenceError: asdf is not defined", `
   function bar() {
     asdf()
   }
   function foo() {
     bar()
   }
 
   foo()
 `);
 
-pageError.set("Redeclaration Error", `
+pageError.set("SyntaxError: redeclaration of let a", `
   let a, a;
 `);
 
+pageError.set("TypeError longString message",
+  `throw new Error("Long error ".repeat(10000))`);
+
 module.exports = {
   consoleApi,
   cssMessage,
   evaluationResult,
   networkEvent,
   pageError,
 };
--- a/devtools/client/webconsole/new-console-output/test/fixtures/stubs/evaluationResult.js
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stubs/evaluationResult.js
@@ -85,16 +85,41 @@ stubPreparedMessages.set("1 + @", new Co
     "line": 1,
     "column": 4
   },
   "groupId": null,
   "userProvidedStyles": null,
   "notes": null
 }));
 
+stubPreparedMessages.set("longString message Error", new ConsoleMessage({
+  "id": "1",
+  "allowRepeating": true,
+  "source": "javascript",
+  "timeStamp": 1493108241073,
+  "type": "result",
+  "level": "error",
+  "messageText": {
+    "type": "longString",
+    "initial": "Error: Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Lon",
+    "length": 110007,
+    "actor": "server1.conn0.child1/longString37"
+  },
+  "parameters": {
+    "type": "undefined"
+  },
+  "repeat": 1,
+  "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"javascript\",\"timeStamp\":1493108241073,\"type\":\"result\",\"level\":\"error\",\"messageText\":{\"type\":\"longString\",\"initial\":\"Error: Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Lon\",\"length\":110007,\"actor\":\"server1.conn0.child1/longString37\"},\"parameters\":{\"type\":\"undefined\"},\"repeatId\":null,\"stacktrace\":null,\"frame\":null,\"groupId\":null,\"userProvidedStyles\":null,\"notes\":null}",
+  "stacktrace": null,
+  "frame": null,
+  "groupId": null,
+  "userProvidedStyles": null,
+  "notes": null
+}));
+
 stubPackets.set("new Date(0)", {
   "from": "server1.conn0.child1/consoleActor2",
   "input": "new Date(0)",
   "result": {
     "type": "object",
     "actor": "server1.conn0.child1/obj30",
     "class": "Date",
     "extensible": true,
@@ -178,12 +203,53 @@ stubPackets.set("1 + @", {
     "source": "debugger eval code",
     "line": 1,
     "column": 4
   },
   "helperResult": null,
   "notes": null
 });
 
+stubPackets.set("longString message Error", {
+  "from": "server1.conn0.child1/consoleActor2",
+  "input": "throw new Error(\"Long error \".repeat(10000))",
+  "result": {
+    "type": "undefined"
+  },
+  "timestamp": 1493108241073,
+  "exception": {
+    "type": "object",
+    "actor": "server1.conn0.child1/obj35",
+    "class": "Error",
+    "extensible": true,
+    "frozen": false,
+    "sealed": false,
+    "ownPropertyLength": 4,
+    "preview": {
+      "kind": "Error",
+      "name": "Error",
+      "message": {
+        "type": "longString",
+        "initial": "Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error",
+        "length": 110000,
+        "actor": "server1.conn0.child1/longString36"
+      },
+      "stack": "@debugger eval code:1:7\n",
+      "fileName": "debugger eval code",
+      "lineNumber": 1,
+      "columnNumber": 7
+    }
+  },
+  "exceptionMessage": {
+    "type": "longString",
+    "initial": "Error: Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Lon",
+    "length": 110007,
+    "actor": "server1.conn0.child1/longString37"
+  },
+  "frame": null,
+  "helperResult": null,
+  "notes": null
+});
+
 module.exports = {
   stubPreparedMessages,
   stubPackets,
 };
--- a/devtools/client/webconsole/new-console-output/test/fixtures/stubs/pageError.js
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stubs/pageError.js
@@ -106,16 +106,62 @@ stubPreparedMessages.set("SyntaxError: r
         "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
         "line": 2,
         "column": 6
       }
     }
   ]
 }));
 
+stubPreparedMessages.set("TypeError longString message", new ConsoleMessage({
+  "id": "1",
+  "allowRepeating": true,
+  "source": "javascript",
+  "timeStamp": 1493109507061,
+  "type": "log",
+  "level": "error",
+  "messageText": {
+    "type": "longString",
+    "initial": "Error: Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Lon",
+    "length": 110007,
+    "actor": "server1.conn0.child1/longString30"
+  },
+  "parameters": null,
+  "repeat": 1,
+  "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"javascript\",\"timeStamp\":1493109507061,\"type\":\"log\",\"level\":\"error\",\"messageText\":{\"type\":\"longString\",\"initial\":\"Error: Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Lon\",\"length\":110007,\"actor\":\"server1.conn0.child1/longString30\"},\"parameters\":null,\"repeatId\":null,\"stacktrace\":[{\"filename\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html\",\"lineNumber\":1,\"columnNumber\":7,\"functionName\":null},{\"filename\":\"resource://testing-common/content-task.js line 52 > eval\",\"lineNumber\":6,\"columnNumber\":9,\"functionName\":null},{\"filename\":\"resource://testing-common/content-task.js\",\"lineNumber\":53,\"columnNumber\":20,\"functionName\":null}],\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html\",\"line\":1,\"column\":7},\"groupId\":null,\"userProvidedStyles\":null,\"notes\":null}",
+  "stacktrace": [
+    {
+      "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
+      "lineNumber": 1,
+      "columnNumber": 7,
+      "functionName": null
+    },
+    {
+      "filename": "resource://testing-common/content-task.js line 52 > eval",
+      "lineNumber": 6,
+      "columnNumber": 9,
+      "functionName": null
+    },
+    {
+      "filename": "resource://testing-common/content-task.js",
+      "lineNumber": 53,
+      "columnNumber": 20,
+      "functionName": null
+    }
+  ],
+  "frame": {
+    "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
+    "line": 1,
+    "column": 7
+  },
+  "groupId": null,
+  "userProvidedStyles": null,
+  "notes": null
+}));
+
 stubPackets.set("ReferenceError: asdf is not defined", {
   "from": "server1.conn0.child1/consoleActor2",
   "type": "pageError",
   "pageError": {
     "errorMessage": "ReferenceError: asdf is not defined",
     "errorMessageName": "JSMSG_NOT_DEFINED",
     "exceptionDocURL": "https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Not_defined?utm_source=mozilla&utm_medium=firefox-console-errors&utm_campaign=default",
     "sourceName": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
@@ -206,12 +252,59 @@ stubPackets.set("SyntaxError: redeclarat
           "line": 2,
           "column": 6
         }
       }
     ]
   }
 });
 
+stubPackets.set("TypeError longString message", {
+  "from": "server1.conn0.child1/consoleActor2",
+  "type": "pageError",
+  "pageError": {
+    "errorMessage": {
+      "type": "longString",
+      "initial": "Error: Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Long error Lon",
+      "length": 110007,
+      "actor": "server1.conn0.child1/longString30"
+    },
+    "errorMessageName": "",
+    "sourceName": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
+    "lineText": "",
+    "lineNumber": 1,
+    "columnNumber": 7,
+    "category": "content javascript",
+    "timeStamp": 1493109507061,
+    "warning": false,
+    "error": false,
+    "exception": true,
+    "strict": false,
+    "info": false,
+    "private": false,
+    "stacktrace": [
+      {
+        "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
+        "lineNumber": 1,
+        "columnNumber": 7,
+        "functionName": null
+      },
+      {
+        "filename": "resource://testing-common/content-task.js line 52 > eval",
+        "lineNumber": 6,
+        "columnNumber": 9,
+        "functionName": null
+      },
+      {
+        "filename": "resource://testing-common/content-task.js",
+        "lineNumber": 53,
+        "columnNumber": 20,
+        "functionName": null
+      }
+    ],
+    "notes": null
+  }
+});
+
 module.exports = {
   stubPreparedMessages,
   stubPackets,
 };
--- a/dom/animation/test/crashtests/1239889-1.html
+++ b/dom/animation/test/crashtests/1239889-1.html
@@ -1,12 +1,16 @@
 <!doctype html>
-<html>
+<html class="reftest-wait">
   <head>
     <title>Bug 1239889</title>
   </head>
   <body>
   </body>
   <script>
     var div = document.createElement('div');
     var effect = new KeyframeEffectReadOnly(div, { opacity: [0, 1] });
+    requestAnimationFrame(() => {
+      document.body.appendChild(div);
+      document.documentElement.classList.remove("reftest-wait");
+    });
   </script>
 </html>
--- a/dom/base/nsPlainTextSerializer.cpp
+++ b/dom/base/nsPlainTextSerializer.cpp
@@ -50,16 +50,19 @@ static const  char16_t kSPACE = ' ';
 static int32_t HeaderLevel(nsIAtom* aTag);
 static int32_t GetUnicharWidth(char16_t ucs);
 static int32_t GetUnicharStringWidth(const char16_t* pwcs, int32_t n);
 
 // Someday may want to make this non-const:
 static const uint32_t TagStackSize = 500;
 static const uint32_t OLStackSize = 100;
 
+static bool gPreferenceInitialized = false;
+static bool gAlwaysIncludeRuby = false;
+
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsPlainTextSerializer)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsPlainTextSerializer)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsPlainTextSerializer)
   NS_INTERFACE_MAP_ENTRY(nsIContentSerializer)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
@@ -113,16 +116,22 @@ nsPlainTextSerializer::nsPlainTextSerial
 
   // initialize the OL stack, where numbers for ordered lists are kept
   mOLStack = new int32_t[OLStackSize];
   mOLStackIndex = 0;
 
   mULCount = 0;
 
   mIgnoredChildNodeLevel = 0;
+
+  if (!gPreferenceInitialized) {
+    Preferences::AddBoolVarCache(&gAlwaysIncludeRuby, PREF_ALWAYS_INCLUDE_RUBY,
+                                 true);
+    gPreferenceInitialized = true;
+  }
 }
 
 nsPlainTextSerializer::~nsPlainTextSerializer()
 {
   delete[] mTagStack;
   delete[] mOLStack;
   NS_WARNING_ASSERTION(mHeadLevel == 0, "Wrong head level!");
 }
@@ -185,17 +194,17 @@ nsPlainTextSerializer::Init(uint32_t aFl
     mHeaderStrategy =
       Preferences::GetInt(PREF_HEADER_STRATEGY, mHeaderStrategy);
   }
 
   // The pref is default inited to false in libpref, but we use true
   // as fallback value because we don't want to affect behavior in
   // other places which use this serializer currently.
   mWithRubyAnnotation =
-    Preferences::GetBool(PREF_ALWAYS_INCLUDE_RUBY, true) ||
+    gAlwaysIncludeRuby ||
     (mFlags & nsIDocumentEncoder::OutputRubyAnnotation);
 
   // XXX We should let the caller decide whether to do this or not
   mFlags &= ~nsIDocumentEncoder::OutputNoFramesContent;
 
   return NS_OK;
 }
 
--- a/dom/html/test/test_filepicker_default_directory.html
+++ b/dom/html/test/test_filepicker_default_directory.html
@@ -16,31 +16,26 @@ https://bugzilla.mozilla.org/show_bug.cg
 </div>
 <pre id="text">
 <script class="testbody" type="application/javascript">
 
 SimpleTest.waitForExplicitFinish();
 const { Cc: Cc, Ci: Ci } = SpecialPowers;
 
 // Platform-independent directory names are #define'd in xpcom/io/nsDirectoryServiceDefs.h
-var defaultUploadDirectory = Cc["@mozilla.org/file/directory_service;1"]
-                            .getService(Ci.nsIDirectoryService)
-                            .QueryInterface(Ci.nsIProperties)
-                            .get("Desk", Ci.nsIFile);
 
 // When we want to test an upload directory other than the default, we need to
 // get a valid directory in a platform-independent way. Since NS_OS_DESKTOP_DIR
 // may fallback to NS_OS_HOME_DIR, let's use NS_OS_TMP_DIR.
 var customUploadDirectory = Cc["@mozilla.org/file/directory_service;1"]
                             .getService(Ci.nsIDirectoryService)
                             .QueryInterface(Ci.nsIProperties)
                             .get("TmpD", Ci.nsIFile);
 
 // Useful for debugging
-//info("defaultUploadDirectory" + defaultUploadDirectory.path);
 //info("customUploadDirectory" + customUploadDirectory.path);
 
 var MockFilePicker = SpecialPowers.MockFilePicker;
 MockFilePicker.init(window);
 
 // need to show the MockFilePicker so .displayDirectory gets set
 var f = document.getElementById("f");
 f.focus();
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -2036,19 +2036,26 @@ StateObject::HandleResumeVideoDecoding(c
 
   // Local reference to mInfo, so that it will be copied in the lambda below.
   auto& info = Info();
   bool hw = Reader()->VideoIsHardwareAccelerated();
 
   // Start video-only seek to the current time.
   SeekJob seekJob;
 
-  const SeekTarget::Type type = mMaster->HasAudio()
-                                ? SeekTarget::Type::Accurate
-                                : SeekTarget::Type::PrevSyncPoint;
+  // We use fastseek to optimize the resuming time.
+  // FastSeek is only used for video-only media since we don't need to worry
+  // about A/V sync.
+  // Don't use fastSeek if we want to seek to the end because it might seek to a
+  // keyframe before the last frame (if the last frame itself is not a keyframe)
+  // and we always want to present the final frame to the user when seeking to
+  // the end.
+  const auto type = mMaster->HasAudio() || aTarget == mMaster->Duration()
+                    ? SeekTarget::Type::Accurate
+                    : SeekTarget::Type::PrevSyncPoint;
 
   seekJob.mTarget.emplace(aTarget, type, true /* aVideoOnly */);
 
   // Hold mMaster->mAbstractMainThread here because this->mMaster will be
   // invalid after the current state object is deleted in SetState();
   RefPtr<AbstractThread> mainThread = mMaster->mAbstractMainThread;
 
   SetSeekingState(Move(seekJob), EventVisibility::Suppressed)->Then(
--- a/dom/media/gmp/ChromiumCDMChild.cpp
+++ b/dom/media/gmp/ChromiumCDMChild.cpp
@@ -366,16 +366,32 @@ ChromiumCDMChild::~ChromiumCDMChild()
 }
 
 bool
 ChromiumCDMChild::IsOnMessageLoopThread()
 {
   return mPlugin && mPlugin->GMPMessageLoop() == MessageLoop::current();
 }
 
+void
+ChromiumCDMChild::PurgeShmems()
+{
+  for (ipc::Shmem& shmem : mBuffers) {
+    DeallocShmem(shmem);
+  }
+  mBuffers.Clear();
+}
+
+ipc::IPCResult
+ChromiumCDMChild::RecvPurgeShmems()
+{
+  PurgeShmems();
+  return IPC_OK();
+}
+
 mozilla::ipc::IPCResult
 ChromiumCDMChild::RecvInit(const bool& aAllowDistinctiveIdentifier,
                            const bool& aAllowPersistentState)
 {
   MOZ_ASSERT(IsOnMessageLoopThread());
   GMP_LOG("ChromiumCDMChild::RecvInit(distinctiveId=%d, persistentState=%d)",
           aAllowDistinctiveIdentifier,
           aAllowPersistentState);
@@ -634,20 +650,17 @@ ChromiumCDMChild::RecvDeinitializeVideoD
 {
   MOZ_ASSERT(IsOnMessageLoopThread());
   GMP_LOG("ChromiumCDMChild::RecvDeinitializeVideoDecoder()");
   MOZ_ASSERT(mDecoderInitialized);
   if (mDecoderInitialized) {
     mDecoderInitialized = false;
     mCDM->DeinitializeDecoder(cdm::kStreamTypeVideo);
   }
-  for (ipc::Shmem& shmem : mBuffers) {
-    DeallocShmem(shmem);
-  }
-  mBuffers.Clear();
+  PurgeShmems();
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 ChromiumCDMChild::RecvResetVideoDecoder()
 {
   MOZ_ASSERT(IsOnMessageLoopThread());
   GMP_LOG("ChromiumCDMChild::RecvResetVideoDecoder()");
--- a/dom/media/gmp/ChromiumCDMChild.h
+++ b/dom/media/gmp/ChromiumCDMChild.h
@@ -77,17 +77,18 @@ public:
   void GiveBuffer(ipc::Shmem&& aBuffer);
 
 protected:
   ~ChromiumCDMChild();
 
   bool IsOnMessageLoopThread();
 
   ipc::IPCResult RecvGiveBuffer(ipc::Shmem&& aShmem) override;
-
+  ipc::IPCResult RecvPurgeShmems() override;
+  void PurgeShmems();
   ipc::IPCResult RecvInit(const bool& aAllowDistinctiveIdentifier,
                           const bool& aAllowPersistentState) override;
   ipc::IPCResult RecvSetServerCertificate(
     const uint32_t& aPromiseId,
     nsTArray<uint8_t>&& aServerCert) override;
   ipc::IPCResult RecvCreateSessionAndGenerateRequest(
     const uint32_t& aPromiseId,
     const uint32_t& aSessionType,
--- a/dom/media/gmp/ChromiumCDMParent.cpp
+++ b/dom/media/gmp/ChromiumCDMParent.cpp
@@ -17,17 +17,17 @@
 
 namespace mozilla {
 namespace gmp {
 
 ChromiumCDMParent::ChromiumCDMParent(GMPContentParent* aContentParent,
                                      uint32_t aPluginId)
   : mPluginId(aPluginId)
   , mContentParent(aContentParent)
-  , mVideoShmemCount(MediaPrefs::EMEChromiumAPIVideoShmemCount())
+  , mVideoShmemLimit(MediaPrefs::EMEChromiumAPIVideoShmemCount())
 {
   GMP_LOG(
     "ChromiumCDMParent::ChromiumCDMParent(this=%p, contentParent=%p, id=%u)",
     this,
     aContentParent,
     aPluginId);
 }
 
@@ -595,75 +595,193 @@ ChromiumCDMParent::RecvDecrypted(const u
         MakeSpan<const uint8_t>(aShmem.get<uint8_t>(), aShmem.Size<uint8_t>()));
       mDecrypts.RemoveElementAt(i);
       break;
     }
   }
   return IPC_OK();
 }
 
+bool
+ChromiumCDMParent::PurgeShmems()
+{
+  GMP_LOG("ChromiumCDMParent::PurgeShmems(this=%p) frame_size=%" PRIuSIZE
+          " limit=%" PRIu32 " active=%" PRIu32,
+          this,
+          mVideoFrameBufferSize,
+          mVideoShmemLimit,
+          mVideoShmemsActive);
+
+  if (mVideoShmemsActive == 0) {
+    // We haven't allocated any shmems, nothing to do here.
+    return true;
+  }
+  if (!SendPurgeShmems()) {
+    return false;
+  }
+  mVideoShmemsActive = 0;
+  return true;
+}
+
+bool
+ChromiumCDMParent::EnsureSufficientShmems(size_t aVideoFrameSize)
+{
+  GMP_LOG("ChromiumCDMParent::EnsureSufficientShmems(this=%p) "
+          "size=%" PRIuSIZE " expected_size=%" PRIuSIZE " limit=%" PRIu32
+          " active=%" PRIu32,
+          this,
+          aVideoFrameSize,
+          mVideoFrameBufferSize,
+          mVideoShmemLimit,
+          mVideoShmemsActive);
+
+  // The Chromium CDM API requires us to implement a synchronous
+  // interface to supply buffers to the CDM for it to write decrypted samples
+  // into. We want our buffers to be backed by shmems, in order to reduce
+  // the overhead of transferring decoded frames. However due to sandboxing
+  // restrictions, the CDM process cannot allocate shmems itself.
+  // We don't want to be doing synchronous IPC to request shmems from the
+  // content process, nor do we want to have to do intr IPC or make async
+  // IPC conform to the sync allocation interface. So instead we have the
+  // content process pre-allocate a set of shmems and give them to the CDM
+  // process in advance of them being needed.
+  //
+  // When the CDM needs to allocate a buffer for storing a decoded video
+  // frame, the CDM host gives it one of these shmems' buffers. When this
+  // is sent back to the content process, we upload it to a GPU surface,
+  // and send the shmem back to the CDM process so it can reuse it.
+  //
+  // Normally the CDM won't allocate more than one buffer at once, but
+  // we've seen cases where it allocates multiple buffers, returns one and
+  // holds onto the rest. So we need to ensure we have several extra
+  // shmems pre-allocated for the CDM. This threshold is set by the pref
+  // media.eme.chromium-api.video-shmems.
+  //
+  // We also have a failure recovery mechanism; if the CDM asks for more
+  // buffers than we have shmem's available, ChromiumCDMChild gives the
+  // CDM a non-shared memory buffer, and returns the frame to the parent
+  // in an nsTArray<uint8_t> instead of a shmem. Every time this happens,
+  // the parent sends an extra shmem to the CDM process for it to add to the
+  // set of shmems with which to return output. Via this mechanism we should
+  // recover from incorrectly predicting how many shmems to pre-allocate.
+  //
+  // At decoder start up, we guess how big the shmems need to be based on
+  // the video frame dimensions. If we guess wrong, the CDM will follow
+  // the non-shmem path, and we'll re-create the shmems of the correct size.
+  // This meanns we can recover from guessing the shmem size wrong.
+  // We must re-take this path after every decoder de-init/re-init, as the
+  // frame sizes should change every time we switch video stream.
+
+  if (mVideoFrameBufferSize < aVideoFrameSize) {
+    if (!PurgeShmems()) {
+      return false;
+    }
+    mVideoFrameBufferSize = aVideoFrameSize;
+  } else {
+    // Put an upper limit on the number of shmems we tolerate the CDM asking
+    // for, to prevent a memory blow-out. In practice, we expect the CDM to
+    // need less than 5, but some encodings require more.
+    // We'd expect CDMs to not have video frames larger than 720p-1080p
+    // (due to DRM robustness requirements), which is about 1.5MB-3MB per
+    // frame.
+    if (mVideoShmemLimit > 50) {
+      return false;
+    }
+    mVideoShmemLimit++;
+  }
+
+  while (mVideoShmemsActive < mVideoShmemLimit) {
+    if (!SendBufferToCDM(mVideoFrameBufferSize)) {
+      return false;
+    }
+    mVideoShmemsActive++;
+  }
+  return true;
+}
+
 ipc::IPCResult
 ChromiumCDMParent::RecvDecodedData(const CDMVideoFrame& aFrame,
                                    nsTArray<uint8_t>&& aData)
 {
-  GMP_LOG("ChromiumCDMParent::RecvDecodedData(this=%p) "
-          "mVideoShmemCount=%" PRIu32,
-          this,
-          mVideoShmemCount);
-  // We'd expect CDMs to not have video frames larger than 1280x720 (due to
-  // DRM robustness requirements), which is about 1.5MB per frame. So put an
-  // upper limit on the number of shmems we tolerate the CDM asking for. In
-  // practice, we expect the CDM to need less than 5, but some encodings
-  // require more.
-  Shmem shmem;
-  if (mVideoShmemCount >= 50 || !AllocShmem(mVideoFrameBufferSize,
-                                            Shmem::SharedMemory::TYPE_BASIC,
-                                            &shmem)) {
-    GMP_LOG("ChromiumCDMParent::RecvDecodedData(this=%p) "
-            "failed to allocate shmem for CDM.",
-            this);
-    mVideoDecoderInitialized = false;
+  GMP_LOG("ChromiumCDMParent::RecvDecodedData(this=%p)", this);
+
+  if (mIsShutdown || mDecodePromise.IsEmpty()) {
+    return IPC_OK();
+  }
+
+  if (!EnsureSufficientShmems(aData.Length())) {
     mDecodePromise.RejectIfExists(
-      MediaResult(
-        NS_ERROR_DOM_MEDIA_FATAL_ERR,
-        RESULT_DETAIL("Failled to send shmems to CDM after decode init.")),
+      MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                  RESULT_DETAIL("Failled to ensure CDM has enough shmems.")),
       __func__);
     return IPC_OK();
   }
-  mVideoShmemCount++;
 
-  ProcessDecoded(aFrame, aData, Move(shmem));
+  RefPtr<VideoData> v = CreateVideoFrame(aFrame, aData);
+  if (!v) {
+    mDecodePromise.RejectIfExists(
+      MediaResult(NS_ERROR_OUT_OF_MEMORY,
+                  RESULT_DETAIL("Can't create VideoData")),
+      __func__);
+    return IPC_OK();
+  }
+
+  mDecodePromise.ResolveIfExists({ Move(v) }, __func__);
 
   return IPC_OK();
 }
 
 ipc::IPCResult
 ChromiumCDMParent::RecvDecodedShmem(const CDMVideoFrame& aFrame,
                                     ipc::Shmem&& aShmem)
 {
-  ProcessDecoded(
-    aFrame,
-    MakeSpan<uint8_t>(aShmem.get<uint8_t>(), aShmem.Size<uint8_t>()),
-    Move(aShmem));
+  GMP_LOG("ChromiumCDMParent::RecvDecodedShmem(this=%p)", this);
+
+  // On failure we need to deallocate the shmem we're to return to the
+  // CDM. On success we return it to the CDM to be reused.
+  auto autoDeallocateShmem =
+    MakeScopeExit([&, this] { this->DeallocShmem(aShmem); });
+
+  if (mIsShutdown || mDecodePromise.IsEmpty()) {
+    return IPC_OK();
+  }
+
+  RefPtr<VideoData> v = CreateVideoFrame(
+    aFrame, MakeSpan<uint8_t>(aShmem.get<uint8_t>(), aShmem.Size<uint8_t>()));
+  if (!v) {
+    mDecodePromise.RejectIfExists(
+      MediaResult(NS_ERROR_OUT_OF_MEMORY,
+                  RESULT_DETAIL("Can't create VideoData")),
+      __func__);
+    return IPC_OK();
+  }
+
+  // Return the shmem to the CDM so the shmem can be reused to send us
+  // another frame.
+  if (!SendGiveBuffer(aShmem)) {
+    mDecodePromise.RejectIfExists(
+      MediaResult(NS_ERROR_OUT_OF_MEMORY,
+                  RESULT_DETAIL("Can't return shmem to CDM process")),
+      __func__);
+    return IPC_OK();
+  }
+
+  // Don't need to deallocate the shmem since the CDM process is responsible
+  // for it again.
+  autoDeallocateShmem.release();
+
+  mDecodePromise.ResolveIfExists({ Move(v) }, __func__);
+
   return IPC_OK();
 }
 
-void
-ChromiumCDMParent::ProcessDecoded(const CDMVideoFrame& aFrame,
-                                  Span<uint8_t> aData,
-                                  ipc::Shmem&& aGiftShmem)
+already_AddRefed<VideoData>
+ChromiumCDMParent::CreateVideoFrame(const CDMVideoFrame& aFrame,
+                                    Span<uint8_t> aData)
 {
-  // On failure we need to deallocate the shmem we're to return to the
-  // CDM. On success we return it to the CDM to be reused.
-  auto autoDeallocateShmem =
-    MakeScopeExit([&, this] { this->DeallocShmem(aGiftShmem); });
-
-  if (mIsShutdown || mDecodePromise.IsEmpty()) {
-    return;
-  }
   VideoData::YCbCrBuffer b;
   MOZ_ASSERT(aData.Length() > 0);
 
   b.mPlanes[0].mData = aData.Elements();
   b.mPlanes[0].mWidth = aFrame.mImageWidth();
   b.mPlanes[0].mHeight = aFrame.mImageHeight();
   b.mPlanes[0].mStride = aFrame.mYPlane().mStride();
   b.mPlanes[0].mOffset = aFrame.mYPlane().mPlaneOffset();
@@ -690,37 +808,17 @@ ChromiumCDMParent::ProcessDecoded(const 
     mLastStreamOffset,
     media::TimeUnit::FromMicroseconds(aFrame.mTimestamp()),
     media::TimeUnit::FromMicroseconds(aFrame.mDuration()),
     b,
     false,
     media::TimeUnit::FromMicroseconds(-1),
     pictureRegion);
 
-  // Return the shmem to the CDM so the shmem can be reused to send us
-  // another frame.
-  if (!SendGiveBuffer(aGiftShmem)) {
-    mDecodePromise.RejectIfExists(
-      MediaResult(NS_ERROR_OUT_OF_MEMORY,
-                  RESULT_DETAIL("Can't return shmem to CDM process")),
-      __func__);
-    return;
-  }
-  // Don't need to deallocate the shmem since the CDM process is responsible
-  // for it again.
-  autoDeallocateShmem.release();
-
-  if (v) {
-    mDecodePromise.ResolveIfExists({ Move(v) }, __func__);
-  } else {
-    mDecodePromise.RejectIfExists(
-      MediaResult(NS_ERROR_OUT_OF_MEMORY,
-                  RESULT_DETAIL("CallBack::CreateAndCopyData")),
-      __func__);
-  }
+  return v.forget();
 }
 
 ipc::IPCResult
 ChromiumCDMParent::RecvDecodeFailed(const uint32_t& aStatus)
 {
   if (mIsShutdown) {
     MOZ_ASSERT(mDecodePromise.IsEmpty());
     return IPC_OK();
@@ -782,25 +880,32 @@ ChromiumCDMParent::InitializeVideoDecode
 {
   if (mIsShutdown) {
     return MediaDataDecoder::InitPromise::CreateAndReject(
       MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                   RESULT_DETAIL("ChromiumCDMParent is shutdown")),
       __func__);
   }
 
-  const int32_t bufferSize =
+  const size_t bufferSize =
     I420FrameBufferSizePadded(aInfo.mImage.width, aInfo.mImage.height);
   if (bufferSize <= 0) {
     return MediaDataDecoder::InitPromise::CreateAndReject(
       MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                   RESULT_DETAIL("Video frame buffer size is invalid.")),
       __func__);
   }
 
+  if (!EnsureSufficientShmems(bufferSize)) {
+    return MediaDataDecoder::InitPromise::CreateAndReject(
+      MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                  RESULT_DETAIL("Failed to init shmems for video decoder")),
+      __func__);
+  }
+
   if (!SendInitializeVideoDecoder(aConfig)) {
     return MediaDataDecoder::InitPromise::CreateAndReject(
       MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                   RESULT_DETAIL("Failed to send init video decoder to CDM")),
       __func__);
   }
 
   mVideoDecoderInitialized = true;
@@ -817,61 +922,16 @@ ChromiumCDMParent::RecvOnDecoderInitDone
   GMP_LOG("ChromiumCDMParent::RecvOnDecoderInitDone(this=%p, status=%u)",
           this,
           aStatus);
   if (mIsShutdown) {
     MOZ_ASSERT(mInitVideoDecoderPromise.IsEmpty());
     return IPC_OK();
   }
   if (aStatus == static_cast<uint32_t>(cdm::kSuccess)) {
-    // The Chromium CDM API requires us to implement a synchronous
-    // interface to supply buffers to the CDM for it to write decrypted samples
-    // into. We want our buffers to be backed by shmems, in order to reduce
-    // the overhead of transferring decoded frames. However due to sandboxing
-    // restrictions, the CDM process cannot allocate shmems itself.
-    // We don't want to be doing synchronous IPC to request shmems from the
-    // content process, nor do we want to have to do intr IPC or make async
-    // IPC conform to the sync allocation interface. So instead we have the
-    // content process pre-allocate a set of shmems and give them to the CDM
-    // process in advance of them being needed.
-    //
-    // When the CDM needs to allocate a buffer for storing a decrypted sample,
-    // the CDM host gives it one of these shmems' buffers. When this is sent
-    // back to the content process, we copy the result out (uploading to a
-    // GPU surface for video frames), and send the shmem back to the CDM
-    // process so it can reuse it.
-    //
-    // We predict the size of buffer the CDM will allocate, and prepopulate
-    // the CDM's list of shmems with shmems of at least that size, plus a bit
-    // of padding for safety.
-    //
-    // Normally the CDM won't allocate more than one buffer at once, but
-    // we've seen cases where it allocates multiple buffers, returns one and
-    // holds onto the rest. So we need to ensure we have a minimum number of
-    // shmems pre-allocated for the CDM. This minimum is set by the pref
-    // media.eme.chromium-api.video-shmems.
-    //
-    // We also have a failure recovery mechanism; if the CDM asks for more
-    // buffers than we have shmem's available, ChromiumCDMChild gives the
-    // CDM a non-shared memory buffer, and returns the frame to the parent
-    // in an nsTArray<uint8_t> instead of a shmem. Every time this happens,
-    // the parent sends an extra shmem to the CDM process for it to add to the
-    // set of shmems with which to return output. Via this mechanism we should
-    // recover from incorrectly predicting how many shmems to pre-allocate.
-    for (uint32_t i = 0; i < mVideoShmemCount; i++) {
-      if (!SendBufferToCDM(mVideoFrameBufferSize)) {
-        mVideoDecoderInitialized = false;
-        mInitVideoDecoderPromise.RejectIfExists(
-          MediaResult(
-            NS_ERROR_DOM_MEDIA_FATAL_ERR,
-            RESULT_DETAIL("Failled to send shmems to CDM after decode init.")),
-          __func__);
-        return IPC_OK();
-      }
-    }
     mInitVideoDecoderPromise.ResolveIfExists(TrackInfo::kVideoTrack, __func__);
   } else {
     mVideoDecoderInitialized = false;
     mInitVideoDecoderPromise.RejectIfExists(
       MediaResult(
         NS_ERROR_DOM_MEDIA_FATAL_ERR,
         RESULT_DETAIL("CDM init decode failed with %" PRIu32, aStatus)),
       __func__);
@@ -983,29 +1043,46 @@ ChromiumCDMParent::ShutdownVideoDecoder(
   mInitVideoDecoderPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED,
                                           __func__);
   mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
   MOZ_ASSERT(mFlushDecoderPromise.IsEmpty());
   if (!SendDeinitializeVideoDecoder()) {
     return ShutdownPromise::CreateAndResolve(true, __func__);
   }
   mVideoDecoderInitialized = false;
+
+  // The ChromiumCDMChild will purge its shmems, so if the decoder is
+  // reinitialized the shmems need to be re-allocated, and they may need
+  // to be a different size.
+  mVideoShmemsActive = 0;
+  mVideoFrameBufferSize = 0;
   return ShutdownPromise::CreateAndResolve(true, __func__);
 }
 
 void
 ChromiumCDMParent::Shutdown()
 {
   GMP_LOG("ChromiumCDMParent::Shutdown(this=%p)", this);
 
   if (mIsShutdown) {
     return;
   }
   mIsShutdown = true;
 
+  // If we're shutting down due to the plugin shutting down due to application
+  // shutdown, we should tell the CDM proxy to also shutdown. Otherwise the
+  // proxy will shutdown when the owning MediaKeys is destroyed during cycle
+  // collection, and that will not shut down cleanly as the GMP thread will be
+  // shutdown by then.
+  if (mProxy) {
+    RefPtr<Runnable> task =
+      NewRunnableMethod(mProxy, &ChromiumCDMProxy::Shutdown);
+    NS_DispatchToMainThread(task.forget());
+  }
+
   // We may be called from a task holding the last reference to the proxy, so
   // let's clear our local weak pointer to ensure it will not be used afterward
   // (including from an already-queued task, e.g.: ActorDestroy).
   mProxy = nullptr;
 
   for (RefPtr<DecryptJob>& decrypt : mDecrypts) {
     decrypt->PostResult(AbortedErr);
   }
--- a/dom/media/gmp/ChromiumCDMParent.h
+++ b/dom/media/gmp/ChromiumCDMParent.h
@@ -116,36 +116,36 @@ protected:
                                ipc::Shmem&& aData) override;
   ipc::IPCResult RecvDecryptFailed(const uint32_t& aId,
                                    const uint32_t& aStatus) override;
   ipc::IPCResult RecvOnDecoderInitDone(const uint32_t& aStatus) override;
   ipc::IPCResult RecvDecodedShmem(const CDMVideoFrame& aFrame,
                                   ipc::Shmem&& aShmem) override;
   ipc::IPCResult RecvDecodedData(const CDMVideoFrame& aFrame,
                                  nsTArray<uint8_t>&& aData) override;
-
-  void ProcessDecoded(const CDMVideoFrame& aFrame,
-                      Span<uint8_t> aData,
-                      ipc::Shmem&& aGiftShmem);
-
   ipc::IPCResult RecvDecodeFailed(const uint32_t& aStatus) override;
   ipc::IPCResult RecvShutdown() override;
   ipc::IPCResult RecvResetVideoDecoderComplete() override;
   ipc::IPCResult RecvDrainComplete() override;
   void ActorDestroy(ActorDestroyReason aWhy) override;
   bool SendBufferToCDM(uint32_t aSizeInBytes);
 
   void RejectPromise(uint32_t aPromiseId,
                      nsresult aError,
                      const nsCString& aErrorMessage);
 
   void ResolvePromise(uint32_t aPromiseId);
 
   bool InitCDMInputBuffer(gmp::CDMInputBuffer& aBuffer, MediaRawData* aSample);
 
+  bool PurgeShmems();
+  bool EnsureSufficientShmems(size_t aVideoFrameSize);
+  already_AddRefed<VideoData> CreateVideoFrame(const CDMVideoFrame& aFrame,
+                                               Span<uint8_t> aData);
+
   const uint32_t mPluginId;
   GMPContentParent* mContentParent;
   // Note: this pointer is a weak reference because otherwise it would cause
   // a cycle, as ChromiumCDMProxy has a strong reference to the
   // ChromiumCDMParent.
   ChromiumCDMProxy* mProxy = nullptr;
   nsDataHashtable<nsUint32HashKey, uint32_t> mPromiseToCreateSessionToken;
   nsTArray<RefPtr<DecryptJob>> mDecrypts;
@@ -154,21 +154,23 @@ protected:
   MozPromiseHolder<MediaDataDecoder::DecodePromise> mDecodePromise;
 
   RefPtr<layers::ImageContainer> mImageContainer;
   VideoInfo mVideoInfo;
   uint64_t mLastStreamOffset = 0;
 
   MozPromiseHolder<MediaDataDecoder::FlushPromise> mFlushDecoderPromise;
 
-  int32_t mVideoFrameBufferSize = 0;
+  size_t mVideoFrameBufferSize = 0;
 
   // Count of the number of shmems in the set used to return decoded video
   // frames from the CDM to Gecko.
-  uint32_t mVideoShmemCount;
+  uint32_t mVideoShmemsActive = 0;
+  // Maximum number of shmems to use to return decoded video frames.
+  uint32_t mVideoShmemLimit;
 
   bool mIsShutdown = false;
   bool mVideoDecoderInitialized = false;
   bool mActorDestroyed = false;
 };
 
 } // namespace gmp
 } // namespace mozilla
--- a/dom/media/gmp/GMPUtils.cpp
+++ b/dom/media/gmp/GMPUtils.cpp
@@ -234,28 +234,28 @@ LogToConsole(const nsAString& aMsg)
 RefPtr<AbstractThread>
 GetGMPAbstractThread()
 {
   RefPtr<gmp::GeckoMediaPluginService> service =
     gmp::GeckoMediaPluginService::GetGeckoMediaPluginService();
   return service ? service->GetAbstractGMPThread() : nullptr;
 }
 
-static int32_t
-Align16(int32_t aNumber)
+static size_t
+Align16(size_t aNumber)
 {
-  const uint32_t mask = 15; // Alignment - 1.
+  const size_t mask = 15; // Alignment - 1.
   return (aNumber + mask) & ~mask;
 }
 
-int32_t
+size_t
 I420FrameBufferSizePadded(int32_t aWidth, int32_t aHeight)
 {
   if (aWidth <= 0 || aHeight <= 0 || aWidth > MAX_VIDEO_WIDTH ||
       aHeight > MAX_VIDEO_HEIGHT) {
     return 0;
   }
 
-  int32_t ySize = Align16(aWidth) * Align16(aHeight);
+  size_t ySize = Align16(aWidth) * Align16(aHeight);
   return ySize + (ySize / 4) * 2;
 }
 
 } // namespace mozilla
--- a/dom/media/gmp/GMPUtils.h
+++ b/dom/media/gmp/GMPUtils.h
@@ -85,14 +85,14 @@ HaveGMPFor(const nsCString& aAPI,
 void
 LogToConsole(const nsAString& aMsg);
 
 RefPtr<AbstractThread>
 GetGMPAbstractThread();
 
 // Returns the number of bytes required to store an aWidth x aHeight image in
 // I420 format, padded so that the width and height are multiples of 16.
-int32_t
+size_t
 I420FrameBufferSizePadded(int32_t aWidth, int32_t aHeight);
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/gmp/PChromiumCDM.ipdl
+++ b/dom/media/gmp/PChromiumCDM.ipdl
@@ -51,16 +51,18 @@ child:
   async DecryptAndDecodeFrame(CDMInputBuffer aBuffer);
 
   async Drain();
 
   async Destroy();
 
   async GiveBuffer(Shmem aShmem);
 
+  async PurgeShmems();
+
 parent:
   async __delete__();
 
   // cdm::Host8
   async OnResolveNewSessionPromise(uint32_t aPromiseId, nsCString aSessionId);
 
   async OnResolvePromise(uint32_t aPromiseId);
 
--- a/dom/media/test/background_video.js
+++ b/dom/media/test/background_video.js
@@ -93,16 +93,29 @@ function testVideoResumesWhenShown(video
     ok(true, `${video.token} resumes`);
   });
   Log(video.token, "Set visible");
   video.setVisible(true);
   return p;
 }
 
 /**
+ * @param {HTMLMediaElement} video Video element under test.
+ * @returns {Promise} Promise that is resolved when video decode resumes.
+ */
+function testVideoOnlySeekCompletedWhenShown(video) {
+  var p  = once(video, 'mozvideoonlyseekcompleted').then(() => {
+    ok(true, `${video.token} resumes`);
+  });
+  Log(video.token, "Set visible");
+  video.setVisible(true);
+  return p;
+}
+
+/**
  * @param {HTMLVideoElement} video Video element under test.
  * @returns {Promise} Promise that is resolved if video ends and rejects if video suspends.
  */
 function checkVideoDoesntSuspend(video) {
   let p = Promise.race([
     waitUntilEnded(video).then(() => { ok(true, `${video.token} ended before decode was suspended`)}),
     once(video, 'mozentervideosuspend', () => { Promise.reject(new Error(`${video.token} suspended`)) })
   ]);
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -1146,16 +1146,19 @@ tags = suspend
 skip-if = toolkit == 'android' # android(bug 1304480)
 tags = suspend
 [test_background_video_no_suspend_short_vid.html]
 skip-if = toolkit == 'android' # android(bug 1304480)
 tags = suspend
 [test_background_video_no_suspend_not_in_tree.html]
 skip-if = toolkit == 'android' # bug 1346705
 tags = suspend
+[test_background_video_resume_after_end_show_last_frame.html]
+skip-if = toolkit == 'android' # bug 1346705
+tags = suspend
 [test_background_video_suspend.html]
 skip-if = toolkit == 'android' # android(bug 1304480)
 tags = suspend
 [test_background_video_suspend_ends.html]
 skip-if = toolkit == 'android' # bug 1295884, android(bug 1304480, bug 1232305)
 tags = suspend
 [test_background_video_tainted_by_capturestream.html]
 skip-if = toolkit == 'android' # bug 1346705
new file mode 100644
--- /dev/null
+++ b/dom/media/test/test_background_video_resume_after_end_show_last_frame.html
@@ -0,0 +1,94 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test Background Video Suspends</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="manifest.js"></script>
+<script src="background_video.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<script type="text/javascript">
+"use strict";
+
+var manager = new MediaTestManager;
+
+function testSameContent(video1, video2) {
+  if (video1.videoWidth != video2.videoWidth ||
+      video1.videoHeight != video2.videoHeight) {
+    ok(false, `${video1.token} video1 and video2 have different dimensions.`);
+    return;
+  }
+
+  let w = video1.videoWidth;
+  let h = video1.videoHeight;
+  let c1 = document.createElement('canvas');
+  let c2 = document.createElement('canvas');
+  c1.width = w;
+  c1.height = h;
+  c2.width = w;
+  c2.height = h;
+
+  let gfx1 = c1.getContext('2d');
+  let gfx2 = c2.getContext('2d');
+  if (!gfx1 || !gfx2) {
+    ok(false, "Unable to obtain context '2d' from canvas");
+    return;
+  }
+
+  gfx1.drawImage(video1, 0, 0, w, h);
+  gfx2.drawImage(video2, 0, 0, w, h);
+
+  // Get content out.
+  let contentWidth = 4;
+  let contentHeight = 4;
+  let imageData1 = gfx1.getImageData(0, 0, contentWidth, contentHeight);
+  let imageData2 = gfx2.getImageData(0, 0, contentWidth, contentHeight);
+  let pixels1 = imageData1.data;
+  let pixels2 = imageData2.data;
+
+  // Check that the content of two video are identical.
+  for (let i = 0; i < contentWidth*contentHeight; i++) {
+    let pixelCount = 4 * i;
+    if (pixels1[pixelCount+0] != pixels2[pixelCount+0] ||
+        pixels1[pixelCount+1] != pixels2[pixelCount+1] ||
+        pixels1[pixelCount+2] != pixels2[pixelCount+2] ||
+        pixels1[pixelCount+3] != pixels2[pixelCount+3]) {
+      ok(false, `${video1.token} video1 and video2 have different content.`);
+      return;
+    }
+  }
+
+  ok(true, `${video1.token} video1 and video2 have identical content.`);
+}
+
+startTest({
+  desc: "Test resume an ended video shows the last frame.",
+  prefs: [
+    [ "media.test.video-suspend", true ],
+    [ "media.suspend-bkgnd-video.enabled", true ],
+    [ "media.suspend-bkgnd-video.delay-ms", 100 ]
+  ],
+  tests: gDecodeSuspendTests,
+  runTest: (test, token) => {
+    let v = appendVideoToDoc(test.name, token);
+    let vReference = appendVideoToDoc(test.name, token);
+    manager.started(token);
+
+    /*
+     * This test checks that, after a video element had finished its playback,
+     * resuming video decoder should seek to the last frame.
+     * This issue was found in bug 1358057.
+     */
+    Promise.all([waitUntilPlaying(v), waitUntilPlaying(vReference)])
+      .then(() => testVideoSuspendsWhenHidden(v))
+      .then(() => {
+        return Promise.all([waitUntilEnded(v), waitUntilEnded(vReference)]);
+      })
+      .then(() => testVideoOnlySeekCompletedWhenShown(v))
+      .then(() => {
+        testSameContent(v, vReference);
+        removeNodeAndSource(v);
+        removeNodeAndSource(vReference);
+        manager.finished(token);
+      });
+    }
+});
+</script>
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -1180,17 +1180,17 @@ ToDisassemblySource(JSContext* cx, Handl
     if (v.isString()) {
         Sprinter sprinter(cx);
         if (!sprinter.init())
             return false;
         char* nbytes = QuoteString(&sprinter, v.toString(), '"');
         if (!nbytes)
             return false;
         UniqueChars copy = JS_smprintf("%s", nbytes);
-        if (!nbytes) {
+        if (!copy) {
             ReportOutOfMemory(cx);
             return false;
         }
         bytes->initBytes(Move(copy));
         return true;
     }
 
     if (JS::CurrentThreadIsHeapBusy() || !cx->isAllocAllowed()) {
--- a/layout/base/crashtests/crashtests.list
+++ b/layout/base/crashtests/crashtests.list
@@ -151,17 +151,17 @@ load 374193-1.xhtml
 load 374297-1.html
 load 374297-2.html
 load 376223-1.xhtml
 load 378325-1.html
 load 378682.html
 load 379105-1.xhtml
 load 379419-1.xhtml
 load 379768-1.html
-asserts-if(stylo,6) load 379799-1.html # bug 1324977
+load 379799-1.html
 load 379920-1.svg
 load 379920-2.svg
 load 379975.html
 load 380096-1.html
 load 382204-1.html # bug 1323680
 load 383102-1.xhtml
 load 383129-1.html
 load 383806-1.xhtml
@@ -321,17 +321,17 @@ load 497519-1.xhtml
 load 497519-2.xhtml
 load 497519-3.xhtml
 load 497519-4.xhtml
 load 499741-1.xhtml
 load 499841-1.xhtml
 load 499858-1.xhtml
 load 500467-1.html
 load 501878-1.html
-asserts-if(stylo,4) load 503936-1.html # bug 1324658
+load 503936-1.html
 load 507119.html
 load 514104-1.xul
 load 522374-1.html
 load 522374-2.html
 load 526378-1.xul
 load 534367-1.xhtml
 load 534368-1.xhtml
 load 534768-1.html
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -8536,16 +8536,17 @@ nsCSSFrameConstructor::ContentRemoved(ns
     // XXXbz the GetContent() != aChild check is needed due to bug 135040.
     // Remove it once that's fixed.
     ClearUndisplayedContentIn(aChild, aContainer);
   }
   MOZ_ASSERT(!childFrame || !GetDisplayContentsStyleFor(aChild),
              "display:contents nodes shouldn't have a frame");
   if (!childFrame && GetDisplayContentsStyleFor(aChild)) {
     nsIContent* ancestor = aContainer;
+    MOZ_ASSERT(ancestor, "display: contents on the root?");
     while (!ancestor->GetPrimaryFrame()) {
       // FIXME(emilio): Should this use the flattened tree parent instead?
       ancestor = ancestor->GetParent();
       MOZ_ASSERT(ancestor, "we can't have a display: contents subtree root!");
     }
 
     nsIFrame* ancestorFrame = ancestor->GetPrimaryFrame();
     if (ancestorFrame->Properties().Get(nsIFrame::GenConProperty())) {
--- a/layout/generic/crashtests/crashtests.list
+++ b/layout/generic/crashtests/crashtests.list
@@ -77,17 +77,17 @@ load 368568.html
 load 368752.html
 load 368860-1.html
 load 368863-1.html
 load 369038-1.xhtml
 load 369150-1.html
 load 369150-2.html
 load 369227-1.xhtml
 load 369542-1.html
-asserts-if(stylo,2) load 369542-2.html # bug 1324646
+load 369542-2.html
 load 369547-1.html
 load 370174-1.html
 load 370174-2.html
 load 370174-3.html
 load 370174-4.html
 load 370699-1.html
 load 370794-1.html
 load 370866-1.xhtml
@@ -410,17 +410,17 @@ load 553504-1.xhtml
 load 564368-1.xhtml
 load 564968.xhtml
 load 569193-1.html
 load 570160.html
 load 570289-1.html
 load 571618-1.svg
 asserts(1) asserts-if(stylo,0-1) load 571975-1.html # bug 574889
 load 571995.xhtml
-asserts-if(stylo,2) load 574958.xhtml # bug 1324646
+load 574958.xhtml
 asserts(0-4) load 578977.html # bug 757305
 load 580504-1.xhtml
 load 585598-1.xhtml
 load 586806-1.html
 load 586806-2.html
 load 586806-3.html
 load 586973-1.html
 load 589002-1.html
@@ -631,17 +631,17 @@ load text-overflow-bug671796.xhtml
 load text-overflow-bug713610.html
 load text-overflow-form-elements.html
 load text-overflow-iframe.html
 asserts-if(Android,2-4) asserts-if(!Android,4) load 1225005.html # bug 682647 and bug 448083
 load 1233191.html
 asserts-if(stylo,0-15) load 1271765.html # bug 1324684
 asserts(2) load 1272983-1.html # bug 586628
 asserts(2) load 1272983-2.html # bug 586628
-load 1275059.html
+asserts-if(stylo,2) load 1275059.html # Bug 1360221
 load 1278007.html
 load 1278080.html
 load 1279814.html
 load large-border-radius-dashed.html
 load large-border-radius-dashed2.html
 load large-border-radius-dotted.html
 load large-border-radius-dotted2.html
 load 1278461-1.html
--- a/layout/reftests/first-letter/reftest.list
+++ b/layout/reftests/first-letter/reftest.list
@@ -19,33 +19,33 @@ fails-if(!stylo) == quote-1c.html quote-
 == quote-1c.html quote-1b.html
 fails-if(!stylo) == quote-1d.html quote-1-ref.html
 == quote-1d.html quote-1b.html
 fails-if(!stylo) == quote-1e.html quote-1-ref.html # bug 509685
 == quote-1e.html quote-1b.html
 == quote-1f.html quote-1-ref.html
 fails-if(!stylo) fails-if(stylo) == dynamic-1.html dynamic-1-ref.html # bug 8253
 random-if(d2d) == dynamic-2.html dynamic-2-ref.html
-== dynamic-3a.html dynamic-3-ref.html
-== dynamic-3b.html dynamic-3-ref.html
+fails-if(stylo) asserts-if(stylo,6) == dynamic-3a.html dynamic-3-ref.html
+fails-if(stylo) asserts-if(stylo,6) == dynamic-3b.html dynamic-3-ref.html
 == 23605-1.html 23605-1-ref.html
 == 23605-2.html 23605-2-ref.html
 == 23605-3.html 23605-3-ref.html
 == 23605-4.html 23605-4-ref.html
 == 23605-5.html 23605-5-ref.html
 == 23605-6.html 23605-6-ref.html
 != 229764-1.html 229764-ref.html
 fails-if(stylo) == 229764-2.html 229764-ref.html
 == 329069-1.html 329069-1-ref.html
 fails-if(Android) == 329069-2.html 329069-2-ref.html # Bug 999139
 == 329069-3.html 329069-3-ref.html
 == 329069-4.html 329069-4-ref.html
 HTTP(..) == 329069-5.html 329069-5-ref.html
 == 342120-1.xhtml 342120-1-ref.xhtml
-== 379799-1.html 379799-1-ref.html
+fails-if(stylo) asserts-if(stylo,6) == 379799-1.html 379799-1-ref.html
 == 399941-1.html 399941-1-ref.html
 == 399941-2.html 399941-2-ref.html
 == 399941-3.html 399941-3-ref.html
 == 399941-4.html 399941-4-ref.html
 == 399941-5.html 399941-5-ref.html
 == 399941-6.html 399941-6-ref.html
 == 399941-7.html 399941-7-ref.html
 == 399941-8.html 399941-8-ref.html
--- a/layout/style/nsStyleContext.cpp
+++ b/layout/style/nsStyleContext.cpp
@@ -697,44 +697,50 @@ ShouldBlockifyChildren(const nsStyleDisp
     mozilla::StyleDisplay::InlineFlex == displayVal ||
     mozilla::StyleDisplay::Grid == displayVal ||
     mozilla::StyleDisplay::InlineGrid == displayVal;
 }
 
 void
 nsStyleContext::SetStyleBits()
 {
-  // XXXbholley: We should get this information directly from the
-  // ServoComputedValues rather than computing it here. This setup for
-  // ServoComputedValues-backed nsStyleContexts is probably not something
-  // we should ship.
-  //
-  // For example, NS_STYLE_IS_TEXT_COMBINED is still set in ApplyStyleFixups,
-  // which isn't called for ServoComputedValues.
+  // Here we set up various style bits for both the Gecko and Servo paths.
+  // _Only_ change the bits here.  For fixups of the computed values, you can
+  // add to ApplyStyleFixups in Gecko and StyleAdjuster as part of Servo's
+  // cascade.
 
   // See if we have any text decorations.
   // First see if our parent has text decorations.  If our parent does, then we inherit the bit.
   if (mParent && mParent->HasTextDecorationLines()) {
-    mBits |= NS_STYLE_HAS_TEXT_DECORATION_LINES;
+    AddStyleBit(NS_STYLE_HAS_TEXT_DECORATION_LINES);
   } else {
     // We might have defined a decoration.
     if (StyleTextReset()->HasTextDecorationLines()) {
-      mBits |= NS_STYLE_HAS_TEXT_DECORATION_LINES;
+      AddStyleBit(NS_STYLE_HAS_TEXT_DECORATION_LINES);
     }
   }
 
   if ((mParent && mParent->HasPseudoElementData()) || IsPseudoElement()) {
-    mBits |= NS_STYLE_HAS_PSEUDO_ELEMENT_DATA;
+    AddStyleBit(NS_STYLE_HAS_PSEUDO_ELEMENT_DATA);
   }
 
   // Set the NS_STYLE_IN_DISPLAY_NONE_SUBTREE bit
   const nsStyleDisplay* disp = StyleDisplay();
   if ((mParent && mParent->IsInDisplayNoneSubtree()) ||
       disp->mDisplay == mozilla::StyleDisplay::None) {
-    mBits |= NS_STYLE_IN_DISPLAY_NONE_SUBTREE;
+    AddStyleBit(NS_STYLE_IN_DISPLAY_NONE_SUBTREE);
+  }
+
+  // Mark text combined for text-combine-upright, as needed.
+  if (mPseudoTag == nsCSSAnonBoxes::mozText && mParent &&
+      mParent->StyleVisibility()->mWritingMode !=
+        NS_STYLE_WRITING_MODE_HORIZONTAL_TB &&
+      mParent->StyleText()->mTextCombineUpright ==
+        NS_STYLE_TEXT_COMBINE_UPRIGHT_ALL) {
+    AddStyleBit(NS_STYLE_IS_TEXT_COMBINED);
   }
 }
 
 void
 nsStyleContext::ApplyStyleFixups(bool aSkipParentDisplayBasedStyleFixup)
 {
   MOZ_ASSERT(!mSource.IsServoComputedValues(),
              "Can't do Gecko style fixups on Servo values");
@@ -788,17 +794,16 @@ nsStyleContext::ApplyStyleFixups(bool aS
         NS_STYLE_WRITING_MODE_HORIZONTAL_TB &&
       mParent->StyleText()->mTextCombineUpright ==
         NS_STYLE_TEXT_COMBINE_UPRIGHT_ALL) {
     MOZ_ASSERT(!PeekStyleVisibility(), "If StyleVisibility was already "
                "computed, some properties may have been computed "
                "incorrectly based on the old writing mode value");
     nsStyleVisibility* mutableVis = GET_UNIQUE_STYLE_DATA(Visibility);
     mutableVis->mWritingMode = NS_STYLE_WRITING_MODE_HORIZONTAL_TB;
-    AddStyleBit(NS_STYLE_IS_TEXT_COMBINED);
   }
 
   // CSS 2.1 10.1: Propagate the root element's 'direction' to the ICB.
   // (PageContentFrame/CanvasFrame etc will inherit 'direction')
   if (mPseudoTag == nsCSSAnonBoxes::viewport) {
     nsPresContext* presContext = PresContext();
     mozilla::dom::Element* docElement = presContext->Document()->GetRootElement();
     if (docElement) {
--- a/layout/style/test/stylo-failures.md
+++ b/layout/style/test/stylo-failures.md
@@ -185,18 +185,16 @@ to mochitest command.
   * -webkit-{flex,inline-flex} for display servo/servo#15400
     * test_webkit_flex_display.html [4]
   * test_pixel_lengths.html `mozmm`: mozmm unit bug 1356104 [3]
 * Unsupported values
   * SVG-only values of pointer-events not recognized
     * test_value_storage.html `pointer-events` [1]
   * new syntax of rgba?() and hsla?() functions servo/rust-cssparser#113
     * test_computed_style.html `css-color-4` [2]
-  * color interpolation hint not supported servo/servo#15166
-    * test_value_storage.html `'linear-gradient` [50]
   * SVG-in-OpenType values not supported servo/servo#15211 bug 1355412
     * test_value_storage.html `context-` [7]
     * test_bug798843_pref.html [7]
 * Incorrect parsing
   * Incorrect bounds
     * test_bug664955.html `font size is larger than max font size` [2]
   * calc() doesn't support dividing expression servo/servo#15192
     * test_value_storage.html `calc(50px/` [7]
--- a/media/libnestegg/README_MOZILLA
+++ b/media/libnestegg/README_MOZILLA
@@ -1,8 +1,8 @@
 The source from this directory was copied from the nestegg
 git repository using the update.sh script.  The only changes
 made were those applied by update.sh and the addition of
 Makefile.in build files for the Mozilla build system.
 
-The nestegg git repository is: git://github.com/kinetiknz/nestegg.git
+The nestegg git repository is: https://github.com/kinetiknz/nestegg
 
-The git commit ID used was 5e2fb721d5808785475d68f63fc97d45b8a4ef03.
+The git commit ID used was af26fc354ec9eadf5fcd34fb01223be3f6f8a773.
--- a/media/libnestegg/include/nestegg.h
+++ b/media/libnestegg/include/nestegg.h
@@ -22,17 +22,17 @@ extern "C" {
     <tt>libnestegg</tt> is a demultiplexing library for <a
     href="http://www.webmproject.org/code/specs/container/">WebM</a>
     media files.
 
     @section example Example code
 
     @code
     nestegg * demux_ctx;
-    nestegg_init(&demux_ctx, io, NULL);
+    nestegg_init(&demux_ctx, io, NULL, -1);
 
     nestegg_packet * pkt;
     while ((r = nestegg_read_packet(demux_ctx, &pkt)) > 0) {
       unsigned int track;
 
       nestegg_packet_track(pkt, &track);
 
       // This example decodes the first track only.
@@ -66,16 +66,17 @@ extern "C" {
 #define NESTEGG_TRACK_VIDEO   0       /**< Track is of type video. */
 #define NESTEGG_TRACK_AUDIO   1       /**< Track is of type audio. */
 #define NESTEGG_TRACK_UNKNOWN INT_MAX /**< Track is of type unknown. */
 
 #define NESTEGG_CODEC_VP8     0       /**< Track uses Google On2 VP8 codec. */
 #define NESTEGG_CODEC_VORBIS  1       /**< Track uses Xiph Vorbis codec. */
 #define NESTEGG_CODEC_VP9     2       /**< Track uses Google On2 VP9 codec. */
 #define NESTEGG_CODEC_OPUS    3       /**< Track uses Xiph Opus codec. */
+#define NESTEGG_CODEC_AV1     4       /**< Track uses AOMedia AV1 codec. */
 #define NESTEGG_CODEC_UNKNOWN INT_MAX /**< Track uses unknown codec. */
 
 #define NESTEGG_VIDEO_MONO              0 /**< Track is mono video. */
 #define NESTEGG_VIDEO_STEREO_LEFT_RIGHT 1 /**< Track is side-by-side stereo video.  Left first. */
 #define NESTEGG_VIDEO_STEREO_BOTTOM_TOP 2 /**< Track is top-bottom stereo video.  Right first. */
 #define NESTEGG_VIDEO_STEREO_TOP_BOTTOM 3 /**< Track is top-bottom stereo video.  Left first. */
 #define NESTEGG_VIDEO_STEREO_RIGHT_LEFT 11 /**< Track is side-by-side stereo video.  Right first. */
 
--- a/media/libnestegg/src/nestegg.c
+++ b/media/libnestegg/src/nestegg.c
@@ -149,16 +149,17 @@ enum ebml_type_enum {
 
 /* Track Types */
 #define TRACK_TYPE_VIDEO            1
 #define TRACK_TYPE_AUDIO            2
 
 /* Track IDs */
 #define TRACK_ID_VP8                "V_VP8"
 #define TRACK_ID_VP9                "V_VP9"
+#define TRACK_ID_AV1                "V_AV1"
 #define TRACK_ID_VORBIS             "A_VORBIS"
 #define TRACK_ID_OPUS               "A_OPUS"
 
 /* Track Encryption */
 #define CONTENT_ENC_ALGO_AES        5
 #define AES_SETTINGS_CIPHER_CTR     1
 
 /* Packet Encryption */
@@ -1041,17 +1042,17 @@ ne_read_single_master(nestegg * ctx, str
 
   return ne_ctx_push(ctx, desc->children, ctx->ancestor->data + desc->offset);
 }
 
 static int
 ne_read_simple(nestegg * ctx, struct ebml_element_desc * desc, size_t length)
 {
   struct ebml_type * storage;
-  int r;
+  int r = -1;
 
   storage = (struct ebml_type *) (ctx->ancestor->data + desc->offset);
 
   if (storage->read) {
     ctx->log(ctx, NESTEGG_LOG_DEBUG, "element %llx (%s) already read, skipping",
              desc->id, desc->name);
     return 0;
   }
@@ -2365,16 +2366,19 @@ nestegg_track_codec_id(nestegg * ctx, un
     return -1;
 
   if (strcmp(codec_id, TRACK_ID_VP8) == 0)
     return NESTEGG_CODEC_VP8;
 
   if (strcmp(codec_id, TRACK_ID_VP9) == 0)
     return NESTEGG_CODEC_VP9;
 
+  if (strcmp(codec_id, TRACK_ID_AV1) == 0)
+    return NESTEGG_CODEC_AV1;
+
   if (strcmp(codec_id, TRACK_ID_VORBIS) == 0)
     return NESTEGG_CODEC_VORBIS;
 
   if (strcmp(codec_id, TRACK_ID_OPUS) == 0)
     return NESTEGG_CODEC_OPUS;
 
   return NESTEGG_CODEC_UNKNOWN;
 }
--- a/mobile/android/base/java/org/mozilla/gecko/ANRReporter.java
+++ b/mobile/android/base/java/org/mozilla/gecko/ANRReporter.java
@@ -17,16 +17,17 @@ import java.io.OutputStream;
 import java.io.Reader;
 import java.util.Locale;
 import java.util.UUID;
 import java.util.regex.Pattern;
 
 import org.json.JSONObject;
 import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.AppConstants.Versions;
+import org.mozilla.gecko.util.IOUtils;
 import org.mozilla.gecko.util.StringUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.Handler;
@@ -149,18 +150,20 @@ public final class ANRReporter extends B
 
         // Find the traces file name if we can.
         try {
             // getprop [prop-name [default-value]]
             Process propProc = (new ProcessBuilder())
                 .command("/system/bin/getprop", "dalvik.vm.stack-trace-file")
                 .redirectErrorStream(true)
                 .start();
+
+            BufferedReader buf = null;
             try {
-                BufferedReader buf = new BufferedReader(
+                buf = new BufferedReader(
                     new InputStreamReader(
                             propProc.getInputStream(), StringUtils.UTF_8), TRACES_LINE_SIZE);
                 String propVal = buf.readLine();
                 if (DEBUG) {
                     Log.d(LOGTAG, "getprop returned " + String.valueOf(propVal));
                 }
                 // getprop can return empty string when the prop value is empty
                 // or prop is undefined, treat both cases the same way
@@ -171,16 +174,18 @@ public final class ANRReporter extends B
                     } else if (DEBUG) {
                         Log.d(LOGTAG, "cannot access traces file");
                     }
                 } else if (DEBUG) {
                     Log.d(LOGTAG, "empty getprop result");
                 }
             } finally {
                 propProc.destroy();
+
+                IOUtils.safeStreamClose(buf);
             }
         } catch (IOException e) {
             Log.w(LOGTAG, e);
         } catch (ClassCastException e) {
             Log.w(LOGTAG, e); // Bug 975436
         }
         return null;
     }
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -107,17 +107,16 @@ import org.mozilla.gecko.util.GeckoBundl
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.IntentUtils;
 import org.mozilla.gecko.util.MenuUtils;
 import org.mozilla.gecko.util.PrefUtils;
 import org.mozilla.gecko.util.StringUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.widget.ActionModePresenter;
 import org.mozilla.gecko.widget.AnchoredPopup;
-
 import org.mozilla.gecko.widget.GeckoActionProvider;
 
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.ContentResolver;
@@ -180,16 +179,19 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.EnumSet;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Vector;
 import java.util.regex.Pattern;
 
+import static org.mozilla.gecko.Tab.TabType;
+import static org.mozilla.gecko.Tabs.INVALID_TAB_ID;
+
 public class BrowserApp extends GeckoApp
                         implements TabsPanel.TabsLayoutChangeListener,
                                    PropertyAnimator.PropertyAnimationListener,
                                    View.OnKeyListener,
                                    DynamicToolbarAnimator.MetricsListener,
                                    DynamicToolbarAnimator.ToolbarChromeProxy,
                                    BrowserSearch.OnSearchListener,
                                    BrowserSearch.OnEditSuggestionListener,
@@ -349,16 +351,21 @@ public class BrowserApp extends GeckoApp
         } else {
             view = super.onCreateView(name, context, attrs);
         }
         return view;
     }
 
     @Override
     public void onTabChanged(Tab tab, TabEvents msg, String data) {
+        if (!mInitialized) {
+            super.onTabChanged(tab, msg, data);
+            return;
+        }
+
         if (tab == null) {
             // Only RESTORED is allowed a null tab: it's the only event that
             // isn't tied to a specific tab.
             if (msg != Tabs.TabEvents.RESTORED) {
                 throw new IllegalArgumentException("onTabChanged:" + msg + " must specify a tab.");
             }
 
             final Tab selectedTab = Tabs.getInstance().getSelectedTab();
@@ -429,16 +436,21 @@ public class BrowserApp extends GeckoApp
 
         if (HardwareUtils.isTablet() && msg == TabEvents.SELECTED) {
             updateEditingModeForTab(tab);
         }
 
         super.onTabChanged(tab, msg, data);
     }
 
+    @Override
+    protected boolean saveAsLastSelectedTab(Tab tab) {
+        return tab.getType() == TabType.BROWSING;
+    }
+
     private void updateEditingModeForTab(final Tab selectedTab) {
         // (bug 1086983 comment 11) Because the tab may be selected from the gecko thread and we're
         // running this code on the UI thread, the selected tab argument may not still refer to the
         // selected tab. However, that means this code should be run again and the initial state
         // changes will be overridden. As an optimization, we can skip this update, but it may have
         // unknown side-effects so we don't.
         if (!Tabs.getInstance().isSelectedTab(selectedTab)) {
             Log.w(LOGTAG, "updateEditingModeForTab: Given tab is expected to be selected tab");
@@ -1145,16 +1157,53 @@ public class BrowserApp extends GeckoApp
         processTabQueue();
 
         for (BrowserAppDelegate delegate : delegates) {
             delegate.onResume(this);
         }
     }
 
     @Override
+    protected void restoreLastSelectedTab() {
+        if (mResumingAfterOnCreate && !mIsRestoringActivity) {
+            // We're the first activity to run, so our startup code will (have) handle(d) tab selection.
+            return;
+        }
+
+        if (mLastSelectedTabId < 0) {
+            // Normally, session restore will select the correct tab when starting up, however this
+            // is linked to Gecko powering up. If we're not the first activity to launch, the
+            // previously running activity might have already overwritten this by selecting a tab of
+            // its own.
+            // Therefore we check whether the session file parser has left a note for us with the
+            // correct tab to be initially selected on *BrowserApp* startup.
+            SharedPreferences prefs = getSharedPreferencesForProfile();
+            mLastSelectedTabId = prefs.getInt(STARTUP_SELECTED_TAB, INVALID_TAB_ID);
+            mLastSessionUUID = prefs.getString(STARTUP_SESSION_UUID, null);
+
+            SharedPreferences.Editor editor = prefs.edit();
+            editor.remove(STARTUP_SELECTED_TAB);
+            editor.remove(STARTUP_SESSION_UUID);
+            editor.apply();
+        }
+
+        final Tabs tabs = Tabs.getInstance();
+        final Tab tabToSelect = tabs.getTab(mLastSelectedTabId);
+
+        if (tabToSelect != null && GeckoApplication.getSessionUUID().equals(mLastSessionUUID) &&
+                tabToSelect.getType() == TabType.BROWSING) {
+            tabs.selectTab(mLastSelectedTabId);
+        } else {
+            if (!tabs.selectLastTab(TabType.BROWSING)) {
+                tabs.loadUrl(Tabs.getHomepageForStartupTab(this), Tabs.LOADURL_NEW_TAB);
+            }
+        }
+    }
+
+    @Override
     public void onPause() {
         super.onPause();
         if (mIsAbortingAppLaunch) {
             return;
         }
 
         if (mHasResumed) {
             // Register for Prompt:ShowTop so we can foreground this activity even if it's hidden.
@@ -1978,17 +2027,17 @@ public class BrowserApp extends GeckoApp
                 final BrowserDB db = BrowserDB.from(getProfile());
                 final ContentResolver cr = getContentResolver();
 
                 Telemetry.addToHistogram("PLACES_PAGES_COUNT", db.getCount(cr, "history"));
                 Telemetry.addToHistogram("FENNEC_BOOKMARKS_COUNT", db.getCount(cr, "bookmarks"));
                 Telemetry.addToHistogram("BROWSER_IS_USER_DEFAULT",
                         (isDefaultBrowser(Intent.ACTION_VIEW) ? 1 : 0));
                 Telemetry.addToHistogram("FENNEC_CUSTOM_HOMEPAGE",
-                        (TextUtils.isEmpty(Tabs.getHomepage(this)) ? 0 : 1));
+                        (Tabs.hasHomepage(this) ? 1 : 0));
 
                 final SharedPreferences prefs = GeckoSharedPrefs.forProfile(getContext());
                 final boolean hasCustomHomepanels =
                         prefs.contains(HomeConfigPrefsBackend.PREFS_CONFIG_KEY) ||
                         prefs.contains(HomeConfigPrefsBackend.PREFS_CONFIG_KEY_OLD);
 
                 Telemetry.addToHistogram("FENNEC_HOMEPANELS_CUSTOM", hasCustomHomepanels ? 1 : 0);
 
@@ -2758,17 +2807,17 @@ public class BrowserApp extends GeckoApp
         if (mFirstrunAnimationContainer == null) {
             final ViewStub firstrunPagerStub = (ViewStub) findViewById(R.id.firstrun_pager_stub);
             mFirstrunAnimationContainer = (FirstrunAnimationContainer) firstrunPagerStub.inflate();
             mFirstrunAnimationContainer.load(getApplicationContext(), getSupportFragmentManager());
             mFirstrunAnimationContainer.registerOnFinishListener(new FirstrunAnimationContainer.OnFinishListener() {
                 @Override
                 public void onFinish() {
                     if (mFirstrunAnimationContainer.showBrowserHint() &&
-                        TextUtils.isEmpty(Tabs.getHomepage(BrowserApp.this))) {
+                        !Tabs.hasHomepage(BrowserApp.this)) {
                         enterEditingMode();
                     }
                 }
             });
         }
 
         mHomeScreenContainer.setVisibility(View.VISIBLE);
     }
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoActivity.java
@@ -37,16 +37,21 @@ public abstract class GeckoActivity exte
 
         if (getApplication() instanceof GeckoApplication) {
             ((GeckoApplication) getApplication()).onActivityResume(this);
             mGeckoActivityOpened = false;
         }
     }
 
     @Override
+    protected void onNewIntent(Intent externalIntent) {
+        GeckoActivityMonitor.getInstance().onActivityNewIntent(this);
+    }
+
+    @Override
     public void onCreate(android.os.Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         if (AppConstants.MOZ_ANDROID_ANR_REPORTER) {
             ANRReporter.register(getApplicationContext());
         }
     }
 
     @Override
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoActivityMonitor.java
@@ -0,0 +1,89 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * 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/. */
+
+package org.mozilla.gecko;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.Application;
+import android.os.Bundle;
+
+public class GeckoActivityMonitor implements Application.ActivityLifecycleCallbacks {
+    private static final String LOGTAG = "GeckoActivityMonitor";
+
+    // We only hold a reference to the currently running activity - when this activity pauses,
+    // the reference is released or else overwritten by the next activity.
+    @SuppressLint("StaticFieldLeak")
+    private static final GeckoActivityMonitor instance = new GeckoActivityMonitor();
+
+    private Activity currentActivity;
+
+    public static GeckoActivityMonitor getInstance() {
+        return instance;
+    }
+
+    private GeckoActivityMonitor() { }
+
+    public Activity getCurrentActivity() {
+        return currentActivity;
+    }
+
+    @Override
+    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
+        currentActivity = activity;
+    }
+
+    // onNewIntent happens in-between a pause/resume cycle, which means that we wouldn't have
+    // a current activity to report if we were using only the official ActivityLifecycleCallbacks.
+    // For code that wants to know the current activity even at this point we therefore have to
+    // handle this ourselves.
+    public void onActivityNewIntent(Activity activity) {
+        currentActivity = activity;
+    }
+
+    @Override
+    public void onActivityStarted(Activity activity) {
+        currentActivity = activity;
+    }
+
+    @Override
+    public void onActivityResumed(Activity activity) {
+        currentActivity = activity;
+    }
+
+    /**
+     * Intended to be used if the current activity is required to be up-to-date for code that
+     * executes in onCreate/onStart/... before calling the corresponding superclass method.
+     */
+    public void setCurrentActivity(Activity activity) {
+        currentActivity = activity;
+    }
+
+    @Override
+    public void onActivityPaused(Activity activity) {
+        releaseIfCurrentActivity(activity);
+    }
+
+    @Override
+    public void onActivityStopped(Activity activity) {
+        releaseIfCurrentActivity(activity);
+    }
+
+    @Override
+    public void onActivitySaveInstanceState(Activity activity, Bundle outState) { }
+
+    @Override
+    public void onActivityDestroyed(Activity activity) {
+        releaseIfCurrentActivity(activity);
+    }
+
+    private void releaseIfCurrentActivity(Activity activity) {
+        // If the next activity has already started by the time the previous activity is being
+        // stopped/destroyed, we no longer need to clear the previous activity.
+        if (currentActivity == activity) {
+            currentActivity = null;
+        }
+    }
+}
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -40,16 +40,19 @@ import org.mozilla.gecko.util.ActivityRe
 import org.mozilla.gecko.util.ActivityUtils;
 import org.mozilla.gecko.util.BundleEventListener;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.FileUtils;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.PrefUtils;
 import org.mozilla.gecko.util.ThreadUtils;
+import org.mozilla.gecko.util.ViewUtil;
+import org.mozilla.gecko.widget.ActionModePresenter;
+import org.mozilla.gecko.widget.AnchoredPopup;
 
 import android.animation.Animator;
 import android.animation.ObjectAnimator;
 import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.content.Context;
@@ -103,19 +106,16 @@ import android.widget.ListView;
 import android.widget.RelativeLayout;
 import android.widget.SimpleAdapter;
 import android.widget.TextView;
 import android.widget.Toast;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
-import org.mozilla.gecko.util.ViewUtil;
-import org.mozilla.gecko.widget.AnchoredPopup;
-import org.mozilla.gecko.widget.ActionModePresenter;
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.ref.WeakReference;
 import java.net.URL;
 import java.util.ArrayList;
@@ -124,16 +124,18 @@ import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
+import static org.mozilla.gecko.Tabs.INVALID_TAB_ID;
+
 public abstract class GeckoApp
     extends GeckoActivity
     implements
     BundleEventListener,
     ContextGetter,
     GeckoAppShell.GeckoInterface,
     ScreenOrientationDelegate,
     GeckoMenu.Callback,
@@ -153,20 +155,23 @@ public abstract class GeckoApp
     public static final String ACTION_LOAD                 = "org.mozilla.gecko.LOAD";
     public static final String ACTION_INIT_PW              = "org.mozilla.gecko.INIT_PW";
     public static final String ACTION_SWITCH_TAB           = "org.mozilla.gecko.SWITCH_TAB";
 
     public static final String INTENT_REGISTER_STUMBLER_LISTENER = "org.mozilla.gecko.STUMBLER_REGISTER_LOCAL_LISTENER";
 
     public static final String EXTRA_STATE_BUNDLE          = "stateBundle";
 
-    public static final String LAST_SELECTED_TAB           = "lastSelectedTab";
+    protected static final String LAST_SELECTED_TAB        = "lastSelectedTab";
+    protected static final String LAST_SESSION_UUID        = "lastSessionUUID";
+    protected static final String STARTUP_SELECTED_TAB     = "restoredSelectedTab";
+    protected static final String STARTUP_SESSION_UUID     = "restorationSessionUUID";
 
     public static final String PREFS_ALLOW_STATE_BUNDLE    = "allowStateBundle";
-    public static final String PREFS_FLASH_USAGE = "playFlashCount";
+    public static final String PREFS_FLASH_USAGE           = "playFlashCount";
     public static final String PREFS_VERSION_CODE          = "versionCode";
     public static final String PREFS_WAS_STOPPED           = "wasStopped";
     public static final String PREFS_CRASHED_COUNT         = "crashedCount";
     public static final String PREFS_CLEANUP_TEMP_FILES    = "cleanupTempFiles";
 
     // Originally, this was only used for the telemetry core ping logic. To avoid
     // having to write custom migration logic, we just keep the original pref key.
     public static final String PREFS_IS_FIRST_RUN = "telemetry-isFirstRun";
@@ -175,17 +180,18 @@ public abstract class GeckoApp
     public static final String SAVED_STATE_PRIVATE_SESSION = "privateSession";
 
     // Delay before running one-time "cleanup" tasks that may be needed
     // after a version upgrade.
     private static final int CLEANUP_DEFERRAL_SECONDS = 15;
 
     private static boolean sAlreadyLoaded;
 
-    private static WeakReference<GeckoApp> lastActiveGeckoApp;
+    protected boolean mResumingAfterOnCreate;
+    protected static WeakReference<GeckoApp> mLastActiveGeckoApp;
 
     protected RelativeLayout mRootLayout;
     protected RelativeLayout mMainLayout;
 
     protected RelativeLayout mGeckoLayout;
     private OrientationEventListener mCameraOrientationEventListener;
     public List<GeckoAppShell.AppStateListener> mAppStateListeners = new LinkedList<GeckoAppShell.AppStateListener>();
     protected MenuPanel mMenuPanel;
@@ -209,25 +215,29 @@ public abstract class GeckoApp
     private View mFullScreenPluginView;
 
     private final HashMap<String, PowerManager.WakeLock> mWakeLocks = new HashMap<String, PowerManager.WakeLock>();
 
     protected boolean mLastSessionCrashed;
     protected boolean mShouldRestore;
     private boolean mSessionRestoreParsingFinished = false;
 
-    protected int lastSelectedTabId = -1;
+    protected int mLastSelectedTabId = INVALID_TAB_ID;
+    protected String mLastSessionUUID = null;
+    protected boolean mCheckTabSelectionOnResume = false;
 
     private boolean foregrounded = false;
 
     private static final class LastSessionParser extends SessionParser {
         private JSONArray tabs;
         private JSONObject windowObject;
         private boolean isExternalURL;
 
+        private int selectedTabId = INVALID_TAB_ID;
+
         private boolean selectNextTab;
         private boolean tabsWereSkipped;
         private boolean tabsWereProcessed;
 
         private SparseIntArray tabIdMap;
 
         public LastSessionParser(JSONArray tabs, JSONObject windowObject, boolean isExternalURL) {
             this.tabs = tabs;
@@ -237,17 +247,26 @@ public abstract class GeckoApp
             tabIdMap = new SparseIntArray();
         }
 
         public boolean allTabsSkipped() {
             return tabsWereSkipped && !tabsWereProcessed;
         }
 
         public int getNewTabId(int oldTabId) {
-            return tabIdMap.get(oldTabId, -1);
+            return tabIdMap.get(oldTabId, INVALID_TAB_ID);
+        }
+
+        /**
+         * @return The index of the tab that should be selected according to the session store data.
+         *         In conjunction with opening external tabs, this might not be the tab that
+         *         actually gets selected in the end, though.
+         */
+        public int getStoredSelectedTabId() {
+            return selectedTabId;
         }
 
         @Override
         public void onTabRead(final SessionTab sessionTab) {
             if (sessionTab.isAboutHomeWithoutHistory()) {
                 // This is a tab pointing to about:home with no history. We won't restore
                 // this tab. If we end up restoring no tabs then the browser will decide
                 // whether it needs to open about:home or a different 'homepage'. If we'd
@@ -275,31 +294,34 @@ public abstract class GeckoApp
 
             int flags = Tabs.LOADURL_NEW_TAB;
             flags |= ((isExternalURL || !sessionTab.isSelected()) ? Tabs.LOADURL_DELAY_LOAD : 0);
             flags |= (tabObject.optBoolean("desktopMode") ? Tabs.LOADURL_DESKTOP : 0);
             flags |= (tabObject.optBoolean("isPrivate") ? Tabs.LOADURL_PRIVATE : 0);
 
             final Tab tab = Tabs.getInstance().loadUrl(sessionTab.getUrl(), flags);
 
+            if (sessionTab.isSelected() || selectNextTab) {
+                selectedTabId = tab.getId();
+            }
             if (selectNextTab) {
                 // We did not restore the selected tab previously. Now let's select this tab.
                 Tabs.getInstance().selectTab(tab.getId());
                 selectNextTab = false;
             }
 
             ThreadUtils.postToUiThread(new Runnable() {
                 @Override
                 public void run() {
                     tab.updateTitle(sessionTab.getTitle());
                 }
             });
 
             try {
-                int oldTabId = tabObject.optInt("tabId", -1);
+                int oldTabId = tabObject.optInt("tabId", INVALID_TAB_ID);
                 int newTabId = tab.getId();
                 tabObject.put("tabId", newTabId);
                 if  (oldTabId >= 0) {
                     tabIdMap.put(oldTabId, newTabId);
                 }
             } catch (JSONException e) {
                 Log.e(LOGTAG, "JSON error", e);
             }
@@ -403,38 +425,77 @@ public abstract class GeckoApp
     public void removeAppStateListener(GeckoAppShell.AppStateListener listener) {
         mAppStateListeners.remove(listener);
     }
 
     @Override
     public void onTabChanged(Tab tab, Tabs.TabEvents msg, String data) {
         // When a tab is closed, it is always unselected first.
         // When a tab is unselected, another tab is always selected first.
+        // When we're switching activities because of differing tab types,
+        // the first statement is not true.
         switch (msg) {
             case UNSELECTED:
                 break;
 
             case LOCATION_CHANGE:
                 // We only care about location change for the selected tab.
-                if (!Tabs.getInstance().isSelectedTab(tab))
-                    break;
-                // Fall through...
+                if (Tabs.getInstance().isSelectedTab(tab)) {
+                    resetOptionsMenu();
+                    resetFormAssistPopup();
+                }
+                break;
+
             case SELECTED:
-                invalidateOptionsMenu();
-                if (mFormAssistPopup != null)
-                    mFormAssistPopup.hide();
+                resetOptionsMenu();
+                resetFormAssistPopup();
+
+                if (saveAsLastSelectedTab(tab)) {
+                    mLastSelectedTabId = tab.getId();
+                    mLastSessionUUID = GeckoApplication.getSessionUUID();
+                }
+                break;
+
+            case CLOSED:
+                if (saveAsLastSelectedTab(tab)) {
+                    if (mLastSelectedTabId == tab.getId() &&
+                            GeckoApplication.getSessionUUID().equals(mLastSessionUUID)) {
+                        mLastSelectedTabId = Tabs.INVALID_TAB_ID;
+                        mLastSessionUUID = null;
+                    }
+                }
                 break;
 
             case DESKTOP_MODE_CHANGE:
                 if (Tabs.getInstance().isSelectedTab(tab))
-                    invalidateOptionsMenu();
+                    resetOptionsMenu();
                 break;
         }
     }
 
+    private void resetOptionsMenu() {
+        if (mInitialized) {
+            invalidateOptionsMenu();
+        }
+    }
+
+    private void resetFormAssistPopup() {
+        if (mInitialized && mFormAssistPopup != null) {
+            mFormAssistPopup.hide();
+        }
+    }
+
+    /**
+     * Called on tab selection and tab close - return true to allow updating of this activity's
+     * last selected tab.
+     */
+    protected boolean saveAsLastSelectedTab(Tab tab) {
+        return false;
+    }
+
     public void refreshChrome() { }
 
     @Override
     public void invalidateOptionsMenu() {
         if (mMenu == null) {
             return;
         }
 
@@ -630,22 +691,18 @@ public abstract class GeckoApp
     }
 
     @Override
     protected void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
 
         outState.putBoolean(SAVED_STATE_IN_BACKGROUND, isApplicationInBackground());
         outState.putString(SAVED_STATE_PRIVATE_SESSION, mPrivateBrowsingSession);
-        outState.putInt(LAST_SELECTED_TAB, lastSelectedTabId);
-    }
-
-    @Override
-    protected void onRestoreInstanceState(final Bundle inState) {
-        lastSelectedTabId = inState.getInt(LAST_SELECTED_TAB);
+        outState.putInt(LAST_SELECTED_TAB, mLastSelectedTabId);
+        outState.putString(LAST_SESSION_UUID, mLastSessionUUID);
     }
 
     public void addTab() { }
 
     public void addPrivateTab() { }
 
     public void showNormalTabs() { }
 
@@ -1185,16 +1242,21 @@ public abstract class GeckoApp
             finish();
             return;
         }
 
         // The clock starts...now. Better hurry!
         mJavaUiStartupTimer = new Telemetry.UptimeTimer("FENNEC_STARTUP_TIME_JAVAUI");
         mGeckoReadyStartupTimer = new Telemetry.UptimeTimer("FENNEC_STARTUP_TIME_GECKOREADY");
 
+        if (savedInstanceState != null) {
+            mLastSelectedTabId = savedInstanceState.getInt(LAST_SELECTED_TAB);
+            mLastSessionUUID = savedInstanceState.getString(LAST_SESSION_UUID);
+        }
+
         final SafeIntent intent = new SafeIntent(getIntent());
 
         earlyStartJavaSampler(intent);
 
         // GeckoLoader wants to dig some environment variables out of the
         // incoming intent, so pass it in here. GeckoLoader will do its
         // business later and dispose of the reference.
         GeckoLoader.setLastIntent(intent);
@@ -1232,19 +1294,23 @@ public abstract class GeckoApp
         // restart, and will be propagated to Gecko accordingly, so there's
         // no need to touch that here.
         if (BrowserLocaleManager.getInstance().systemLocaleDidChange()) {
             Log.i(LOGTAG, "System locale changed. Restarting.");
             doRestart();
             return;
         }
 
+        mResumingAfterOnCreate = true;
+
         if (sAlreadyLoaded) {
             // This happens when the GeckoApp activity is destroyed by Android
             // without killing the entire application (see Bug 769269).
+            // Now that we've got multiple GeckoApp-based activities, this can
+            // also happen if we're not the first activity to run within a session.
             mIsRestoringActivity = true;
             Telemetry.addToHistogram("FENNEC_RESTORING_ACTIVITY", 1);
 
         } else {
             final String action = intent.getAction();
             final String args = intent.getStringExtra("args");
 
             sAlreadyLoaded = true;
@@ -1332,16 +1398,17 @@ public abstract class GeckoApp
             "Share:Text",
             "SystemUI:Visibility",
             "ToggleChrome:Focus",
             "ToggleChrome:Hide",
             "ToggleChrome:Show",
             null);
 
         Tabs.getInstance().attachToContext(this, mLayerView);
+        Tabs.registerOnTabsChangedListener(this);
 
         // Use global layout state change to kick off additional initialization
         mMainLayout.getViewTreeObserver().addOnGlobalLayoutListener(this);
 
         if (Versions.preMarshmallow) {
             mTextSelection = new ActionBarTextSelection(this, getTextSelectPresenter());
         } else {
             mTextSelection = new FloatingToolbarTextSelection(this, mLayerView);
@@ -1619,18 +1686,17 @@ public abstract class GeckoApp
      */
     protected void loadStartupTab(final int flags, String action) {
         if (!mShouldRestore || Intent.ACTION_VIEW.equals(action)) {
             if (mLastSessionCrashed) {
                 // The Recent Tabs panel no longer exists, but BrowserApp will redirect us
                 // to the Recent Tabs folder of the Combined History panel.
                 Tabs.getInstance().loadUrl(AboutPages.getURLForBuiltinPanelType(PanelType.DEPRECATED_RECENT_TABS), flags);
             } else {
-                final String homepage = Tabs.getHomepage(this);
-                Tabs.getInstance().loadUrl(!TextUtils.isEmpty(homepage) ? homepage : AboutPages.HOME, flags);
+                Tabs.getInstance().loadUrl(Tabs.getHomepageForStartupTab(this), flags);
             }
         }
     }
 
     /**
      * Loads the initial tab at Fennec startup. This tab will load with the given
      * external URL. If that URL is invalid, a startup tab will be loaded.
      *
@@ -1640,20 +1706,30 @@ public abstract class GeckoApp
      */
     protected void loadStartupTab(final String url, final SafeIntent intent, final int flags) {
         // Invalid url
         if (url == null) {
             loadStartupTab(flags, intent.getAction());
             return;
         }
 
-        Tabs.getInstance().loadUrlWithIntentExtras(url, intent, flags);
+        final Tab newTab = Tabs.getInstance().loadUrlWithIntentExtras(url, intent, flags);
+        if (ThreadUtils.isOnUiThread()) {
+            onTabOpenFromIntent(newTab);
+        } else {
+            ThreadUtils.postToUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    onTabOpenFromIntent(newTab);
+                }
+            });
+        }
     }
 
-    private String getIntentURI(SafeIntent intent) {
+    protected String getIntentURI(SafeIntent intent) {
         final String passedUri;
         final String uri = getURIFromIntent(intent);
 
         if (!TextUtils.isEmpty(uri)) {
             passedUri = uri;
         } else {
             passedUri = null;
         }
@@ -1692,36 +1768,37 @@ public abstract class GeckoApp
         final String passedUri = getIntentURI(intent);
 
         final boolean isExternalURL = invokedWithExternalURL(passedUri);
 
         // Start migrating as early as possible, can do this in
         // parallel with Gecko load.
         checkMigrateProfile();
 
-        Tabs.registerOnTabsChangedListener(this);
-
         initializeChrome();
 
         // We need to wait here because mShouldRestore can revert back to
         // false if a parsing error occurs and the startup tab we load
         // depends on whether we restore tabs or not.
         synchronized (this) {
             while (!mSessionRestoreParsingFinished) {
                 try {
                     wait();
                 } catch (final InterruptedException e) {
                     // Ignore and wait again.
                 }
             }
         }
 
+        if (mIsRestoringActivity && hasGeckoTab(intent)) {
+            Tabs.getInstance().notifyListeners(null, Tabs.TabEvents.RESTORED);
+            handleSelectTabIntent(intent);
         // External URLs should always be loaded regardless of whether Gecko is
         // already running.
-        if (isExternalURL) {
+        } else if (isExternalURL) {
             // Restore tabs before opening an external URL so that the new tab
             // is animated properly.
             Tabs.getInstance().notifyListeners(null, Tabs.TabEvents.RESTORED);
             processActionViewIntent(new Runnable() {
                 @Override
                 public void run() {
                     final int flags = getNewTabFlags();
                     loadStartupTab(passedUri, intent, flags);
@@ -1782,16 +1859,20 @@ public abstract class GeckoApp
             }
 
             if (GeckoThread.isRunning()) {
                 geckoConnected();
             }
         }
     }
 
+    protected void onTabOpenFromIntent(Tab tab) { }
+
+    protected void onTabSelectFromIntent(Tab tab) { }
+
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
     @Override
     public void onGlobalLayout() {
         if (Versions.preJB) {
             mMainLayout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
         } else {
             mMainLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
         }
@@ -1855,16 +1936,21 @@ public abstract class GeckoApp
                 // Update all parent tab IDs ...
                 parser.updateParentId(tabs);
                 windowObject.put("tabs", tabs);
                 // ... and for recently closed tabs as well (if we've got any).
                 final JSONArray closedTabs = windowObject.optJSONArray("closedTabs");
                 parser.updateParentId(closedTabs);
                 windowObject.putOpt("closedTabs", closedTabs);
 
+                if (isExternalURL) {
+                    // Pass on the tab we would have selected if we weren't going to open an
+                    // external URL later on.
+                    windowObject.put("selectedTabId", parser.getStoredSelectedTabId());
+                }
                 sessionString = new JSONObject().put(
                         "windows", new JSONArray().put(windowObject)).toString();
             } catch (final JSONException e) {
                 throw new SessionRestoreException(e);
             }
         } else {
             if (parser.allTabsSkipped() || sessionDataValid) {
                 // If we intentionally skipped all tabs we've read from the session file, we
@@ -1872,21 +1958,39 @@ public abstract class GeckoApp
                 // can infer that the exception wasn't due to a damaged session store file.
                 // The same applies if the session file was syntactically valid and
                 // simply didn't contain any tabs.
                 mShouldRestore = false;
             }
             throw new SessionRestoreException("No tabs could be read from session file");
         }
 
+        if (saveSelectedStartupTab()) {
+            // This activity is something other than our normal tabbed browsing interface and is
+            // going to overwrite our tab selection. Therefore we should stash it away for later, so
+            // e.g. BrowserApp can display the correct tab if starting up later during this session.
+            SharedPreferences.Editor prefs = getSharedPreferencesForProfile().edit();
+            prefs.putInt(STARTUP_SELECTED_TAB, parser.getStoredSelectedTabId());
+            prefs.putString(STARTUP_SESSION_UUID, GeckoApplication.getSessionUUID());
+            prefs.apply();
+        }
+
         final GeckoBundle restoreData = new GeckoBundle(1);
         restoreData.putString("sessionString", sessionString);
         return restoreData;
     }
 
+    /**
+     * Activities that don't implement a normal tabbed browsing UI and overwrite the tab selection
+     * made by session restoring should probably override this and return true.
+     */
+    protected boolean saveSelectedStartupTab() {
+        return false;
+    }
+
     @RobocopTarget
     public static @NonNull EventDispatcher getEventDispatcher() {
         final GeckoApp geckoApp = (GeckoApp) GeckoAppShell.getGeckoInterface();
         return geckoApp.getAppEventDispatcher();
     }
 
     @Override
     public @NonNull EventDispatcher getAppEventDispatcher() {
@@ -2173,16 +2277,18 @@ public abstract class GeckoApp
                         halfSize + sHeight),
                 null);
 
         return bitmap;
     }
 
     @Override
     protected void onNewIntent(Intent externalIntent) {
+        super.onNewIntent(externalIntent);
+
         final SafeIntent intent = new SafeIntent(externalIntent);
 
         final boolean isFirstTab = !mWasFirstTabShownAfterActivityUnhidden;
         mWasFirstTabShownAfterActivityUnhidden = true; // Reset since we'll be loading a tab.
 
         // if we were previously OOM killed, we can end up here when launching
         // from external shortcuts, so set this as the intent for initialization
         if (!mInitialized) {
@@ -2195,54 +2301,79 @@ public abstract class GeckoApp
         final String uri = getURIFromIntent(intent);
         final String passedUri;
         if (!TextUtils.isEmpty(uri)) {
             passedUri = uri;
         } else {
             passedUri = null;
         }
 
-        if (ACTION_LOAD.equals(action)) {
+        if (hasGeckoTab(intent)) {
+            // This also covers ACTION_SWITCH_TAB.
+            handleSelectTabIntent(intent);
+        } else if (ACTION_LOAD.equals(action)) {
             Tabs.getInstance().loadUrl(intent.getDataString());
-            lastSelectedTabId = -1;
         } else if (Intent.ACTION_VIEW.equals(action)) {
             processActionViewIntent(new Runnable() {
                 @Override
                 public void run() {
                     final String url = intent.getDataString();
                     int flags = Tabs.LOADURL_NEW_TAB | Tabs.LOADURL_USER_ENTERED | Tabs.LOADURL_EXTERNAL;
                     if (isFirstTab) {
                         flags |= Tabs.LOADURL_FIRST_AFTER_ACTIVITY_UNHIDDEN;
                     }
                     Tabs.getInstance().loadUrlWithIntentExtras(url, intent, flags);
                 }
             });
-            lastSelectedTabId = -1;
         } else if (ACTION_HOMESCREEN_SHORTCUT.equals(action)) {
             mLayerView.loadUri(uri, GeckoView.LOAD_SWITCH_TAB);
         } else if (Intent.ACTION_SEARCH.equals(action)) {
             mLayerView.loadUri(uri, GeckoView.LOAD_NEW_TAB);
         } else if (NotificationHelper.HELPER_BROADCAST_ACTION.equals(action)) {
             NotificationHelper.getInstance(getApplicationContext()).handleNotificationIntent(intent);
         } else if (ACTION_LAUNCH_SETTINGS.equals(action)) {
             // Check if launched from data reporting notification.
             Intent settingsIntent = new Intent(GeckoApp.this, GeckoPreferences.class);
             // Copy extras.
             settingsIntent.putExtras(intent.getUnsafe());
             startActivity(settingsIntent);
-        } else if (ACTION_SWITCH_TAB.equals(action)) {
-            final int tabId = intent.getIntExtra("TabId", -1);
-            Tabs.getInstance().selectTab(tabId);
-            lastSelectedTabId = -1;
         }
 
         recordStartupActionTelemetry(passedUri, action);
     }
 
     /**
+     * Check whether an intent with tab switch extras refers to a tab that
+     * is actually existing at the moment.
+     *
+     * @param intent The intent to be checked.
+     * @return True if the tab specified in the intent is existing in our Tabs list.
+     */
+    protected boolean hasGeckoTab(SafeIntent intent) {
+        final int tabId = intent.getIntExtra(Tabs.INTENT_EXTRA_TAB_ID, INVALID_TAB_ID);
+        final String intentSessionUUID = intent.getStringExtra(Tabs.INTENT_EXTRA_SESSION_UUID);
+        final Tab tabToCheck = Tabs.getInstance().getTab(tabId);
+
+        // We only care about comparing session UUIDs if one was specified in the intent.
+        // Otherwise, we just try matching the tab ID with one of our open tabs.
+        return tabToCheck != null && (!intent.hasExtra(Tabs.INTENT_EXTRA_SESSION_UUID) ||
+                GeckoApplication.getSessionUUID().equals(intentSessionUUID));
+    }
+
+    protected void handleSelectTabIntent(SafeIntent intent) {
+        final int tabId = intent.getIntExtra(Tabs.INTENT_EXTRA_TAB_ID, INVALID_TAB_ID);
+        final Tab selectedTab = Tabs.getInstance().selectTab(tabId);
+        // If the tab selection has been redirected to a different activity,
+        // the selectedTab within Tabs will not have been updated yet.
+        if (selectedTab == Tabs.getInstance().getSelectedTab()) {
+            onTabSelectFromIntent(selectedTab);
+        }
+    }
+
+    /**
      * Handles getting a URI from an intent in a way that is backwards-
      * compatible with our previous implementations.
      */
     protected String getURIFromIntent(SafeIntent intent) {
         final String action = intent.getAction();
         if (ACTION_ALERT_CALLBACK.equals(action) ||
                 NotificationHelper.HELPER_BROADCAST_ACTION.equals(action)) {
             return null;
@@ -2273,17 +2404,21 @@ public abstract class GeckoApp
             return;
         }
 
         foregrounded = true;
 
         GeckoAppShell.setGeckoInterface(this);
         GeckoAppShell.setScreenOrientationDelegate(this);
 
-        restoreLastSelectedTab();
+        if (mLastActiveGeckoApp == null || mLastActiveGeckoApp.get() != this ||
+                mCheckTabSelectionOnResume) {
+            mCheckTabSelectionOnResume = false;
+            restoreLastSelectedTab();
+        }
 
         int newOrientation = getResources().getConfiguration().orientation;
         if (GeckoScreenOrientation.getInstance().update(newOrientation)) {
             refreshChrome();
         }
 
         if (mAppStateListeners != null) {
             for (GeckoAppShell.AppStateListener listener : mAppStateListeners) {
@@ -2323,23 +2458,25 @@ public abstract class GeckoApp
                     rec.processDelayed();
                 } else {
                     Log.w(LOGTAG, "Can't record session: rec is null.");
                 }
             }
         });
 
         Restrictions.update(this);
+
+        mResumingAfterOnCreate = false;
     }
 
-    protected void restoreLastSelectedTab() {
-        if (lastSelectedTabId >= 0 && (lastActiveGeckoApp == null || lastActiveGeckoApp.get() != this)) {
-            Tabs.getInstance().selectTab(lastSelectedTabId);
-        }
-    }
+    /**
+     * Called on activity resume if a different (or no) GeckoApp-based activity was previously
+     * active within our application.
+     */
+    protected void restoreLastSelectedTab() { }
 
     @Override
     public void onWindowFocusChanged(boolean hasFocus) {
         super.onWindowFocusChanged(hasFocus);
 
         if (!mWindowFocusInitialized && hasFocus) {
             mWindowFocusInitialized = true;
             // XXX our editor tests require the GeckoView to have focus to pass, so we have to
@@ -2359,21 +2496,17 @@ public abstract class GeckoApp
 
         if (mIsAbortingAppLaunch) {
             super.onPause();
             return;
         }
 
         foregrounded = false;
 
-        final Tab selectedTab = Tabs.getInstance().getSelectedTab();
-        if (selectedTab != null) {
-            lastSelectedTabId = selectedTab.getId();
-        }
-        lastActiveGeckoApp = new WeakReference<GeckoApp>(this);
+        mLastActiveGeckoApp = new WeakReference<GeckoApp>(this);
 
         final HealthRecorder rec = mHealthRecorder;
         final Context context = this;
 
         // In some way it's sad that Android will trigger StrictMode warnings
         // here as the whole point is to save to disk while the activity is not
         // interacting with the user.
         ThreadUtils.postToBackgroundThread(new Runnable() {
@@ -2717,22 +2850,25 @@ public abstract class GeckoApp
 
                 if (tab.doBack()) {
                     return;
                 }
 
                 if (tab.isExternal()) {
                     onDone();
                     Tab nextSelectedTab = Tabs.getInstance().getNextTab(tab);
-                    if (nextSelectedTab != null) {
+                    // Closing the tab will select the next tab. There's no need to unzombify it
+                    // if we're really exiting - switching activities is a different matter, though.
+                    if (nextSelectedTab != null && nextSelectedTab.getType() == tab.getType()) {
                         final GeckoBundle data = new GeckoBundle(1);
                         data.putInt("nextSelectedTabId", nextSelectedTab.getId());
                         EventDispatcher.getInstance().dispatch("Tab:KeepZombified", data);
                     }
-                    tabs.closeTab(tab);
+                    tabs.closeTabNoActivitySwitch(tab);
+                    mCheckTabSelectionOnResume = true;
                     return;
                 }
 
                 final int parentId = tab.getParentId();
                 final Tab parent = tabs.getTab(parentId);
                 if (parent != null) {
                     // The back button should always return to the parent (not a sibling).
                     tabs.closeTab(tab, parent);
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java
@@ -28,33 +28,37 @@ import org.mozilla.gecko.notifications.N
 import org.mozilla.gecko.notifications.NotificationHelper;
 import org.mozilla.gecko.preferences.DistroSharedPrefsImport;
 import org.mozilla.gecko.util.BundleEventListener;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.PRNGFixes;
 import org.mozilla.gecko.util.ThreadUtils;
+import org.mozilla.gecko.util.UUIDUtil;
 
 import java.io.File;
 import java.lang.reflect.Method;
+import java.util.UUID;
 
 public class GeckoApplication extends Application
     implements ContextGetter {
     private static final String LOG_TAG = "GeckoApplication";
     private static final String MEDIA_DECODING_PROCESS_CRASH = "MEDIA_DECODING_PROCESS_CRASH";
 
     private boolean mInBackground;
     private boolean mPausedGecko;
     private boolean mIsInitialResume;
 
     private LightweightTheme mLightweightTheme;
 
     private RefWatcher mRefWatcher;
 
+    private static String sSessionUUID = null;
+
     public GeckoApplication() {
         super();
     }
 
     public static RefWatcher getRefWatcher(Context context) {
         GeckoApplication app = (GeckoApplication) context.getApplicationContext();
         return app.mRefWatcher;
     }
@@ -62,16 +66,23 @@ public class GeckoApplication extends Ap
     public static void watchReference(Context context, Object object) {
         if (context == null) {
             return;
         }
 
         getRefWatcher(context).watch(object);
     }
 
+    /**
+     * @return The string representation of an UUID that changes on each application startup.
+     */
+    public static String getSessionUUID() {
+        return sSessionUUID;
+    }
+
     @Override
     public Context getContext() {
         return this;
     }
 
     @Override
     public SharedPreferences getSharedPreferences() {
         return GeckoSharedPrefs.forApp(this);
@@ -161,16 +172,20 @@ public class GeckoApplication extends Ap
             // Not much to be done here: it was weak before, so it's weak now.  Not worth aborting.
             Log.e(LOG_TAG, "Got exception applying PRNGFixes! Cryptographic data produced on this device may be weak. Ignoring.", e);
         }
 
         mIsInitialResume = true;
 
         mRefWatcher = LeakCanary.install(this);
 
+        sSessionUUID = UUID.randomUUID().toString();
+
+        registerActivityLifecycleCallbacks(GeckoActivityMonitor.getInstance());
+
         final Context context = getApplicationContext();
         GeckoAppShell.setApplicationContext(context);
         HardwareUtils.init(context);
         FilePicker.init(context);
         DownloadsIntegration.init();
         HomePanelsManager.getInstance().init(context);
 
         GlobalPageMetadata.getInstance().init();
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/SingleTabActivity.java
@@ -0,0 +1,179 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * 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/. */
+
+package org.mozilla.gecko;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import org.mozilla.gecko.mozglue.SafeIntent;
+
+import static org.mozilla.gecko.Tabs.INTENT_EXTRA_SESSION_UUID;
+import static org.mozilla.gecko.Tabs.INTENT_EXTRA_TAB_ID;
+import static org.mozilla.gecko.Tabs.INVALID_TAB_ID;
+
+public abstract class SingleTabActivity extends GeckoApp {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        final Intent externalIntent = getIntent();
+        // We need the current activity to already be up-to-date before
+        // calling into the superclass.
+        GeckoActivityMonitor.getInstance().setCurrentActivity(this);
+
+        decideTabAction(new SafeIntent(externalIntent), savedInstanceState);
+
+        super.onCreate(savedInstanceState);
+        // GeckoApp's default behaviour is to reset the intent if we've got any
+        // savedInstanceState, which we don't want here.
+        setIntent(externalIntent);
+    }
+
+    @Override
+    protected void onNewIntent(Intent externalIntent) {
+        final SafeIntent intent = new SafeIntent(externalIntent);
+        // We need the current activity to already be up-to-date before
+        // calling into the superclass.
+        GeckoActivityMonitor.getInstance().setCurrentActivity(this);
+
+        if (decideTabAction(intent, null)) {
+            // GeckoApp will handle tab selection.
+            super.onNewIntent(intent.getUnsafe());
+        } else {
+            // We're not calling the superclass in this code path, so we'll
+            // have to notify the activity monitor ourselves.
+            GeckoActivityMonitor.getInstance().onActivityNewIntent(this);
+            loadTabFromIntent(intent);
+        }
+        // Again, unlike GeckoApp's default behaviour we want to keep the intent around
+        // because we might still require its data (e.g. to get custom tab customisations).
+        setIntent(intent.getUnsafe());
+    }
+
+    @Override
+    protected boolean saveSelectedStartupTab() {
+        // We ignore the tab selection made by session restoring in order to display our own tab,
+        // so we should save that tab's ID in case the user starts up our normal browsing UI later
+        // during the session.
+        return true;
+    }
+
+    @Override
+    protected void restoreLastSelectedTab() {
+        if (!mInitialized) {
+            // During startup from onCreate(), initialize() will handle selecting the startup tab.
+            // If this here is called afterwards, it's a no-op anyway. If for some reason
+            // (e.g. debugging) initialize() takes longer than usual and hasn't finished by the time
+            // onResume() runs and calls us, we just exit early so as not to interfere.
+            return;
+        }
+
+        final Tabs tabs = Tabs.getInstance();
+        final Tab tabToSelect = tabs.getTab(mLastSelectedTabId);
+
+        // If the tab we've stored is still existing and valid select it...
+        if (tabToSelect != null && GeckoApplication.getSessionUUID().equals(mLastSessionUUID) &&
+                tabs.currentActivityMatchesTab(tabToSelect)) {
+            tabs.selectTab(mLastSelectedTabId);
+        } else {
+            // ... otherwise fall back to the intent data and open a new tab.
+            loadTabFromIntent(new SafeIntent(getIntent()));
+        }
+    }
+
+    private void loadTabFromIntent(final SafeIntent intent) {
+        final int flags = getNewTabFlags();
+        loadStartupTab(getIntentURI(intent), intent, flags);
+    }
+
+    /**
+     * @return True if we're going to select an existing tab, false if we want to load a new tab.
+     */
+    private boolean decideTabAction(@NonNull final SafeIntent intent,
+                                    @Nullable final Bundle savedInstanceState) {
+        final Tabs tabs = Tabs.getInstance();
+
+        if (hasGeckoTab(intent)) {
+            final Tab tabToSelect = tabs.getTab(intent.getIntExtra(INTENT_EXTRA_TAB_ID, INVALID_TAB_ID));
+            if (tabs.currentActivityMatchesTab(tabToSelect)) {
+                // Nothing further to do here, GeckoApp will select the correct
+                // tab from the intent.
+                return true;
+            }
+        }
+        // The intent doesn't refer to a valid tab, so don't pass that data on.
+        intent.getUnsafe().removeExtra(INTENT_EXTRA_TAB_ID);
+        intent.getUnsafe().removeExtra(INTENT_EXTRA_SESSION_UUID);
+        // The tab data in the intent can become stale if we've been killed, or have
+        // closed the tab/changed its type since the original intent.
+        // We therefore attempt to fall back to the last selected tab. In onNewIntent,
+        // we can directly use the stored data, otherwise we'll look for it in the
+        // savedInstanceState.
+        final int lastSelectedTabId;
+        final String lastSessionUUID;
+
+        if (savedInstanceState != null) {
+            lastSelectedTabId = savedInstanceState.getInt(LAST_SELECTED_TAB);
+            lastSessionUUID = savedInstanceState.getString(LAST_SESSION_UUID);
+        } else {
+            lastSelectedTabId = mLastSelectedTabId;
+            lastSessionUUID = mLastSessionUUID;
+        }
+
+        final Tab tabToSelect = tabs.getTab(lastSelectedTabId);
+        if (tabToSelect != null && GeckoApplication.getSessionUUID().equals(lastSessionUUID) &&
+                tabs.currentActivityMatchesTab(tabToSelect)) {
+            intent.getUnsafe().putExtra(INTENT_EXTRA_TAB_ID, lastSelectedTabId);
+            intent.getUnsafe().putExtra(INTENT_EXTRA_SESSION_UUID, lastSessionUUID);
+            return true;
+        }
+
+        // If we end up here, this means that there's no suitable tab we can take over.
+        // Instead, we'll just open a new tab from the data specified in the intent.
+        return false;
+    }
+
+    @Override
+    protected void onDone() {
+        // Our startup logic should be robust enough to cope with it's tab having been closed even
+        // though the activity might survive, so we don't have to call finish() just to make sure
+        // that a new tab is opened in that case. This also has the advantage that we'll remain in
+        // memory as long as the low-memory killer permits, so we can potentially avoid a costly
+        // re-startup of Gecko if the user returns to us soon.
+        moveTaskToBack(true);
+    }
+
+    /**
+     * For us here, mLastSelectedTabId/Hash will hold the tab that will be selected when the
+     * activity is resumed/recreated, unless
+     * - it has been explicitly overridden through an intent
+     * - the tab cannot be found, in which case the URI passed as intent data will instead be
+     *   opened in a new tab.
+     * Therefore, we only update the stored tab data from those two locations.
+     */
+
+    /**
+     * Called when an intent or onResume() has caused us to load and select a new tab.
+     *
+     * @param tab The new tab that has been opened and selected.
+     */
+    @Override
+    protected void onTabOpenFromIntent(Tab tab) {
+        mLastSelectedTabId = tab.getId();
+        mLastSessionUUID = GeckoApplication.getSessionUUID();
+    }
+
+    /**
+     * Called when an intent has caused us to select an already existing tab.
+     *
+     * @param tab The already existing tab that has been selected for this activity.
+     */
+    @Override
+    protected void onTabSelectFromIntent(Tab tab) {
+        mLastSelectedTabId = tab.getId();
+        mLastSessionUUID = GeckoApplication.getSessionUUID();
+    }
+}
--- a/mobile/android/base/java/org/mozilla/gecko/Tab.java
+++ b/mobile/android/base/java/org/mozilla/gecko/Tab.java
@@ -15,16 +15,17 @@ import org.mozilla.gecko.annotation.Robo
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.db.URLMetadata;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.icons.IconCallback;
 import org.mozilla.gecko.icons.IconDescriptor;
 import org.mozilla.gecko.icons.IconRequestBuilder;
 import org.mozilla.gecko.icons.IconResponse;
 import org.mozilla.gecko.icons.Icons;
+import org.mozilla.gecko.mozglue.SafeIntent;
 import org.mozilla.gecko.reader.ReaderModeUtils;
 import org.mozilla.gecko.reader.ReadingListHelper;
 import org.mozilla.gecko.toolbar.BrowserToolbar.TabEditingState;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.widget.SiteLogins;
 
 import android.content.ContentResolver;
@@ -53,17 +54,19 @@ public class Tab {
     private Bitmap mFavicon;
     private String mFaviconUrl;
     private String mApplicationId; // Intended to be null after explicit user action.
 
     private IconRequestBuilder mIconRequestBuilder;
     private Future<IconResponse> mRunningIconRequest;
 
     private boolean mHasFeeds;
+    private SafeIntent mCustomTabIntent;
     private String mManifestUrl;
+    private String mManifestPath;
     private boolean mHasOpenSearch;
     private final SiteIdentity mSiteIdentity;
     private SiteLogins mSiteLogins;
     private BitmapDrawable mThumbnail;
     private volatile int mParentId;
     // Indicates the url was loaded from a source external to the app. This will be cleared
     // when the user explicitly loads a new url (e.g. clicking a link is not explicit).
     private final boolean mExternal;
@@ -292,20 +295,32 @@ public class Tab {
     public synchronized String getFaviconURL() {
         return mFaviconUrl;
     }
 
     public boolean hasFeeds() {
         return mHasFeeds;
     }
 
+    public SafeIntent getCustomTabIntent() {
+        return mCustomTabIntent;
+    }
+
     public String getManifestUrl() {
         return mManifestUrl;
     }
 
+    /**
+     * @return If not empty, the path to a locally installed copy of the Progressive Web App
+     *         manifest file for this tab.
+     */
+    public String getManifestPath() {
+        return mManifestPath;
+    }
+
     public boolean hasOpenSearch() {
         return mHasOpenSearch;
     }
 
     public boolean hasLoadedFromCache() {
         return mLoadedFromCache;
     }
 
@@ -465,20 +480,28 @@ public class Tab {
         mFavicon = null;
         mFaviconUrl = null;
     }
 
     public void setHasFeeds(boolean hasFeeds) {
         mHasFeeds = hasFeeds;
     }
 
+    public void setCustomTabIntent(SafeIntent intent) {
+        mCustomTabIntent = intent;
+    }
+
     public void setManifestUrl(String manifestUrl) {
         mManifestUrl = manifestUrl;
     }
 
+    public void setManifestPath(String manifestPath) {
+        mManifestPath = manifestPath;
+    }
+
     public void setHasOpenSearch(boolean hasOpenSearch) {
         mHasOpenSearch = hasOpenSearch;
     }
 
     public void setLoadedFromCache(boolean loadedFromCache) {
         mLoadedFromCache = loadedFromCache;
     }
 
--- a/mobile/android/base/java/org/mozilla/gecko/Tabs.java
+++ b/mobile/android/base/java/org/mozilla/gecko/Tabs.java
@@ -1,40 +1,51 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * 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/. */
 
 package org.mozilla.gecko;
 
+import java.io.File;
+import java.io.IOException;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import android.app.Activity;
+import android.content.Intent;
 import android.content.SharedPreferences;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 
+import org.json.JSONException;
+import org.json.JSONObject;
 import org.mozilla.gecko.annotation.JNITarget;
 import org.mozilla.gecko.annotation.RobocopTarget;
+import org.mozilla.gecko.customtabs.CustomTabsActivity;
+import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.distribution.PartnerBrowserCustomizationsClient;
 import org.mozilla.gecko.gfx.LayerView;
 import org.mozilla.gecko.mozglue.SafeIntent;
 import org.mozilla.gecko.notifications.WhatsNewReceiver;
 import org.mozilla.gecko.preferences.GeckoPreferences;
 import org.mozilla.gecko.reader.ReaderModeUtils;
 import org.mozilla.gecko.util.BundleEventListener;
 import org.mozilla.gecko.util.EventCallback;
+import org.mozilla.gecko.util.FileUtils;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.JavaUtil;
 import org.mozilla.gecko.util.ThreadUtils;
+import org.mozilla.gecko.webapps.WebAppActivity;
+import org.mozilla.gecko.webapps.WebAppIndexer;
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.accounts.OnAccountsUpdateListener;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.ContentObserver;
 import android.database.sqlite.SQLiteException;
@@ -47,17 +58,20 @@ import android.support.v4.content.Contex
 import android.text.TextUtils;
 import android.util.Log;
 
 import static org.mozilla.gecko.Tab.TabType;
 
 public class Tabs implements BundleEventListener {
     private static final String LOGTAG = "GeckoTabs";
 
+    public static final String INTENT_EXTRA_TAB_ID = "TabId";
+    public static final String INTENT_EXTRA_SESSION_UUID = "SessionUUID";
     private static final String PRIVATE_TAB_INTENT_EXTRA = "private_tab";
+
     // mOrder and mTabs are always of the same cardinality, and contain the same values.
     private volatile CopyOnWriteArrayList<Tab> mOrder = new CopyOnWriteArrayList<Tab>();
 
     // A cache that maps a tab ID to an mOrder tab position.  All access should be synchronized.
     private final TabPositionCache tabPositionCache = new TabPositionCache();
 
     // All writes to mSelectedTab must be synchronized on the Tabs instance.
     // In general, it's preferred to always use selectTab()).
@@ -216,17 +230,17 @@ public class Tabs implements BundleEvent
     }
 
     public int isOpen(String url) {
         for (Tab tab : mOrder) {
             if (tab.getURL().equals(url)) {
                 return tab.getId();
             }
         }
-        return -1;
+        return INVALID_TAB_ID;
     }
 
     // Must be synchronized to avoid racing on mBookmarksContentObserver.
     private void lazyRegisterBookmarkObserver() {
         if (mBookmarksContentObserver == null) {
             mBookmarksContentObserver = new ContentObserver(null) {
                 @Override
                 public void onChange(boolean selfChange) {
@@ -294,28 +308,40 @@ public class Tabs implements BundleEvent
             Tab tab = getTab(id);
             mOrder.remove(tab);
             mTabs.remove(id);
             tabPositionCache.mTabId = INVALID_TAB_ID;
         }
     }
 
     public synchronized Tab selectTab(int id) {
+        return selectTab(id, true);
+    }
+
+    public synchronized Tab selectTab(int id, boolean switchActivities) {
         if (!mTabs.containsKey(id))
             return null;
 
         final Tab oldTab = getSelectedTab();
         final Tab tab = mTabs.get(id);
 
         // This avoids a NPE below, but callers need to be careful to
         // handle this case.
         if (tab == null || oldTab == tab) {
             return tab;
         }
 
+        if (switchActivities && oldTab != null && oldTab.getType() != tab.getType() &&
+                !currentActivityMatchesTab(tab)) {
+            // We're in the wrong activity for this kind of tab, so launch the correct one
+            // and then try again.
+            launchActivityForTab(tab);
+            return tab;
+        }
+
         mSelectedTab = tab;
         notifyListeners(tab, TabEvents.SELECTED);
 
         if (mLayerView != null) {
             mLayerView.setClearColor(getTabColor(tab));
         }
 
         if (oldTab != null) {
@@ -324,25 +350,118 @@ public class Tabs implements BundleEvent
 
         // Pass a message to Gecko to update tab state in BrowserApp.
         final GeckoBundle data = new GeckoBundle(1);
         data.putInt("id", tab.getId());
         EventDispatcher.getInstance().dispatch("Tab:Selected", data);
         return tab;
     }
 
+    /**
+     * Check whether the currently active activity matches the tab type of the passed tab.
+     */
+    public boolean currentActivityMatchesTab(Tab tab) {
+        final Activity currentActivity = GeckoActivityMonitor.getInstance().getCurrentActivity();
+
+        if (currentActivity == null) {
+            return false;
+        }
+        String currentActivityName = currentActivity.getClass().getName();
+        return currentActivityName.equals(getClassNameForTab(tab));
+    }
+
+    private void launchActivityForTab(Tab tab) {
+        final Intent intent;
+        switch (tab.getType()) {
+            case CUSTOMTAB:
+                if (tab.getCustomTabIntent() != null) {
+                    intent = tab.getCustomTabIntent().getUnsafe();
+                } else {
+                    intent = new Intent(Intent.ACTION_VIEW);
+                    intent.setData(Uri.parse(tab.getURL()));
+                }
+                break;
+            case WEBAPP:
+                intent = new Intent(GeckoApp.ACTION_WEBAPP);
+                final String manifestPath = tab.getManifestPath();
+                try {
+                    intent.setData(getStartUriFromManifest(manifestPath));
+                } catch (IOException | JSONException e) {
+                    Log.e(LOGTAG, "Failed to get start URI from manifest", e);
+                    intent.setData(Uri.parse(tab.getURL()));
+                }
+                intent.putExtra(WebAppActivity.MANIFEST_PATH, manifestPath);
+                break;
+            default:
+                intent = new Intent(GeckoApp.ACTION_SWITCH_TAB);
+                break;
+        }
+
+        intent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, getClassNameForTab(tab));
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(BrowserContract.SKIP_TAB_QUEUE_FLAG, true);
+        intent.putExtra(INTENT_EXTRA_TAB_ID, tab.getId());
+        intent.putExtra(INTENT_EXTRA_SESSION_UUID, GeckoApplication.getSessionUUID());
+        mAppContext.startActivity(intent);
+    }
+
+    // TODO: When things have settled down a bit, we should split this and everything similar
+    // TODO: in the WebAppActivity into a dedicated WebAppManifest class (bug 1353868).
+    private Uri getStartUriFromManifest(String manifestPath) throws IOException, JSONException {
+        File manifestFile = new File(manifestPath);
+        final JSONObject manifest = FileUtils.readJSONObjectFromFile(manifestFile);
+        final JSONObject manifestField = manifest.getJSONObject("manifest");
+
+        return Uri.parse(manifestField.getString("start_url"));
+    }
+
+    /**
+     * Get the class name of the activity that should be displaying this tab.
+     */
+    private String getClassNameForTab(Tab tab) {
+        TabType type = tab.getType();
+
+        switch (type) {
+            case CUSTOMTAB:
+                return CustomTabsActivity.class.getName();
+            case WEBAPP:
+                final int index =  WebAppIndexer.getInstance().getIndexForManifest(
+                        tab.getManifestPath(), mAppContext);
+                return WebAppIndexer.WEBAPP_CLASS + index;
+            default:
+                return AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS;
+        }
+    }
+
     public synchronized boolean selectLastTab() {
         if (mOrder.isEmpty()) {
             return false;
         }
 
         selectTab(mOrder.get(mOrder.size() - 1).getId());
         return true;
     }
 
+    public synchronized boolean selectLastTab(Tab.TabType targetType) {
+        if (mOrder.isEmpty()) {
+            return false;
+        }
+
+        Tab tabToSelect = mOrder.get(mOrder.size() - 1);
+        if (tabToSelect.getType() != targetType) {
+            tabToSelect = getPreviousTabFrom(tabToSelect, false, targetType);
+            if (tabToSelect == null) {
+                return false;
+            }
+        }
+
+        selectTab(tabToSelect.getId());
+        return true;
+    }
+
     private int getIndexOf(Tab tab) {
         return mOrder.lastIndexOf(tab);
     }
 
     private Tab getNextTabFrom(Tab tab, boolean getPrivate, TabType type) {
         int numTabs = mOrder.size();
         int index = getIndexOf(tab);
         for (int i = index + 1; i < numTabs; i++) {
@@ -384,17 +503,17 @@ public class Tabs implements BundleEvent
 
     public boolean isSelectedTabId(int tabId) {
         final Tab selected = mSelectedTab;
         return selected != null && selected.getId() == tabId;
     }
 
     @RobocopTarget
     public synchronized Tab getTab(int id) {
-        if (id == -1)
+        if (id == INVALID_TAB_ID)
             return null;
 
         if (mTabs.size() == 0)
             return null;
 
         if (!mTabs.containsKey(id))
            return null;
 
@@ -416,37 +535,43 @@ public class Tabs implements BundleEvent
     }
 
     /** Close tab and then select the default next tab */
     @RobocopTarget
     public synchronized void closeTab(Tab tab) {
         closeTab(tab, getNextTab(tab));
     }
 
+    /** Don't switch activities even if the default next tab is of a different tab type */
+    public synchronized void closeTabNoActivitySwitch(Tab tab) {
+        closeTab(tab, getNextTab(tab), false, false);
+    }
+
     public synchronized void closeTab(Tab tab, Tab nextTab) {
-        closeTab(tab, nextTab, false);
+        closeTab(tab, nextTab, false, true);
     }
 
     public synchronized void closeTab(Tab tab, boolean showUndoToast) {
-        closeTab(tab, getNextTab(tab), showUndoToast);
+        closeTab(tab, getNextTab(tab), showUndoToast, true);
     }
 
     /** Close tab and then select nextTab */
-    public synchronized void closeTab(final Tab tab, Tab nextTab, boolean showUndoToast) {
+    public synchronized void closeTab(final Tab tab, Tab nextTab,
+                                      boolean showUndoToast, boolean switchActivities) {
         if (tab == null)
             return;
 
         int tabId = tab.getId();
         removeTab(tabId);
 
         if (nextTab == null) {
             nextTab = loadUrl(getHomepageForNewTab(mAppContext), LOADURL_NEW_TAB);
         }
 
-        selectTab(nextTab.getId());
+        selectTab(nextTab.getId(), switchActivities);
 
         tab.onDestroy();
 
         // Pass a message to Gecko to update tab state in BrowserApp
         final GeckoBundle data = new GeckoBundle(2);
         data.putInt("tabId", tabId);
         data.putBoolean("showUndoToast", showUndoToast);
         EventDispatcher.getInstance().dispatch("Tab:Closed", data);
@@ -547,17 +672,17 @@ public class Tabs implements BundleEvent
                         notifyListeners(tab, TabEvents.LOCATION_CHANGE, tab.getURL());
                     }
                 }
             });
             return;
         }
 
         // All other events handled below should contain a tabID property
-        final int id = message.getInt("tabID", -1);
+        final int id = message.getInt("tabID", INVALID_TAB_ID);
         Tab tab = getTab(id);
 
         // "Tab:Added" is a special case because tab will be null if the tab was just added
         if ("Tab:Added".equals(event)) {
             String url = message.getString("uri");
 
             if (message.getBoolean("cancelEditMode")) {
                 final Tab oldTab = getSelectedTab();
@@ -686,17 +811,17 @@ public class Tabs implements BundleEvent
             if (status.equals("resume")) {
                 notifyListeners(tab, TabEvents.MEDIA_PLAYING_RESUME);
             } else {
                 tab.setIsMediaPlaying(status.equals("start"));
                 notifyListeners(tab, TabEvents.MEDIA_PLAYING_CHANGE);
             }
 
         } else if ("Tab:SetParentId".equals(event)) {
-            tab.setParentId(message.getInt("parentID", -1));
+            tab.setParentId(message.getInt("parentID", INVALID_TAB_ID));
         }
     }
 
     public void refreshThumbnails() {
         final BrowserDB db = BrowserDB.from(mAppContext);
         ThreadUtils.postToBackgroundThread(new Runnable() {
             @Override
             public void run() {
@@ -926,43 +1051,43 @@ public class Tabs implements BundleEvent
      *
      * @param url   URL of page to load
      * @param flags flags used to load tab
      *
      * @return      the Tab if a new one was created; null otherwise
      */
     @RobocopTarget
     public Tab loadUrl(String url, int flags) {
-        return loadUrl(url, null, -1, null, flags);
+        return loadUrl(url, null, INVALID_TAB_ID, null, flags);
     }
 
     public Tab loadUrlWithIntentExtras(final String url, final SafeIntent intent, final int flags) {
         // We can't directly create a listener to tell when the user taps on the "What's new"
         // notification, so we use this intent handling as a signal that they tapped the notification.
         if (intent.getBooleanExtra(WhatsNewReceiver.EXTRA_WHATSNEW_NOTIFICATION, false)) {
             Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.NOTIFICATION,
                     WhatsNewReceiver.EXTRA_WHATSNEW_NOTIFICATION);
         }
 
         // Note: we don't get the URL from the intent so the calling
         // method has the opportunity to change the URL if applicable.
-        return loadUrl(url, null, -1, intent, flags);
+        return loadUrl(url, null, INVALID_TAB_ID, intent, flags);
     }
 
     public Tab loadUrl(final String url, final String searchEngine, final int parentId, final int flags) {
         return loadUrl(url, searchEngine, parentId, null, flags);
     }
 
     /**
      * Loads a tab with the given URL.
      *
      * @param url          URL of page to load, or search term used if searchEngine is given
      * @param searchEngine if given, the search engine with this name is used
      *                     to search for the url string; if null, the URL is loaded directly
-     * @param parentId     ID of this tab's parent, or -1 if it has no parent
+     * @param parentId     ID of this tab's parent, or INVALID_TAB_ID (-1) if it has no parent
      * @param intent       an intent whose extras are used to modify the request
      * @param flags        flags used to load tab
      *
      * @return             the Tab if a new one was created; null otherwise
      */
     public Tab loadUrl(final String url, final String searchEngine, final int parentId,
                    final SafeIntent intent, final int flags) {
         final GeckoBundle data = new GeckoBundle();
@@ -1036,16 +1161,26 @@ public class Tabs implements BundleEvent
             String tabUrl = (url != null && Uri.parse(url).getScheme() != null) ? url : null;
 
             // Add the new tab to the end of the tab order.
             final int tabIndex = NEW_LAST_INDEX;
 
             tabToSelect = addTab(tabId, tabUrl, external, parentId, url, isPrivate, tabIndex, type);
             tabToSelect.setDesktopMode(desktopMode);
             tabToSelect.setApplicationId(applicationId);
+            if (intent != null) {
+                if (customTab) {
+                    // The intent can contain all sorts of customisations, so we save it in case
+                    // we need to launch a new custom tab activity for this tab.
+                    tabToSelect.setCustomTabIntent(intent);
+                }
+                if (intent.hasExtra(WebAppActivity.MANIFEST_PATH)) {
+                    tabToSelect.setManifestPath(intent.getStringExtra(WebAppActivity.MANIFEST_PATH));
+                }
+            }
             if (isFirstShownAfterActivityUnhidden) {
                 // We just opened Firefox so we want to show
                 // the toolbar but not animate it to avoid jank.
                 tabToSelect.setShouldShowToolbarWithoutAnimationOnFirstSelection(true);
             }
         }
 
         EventDispatcher.getInstance().dispatch("Tab:Load", data);
@@ -1061,16 +1196,20 @@ public class Tabs implements BundleEvent
         // Load favicon instantly for about:home page because it's already cached
         if (AboutPages.isBuiltinIconPage(url)) {
             tabToSelect.loadFavicon();
         }
 
         return tabToSelect;
     }
 
+    /**
+     * Opens a new tab and loads either about:home or, if PREFS_HOMEPAGE_FOR_EVERY_NEW_TAB is set,
+     * the user's homepage.
+     */
     public Tab addTab() {
         return loadUrl(getHomepageForNewTab(mAppContext), Tabs.LOADURL_NEW_TAB);
     }
 
     public Tab addPrivateTab() {
         return loadUrl(AboutPages.PRIVATEBROWSING, Tabs.LOADURL_NEW_TAB | Tabs.LOADURL_PRIVATE);
     }
 
@@ -1093,17 +1232,17 @@ public class Tabs implements BundleEvent
                 selectTab(tab.getId());
                 return;
             }
         }
 
         // getSelectedTab() can return null if no tab has been created yet
         // (i.e., we're restoring a session after a crash). In these cases,
         // don't mark any tabs as a parent.
-        int parentId = -1;
+        int parentId = INVALID_TAB_ID;
         int flags = LOADURL_NEW_TAB;
 
         final Tab selectedTab = getSelectedTab();
         if (selectedTab != null) {
             parentId = selectedTab.getId();
             if (selectedTab.isPrivate()) {
                 flags = flags | LOADURL_PRIVATE;
             }
@@ -1227,30 +1366,45 @@ public class Tabs implements BundleEvent
         final GeckoBundle data = new GeckoBundle();
         data.putInt("fromTabId", fromTabId);
         data.putInt("fromPosition", fromPosition);
         data.putInt("toTabId", toTabId);
         data.putInt("toPosition", toPosition);
         EventDispatcher.getInstance().dispatch("Tab:Move", data);
     }
 
+    /**
+     * @return True if the homepage preference is not empty.
+     */
+    public static boolean hasHomepage(Context context) {
+        return !TextUtils.isEmpty(getHomepage(context));
+    }
+
+    /**
+     * Note: For opening a new tab while respecting the user's preferences, just use
+     *       {@link Tabs#addTab()} instead.
+     *
+     * @return The user's homepage (falling back to about:home) if PREFS_HOMEPAGE_FOR_EVERY_NEW_TAB
+     *         is enabled, or else about:home.
+     */
     @NonNull
-    public static String getHomepageForNewTab(Context context) {
+    private static String getHomepageForNewTab(Context context) {
         final SharedPreferences preferences = GeckoSharedPrefs.forApp(context);
         final boolean forEveryNewTab = preferences.getBoolean(GeckoPreferences.PREFS_HOMEPAGE_FOR_EVERY_NEW_TAB, false);
 
-        if (forEveryNewTab) {
-            final String homePage = getHomepage(context);
-            if (TextUtils.isEmpty(homePage)) {
-                return AboutPages.HOME;
-            } else {
-                return homePage;
-            }
-        }
-        return AboutPages.HOME;
+        return forEveryNewTab ? getHomepageForStartupTab(context) : AboutPages.HOME;
+    }
+
+    /**
+     * @return The user's homepage, or about:home if none is set.
+     */
+    @NonNull
+    public static String getHomepageForStartupTab(Context context) {
+        final String homepage = Tabs.getHomepage(context);
+        return TextUtils.isEmpty(homepage) ? AboutPages.HOME : homepage;
     }
 
     @Nullable
     public static String getHomepage(Context context) {
         final SharedPreferences preferences = GeckoSharedPrefs.forProfile(context);
         final String homepagePreference = preferences.getString(GeckoPreferences.PREFS_HOMEPAGE, AboutPages.HOME);
 
         final boolean readFromPartnerProvider = preferences.getBoolean(
--- a/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
@@ -27,145 +27,158 @@ import android.text.TextUtils;
 import android.util.Log;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.View;
 import android.widget.ProgressBar;
 
 import org.mozilla.gecko.EventDispatcher;
-import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.R;
+import org.mozilla.gecko.SingleTabActivity;
 import org.mozilla.gecko.SnackbarBuilder;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.gfx.DynamicToolbarAnimator;
 import org.mozilla.gecko.gfx.DynamicToolbarAnimator.PinReason;
 import org.mozilla.gecko.menu.GeckoMenu;
 import org.mozilla.gecko.menu.GeckoMenuInflater;
+import org.mozilla.gecko.mozglue.SafeIntent;
 import org.mozilla.gecko.util.Clipboard;
 import org.mozilla.gecko.util.ColorUtil;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.IntentUtils;
 import org.mozilla.gecko.widget.ActionModePresenter;
 import org.mozilla.gecko.widget.GeckoPopupMenu;
 
 import java.util.List;
 
-public class CustomTabsActivity extends GeckoApp implements Tabs.OnTabsChangedListener {
+import static org.mozilla.gecko.Tabs.TabEvents;
+
+public class CustomTabsActivity extends SingleTabActivity implements Tabs.OnTabsChangedListener {
+
     private static final String LOGTAG = "CustomTabsActivity";
-    private static final String SAVED_START_INTENT = "saved_intent_which_started_this_activity";
 
     private final SparseArrayCompat<PendingIntent> menuItemsIntent = new SparseArrayCompat<>();
     private GeckoPopupMenu popupMenu;
     private View doorhangerOverlay;
     private ActionBarPresenter actionBarPresenter;
     private ProgressBar mProgressView;
     // A state to indicate whether this activity is finishing with customize animation
     private boolean usingCustomAnimation = false;
 
-    // Bug 1351605 - getIntent() not always returns the intent which started this activity.
-    // Therefore we make a copy in case of this Activity is re-created.
-    private Intent startIntent;
-
     private MenuItem menuItemControl;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        if (savedInstanceState != null) {
-            startIntent = savedInstanceState.getParcelable(SAVED_START_INTENT);
-        } else {
-            sendTelemetry();
-            startIntent = getIntent();
-            final String host = getReferrerHost();
-            recordCustomTabUsage(host);
-        }
+        final SafeIntent intent = new SafeIntent(getIntent());
 
         setThemeFromToolbarColor();
 
         doorhangerOverlay = findViewById(R.id.custom_tabs_doorhanger_overlay);
 
         mProgressView = (ProgressBar) findViewById(R.id.page_progress);
         final Toolbar toolbar = (Toolbar) findViewById(R.id.actionbar);
         setSupportActionBar(toolbar);
         final ActionBar actionBar = getSupportActionBar();
         bindNavigationCallback(toolbar);
 
         actionBarPresenter = new ActionBarPresenter(actionBar);
-        actionBarPresenter.displayUrlOnly(startIntent.getDataString());
-        actionBarPresenter.setBackgroundColor(IntentUtil.getToolbarColor(startIntent), getWindow());
+        actionBarPresenter.displayUrlOnly(intent.getDataString());
+        actionBarPresenter.setBackgroundColor(IntentUtil.getToolbarColor(intent), getWindow());
         actionBarPresenter.setTextLongClickListener(new UrlCopyListener());
 
         Tabs.registerOnTabsChangedListener(this);
     }
 
+    @Override
+    protected void onTabOpenFromIntent(Tab tab) {
+        super.onTabOpenFromIntent(tab);
+
+        final String host = getReferrerHost();
+        recordCustomTabUsage(host);
+        sendTelemetry();
+    }
+
+    @Override
+    protected void onTabSelectFromIntent(Tab tab) {
+        super.onTabSelectFromIntent(tab);
+
+        // We already listen for SELECTED events, but if the activity has been destroyed and
+        // subsequently recreated without a different tab having been selected in Gecko in the
+        // meantime, our startup won't trigger a SELECTED event because the selected tab in Gecko
+        // doesn't actually change.
+        actionBarPresenter.update(tab);
+    }
+
     private void sendTelemetry() {
+        final SafeIntent startIntent = new SafeIntent(getIntent());
+
         Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT, "customtab");
         if (IntentUtil.hasToolbarColor(startIntent)) {
             Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT, "customtab-hasToolbarColor");
         }
         if (IntentUtil.hasActionButton(startIntent)) {
             Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT, "customtab-hasActionButton");
         }
         if (IntentUtil.isActionButtonTinted(startIntent)) {
             Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT, "customtab-isActionButtonTinted");
         }
         if (IntentUtil.hasShareItem(startIntent)) {
             Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT, "customtab-hasShareItem");
         }
-
-
     }
 
     private void recordCustomTabUsage(final String host) {
         final GeckoBundle data = new GeckoBundle(1);
         if (host != null) {
             data.putString("client", host);
         } else {
             data.putString("client", "unknown");
         }
         // Pass a message to Gecko to send Telemetry data
         EventDispatcher.getInstance().dispatch("Telemetry:CustomTabsPing", data);
     }
 
     private void setThemeFromToolbarColor() {
-        final int color = ColorUtil.getReadableTextColor(IntentUtil.getToolbarColor(startIntent));
+        final int color = ColorUtil.getReadableTextColor(IntentUtil.getToolbarColor(new SafeIntent(getIntent())));
         @StyleRes final int styleRes = (color == Color.BLACK)
                 ? R.style.GeckoCustomTabs_Light
                 : R.style.GeckoCustomTabs;
 
         setTheme(styleRes);
     }
 
     // Bug 1329145: 3rd party app could specify customized exit-animation to this activity.
     // Activity.overridePendingTransition will invoke getPackageName to retrieve that animation resource.
     // In that case, to return different package name to get customized animation resource.
     @Override
     public String getPackageName() {
         if (usingCustomAnimation) {
             // Use its package name to retrieve animation resource
-            return IntentUtil.getAnimationPackageName(startIntent);
+            return IntentUtil.getAnimationPackageName(new SafeIntent(getIntent()));
         } else {
             return super.getPackageName();
         }
     }
 
     @Override
     public void finish() {
         super.finish();
 
+        final SafeIntent intent = new SafeIntent(getIntent());
         // When 3rd party app launch this Activity, it could also specify custom exit-animation.
-        if (IntentUtil.hasExitAnimation(startIntent)) {
+        if (IntentUtil.hasExitAnimation(intent)) {
             usingCustomAnimation = true;
-            overridePendingTransition(IntentUtil.getEnterAnimationRes(startIntent),
-                    IntentUtil.getExitAnimationRes(startIntent));
+            overridePendingTransition(IntentUtil.getEnterAnimationRes(intent),
+                    IntentUtil.getExitAnimationRes(intent));
             usingCustomAnimation = false;
         }
     }
 
     @Override
     protected int getNewTabFlags() {
         return Tabs.LOADURL_CUSTOMTAB | super.getNewTabFlags();
     }
@@ -182,66 +195,47 @@ public class CustomTabsActivity extends 
     }
 
     @Override
     public View getDoorhangerOverlay() {
         return doorhangerOverlay;
     }
 
     @Override
-    protected void onDone() {
-        finish();
-    }
-
-    @Override
-    public void onTabChanged(Tab tab, Tabs.TabEvents msg, String data) {
-        if (!Tabs.getInstance().isSelectedTab(tab)) {
+    public void onTabChanged(Tab tab, TabEvents msg, String data) {
+        if (!Tabs.getInstance().isSelectedTab(tab) ||
+                tab.getType() != Tab.TabType.CUSTOMTAB) {
             return;
         }
 
-        if (msg == Tabs.TabEvents.START
-                || msg == Tabs.TabEvents.STOP
-                || msg == Tabs.TabEvents.ADDED
-                || msg == Tabs.TabEvents.LOAD_ERROR
-                || msg == Tabs.TabEvents.LOADED
-                || msg == Tabs.TabEvents.LOCATION_CHANGE) {
+        if (msg == TabEvents.START
+                || msg == TabEvents.STOP
+                || msg == TabEvents.ADDED
+                || msg == TabEvents.LOAD_ERROR
+                || msg == TabEvents.LOADED
+                || msg == TabEvents.LOCATION_CHANGE
+                || msg == TabEvents.SELECTED) {
 
             updateProgress((tab.getState() == Tab.STATE_LOADING),
                     tab.getLoadProgress());
         }
 
-        if (msg == Tabs.TabEvents.LOCATION_CHANGE
-                || msg == Tabs.TabEvents.SECURITY_CHANGE
-                || msg == Tabs.TabEvents.TITLE) {
+        if (msg == TabEvents.LOCATION_CHANGE
+                || msg == TabEvents.SECURITY_CHANGE
+                || msg == TabEvents.TITLE
+                || msg == TabEvents.SELECTED) {
             actionBarPresenter.update(tab);
         }
 
         updateMenuItemForward();
     }
 
     @Override
-    protected void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
-        outState.putParcelable(SAVED_START_INTENT, startIntent);
-    }
-
-    @Override
     public void onResume() {
-        if (lastSelectedTabId >= 0) {
-            final Tabs tabs = Tabs.getInstance();
-            final Tab tab = tabs.getTab(lastSelectedTabId);
-            if (tab == null) {
-                finish();
-            } else {
-                // we are restoring
-                actionBarPresenter.update(tab);
-            }
-        }
         super.onResume();
-
         mLayerView.getDynamicToolbarAnimator().setPinned(true, PinReason.CUSTOM_TAB);
     }
 
     @Override
     public void onPause() {
         super.onPause();
         mLayerView.getDynamicToolbarAnimator().setPinned(false, PinReason.CUSTOM_TAB);
     }
@@ -249,20 +243,21 @@ public class CustomTabsActivity extends 
     // Usually should use onCreateOptionsMenu() to initialize menu items. But GeckoApp overwrite
     // it to support custom menu(Bug 739412). Then the parameter *menu* in this.onCreateOptionsMenu()
     // and this.onPrepareOptionsMenu() are different instances - GeckoApp.onCreatePanelMenu() changed it.
     // CustomTabsActivity only use standard menu in ActionBar, so initialize menu here.
     @Override
     public boolean onCreatePanelMenu(final int id, final Menu menu) {
 
         // if 3rd-party app asks to add an action button
-        if (IntentUtil.hasActionButton(startIntent)) {
-            final Bitmap bitmap = IntentUtil.getActionButtonIcon(startIntent);
+        SafeIntent intent = new SafeIntent(getIntent());
+        if (IntentUtil.hasActionButton(intent)) {
+            final Bitmap bitmap = IntentUtil.getActionButtonIcon(intent);
             final Drawable icon = new BitmapDrawable(getResources(), bitmap);
-            final boolean shouldTint = IntentUtil.isActionButtonTinted(startIntent);
+            final boolean shouldTint = IntentUtil.isActionButtonTinted(intent);
             actionBarPresenter.addActionButton(menu, icon, shouldTint)
                     .setOnClickListener(new View.OnClickListener() {
                         @Override
                         public void onClick(View v) {
                             onActionButtonClicked();
                         }
                     });
         }
@@ -345,20 +340,21 @@ public class CustomTabsActivity extends 
             }
         };
     }
 
     private void bindNavigationCallback(@NonNull final Toolbar toolbar) {
         toolbar.setNavigationOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
+                onDone();
                 final Tabs tabs = Tabs.getInstance();
                 final Tab tab = tabs.getSelectedTab();
-                tabs.closeTab(tab);
-                finish();
+                tabs.closeTabNoActivitySwitch(tab);
+                mCheckTabSelectionOnResume = true;
             }
         });
     }
 
     private void performPendingIntent(@NonNull PendingIntent pendingIntent) {
         // bug 1337771: If intent-creator haven't set data url, call send() directly won't work.
         final Intent additional = new Intent();
         final Tab tab = Tabs.getInstance().getSelectedTab();
@@ -374,37 +370,38 @@ public class CustomTabsActivity extends 
      * To generate a popup menu which looks like an ordinary option menu, but have extra elements
      * such as footer.
      *
      * @return a GeckoPopupMenu which can be placed on any view.
      */
     private GeckoPopupMenu createCustomPopupMenu() {
         final GeckoPopupMenu popupMenu = new GeckoPopupMenu(this);
         final GeckoMenu geckoMenu = popupMenu.getMenu();
+        final SafeIntent intent = new SafeIntent(getIntent());
 
         // pass to to Activity.onMenuItemClick for consistency.
         popupMenu.setOnMenuItemClickListener(new GeckoPopupMenu.OnMenuItemClickListener() {
             @Override
             public boolean onMenuItemClick(MenuItem item) {
                 return CustomTabsActivity.this.onMenuItemClick(item);
             }
         });
 
         // to add custom menu items
-        final List<String> titles = IntentUtil.getMenuItemsTitle(startIntent);
-        final List<PendingIntent> intents = IntentUtil.getMenuItemsPendingIntent(startIntent);
+        final List<String> titles = IntentUtil.getMenuItemsTitle(intent);
+        final List<PendingIntent> intents = IntentUtil.getMenuItemsPendingIntent(intent);
         menuItemsIntent.clear();
         for (int i = 0; i < titles.size(); i++) {
             final int menuId = Menu.FIRST + i;
             geckoMenu.add(Menu.NONE, menuId, Menu.NONE, titles.get(i));
             menuItemsIntent.put(menuId, intents.get(i));
         }
 
         // to add share menu item, if necessary
-        if (IntentUtil.hasShareItem(startIntent) && !TextUtils.isEmpty(startIntent.getDataString())) {
+        if (IntentUtil.hasShareItem(intent) && !TextUtils.isEmpty(intent.getDataString())) {
             geckoMenu.add(Menu.NONE, R.id.share, Menu.NONE, getString(R.string.share));
         }
 
         final MenuInflater inflater = new GeckoMenuInflater(this);
         inflater.inflate(R.menu.customtabs_menu, geckoMenu);
 
         // insert default browser name to title of menu-item-Open-In
         final MenuItem openItem = geckoMenu.findItem(R.id.custom_tabs_menu_open_in);
@@ -495,17 +492,17 @@ public class CustomTabsActivity extends 
             intent.setAction(Intent.ACTION_VIEW);
             startActivity(intent);
             finish();
         }
     }
 
     private void onActionButtonClicked() {
         Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.MENU, "customtab-action-button");
-        PendingIntent pendingIntent = IntentUtil.getActionButtonPendingIntent(startIntent);
+        PendingIntent pendingIntent = IntentUtil.getActionButtonPendingIntent(new SafeIntent(getIntent()));
         performPendingIntent(pendingIntent);
     }
 
 
     /**
      * Callback for Share menu item.
      */
     private void onShareClicked() {
--- a/mobile/android/base/java/org/mozilla/gecko/customtabs/IntentUtil.java
+++ b/mobile/android/base/java/org/mozilla/gecko/customtabs/IntentUtil.java
@@ -1,25 +1,26 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * 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/. */
 
 package org.mozilla.gecko.customtabs;
 
 import android.app.PendingIntent;
-import android.content.Intent;
 import android.graphics.Bitmap;
 import android.os.Build;
 import android.os.Bundle;
 import android.support.annotation.ColorInt;
 import android.support.annotation.NonNull;
 import android.support.annotation.VisibleForTesting;
 import android.support.customtabs.CustomTabsIntent;
 
+import org.mozilla.gecko.mozglue.SafeIntent;
+
 import java.util.ArrayList;
 import java.util.List;
 
 /**
  * A utility class for CustomTabsActivity to extract information from intent.
  * For example, this class helps to extract exit-animation resource id.
  */
 class IntentUtil {
@@ -39,225 +40,225 @@ class IntentUtil {
     private static final String KEY_ANIM_EXIT_RES_ID = PREFIX + "animExitRes";
 
     /**
      * To determine whether the intent has necessary information to build an Action-Button.
      *
      * @param intent which to launch a Custom-Tabs-Activity
      * @return true, if intent has all necessary information.
      */
-    static boolean hasActionButton(@NonNull Intent intent) {
+    static boolean hasActionButton(@NonNull SafeIntent intent) {
         return (getActionButtonBundle(intent) != null)
                 && (getActionButtonIcon(intent) != null)
                 && (getActionButtonDescription(intent) != null)
                 && (getActionButtonPendingIntent(intent) != null);
     }
 
     /**
      * To determine whether the intent requires to add share action to menu item.
      *
      * @param intent which to launch a Custom-Tabs-Activity
      * @return true, if intent requires to add share action to menu item.
      */
-    static boolean hasShareItem(@NonNull Intent intent) {
+    static boolean hasShareItem(@NonNull SafeIntent intent) {
         return intent.getBooleanExtra(CustomTabsIntent.EXTRA_DEFAULT_SHARE_MENU_ITEM, false);
     }
 
     /**
      * To extract bitmap icon from intent for Action-Button.
      *
      * @param intent which to launch a Custom-Tabs-Activity
      * @return bitmap icon, if any. Otherwise, null.
      */
-    static Bitmap getActionButtonIcon(@NonNull Intent intent) {
+    static Bitmap getActionButtonIcon(@NonNull SafeIntent intent) {
         final Bundle bundle = getActionButtonBundle(intent);
         return (bundle == null) ? null : (Bitmap) bundle.getParcelable(CustomTabsIntent.KEY_ICON);
     }
 
     /**
      * Only for telemetry to understand caller app's customization
      * This method should only be called once during one usage.
      *
      * @param intent which to launch a Custom-Tabs-Activity
      * @return true, if the caller customized the color.
      */
-    static boolean hasToolbarColor(@NonNull Intent intent) {
+    static boolean hasToolbarColor(@NonNull SafeIntent intent) {
         return intent.hasExtra(CustomTabsIntent.EXTRA_TOOLBAR_COLOR);
     }
 
     /**
      * To extract color code from intent for top toolbar.
      * It also ensure the color is not translucent.
      *
      * @param intent which to launch a Custom-Tabs-Activity
      * @return color code in integer type.
      */
     @ColorInt
-    static int getToolbarColor(@NonNull Intent intent) {
+    static int getToolbarColor(@NonNull SafeIntent intent) {
         @ColorInt int toolbarColor = intent.getIntExtra(CustomTabsIntent.EXTRA_TOOLBAR_COLOR,
                 DEFAULT_ACTION_BAR_COLOR);
 
         // Translucent color does not make sense for toolbar color. Ensure it is 0xFF.
         toolbarColor = 0xFF000000 | toolbarColor;
         return toolbarColor;
     }
 
     /**
      * To extract description from intent for Action-Button. This description is used for
      * accessibility.
      *
      * @param intent which to launch a Custom-Tabs-Activity
      * @return description, if any. Otherwise, null.
      */
-    static String getActionButtonDescription(@NonNull Intent intent) {
+    static String getActionButtonDescription(@NonNull SafeIntent intent) {
         final Bundle bundle = getActionButtonBundle(intent);
         return (bundle == null) ? null : bundle.getString(CustomTabsIntent.KEY_DESCRIPTION);
     }
 
     /**
      * To extract pending-intent from intent for Action-Button.
      *
      * @param intent which to launch a Custom-Tabs-Activity
      * @return PendingIntent, if any. Otherwise, null.
      */
-    static PendingIntent getActionButtonPendingIntent(@NonNull Intent intent) {
+    static PendingIntent getActionButtonPendingIntent(@NonNull SafeIntent intent) {
         final Bundle bundle = getActionButtonBundle(intent);
         return (bundle == null)
                 ? null
                 : (PendingIntent) bundle.getParcelable(CustomTabsIntent.KEY_PENDING_INTENT);
     }
 
     /**
      * To know whether the Action-Button should be tinted.
      *
      * @param intent which to launch a Custom-Tabs-Activity
      * @return true, if Action-Button should be tinted. Default value is false.
      */
-    static boolean isActionButtonTinted(@NonNull Intent intent) {
+    static boolean isActionButtonTinted(@NonNull SafeIntent intent) {
         return intent.getBooleanExtra(CustomTabsIntent.EXTRA_TINT_ACTION_BUTTON, false);
     }
 
     /**
      * To extract extra Action-button bundle from an intent.
      *
      * @param intent which to launch a Custom-Tabs-Activity
      * @return bundle for Action-Button, if any. Otherwise, null.
      */
-    private static Bundle getActionButtonBundle(@NonNull Intent intent) {
+    private static Bundle getActionButtonBundle(@NonNull SafeIntent intent) {
         return intent.getBundleExtra(CustomTabsIntent.EXTRA_ACTION_BUTTON_BUNDLE);
     }
 
     /**
      * To get package name of 3rd-party-app from an intent.
      * If the app defined extra exit-animation to use, it should also provide its package name
      * to get correct animation resource.
      *
      * @param intent which to launch a Custom-Tabs-Activity
      * @return package name, if the intent defined extra exit-animation bundle. Otherwise, null.
      */
-    static String getAnimationPackageName(@NonNull Intent intent) {
+    static String getAnimationPackageName(@NonNull SafeIntent intent) {
         final Bundle bundle = getAnimationBundle(intent);
         return (bundle == null) ? null : bundle.getString(KEY_PACKAGE_NAME);
     }
 
     /**
      * To get titles for Menu Items from an intent.
      * 3rd-party-app is able to add and customize up to five menu items. This method helps to
      * get titles for each menu items.
      *
      * @param intent which to launch a Custom-Tabs-Activity
      * @return A list of string as title for each menu items
      */
-    static List<String> getMenuItemsTitle(@NonNull Intent intent) {
+    static List<String> getMenuItemsTitle(@NonNull SafeIntent intent) {
         final List<Bundle> bundles = getMenuItemsBundle(intent);
         final List<String> titles = new ArrayList<>();
         for (Bundle b : bundles) {
             titles.add(b.getString(CustomTabsIntent.KEY_MENU_ITEM_TITLE));
         }
         return titles;
     }
 
     /**
      * To get pending-intent for Menu Items from an intent.
      * 3rd-party-app is able to add and customize up to five menu items. This method helps to
      * get pending-intent for each menu items.
      *
      * @param intent which to launch a Custom-Tabs-Activity
      * @return A list of pending-intent for each menu items
      */
-    static List<PendingIntent> getMenuItemsPendingIntent(@NonNull Intent intent) {
+    static List<PendingIntent> getMenuItemsPendingIntent(@NonNull SafeIntent intent) {
         final List<Bundle> bundles = getMenuItemsBundle(intent);
         final List<PendingIntent> intents = new ArrayList<>();
         for (Bundle b : bundles) {
             PendingIntent p = b.getParcelable(CustomTabsIntent.KEY_PENDING_INTENT);
             intents.add(p);
         }
         return intents;
     }
 
     /**
      * To check whether the intent has necessary information to apply customize exit-animation.
      *
      * @param intent which to launch a Custom-Tabs-Activity
      * @return true, if the intent has necessary information.
      */
-    static boolean hasExitAnimation(@NonNull Intent intent) {
+    static boolean hasExitAnimation(@NonNull SafeIntent intent) {
         final Bundle bundle = getAnimationBundle(intent);
         return (bundle != null)
                 && (getAnimationPackageName(intent) != null)
                 && (getEnterAnimationRes(intent) != NO_ANIMATION_RESOURCE)
                 && (getExitAnimationRes(intent) != NO_ANIMATION_RESOURCE);
     }
 
     /**
      * To get enter-animation resource id of 3rd-party-app from an intent.
      * If the app defined extra exit-animation to use, it should also provide its animation resource
      * id.
      *
      * @param intent which to launch a Custom-Tabs-Activity
      * @return animation resource id if any; otherwise, NO_ANIMATION_RESOURCE;
      */
-    static int getEnterAnimationRes(@NonNull Intent intent) {
+    static int getEnterAnimationRes(@NonNull SafeIntent intent) {
         final Bundle bundle = getAnimationBundle(intent);
         return (bundle == null)
                 ? NO_ANIMATION_RESOURCE
                 : bundle.getInt(KEY_ANIM_ENTER_RES_ID, NO_ANIMATION_RESOURCE);
     }
 
     /**
      * To get exit-animation resource id of 3rd-party-app from an intent.
      * If the app defined extra exit-animation to use, it should also provide its animation resource
      * id.
      *
      * @param intent which to launch a Custom-Tabs-Activity
      * @return animation resource id if any; otherwise, NO_ANIMATION_RESOURCE.
      */
-    static int getExitAnimationRes(@NonNull Intent intent) {
+    static int getExitAnimationRes(@NonNull SafeIntent intent) {
         final Bundle bundle = getAnimationBundle(intent);
         return (bundle == null)
                 ? NO_ANIMATION_RESOURCE
                 : bundle.getInt(KEY_ANIM_EXIT_RES_ID, NO_ANIMATION_RESOURCE);
     }
 
     /**
      * To extract extra exit-animation bundle from an intent.
      *
      * @param intent which to launch a Custom-Tabs-Activity
      * @return bundle for extra exit-animation, if any. Otherwise, null.
      */
-    private static Bundle getAnimationBundle(@NonNull Intent intent) {
+    private static Bundle getAnimationBundle(@NonNull SafeIntent intent) {
         return intent.getBundleExtra(CustomTabsIntent.EXTRA_EXIT_ANIMATION_BUNDLE);
     }
 
     /**
      * To extract bundles for Menu Items from an intent.
      * 3rd-party-app is able to add and customize up to five menu items. This method helps to
      * extract bundles to build menu items.
      *
      * @param intent which to launch a Custom-Tabs-Activity
      * @return bundle for menu items, if any. Otherwise, an empty list.
      */
-    private static List<Bundle> getMenuItemsBundle(@NonNull Intent intent) {
-        ArrayList<Bundle> extra = intent.getParcelableArrayListExtra(
+    private static List<Bundle> getMenuItemsBundle(@NonNull SafeIntent intent) {
+        ArrayList<Bundle> extra = intent.getUnsafe().getParcelableArrayListExtra(
                 CustomTabsIntent.EXTRA_MENU_ITEMS);
         return (extra == null) ? new ArrayList<Bundle>() : extra;
     }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/db/SuggestedSites.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/SuggestedSites.java
@@ -36,17 +36,19 @@ import org.json.JSONException;
 import org.json.JSONObject;
 import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.GeckoSharedPrefs;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.Locales;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.distribution.Distribution;
 import org.mozilla.gecko.restrictions.Restrictions;
+import org.mozilla.gecko.util.IOUtils;
 import org.mozilla.gecko.util.RawResource;
+import org.mozilla.gecko.util.StringUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.preferences.GeckoPreferences;
 
 /**
  * {@code SuggestedSites} provides API to get a list of locale-specific
  * suggested sites to be used in Fennec's top sites panel. It provides
  * only a single method to fetch the list as a {@code Cursor}. This cursor
  * will then be wrapped by {@code TopSitesCursorWrapper} to blend top,
@@ -263,38 +265,32 @@ public class SuggestedSites {
      */
     static void saveSites(File f, Map<String, Site> sites) {
         ThreadUtils.assertNotOnUiThread();
 
         if (sites == null || sites.isEmpty()) {
             return;
         }
 
-        OutputStreamWriter osw = null;
+        OutputStreamWriter outputStreamWriter = null;
 
         try {
             final JSONArray jsonSites = new JSONArray();
             for (Site site : sites.values()) {
                 jsonSites.put(site.toJSON());
             }
 
-            osw = new OutputStreamWriter(new FileOutputStream(f), "UTF-8");
+            outputStreamWriter = new OutputStreamWriter(new FileOutputStream(f), StringUtils.UTF_8);
 
             final String jsonString = jsonSites.toString();
-            osw.write(jsonString, 0, jsonString.length());
+            outputStreamWriter.write(jsonString, 0, jsonString.length());
         } catch (Exception e) {
             Log.e(LOGTAG, "Failed to save suggested sites", e);
         } finally {
-            if (osw != null) {
-                try {
-                    osw.close();
-                } catch (IOException e) {
-                    // Ignore.
-                }
-            }
+            IOUtils.safeStreamClose(outputStreamWriter);
         }
     }
 
     private void maybeWaitForDistribution() {
         if (distribution == null) {
             return;
         }
 
--- a/mobile/android/base/java/org/mozilla/gecko/distribution/Distribution.java
+++ b/mobile/android/base/java/org/mozilla/gecko/distribution/Distribution.java
@@ -644,17 +644,26 @@ public class Distribution {
 
         final String contentType = connection.getContentType();
         if (contentType == null || !contentType.startsWith(EXPECTED_CONTENT_TYPE)) {
             Log.w(LOGTAG, "Malformed response: invalid Content-Type.");
             Telemetry.addToHistogram(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_FETCH_INVALID_CONTENT_TYPE);
             return null;
         }
 
-        return new JarInputStream(new BufferedInputStream(connection.getInputStream()), true);
+        final BufferedInputStream bufferedInputStream = new BufferedInputStream(connection.getInputStream());
+        try {
+            return new JarInputStream(bufferedInputStream, true);
+        } catch (IOException e) {
+            // Thrown e.g. if JarInputStream can't parse the input as a valid Zip.
+            // In that case we need to ensure the bufferedInputStream gets closed since it won't
+            // be used anywhere (while still passing the Exception up the stack).
+            bufferedInputStream.close();
+            throw e;
+        }
     }
 
     private static void recordFetchTelemetry(final Exception exception) {
         if (exception == null) {
             // Should never happen.
             Telemetry.addToHistogram(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_FETCH_EXCEPTION);
             return;
         }
--- a/mobile/android/base/java/org/mozilla/gecko/home/BookmarksPanel.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/BookmarksPanel.java
@@ -292,26 +292,32 @@ public class BookmarksPanel extends Home
                     && (isRootFolder || mFolderInfo.id <= Bookmarks.FAKE_PARTNER_BOOKMARKS_START)) {
                 partnerCursor = contentResolver.query(PartnerBookmarksProviderProxy.getUriForBookmarks(getContext(), mFolderInfo.id), null, null, null, null);
             }
 
             if (isRootFolder || mFolderInfo.id > Bookmarks.FAKE_PARTNER_BOOKMARKS_START) {
                 userCursor = mDB.getBookmarksInFolder(contentResolver, mFolderInfo.id);
             }
 
-
+            // MergeCursor is only partly capable of handling null cursors, hence the complicated
+            // logic here. The main issue is CursorAdapter always queries the _id column when
+            // swapping a cursor. If you haven't started iterating over the cursor, MergeCursor will
+            // try to fetch columns from the first Cursor in the list - if that item is null,
+            // we can't getColumnIndexOrThrow("_id"), and CursorAdapter crashes.
             if (partnerCursor == null && userCursor == null) {
                 return null;
             } else if (partnerCursor == null) {
                 return userCursor;
             } else if (userCursor == null) {
                 return partnerCursor;
             } else {
-                return new MergeCursor(new Cursor[] { partnerCursor, userCursor });
+                return new MergeCursor(new Cursor[]{ partnerCursor, userCursor });
             }
+
+
         }
 
         @Override
         public void onContentChanged() {
             // Invalidate the cached value that keeps track of whether or
             // not desktop bookmarks exist.
             mDB.invalidate();
             super.onContentChanged();
--- a/mobile/android/base/java/org/mozilla/gecko/media/MediaControlService.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/MediaControlService.java
@@ -437,17 +437,17 @@ public class MediaControlService extends
         final Intent intent = new Intent(getApplicationContext(), MediaControlService.class);
         intent.setAction(action);
         return intent;
     }
 
     private PendingIntent createContentIntent(int tabId) {
         Intent intent = new Intent(getApplicationContext(), BrowserApp.class);
         intent.setAction(GeckoApp.ACTION_SWITCH_TAB);
-        intent.putExtra("TabId", tabId);
+        intent.putExtra(Tabs.INTENT_EXTRA_TAB_ID, tabId);
         return PendingIntent.getActivity(getApplicationContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
     }
 
     private PendingIntent createDeleteIntent() {
         Intent intent = new Intent(getApplicationContext(), MediaControlService.class);
         intent.setAction(ACTION_STOP);
         return  PendingIntent.getService(getApplicationContext(), 1, intent, 0);
     }
--- a/mobile/android/base/java/org/mozilla/gecko/switchboard/SwitchBoard.java
+++ b/mobile/android/base/java/org/mozilla/gecko/switchboard/SwitchBoard.java
@@ -29,16 +29,17 @@ import java.util.Locale;
 import java.util.MissingResourceException;
 import java.util.zip.CRC32;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.json.JSONArray;
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.util.HardwareUtils;
+import org.mozilla.gecko.util.IOUtils;
 import org.mozilla.gecko.util.ProxySelector;
 
 import android.content.Context;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.os.Build;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.text.TextUtils;
@@ -373,37 +374,46 @@ public class SwitchBoard {
     }
 
     /**
      * Returns a String containing the server response from a GET request
      * @param url URL for GET request.
      * @return Returns String from server or null when failed.
      */
     @Nullable private static String readFromUrlGET(URL url) {
+        HttpURLConnection connection = null;
+        InputStreamReader inputStreamReader = null;
+        BufferedReader bufferReader = null;
         try {
-            HttpURLConnection connection = (HttpURLConnection) ProxySelector.openConnectionWithProxy(url.toURI());
+            connection = (HttpURLConnection) ProxySelector.openConnectionWithProxy(url.toURI());
             connection.setRequestProperty("User-Agent", HardwareUtils.isTablet() ?
                     AppConstants.USER_AGENT_FENNEC_TABLET :
                     AppConstants.USER_AGENT_FENNEC_MOBILE);
             connection.setRequestMethod("GET");
             connection.setUseCaches(false);
 
-            InputStream is = connection.getInputStream();
-            InputStreamReader inputStreamReader = new InputStreamReader(is);
-            BufferedReader bufferReader = new BufferedReader(inputStreamReader, 8192);
+            // BufferedReader(Reader, int) can throw, hence we need to keep a separate reference
+            // to the InputStreamReader in order to always be able to close it:
+            inputStreamReader = new InputStreamReader(connection.getInputStream());
+            bufferReader = new BufferedReader(inputStreamReader, 8192);
             String line;
             StringBuilder resultContent = new StringBuilder();
             while ((line = bufferReader.readLine()) != null) {
                 resultContent.append(line);
             }
-            bufferReader.close();
 
             return resultContent.toString();
         } catch (IOException | URISyntaxException e) {
             e.printStackTrace();
+        } finally {
+            IOUtils.safeStreamClose(bufferReader);
+            IOUtils.safeStreamClose(inputStreamReader);
+            if (connection != null) {
+                connection.disconnect();
+            }
         }
 
         return null;
     }
 
     /**
      * Return the bucket number of the user. There are 100 possible buckets.
      */
--- a/mobile/android/base/java/org/mozilla/gecko/updater/UpdateService.java
+++ b/mobile/android/base/java/org/mozilla/gecko/updater/UpdateService.java
@@ -7,16 +7,17 @@ package org.mozilla.gecko.updater;
 
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.CrashHandler;
 import org.mozilla.gecko.R;
 
 import org.mozilla.apache.commons.codec.binary.Hex;
 
 import org.mozilla.gecko.permissions.Permissions;
+import org.mozilla.gecko.util.IOUtils;
 import org.mozilla.gecko.util.ProxySelector;
 import org.w3c.dom.Document;
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 
 import android.Manifest;
 import android.app.AlarmManager;
 import android.app.IntentService;
@@ -43,16 +44,17 @@ import android.util.Log;
 
 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.net.HttpURLConnection;
 import java.net.URI;
 import java.net.URL;
 import java.net.URLConnection;
 import java.security.MessageDigest;
 import java.util.Calendar;
 import java.util.GregorianCalendar;
 import java.util.List;
 import java.util.TimeZone;
@@ -368,26 +370,28 @@ public class UpdateService extends Inten
             builder.setContentText(getString(R.string.updater_apply_select));
             builder.setContentIntent(contentIntent);
 
             mNotificationManager.notify(NOTIFICATION_ID, builder.build());
         }
     }
 
     private UpdateInfo findUpdate(boolean force) {
+        URLConnection conn = null;
         try {
             URI uri = getUpdateURI(force);
 
             if (uri == null) {
               Log.e(LOGTAG, "failed to get update URI");
               return null;
             }
 
             DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
-            Document dom = builder.parse(ProxySelector.openConnectionWithProxy(uri).getInputStream());
+            conn = ProxySelector.openConnectionWithProxy(uri);
+            Document dom = builder.parse(conn.getInputStream());
 
             NodeList nodes = dom.getElementsByTagName("update");
             if (nodes == null || nodes.getLength() == 0)
                 return null;
 
             Node updateNode = nodes.item(0);
             Node buildIdNode = updateNode.getAttributes().getNamedItem("buildID");
             if (buildIdNode == null)
@@ -427,16 +431,24 @@ public class UpdateService extends Inten
                 Log.e(LOGTAG, "missing some required update information, have: " + info);
                 return null;
             }
 
             return info;
         } catch (Exception e) {
             Log.e(LOGTAG, "failed to check for update: ", e);
             return null;
+        } finally {
+            // conn isn't guaranteed to be an HttpURLConnection, hence we don't want to cast earlier
+            // in this method. However in our current implementation it usually is, so we need to
+            // make sure we close it in that case:
+            final HttpURLConnection httpConn = (HttpURLConnection) conn;
+            if (httpConn != null) {
+                httpConn.disconnect();
+            }
         }
     }
 
     private MessageDigest createMessageDigest(String hashFunction) {
         String javaHashFunction = null;
 
         if ("sha512".equalsIgnoreCase(hashFunction)) {
             javaHashFunction = "SHA-512";
@@ -546,29 +558,30 @@ public class UpdateService extends Inten
             deleteUpdatePackage(getLastFileName());
         }
 
         Log.i(LOGTAG, "downloading update package");
         sendCheckUpdateResult(CheckUpdateResult.DOWNLOADING);
 
         OutputStream output = null;
         InputStream input = null;
+        URLConnection conn = null;
 
         mDownloading = true;
         mCancelDownload = false;
         showDownloadNotification(downloadFile);
 
         try {
             NetworkInfo netInfo = mConnectivityManager.getActiveNetworkInfo();
             if (netInfo != null && netInfo.isConnected() &&
                 netInfo.getType() == ConnectivityManager.TYPE_WIFI) {
                 mWifiLock.acquire();
             }
 
-            URLConnection conn = ProxySelector.openConnectionWithProxy(info.uri);
+            conn = ProxySelector.openConnectionWithProxy(info.uri);
             int length = conn.getContentLength();
 
             output = new BufferedOutputStream(new FileOutputStream(downloadFile));
             input = new BufferedInputStream(conn.getInputStream());
 
             byte[] buf = new byte[BUFSIZE];
             int len = 0;
 
@@ -601,31 +614,32 @@ public class UpdateService extends Inten
             }
         } catch (Exception e) {
             downloadFile.delete();
             showDownloadFailure();
 
             Log.e(LOGTAG, "failed to download update: ", e);
             return null;
         } finally {
-            try {
-                if (input != null)
-                    input.close();
-            } catch (java.io.IOException e) { }
-
-            try {
-                if (output != null)
-                    output.close();
-            } catch (java.io.IOException e) { }
+            IOUtils.safeStreamClose(input);
+            IOUtils.safeStreamClose(output);
 
             mDownloading = false;
 
             if (mWifiLock.isHeld()) {
                 mWifiLock.release();
             }
+
+            // conn isn't guaranteed to be an HttpURLConnection, hence we don't want to cast earlier
+            // in this method. However in our current implementation it usually is, so we need to
+            // make sure we close it in that case:
+            final HttpURLConnection httpConn = (HttpURLConnection) conn;
+            if (httpConn != null) {
+                httpConn.disconnect();
+            }
         }
     }
 
     private boolean verifyDownloadedPackage(File updateFile) {
         MessageDigest digest = createMessageDigest(getLastHashFunction());
         if (digest == null)
             return false;
 
--- a/mobile/android/base/java/org/mozilla/gecko/webapps/WebAppActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/webapps/WebAppActivity.java
@@ -7,73 +7,76 @@ package org.mozilla.gecko.webapps;
 
 import java.io.File;
 import java.io.IOException;
 
 import android.app.ActivityManager;
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.net.Uri;
-import android.os.Build;
 import android.os.Bundle;
 import android.support.v7.widget.Toolbar;
 import android.support.v7.app.ActionBar;
+import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
 import android.view.Window;
 import android.view.WindowManager;
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
 import org.json.JSONObject;
 import org.json.JSONException;
 
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.EventDispatcher;
-import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.GeckoProfile;
+import org.mozilla.gecko.SingleTabActivity;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.icons.decoders.FaviconDecoder;
 import org.mozilla.gecko.icons.decoders.LoadFaviconResult;
 import org.mozilla.gecko.mozglue.SafeIntent;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.util.ColorUtil;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.FileUtils;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.widget.AnchoredPopup;
 
-public class WebAppActivity extends GeckoApp {
+import static org.mozilla.gecko.Tabs.TabEvents;
 
-    public static final String INTENT_KEY = "IS_A_WEBAPP";
+public class WebAppActivity extends SingleTabActivity {
+    private static final String LOGTAG = "WebAppActivity";
+
     public static final String MANIFEST_PATH = "MANIFEST_PATH";
-
-    private static final String LOGTAG = "WebAppActivity";
+    private static final String SAVED_INTENT = "savedIntent";
 
     private TextView mUrlView;
     private View doorhangerOverlay;
 
-    private String mManifestPath;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT, "webapp");
+        if ((getIntent().getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0 &&
+        savedInstanceState != null) {
+            // Even though we're a single task activity, Android's task switcher has the
+            // annoying habit of never updating its stored intent after our initial creation,
+            // even if we've been subsequently started with a new intent.
 
-        if (savedInstanceState != null) {
-            mManifestPath = savedInstanceState.getString(WebAppActivity.MANIFEST_PATH, null);
-        } else {
-            mManifestPath = getIntent().getStringExtra(WebAppActivity.MANIFEST_PATH);
+            // This below is needed if we should ever decide to store a custom class as intent extra.
+            savedInstanceState.setClassLoader(getClass().getClassLoader());
+
+            Intent lastLaunchIntent = savedInstanceState.getParcelable(SAVED_INTENT);
+            setIntent(lastLaunchIntent);
         }
-        loadManifest(mManifestPath);
+
+        super.onCreate(savedInstanceState);
 
         final Toolbar toolbar = (Toolbar) findViewById(R.id.actionbar);
         setSupportActionBar(toolbar);
 
         final ProgressBar progressBar = (ProgressBar) findViewById(R.id.page_progress);
         progressBar.setVisibility(View.GONE);
 
         final ActionBar actionBar = getSupportActionBar();
@@ -103,43 +106,50 @@ public class WebAppActivity extends Geck
     @Override
     public int getLayout() {
         return R.layout.customtabs_activity;
     }
 
     @Override
     public void handleMessage(final String event, final GeckoBundle message,
                               final EventCallback callback) {
+        if (message == null ||
+                !message.containsKey("tabId") || message.getInt("tabId") != mLastSelectedTabId) {
+            return;
+        }
+
         switch (event) {
             case "Website:AppEntered":
                 getSupportActionBar().hide();
                 break;
 
             case "Website:AppLeft":
                 getSupportActionBar().show();
                 break;
         }
     }
 
     @Override
     public void onTabChanged(Tab tab, Tabs.TabEvents msg, String data) {
-        if (!Tabs.getInstance().isSelectedTab(tab)) {
+        if (tab == null || !Tabs.getInstance().isSelectedTab(tab) ||
+                tab.getType() != Tab.TabType.WEBAPP) {
             return;
         }
 
-        if (msg == Tabs.TabEvents.LOCATION_CHANGE) {
+        if (msg == TabEvents.LOCATION_CHANGE ||
+                msg == TabEvents.SELECTED) {
             mUrlView.setText(tab.getURL());
         }
     }
 
     @Override
     protected void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
 
-        outState.putString(WebAppActivity.MANIFEST_PATH, mManifestPath);
+        outState.putParcelable(SAVED_INTENT, getIntent());
     }
 
     @Override
     public void onDestroy() {
         super.onDestroy();
         EventDispatcher.getInstance().unregisterUiThreadListener(this,
                 "Website:AppEntered",
                 "Website:AppLeft",
@@ -147,42 +157,54 @@ public class WebAppActivity extends Geck
         Tabs.unregisterOnTabsChangedListener(this);
     }
 
     @Override
     protected int getNewTabFlags() {
         return Tabs.LOADURL_WEBAPP | super.getNewTabFlags();
     }
 
+    @Override
+    protected void onTabOpenFromIntent(Tab tab) {
+        super.onTabOpenFromIntent(tab);
+        Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT, "webapp");
+        loadManifest(tab.getManifestPath());
+    }
+
     /**
-     * In case this activity is reused (the user has opened > 10 current web apps)
-     * we check that app launched is still within the same host as the
-     * shortcut has set, if not we reload the homescreens url
+     * In case this activity and its tab are reused (the user has opened
+     *  > 10 current web apps), we check that app launched is still within
+     * the same host as the intent has set.
+     * If it isn't, we reload the intent URL.
      */
     @Override
-    protected void onNewIntent(Intent externalIntent) {
-
-        restoreLastSelectedTab();
+    protected void onTabSelectFromIntent(Tab tab) {
+        super.onTabSelectFromIntent(tab);
 
-        final SafeIntent intent = new SafeIntent(externalIntent);
+        SafeIntent intent = new SafeIntent(getIntent());
+
         final String launchUrl = intent.getDataString();
-        final String currentUrl = Tabs.getInstance().getSelectedTab().getURL();
+        final String currentUrl = tab.getURL();
         final boolean isSameDomain = Uri.parse(currentUrl).getHost()
                 .equals(Uri.parse(launchUrl).getHost());
 
+        final String manifestPath;
         if (!isSameDomain) {
             Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT, "webapp");
-            mManifestPath = externalIntent.getStringExtra(WebAppActivity.MANIFEST_PATH);
-            loadManifest(mManifestPath);
+            manifestPath = intent.getStringExtra(MANIFEST_PATH);
+            tab.setManifestUrl(manifestPath);
             Tabs.getInstance().loadUrl(launchUrl);
+        } else {
+            manifestPath = tab.getManifestPath();
         }
+        loadManifest(manifestPath);
     }
 
     private void loadManifest(String manifestPath) {
-        if (manifestPath == null) {
+        if (TextUtils.isEmpty(manifestPath)) {
             Log.e(LOGTAG, "Missing manifest");
             return;
         }
         // The customisations defined in the manifest only work on Android API 21+
         if (AppConstants.Versions.preLollipop) {
             return;
         }
 
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -581,16 +581,17 @@ gbjar.sources += ['java/org/mozilla/geck
     'firstrun/FirstrunPager.java',
     'firstrun/FirstrunPagerConfig.java',
     'firstrun/FirstrunPanel.java',
     'firstrun/RestrictedWelcomePanel.java',
     'firstrun/SyncPanel.java',
     'firstrun/TabQueuePanel.java',
     'FormAssistPopup.java',
     'GeckoActivity.java',
+    'GeckoActivityMonitor.java',
     'GeckoActivityStatus.java',
     'GeckoApp.java',
     'GeckoApplication.java',
     'GeckoFontScaleListener.java',
     'GeckoJavaSampler.java',
     'GeckoMessageReceiver.java',
     'GeckoProfilesProvider.java',
     'GeckoService.java',
@@ -815,16 +816,17 @@ gbjar.sources += ['java/org/mozilla/geck
     'restrictions/RestrictionConfiguration.java',
     'restrictions/RestrictionProvider.java',
     'restrictions/Restrictions.java',
     'ScreenshotObserver.java',
     'search/SearchEngine.java',
     'search/SearchEngineManager.java',
     'SessionParser.java',
     'SharedPreferencesHelper.java',
+    'SingleTabActivity.java',
     'SiteIdentity.java',
     'SnackbarBuilder.java',
     'SuggestClient.java',
     'switchboard/AsyncConfigLoader.java',
     'switchboard/DeviceUuidFactory.java',
     'switchboard/Preferences.java',
     'switchboard/Switch.java',
     'switchboard/SwitchBoard.java',
--- a/mobile/android/chrome/content/aboutHealthReport.js
+++ b/mobile/android/chrome/content/aboutHealthReport.js
@@ -119,17 +119,17 @@ var healthReportWrapper = {
     let iframe = document.getElementById("remote-report");
     iframe.contentWindow.postMessage(data, reportUrl);
   },
 
   showSettings: function () {
     console.log("AboutHealthReport: showing settings.");
     EventDispatcher.instance.sendRequest({
       type: "Settings:Show",
-      resource: "preferences_vendor",
+      resource: "preferences_privacy",
     });
   },
 
   launchUpdater: function () {
     console.log("AboutHealthReport: launching updater.");
     EventDispatcher.instance.sendRequest({
       type: "Updater:Launch",
     });
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -2196,22 +2196,26 @@ var BrowserApp = {
 
     return {
       "historyItems": listitems,
       "toIndex": toIndex
     };
   },
 };
 
-async function notifyManifestStatus(browser) {
+async function notifyManifestStatus(tab) {
   try {
-    const manifest = await Manifests.getManifest(browser);
+    const manifest = await Manifests.getManifest(tab.browser);
     const evtType = (manifest && manifest.installed) ?
       "Website:AppEntered" : "Website:AppLeft";
-    GlobalEventDispatcher.sendRequest({type: evtType});
+
+    GlobalEventDispatcher.sendRequest({
+      type: evtType,
+      tabId: tab.id,
+    });
   } catch (err) {
     Cu.reportError("Error sending status: " + err.message);
   }
 }
 
 async function installManifest(browser, data) {
   try {
     const manifest = await Manifests.getManifest(browser, data.manifestUrl);
@@ -3655,17 +3659,18 @@ Tab.prototype = {
       entries: [{
         url: aURL,
         title: truncate(title, MAX_TITLE_LENGTH)
       }],
       index: 1,
       desktopMode: this.desktopMode,
       isPrivate: isPrivate,
       tabId: this.id,
-      parentId: this.parentId
+      parentId: this.parentId,
+      type: this.type
     };
 
     if (aParams.delayLoad) {
       // If this is a zombie tab, mark the browser for delay loading, which will
       // restore the tab when selected using the session data added above
       this.browser.__SS_restore = true;
       this.browser.setAttribute("pending", "true");
     } else {
@@ -4508,17 +4513,17 @@ Tab.prototype = {
       sameDocument: sameDocument,
 
       canGoBack: webNav.canGoBack,
       canGoForward: webNav.canGoForward,
     };
 
     GlobalEventDispatcher.sendRequest(message);
 
-    notifyManifestStatus(this.browser);
+    notifyManifestStatus(this);
 
     if (!sameDocument) {
       // XXX This code assumes that this is the earliest hook we have at which
       // browser.contentDocument is changed to the new document we're loading
       this.contentDocumentIsDisplayed = false;
       this.hasTouchListener = false;
       Services.obs.notifyObservers(this.browser, "Session:NotifyLocationChange");
     }
--- a/mobile/android/components/SessionStore.js
+++ b/mobile/android/components/SessionStore.js
@@ -35,16 +35,19 @@ function log(a) {
   }
   Log.d("SessionStore", a);
 }
 
 // -----------------------------------------------------------------------
 // Session Store
 // -----------------------------------------------------------------------
 
+const INVALID_TAB_ID = -1;
+const INVALID_TAB_INDEX = -1;
+
 const STATE_STOPPED = 0;
 const STATE_RUNNING = 1;
 const STATE_QUITTING = -1;
 const STATE_QUITTING_FLUSHED = -2;
 
 const PREFS_RESTORE_FROM_CRASH = "browser.sessionstore.resume_from_crash";
 const PREFS_MAX_CRASH_RESUMES = "browser.sessionstore.max_resumed_crashes";
 const PREFS_MAX_TABS_UNDO = "browser.sessionstore.max_tabs_undo";
@@ -77,25 +80,25 @@ SessionStore.prototype = {
   _writeInProgress: false,
 
   // We only want to start doing backups if we've successfully
   // written the session data at least once.
   _sessionDataIsGood: false,
 
   // The index where the most recently closed tab was in the tabs array
   // when it was closed.
-  _lastClosedTabIndex: -1,
+  _lastClosedTabIndex: INVALID_TAB_INDEX,
 
   // Whether or not to send notifications for changes to the closed tabs.
   _notifyClosedTabs: false,
 
   // If we're simultaneously closing both a tab and Firefox, we don't want
   // to bother reloading the newly selected tab if it is zombified.
   // The Java UI will tell us which tab to watch out for.
-  _keepAsZombieTabId: -1,
+  _keepAsZombieTabId: INVALID_TAB_ID,
 
   init: function ss_init() {
     loggingEnabled = Services.prefs.getBoolPref("browser.sessionstore.debug_logging");
 
     // Get file references
     this._sessionFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
     this._sessionFileBackup = this._sessionFile.clone();
     this._sessionFilePrevious = this._sessionFile.clone();
@@ -152,17 +155,17 @@ SessionStore.prototype = {
     }
   },
 
   _forgetClosedTabs: function ss_forgetClosedTabs() {
     for (let [ssid, win] of Object.entries(this._windows)) {
       win.closedTabs = [];
     }
 
-    this._lastClosedTabIndex = -1;
+    this._lastClosedTabIndex = INVALID_TAB_INDEX;
   },
 
   onEvent: function ss_onEvent(event, data, callback) {
     switch (event) {
       case "ClosedTabs:StartNotifications":
         this._notifyClosedTabs = true;
         log("ClosedTabs:StartNotifications");
         this._sendClosedTabsToJava(Services.wm.getMostRecentWindow("navigator:browser"));
@@ -225,16 +228,18 @@ SessionStore.prototype = {
             type: "Tabs:TabsOpened"
           });
         }
         break;
       }
     }
   },
 
+  // Removal of line below tracked by bug 1360287
+  // eslint-disable-next-line complexity
   observe: function ss_observe(aSubject, aTopic, aData) {
     let observerService = Services.obs;
     switch (aTopic) {
       case "app-startup":
         EventDispatcher.instance.registerListener(this, [
           "ClosedTabs:StartNotifications",
           "ClosedTabs:StopNotifications",
           "Session:Restore",
@@ -399,27 +404,29 @@ SessionStore.prototype = {
         // Reset minimum interval between session store writes back to default.
         log("application-foreground");
         this._interval = Services.prefs.getIntPref("browser.sessionstore.interval");
         this._minSaveDelay = MINIMUM_SAVE_DELAY;
 
         // If we skipped restoring a zombified tab before backgrounding,
         // we might have to do it now instead.
         let window = Services.wm.getMostRecentWindow("navigator:browser");
-        if (window) { // Might not yet be ready during a cold startup.
+        if (window && window.BrowserApp) { // Might not yet be ready during a cold startup.
           let tab = window.BrowserApp.selectedTab;
-          this.restoreZombieTab(tab);
+          if (tab) { // Can be null if closing a tab triggered an activity switch.
+            this.restoreZombieTab(tab);
+          }
         }
         break;
       case "last-pb-context-exited":
         // Clear private closed tab data when we leave private browsing.
         for (let window of Object.values(this._windows)) {
           window.closedTabs = window.closedTabs.filter(tab => !tab.isPrivate);
         }
-        this._lastClosedTabIndex = -1;
+        this._lastClosedTabIndex = INVALID_TAB_INDEX;
         break;
     }
   },
 
   handleEvent: function ss_handleEvent(aEvent) {
     let window = aEvent.currentTarget.ownerGlobal;
     switch (aEvent.type) {
       case "TabOpen": {
@@ -553,17 +560,17 @@ SessionStore.prototype = {
 
     // Ignore non-browser windows and windows opened while shutting down
     if (aWindow.document.documentElement.getAttribute("windowtype") != "navigator:browser" || this._loadState <= STATE_QUITTING) {
       return;
     }
 
     // Assign it a unique identifier (timestamp) and create its data object
     aWindow.__SSID = "window" + Date.now();
-    this._windows[aWindow.__SSID] = { tabs: [], selected: 0, closedTabs: [] };
+    this._windows[aWindow.__SSID] = { tabs: [], selectedTabId: INVALID_TAB_ID, closedTabs: [] };
 
     // Perform additional initialization when the first window is loading
     if (this._loadState == STATE_STOPPED) {
       this._loadState = STATE_RUNNING;
       this._lastSaveTime = Date.now();
     }
 
     // Add tab change listeners to all already existing tabs
@@ -671,19 +678,31 @@ SessionStore.prototype = {
     log("onTabRemove() ran for tab " + aWindow.BrowserApp.getTabForBrowser(aBrowser).id +
         ", aNoNotification = " + aNoNotification);
     if (!aNoNotification) {
       this.saveStateDelayed();
     }
   },
 
   onTabClose: function ss_onTabClose(aWindow, aBrowser, aTabIndex) {
-    let data = aBrowser.__SS_data || {};
-    if (this._maxTabsUndo == 0 || this._sessionDataIsEmpty(data)) {
-      this._lastClosedTabIndex = -1;
+    let data = aBrowser.__SS_data;
+    let tab = aWindow.BrowserApp.getTabForId(data.tabId);
+
+    let windowData = this._windows[aWindow.__SSID];
+    if (windowData.selectedTabId == tab.id) {
+      // Normally, we will first select another tab anyway before closing the previous tab, which
+      // would make this logic moot. However
+      // - we only update the selected tab when selecting a normal BROWSING-type tab, and
+      // - in conjunction with switching between activities, the event order as we see it can
+      //   become reversed.
+      windowData.selectedTabId = INVALID_TAB_ID;
+    }
+
+    if (this._maxTabsUndo == 0 || this._sessionDataIsEmpty(data) || tab.type != "BROWSING") {
+      this._lastClosedTabIndex = INVALID_TAB_INDEX;
       return;
     }
 
     if (aWindow.BrowserApp.tabs.length > 0) {
       // Bundle this browser's data and extra data and save in the closedTabs
       // window property
       data.extData = aBrowser.__SS_extdata || {};
 
@@ -694,17 +713,17 @@ SessionStore.prototype = {
       }
 
       this._lastClosedTabIndex = aTabIndex;
 
       if (this._notifyClosedTabs) {
         this._sendClosedTabsToJava(aWindow);
       }
 
-      log("onTabClose() ran for tab " + aWindow.BrowserApp.getTabForBrowser(aBrowser).id);
+      log("onTabClose() ran for tab " + tab.id);
       let evt = new Event("SSTabCloseProcessed", {"bubbles":true, "cancelable":false});
       aBrowser.dispatchEvent(evt);
     }
   },
 
   _sessionDataIsEmpty: function ss_sessionDataIsEmpty(aData) {
     if (!aData || !aData.entries || aData.entries.length == 0) {
       return true;
@@ -787,30 +806,31 @@ SessionStore.prototype = {
     this._updateCrashReportURL(aWindow);
   },
 
   onTabSelect: function ss_onTabSelect(aWindow, aBrowser) {
     if (this._loadState != STATE_RUNNING) {
       return;
     }
 
-    let index = aWindow.BrowserApp.selectedTabIndex;
-    this._windows[aWindow.__SSID].selected = parseInt(index) + 1; // 1-based
-
     let tab = aWindow.BrowserApp.getTabForBrowser(aBrowser);
     let tabId = tab.id;
 
+    if (tab.type == "BROWSING") {
+      this._windows[aWindow.__SSID].selectedTabId = tabId;
+    }
+
     // Restore the resurrected browser
     if (tabId != this._keepAsZombieTabId) {
       this.restoreZombieTab(tab);
     } else {
       log("keeping as zombie tab " + tabId);
     }
     // The tab id passed through Tab:KeepZombified is valid for one TabSelect only.
-    this._keepAsZombieTabId = -1;
+    this._keepAsZombieTabId = INVALID_TAB_ID;
 
     log("onTabSelect() ran for tab " + tabId);
     this.saveStateDelayed();
     this._updateCrashReportURL(aWindow);
 
     // If the selected tab has changed while listening for closed tab
     // notifications, we may have switched between different private browsing
     // modes.
@@ -835,17 +855,17 @@ SessionStore.prototype = {
 
   onTabMove: function ss_onTabMove() {
     if (this._loadState != STATE_RUNNING) {
       return;
     }
 
     // The long press that initiated the move canceled any close undo option that may have been
     // present.
-    this._lastClosedTabIndex = -1;
+    this._lastClosedTabIndex = INVALID_TAB_INDEX;
     this.saveStateDelayed();
   },
 
   onTabInput: function ss_onTabInput(aWindow, aBrowser) {
     // If this browser belongs to a zombie tab or the initial restore hasn't yet finished,
     // skip any session save activity.
     if (aBrowser.__SS_restore || !this._startupRestoreFinished || aBrowser.__SS_restoreReloadPending) {
       return;
@@ -1040,33 +1060,40 @@ SessionStore.prototype = {
     log("_saveState() current state collected");
 
     for (let winIndex = 0; winIndex < data.windows.length; ++winIndex) {
       let win = data.windows[winIndex];
       let normalWin = {};
       for (let prop in win) {
         normalWin[prop] = data[prop];
       }
+      // This particular attribute will be converted to a tab index further down
+      // and stored in the appropriate (normal or private) window data.
+      delete normalWin.selectedTabId;
       normalWin.tabs = [];
 
       // Save normal closed tabs. Forget about private closed tabs.
       normalWin.closedTabs = win.closedTabs.filter(tab => !tab.isPrivate);
 
       normalData.windows.push(normalWin);
       privateData.windows.push({ tabs: [] });
 
       // Split the session data into private and non-private data objects.
       // Non-private session data will be saved to disk, and private session
       // data will be sent to Java for Android to hold it in memory.
       for (let i = 0; i < win.tabs.length; ++i) {
         let tab = win.tabs[i];
+        if (tab.type != "BROWSING") {
+          continue;
+        }
+
         let savedWin = tab.isPrivate ? privateData.windows[winIndex] : normalData.windows[winIndex];
         savedWin.tabs.push(tab);
-        if (win.selected == i + 1) {
-          savedWin.selected = savedWin.tabs.length;
+        if (win.selectedTabId === tab.tabId) {
+          savedWin.selected = savedWin.tabs.length; // 1-based index
         }
       }
     }
 
     // Write only non-private data to disk
     if (normalData.windows[0] && normalData.windows[0].tabs) {
       log("_saveState() writing normal data, " +
            normalData.windows[0].tabs.length + " tabs in window[0]");
@@ -1125,18 +1152,21 @@ SessionStore.prototype = {
     // Ignore windows not tracked by SessionStore
     if (!aWindow.__SSID || !this._windows[aWindow.__SSID]) {
       return;
     }
 
     let winData = this._windows[aWindow.__SSID];
     winData.tabs = [];
 
-    let index = aWindow.BrowserApp.selectedTabIndex;
-    winData.selected = parseInt(index) + 1; // 1-based
+    let selectedTab = aWindow.BrowserApp.selectedTab;
+
+    if (selectedTab != null && selectedTab.type == "BROWSING") {
+      winData.selectedTabId = selectedTab.id;
+    }
 
     let tabs = aWindow.BrowserApp.tabs;
     for (let i = 0; i < tabs.length; i++) {
       let browser = tabs[i].browser;
       if (browser.__SS_data) {
         let tabData = browser.__SS_data;
         if (browser.__SS_extdata) {
           tabData.extData = browser.__SS_extdata;
@@ -1402,17 +1432,17 @@ SessionStore.prototype = {
 
       // Don't restore tab if user has already closed it
       if (tab == null) {
         delete tabData.tabId;
         continue;
       }
 
       let parentId = tabData.parentId;
-      if (parentId > -1) {
+      if (parentId > INVALID_TAB_ID) {
         tab.parentId = parentId;
       }
 
       tab.browser.__SS_data = tabData;
       tab.browser.__SS_extdata = tabData.extData;
 
       if (window.BrowserApp.selectedTab == tab) {
         // After we're done restoring, we can lift the general ban on tab data
@@ -1421,23 +1451,34 @@ SessionStore.prototype = {
         tab.browser.__SS_restoreReloadPending = true;
 
         this._restoreTab(tabData, tab.browser);
         this._startupRestoreFinished = true;
         log("startupRestoreFinished = true");
 
         delete tab.browser.__SS_restore;
         tab.browser.removeAttribute("pending");
+
+        this._windows[window.__SSID].selectedTabId = tab.id;
       } else {
         // Mark the browser for delay loading
         tab.browser.__SS_restore = true;
         tab.browser.setAttribute("pending", "true");
       }
     }
 
+    if (state.windows[0].hasOwnProperty("selectedTabId") &&
+      this._windows[window.__SSID].selectedTabId == INVALID_TAB_ID) {
+      // If none of the restored tabs was the selected tab, we might be opening an URL from an
+      // external intent. If this new tab is a normal BROWSING tab, we'll catch its selection
+      // anyway, however if we've opened a custom tab/web app or anything like that we want to
+      // ignore it. So instead, we store the tab we would have selected from the session file.
+      this._windows[window.__SSID].selectedTabId = state.windows[0].selectedTabId;
+    }
+
     // Restore the closed tabs array on the current window.
     if (state.windows[0].closedTabs && this._maxTabsUndo > 0) {
       this._windows[window.__SSID].closedTabs = state.windows[0].closedTabs;
       log("_restoreWindow() loaded " + state.windows[0].closedTabs.length + " closed tabs");
     }
   },
 
   getClosedTabCount: function ss_getClosedTabCount(aWindow) {
@@ -1482,17 +1523,17 @@ SessionStore.prototype = {
       tabIndex: this._lastClosedTabIndex,
       parentId: aCloseTabData.parentId
     };
     let tab = aWindow.BrowserApp.addTab(aCloseTabData.entries[aCloseTabData.index - 1].url, params);
     tab.browser.__SS_data = aCloseTabData;
     tab.browser.__SS_extdata = aCloseTabData.extData;
     this._restoreTab(aCloseTabData, tab.browser);
 
-    this._lastClosedTabIndex = -1;
+    this._lastClosedTabIndex = INVALID_TAB_INDEX;
 
     if (this._notifyClosedTabs) {
       this._sendClosedTabsToJava(aWindow);
     }
 
     return tab.browser;
   },
 
@@ -1509,25 +1550,25 @@ SessionStore.prototype = {
       throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
     }
 
     // remove closed tab from the array
     closedTabs.splice(aIndex, 1);
 
     // Forget the last closed tab index if we're forgetting the last closed tab.
     if (aIndex == 0) {
-      this._lastClosedTabIndex = -1;
+      this._lastClosedTabIndex = INVALID_TAB_INDEX;
     }
     if (this._notifyClosedTabs) {
       this._sendClosedTabsToJava(aWindow);
     }
   },
 
   get canUndoLastCloseTab() {
-    return this._lastClosedTabIndex > -1;
+    return this._lastClosedTabIndex > INVALID_TAB_INDEX;
   },
 
   _sendClosedTabsToJava: function ss_sendClosedTabsToJava(aWindow) {
 
     // If the app is shutting down, we don't need to do anything.
     if (this._loadState <= STATE_QUITTING) {
       return;
     }
--- a/mobile/android/components/extensions/ext-pageAction.js
+++ b/mobile/android/components/extensions/ext-pageAction.js
@@ -1,14 +1,14 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
-                                  "resource://devtools/shared/event-emitter.js");
+                                  "resource://gre/modules/EventEmitter.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 
 // Import the android PageActions module.
 XPCOMUtils.defineLazyModuleGetter(this, "PageActions",
                                   "resource://gre/modules/PageActions.jsm");
 
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
@@ -18,31 +18,28 @@ import java.net.Proxy;
 import java.net.URLConnection;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.StringTokenizer;
 import java.util.TreeMap;
-import java.util.concurrent.ConcurrentHashMap;
 
 import android.annotation.SuppressLint;
 import org.mozilla.gecko.annotation.JNITarget;
 import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.gfx.LayerView;
-import org.mozilla.gecko.gfx.PanZoomController;
 import org.mozilla.gecko.permissions.Permissions;
 import org.mozilla.gecko.process.GeckoProcessManager;
-import org.mozilla.gecko.process.GeckoServiceChildProcess;
-import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.HardwareCodecCapabilityUtils;
 import org.mozilla.gecko.util.HardwareUtils;
+import org.mozilla.gecko.util.IOUtils;
 import org.mozilla.gecko.util.ProxySelector;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.Manifest;
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.Intent;
@@ -1201,21 +1198,24 @@ public class GeckoAppShell
     interface GeckoProcessesVisitor {
         boolean callback(int pid);
     }
 
     private static void EnumerateGeckoProcesses(GeckoProcessesVisitor visiter) {
         int pidColumn = -1;
         int userColumn = -1;
 
+        Process ps = null;
+        InputStreamReader inputStreamReader = null;
+        BufferedReader in = null;
         try {
             // run ps and parse its output
-            java.lang.Process ps = Runtime.getRuntime().exec("ps");
-            BufferedReader in = new BufferedReader(new InputStreamReader(ps.getInputStream()),
-                                                   2048);
+            ps = Runtime.getRuntime().exec("ps");
+            inputStreamReader = new InputStreamReader(ps.getInputStream());
+            in = new BufferedReader(inputStreamReader, 2048);
 
             String headerOutput = in.readLine();
 
             // figure out the column offsets.  We only care about the pid and user fields
             StringTokenizer st = new StringTokenizer(headerOutput);
 
             int tokenSoFar = 0;
             while (st.hasMoreTokens()) {
@@ -1237,54 +1237,58 @@ public class GeckoAppShell
                 if (uid == android.os.Process.myUid() &&
                     !split[split.length - 1].equalsIgnoreCase("ps")) {
                     int pid = Integer.parseInt(split[pidColumn]);
                     boolean keepGoing = visiter.callback(pid);
                     if (keepGoing == false)
                         break;
                 }
             }
-            in.close();
-        }
-        catch (Exception e) {
+        } catch (Exception e) {
             Log.w(LOGTAG, "Failed to enumerate Gecko processes.",  e);
+        } finally {
+            IOUtils.safeStreamClose(in);
+            IOUtils.safeStreamClose(inputStreamReader);
+            if (ps != null) {
+                ps.destroy();
+            }
         }
     }
 
     public static String getAppNameByPID(int pid) {
         BufferedReader cmdlineReader = null;
         String path = "/proc/" + pid + "/cmdline";
         try {
             File cmdlineFile = new File(path);
             if (!cmdlineFile.exists())
                 return "";
             cmdlineReader = new BufferedReader(new FileReader(cmdlineFile));
             return cmdlineReader.readLine().trim();
         } catch (Exception ex) {
             return "";
         } finally {
-            if (null != cmdlineReader) {
-                try {
-                    cmdlineReader.close();
-                } catch (Exception e) { }
-            }
+            IOUtils.safeStreamClose(cmdlineReader);
         }
     }
 
     public static void listOfOpenFiles() {
         int pidColumn = -1;
         int nameColumn = -1;
 
+        // run lsof and parse its output
+        Process process = null;
+        InputStreamReader inputStreamReader = null;
+        BufferedReader in = null;
         try {
             String filter = GeckoProfile.get(getApplicationContext()).getDir().toString();
             Log.d(LOGTAG, "[OPENFILE] Filter: " + filter);
 
-            // run lsof and parse its output
-            java.lang.Process lsof = Runtime.getRuntime().exec("lsof");
-            BufferedReader in = new BufferedReader(new InputStreamReader(lsof.getInputStream()), 2048);
+            process = Runtime.getRuntime().exec("lsof");
+            inputStreamReader = new InputStreamReader(process.getInputStream());
+            in = new BufferedReader(inputStreamReader, 2048);
 
             String headerOutput = in.readLine();
             StringTokenizer st = new StringTokenizer(headerOutput);
             int token = 0;
             while (st.hasMoreTokens()) {
                 String next = st.nextToken();
                 if (next.equalsIgnoreCase("PID"))
                     pidColumn = token;
@@ -1305,18 +1309,24 @@ public class GeckoAppShell
                 if (name == null) {
                     name = getAppNameByPID(pid.intValue());
                     pidNameMap.put(pid, name);
                 }
                 String file = split[nameColumn];
                 if (!TextUtils.isEmpty(name) && !TextUtils.isEmpty(file) && file.startsWith(filter))
                     Log.d(LOGTAG, "[OPENFILE] " + name + "(" + split[pidColumn] + ") : " + file);
             }
-            in.close();
-        } catch (Exception e) { }
+        } catch (Exception e) {
+        } finally {
+            IOUtils.safeStreamClose(in);
+            IOUtils.safeStreamClose(inputStreamReader);
+            if (process != null) {
+                process.destroy();
+            }
+        }
     }
 
     @WrapForJNI(calledFrom = "gecko")
     private static byte[] getIconForExtension(String aExt, int iconSize) {
         try {
             if (iconSize <= 0)
                 iconSize = 16;
 
@@ -2082,24 +2092,26 @@ public class GeckoAppShell
             public synchronized int read(byte[] buffer, int byteOffset, int byteCount)
                                     throws IOException {
                 if (mHaveConnected) {
                     return super.read(buffer, byteOffset, byteCount);
                 }
 
                 final PipedOutputStream output = new PipedOutputStream();
                 connect(output);
+
                 ThreadUtils.postToBackgroundThread(
                     new Runnable() {
                         @Override
                         public void run() {
                             try {
                                 bitmap.compress(Bitmap.CompressFormat.PNG, 100, output);
-                                output.close();
-                            } catch (IOException ioe) { }
+                            } finally {
+                                IOUtils.safeStreamClose(output);
+                            }
                         }
                     });
                 mHaveConnected = true;
                 return super.read(buffer, byteOffset, byteCount);
             }
         }
     }
 
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/FileUtils.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/FileUtils.java
@@ -140,35 +140,36 @@ public class FileUtils {
      * For a higher-level method, see {@link #readStringFromFile(File)}.
      *
      * Since this is generic, it may not be the most performant for your use case.
      *
      * @param bufferSize Size of the underlying buffer for read optimizations - must be > 0.
      */
     public static String readStringFromInputStreamAndCloseStream(final InputStream inputStream, final int bufferSize)
             throws IOException {
-        if (bufferSize <= 0) {
-            // Safe close: it's more important to alert the programmer of
-            // their error than to let them catch and continue on their way.
-            IOUtils.safeStreamClose(inputStream);
-            throw new IllegalArgumentException("Expected buffer size larger than 0. Got: " + bufferSize);
-        }
+        InputStreamReader reader = null;
+        try {
+            if (bufferSize <= 0) {
+                throw new IllegalArgumentException("Expected buffer size larger than 0. Got: " + bufferSize);
+            }
 
-        final StringBuilder stringBuilder = new StringBuilder(bufferSize);
-        final InputStreamReader reader = new InputStreamReader(inputStream, Charset.forName("UTF-8"));
-        try {
+            final StringBuilder stringBuilder = new StringBuilder(bufferSize);
+            reader = new InputStreamReader(inputStream, StringUtils.UTF_8);
+
             int charsRead;
             final char[] buffer = new char[bufferSize];
             while ((charsRead = reader.read(buffer, 0, bufferSize)) != -1) {
                 stringBuilder.append(buffer, 0, charsRead);
             }
+
+            return stringBuilder.toString();
         } finally {
-            reader.close();
+            IOUtils.safeStreamClose(reader);
+            IOUtils.safeStreamClose(inputStream);
         }
-        return stringBuilder.toString();
     }
 
     /**
      * A generic solution to write a JSONObject to a file.
      * See {@link #writeStringToFile(File, String)} for more details.
      */
     public static void writeJSONObjectToFile(final File file, final JSONObject obj) throws IOException {
         writeStringToFile(file, obj.toString());
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/INIParser.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/INIParser.java
@@ -40,22 +40,23 @@ public final class INIParser extends INI
 
         FileWriter outputStream = null;
         try {
             outputStream = new FileWriter(f);
         } catch (IOException e1) {
             e1.printStackTrace();
         }
 
-        BufferedWriter writer = new BufferedWriter(outputStream);
+        final BufferedWriter writer = new BufferedWriter(outputStream);
         try {
             write(writer);
-            writer.close();
         } catch (IOException e) {
             e.printStackTrace();
+        } finally {
+            IOUtils.safeStreamClose(writer);
         }
     }
 
     @Override
     public void write(BufferedWriter writer) throws IOException {
         super.write(writer);
 
         if (mSections != null) {
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/IOUtils.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/IOUtils.java
@@ -85,21 +85,17 @@ public class IOUtils {
                     buffer = newBuffer;
                 }
             }
 
             return new ConsumedInputStream(bPointer + 1, buffer);
         } catch (IOException e) {
             Log.e(LOGTAG, "Error consuming input stream.", e);
         } finally {
-            try {
-                iStream.close();
-            } catch (IOException e) {
-                Log.e(LOGTAG, "Error closing input stream.", e);
-            }
+            IOUtils.safeStreamClose(iStream);
         }
 
         return null;
     }
 
     /**
      * Truncate a given byte[] to a given length. Returns a new byte[] with the first length many
      * bytes of the input.
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/publicsuffix/PublicSuffixPatterns.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/publicsuffix/PublicSuffixPatterns.java
@@ -23,32 +23,30 @@ class PublicSuffixPatterns {
 
     static synchronized Set<String> getExactSet(Context context) {
         if (EXACT != null) {
             return EXACT;
         }
 
         EXACT = new HashSet<>();
 
-        InputStream stream = null;
-
+        BufferedReader reader = null;
         try {
-            stream = context.getAssets().open("publicsuffixlist");
-            BufferedReader reader = new BufferedReader(new InputStreamReader(
-                    new BufferedInputStream(stream)));
+            reader = new BufferedReader(new InputStreamReader(
+                    new BufferedInputStream(context.getAssets().open