Merge mozilla-central to mozilla-inbound. on a CLOSED TREE
authorAndreea Pavel <apavel@mozilla.com>
Thu, 15 Feb 2018 12:37:30 +0200
changeset 403965 99495614cba7396c358b78b3e50aea1b38937c59
parent 403964 3219cd61e87b4f3003079154c437efa87080a917 (current diff)
parent 403912 9b69cc60e5848f2f8802c911fd00771b50eed41f (diff)
child 403966 12b89457c62313feacba9ed2f0cd55af8dd58a36
push id33447
push usernerli@mozilla.com
push dateThu, 15 Feb 2018 19:29:41 +0000
treeherdermozilla-central@ee717948a600 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone60.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. on a CLOSED TREE
browser/base/content/defaultthemes/compact.header.png
devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_repeated_messages_accuracy.js
devtools/client/webconsole/new-console-output/test/mochitest/test-repeated-messages.html
devtools/client/webconsole/new-console-output/test/require-helper.js
docshell/base/nsIDocCharset.idl
docshell/test/test_bug713825.html
dom/base/ShadowRoot.cpp
dom/base/nsDocument.cpp
dom/interfaces/xbl/moz.build
dom/interfaces/xbl/nsIDOMDocumentXBL.idl
dom/webidl/DataChannel.webidl
layout/base/nsCSSFrameConstructor.cpp
layout/tools/layout-debug/ui/content/layoutdebug-overlay.xul
layout/tools/layout-debug/ui/locale/en-US/layoutdebug-overlay.dtd
other-licenses/7zstub/firefox/7zSD.sfx
other-licenses/7zstub/src/7zip/Archive/7z/7zCompressionMode.cpp
other-licenses/7zstub/src/7zip/Archive/7z/7zCompressionMode.h
other-licenses/7zstub/src/7zip/Archive/7z/7zDecode.cpp
other-licenses/7zstub/src/7zip/Archive/7z/7zDecode.h
other-licenses/7zstub/src/7zip/Archive/7z/7zExtract.cpp
other-licenses/7zstub/src/7zip/Archive/7z/7zFolderOutStream.cpp
other-licenses/7zstub/src/7zip/Archive/7z/7zFolderOutStream.h
other-licenses/7zstub/src/7zip/Archive/7z/7zHandler.cpp
other-licenses/7zstub/src/7zip/Archive/7z/7zHandler.h
other-licenses/7zstub/src/7zip/Archive/7z/7zHeader.cpp
other-licenses/7zstub/src/7zip/Archive/7z/7zHeader.h
other-licenses/7zstub/src/7zip/Archive/7z/7zIn.cpp
other-licenses/7zstub/src/7zip/Archive/7z/7zIn.h
other-licenses/7zstub/src/7zip/Archive/7z/7zItem.h
other-licenses/7zstub/src/7zip/Archive/7z/7zMethodID.cpp
other-licenses/7zstub/src/7zip/Archive/7z/7zMethodID.h
other-licenses/7zstub/src/7zip/Archive/7z/7zMethods.cpp
other-licenses/7zstub/src/7zip/Archive/7z/7zMethods.h
other-licenses/7zstub/src/7zip/Archive/7z/7zProperties.cpp
other-licenses/7zstub/src/7zip/Archive/7z/7zProperties.h
other-licenses/7zstub/src/7zip/Archive/Archive.def
other-licenses/7zstub/src/7zip/Archive/Common/CoderMixer2.cpp
other-licenses/7zstub/src/7zip/Archive/Common/CoderMixer2.h
other-licenses/7zstub/src/7zip/Archive/Common/CoderMixer2MT.cpp
other-licenses/7zstub/src/7zip/Archive/Common/CoderMixer2MT.h
other-licenses/7zstub/src/7zip/Archive/Common/CrossThreadProgress.cpp
other-licenses/7zstub/src/7zip/Archive/Common/CrossThreadProgress.h
other-licenses/7zstub/src/7zip/Archive/Common/FilterCoder.cpp
other-licenses/7zstub/src/7zip/Archive/Common/FilterCoder.h
other-licenses/7zstub/src/7zip/Archive/Common/ItemNameUtils.cpp
other-licenses/7zstub/src/7zip/Archive/Common/ItemNameUtils.h
other-licenses/7zstub/src/7zip/Archive/Common/OutStreamWithCRC.cpp
other-licenses/7zstub/src/7zip/Archive/Common/OutStreamWithCRC.h
other-licenses/7zstub/src/7zip/Archive/IArchive.h
other-licenses/7zstub/src/7zip/Bundles/SFXSetup-moz/ExtractCallback.cpp
other-licenses/7zstub/src/7zip/Bundles/SFXSetup-moz/ExtractCallback.h
other-licenses/7zstub/src/7zip/Bundles/SFXSetup-moz/ExtractEngine.cpp
other-licenses/7zstub/src/7zip/Bundles/SFXSetup-moz/ExtractEngine.h
other-licenses/7zstub/src/7zip/Bundles/SFXSetup-moz/Main.cpp
other-licenses/7zstub/src/7zip/Bundles/SFXSetup-moz/SFXSetup-moz.dsp
other-licenses/7zstub/src/7zip/Bundles/SFXSetup-moz/SFXSetup-moz.dsw
other-licenses/7zstub/src/7zip/Bundles/SFXSetup-moz/StdAfx.cpp
other-licenses/7zstub/src/7zip/Bundles/SFXSetup-moz/StdAfx.h
other-licenses/7zstub/src/7zip/Bundles/SFXSetup-moz/makefile
other-licenses/7zstub/src/7zip/Bundles/SFXSetup-moz/resource.h
other-licenses/7zstub/src/7zip/Bundles/SFXSetup-moz/resource.rc
other-licenses/7zstub/src/7zip/Bundles/SFXSetup-moz/setup.ico
other-licenses/7zstub/src/7zip/Common/FilePathAutoRename.cpp
other-licenses/7zstub/src/7zip/Common/FilePathAutoRename.h
other-licenses/7zstub/src/7zip/Common/FileStreams.cpp
other-licenses/7zstub/src/7zip/Common/FileStreams.h
other-licenses/7zstub/src/7zip/Common/InBuffer.cpp
other-licenses/7zstub/src/7zip/Common/InBuffer.h
other-licenses/7zstub/src/7zip/Common/InOutTempBuffer.cpp
other-licenses/7zstub/src/7zip/Common/InOutTempBuffer.h
other-licenses/7zstub/src/7zip/Common/LSBFDecoder.cpp
other-licenses/7zstub/src/7zip/Common/LSBFDecoder.h
other-licenses/7zstub/src/7zip/Common/LSBFEncoder.cpp
other-licenses/7zstub/src/7zip/Common/LSBFEncoder.h
other-licenses/7zstub/src/7zip/Common/LimitedStreams.cpp
other-licenses/7zstub/src/7zip/Common/LimitedStreams.h
other-licenses/7zstub/src/7zip/Common/LockedStream.cpp
other-licenses/7zstub/src/7zip/Common/LockedStream.h
other-licenses/7zstub/src/7zip/Common/MSBFDecoder.h
other-licenses/7zstub/src/7zip/Common/MSBFEncoder.h
other-licenses/7zstub/src/7zip/Common/OffsetStream.cpp
other-licenses/7zstub/src/7zip/Common/OffsetStream.h
other-licenses/7zstub/src/7zip/Common/OutBuffer.cpp
other-licenses/7zstub/src/7zip/Common/OutBuffer.h
other-licenses/7zstub/src/7zip/Common/ProgressUtils.cpp
other-licenses/7zstub/src/7zip/Common/ProgressUtils.h
other-licenses/7zstub/src/7zip/Common/StdAfx.h
other-licenses/7zstub/src/7zip/Common/StreamBinder.cpp
other-licenses/7zstub/src/7zip/Common/StreamBinder.h
other-licenses/7zstub/src/7zip/Common/StreamObjects.cpp
other-licenses/7zstub/src/7zip/Common/StreamObjects.h
other-licenses/7zstub/src/7zip/Common/StreamUtils.cpp
other-licenses/7zstub/src/7zip/Common/StreamUtils.h
other-licenses/7zstub/src/7zip/Compress/Branch/BranchCoder.cpp
other-licenses/7zstub/src/7zip/Compress/Branch/BranchCoder.h
other-licenses/7zstub/src/7zip/Compress/Branch/BranchTypes.h
other-licenses/7zstub/src/7zip/Compress/Branch/BranchX86.c
other-licenses/7zstub/src/7zip/Compress/Branch/BranchX86.h
other-licenses/7zstub/src/7zip/Compress/Branch/x86.cpp
other-licenses/7zstub/src/7zip/Compress/Branch/x86.h
other-licenses/7zstub/src/7zip/Compress/Branch/x86_2.cpp
other-licenses/7zstub/src/7zip/Compress/Branch/x86_2.h
other-licenses/7zstub/src/7zip/Compress/Copy/CopyCoder.cpp
other-licenses/7zstub/src/7zip/Compress/Copy/CopyCoder.h
other-licenses/7zstub/src/7zip/Compress/LZ/LZOutWindow.cpp
other-licenses/7zstub/src/7zip/Compress/LZ/LZOutWindow.h
other-licenses/7zstub/src/7zip/Compress/LZMA/LZMA.h
other-licenses/7zstub/src/7zip/Compress/LZMA/LZMADecoder.cpp
other-licenses/7zstub/src/7zip/Compress/LZMA/LZMADecoder.h
other-licenses/7zstub/src/7zip/Compress/RangeCoder/RangeCoder.h
other-licenses/7zstub/src/7zip/Compress/RangeCoder/RangeCoderBit.cpp
other-licenses/7zstub/src/7zip/Compress/RangeCoder/RangeCoderBit.h
other-licenses/7zstub/src/7zip/Compress/RangeCoder/RangeCoderBitTree.h
other-licenses/7zstub/src/7zip/Compress/RangeCoder/RangeCoderOpt.h
other-licenses/7zstub/src/7zip/FileManager/FormatUtils.cpp
other-licenses/7zstub/src/7zip/FileManager/FormatUtils.h
other-licenses/7zstub/src/7zip/FileManager/Resource/ProgressDialog/ProgressDialog.cpp
other-licenses/7zstub/src/7zip/FileManager/Resource/ProgressDialog/ProgressDialog.h
other-licenses/7zstub/src/7zip/FileManager/Resource/ProgressDialog/StdAfx.h
other-licenses/7zstub/src/7zip/FileManager/Resource/ProgressDialog/resource.h
other-licenses/7zstub/src/7zip/FileManager/Resource/ProgressDialog/resource.rc
other-licenses/7zstub/src/7zip/GuiCommon.rc
other-licenses/7zstub/src/7zip/ICoder.h
other-licenses/7zstub/src/7zip/IPassword.h
other-licenses/7zstub/src/7zip/IProgress.h
other-licenses/7zstub/src/7zip/IStream.h
other-licenses/7zstub/src/7zip/MyVersion.h
other-licenses/7zstub/src/7zip/MyVersionInfo.rc
other-licenses/7zstub/src/7zip/PropID.h
other-licenses/7zstub/src/7zip/UI/Common/ArchiveOpenCallback.cpp
other-licenses/7zstub/src/7zip/UI/Common/ArchiveOpenCallback.h
other-licenses/7zstub/src/7zip/UI/Common/ArchiverInfo.cpp
other-licenses/7zstub/src/7zip/UI/Common/ArchiverInfo.h
other-licenses/7zstub/src/7zip/UI/Common/DefaultName.cpp
other-licenses/7zstub/src/7zip/UI/Common/DefaultName.h
other-licenses/7zstub/src/7zip/UI/Common/OpenArchive.cpp
other-licenses/7zstub/src/7zip/UI/Common/OpenArchive.h
other-licenses/7zstub/src/7zip/UI/Explorer/MyMessages.cpp
other-licenses/7zstub/src/7zip/UI/Explorer/MyMessages.h
other-licenses/7zstub/src/7zip/UI/GUI/OpenCallbackGUI.cpp
other-licenses/7zstub/src/7zip/UI/GUI/OpenCallbackGUI.h
other-licenses/7zstub/src/Common/Alloc.cpp
other-licenses/7zstub/src/Common/Alloc.h
other-licenses/7zstub/src/Common/Buffer.h
other-licenses/7zstub/src/Common/CRC.cpp
other-licenses/7zstub/src/Common/CRC.h
other-licenses/7zstub/src/Common/ComTry.h
other-licenses/7zstub/src/Common/CommandLineParser.cpp
other-licenses/7zstub/src/Common/CommandLineParser.h
other-licenses/7zstub/src/Common/Defs.h
other-licenses/7zstub/src/Common/DynamicBuffer.h
other-licenses/7zstub/src/Common/IntToString.cpp
other-licenses/7zstub/src/Common/IntToString.h
other-licenses/7zstub/src/Common/MyCom.h
other-licenses/7zstub/src/Common/MyUnknown.h
other-licenses/7zstub/src/Common/MyWindows.cpp
other-licenses/7zstub/src/Common/MyWindows.h
other-licenses/7zstub/src/Common/NewHandler.cpp
other-licenses/7zstub/src/Common/NewHandler.h
other-licenses/7zstub/src/Common/Random.cpp
other-licenses/7zstub/src/Common/Random.h
other-licenses/7zstub/src/Common/StdInStream.cpp
other-licenses/7zstub/src/Common/StdInStream.h
other-licenses/7zstub/src/Common/StdOutStream.cpp
other-licenses/7zstub/src/Common/StdOutStream.h
other-licenses/7zstub/src/Common/String.cpp
other-licenses/7zstub/src/Common/String.h
other-licenses/7zstub/src/Common/StringConvert.cpp
other-licenses/7zstub/src/Common/StringConvert.h
other-licenses/7zstub/src/Common/TextConfig.cpp
other-licenses/7zstub/src/Common/TextConfig.h
other-licenses/7zstub/src/Common/Types.h
other-licenses/7zstub/src/Common/UTFConvert.cpp
other-licenses/7zstub/src/Common/UTFConvert.h
other-licenses/7zstub/src/Common/Vector.cpp
other-licenses/7zstub/src/Common/Vector.h
other-licenses/7zstub/src/Common/Wildcard.cpp
other-licenses/7zstub/src/Common/Wildcard.h
other-licenses/7zstub/src/DOC/7zC.txt
other-licenses/7zstub/src/DOC/copying.txt
other-licenses/7zstub/src/DOC/lzma.txt
other-licenses/7zstub/src/DOC/readme.txt
other-licenses/7zstub/src/Windows/COM.cpp
other-licenses/7zstub/src/Windows/COM.h
other-licenses/7zstub/src/Windows/Control/Dialog.cpp
other-licenses/7zstub/src/Windows/Control/Dialog.h
other-licenses/7zstub/src/Windows/Control/ProgressBar.h
other-licenses/7zstub/src/Windows/DLL.cpp
other-licenses/7zstub/src/Windows/DLL.h
other-licenses/7zstub/src/Windows/Defs.h
other-licenses/7zstub/src/Windows/Error.cpp
other-licenses/7zstub/src/Windows/Error.h
other-licenses/7zstub/src/Windows/FileDir.cpp
other-licenses/7zstub/src/Windows/FileDir.h
other-licenses/7zstub/src/Windows/FileFind.cpp
other-licenses/7zstub/src/Windows/FileFind.h
other-licenses/7zstub/src/Windows/FileIO.cpp
other-licenses/7zstub/src/Windows/FileIO.h
other-licenses/7zstub/src/Windows/FileName.cpp
other-licenses/7zstub/src/Windows/FileName.h
other-licenses/7zstub/src/Windows/Handle.h
other-licenses/7zstub/src/Windows/PropVariant.cpp
other-licenses/7zstub/src/Windows/PropVariant.h
other-licenses/7zstub/src/Windows/PropVariantConversions.cpp
other-licenses/7zstub/src/Windows/PropVariantConversions.h
other-licenses/7zstub/src/Windows/ResourceString.cpp
other-licenses/7zstub/src/Windows/ResourceString.h
other-licenses/7zstub/src/Windows/Synchronization.cpp
other-licenses/7zstub/src/Windows/Synchronization.h
other-licenses/7zstub/src/Windows/Thread.h
other-licenses/7zstub/src/Windows/Time.h
other-licenses/7zstub/src/Windows/Window.cpp
other-licenses/7zstub/src/Windows/Window.h
testing/marionette/harness/marionette_harness/www/datetimePage.html
testing/web-platform/tests/web-animations/interfaces/Animation/finish.html
testing/web-platform/tests/web-animations/interfaces/Animation/playbackRate.html
testing/web-platform/tests/web-animations/timing-model/animations/current-time.html
testing/web-platform/tests/web-animations/timing-model/animations/set-the-animation-start-time.html
testing/web-platform/tests/web-animations/timing-model/animations/set-the-target-effect-of-an-animation.html
testing/web-platform/tests/web-animations/timing-model/animations/set-the-timeline-of-an-animation.html
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1689,44 +1689,43 @@ pref("browser.crashReports.unsubmittedCh
 
 // Preferences for the form autofill system extension
 // The truthy values of "extensions.formautofill.available" are "on" and "detect",
 // any other value means autofill isn't available.
 // "detect" means it's enabled if conditions defined in the extension are met.
 #ifdef NIGHTLY_BUILD
 pref("extensions.formautofill.available", "on");
 pref("extensions.formautofill.creditCards.available", true);
-#elif MOZ_UPDATE_CHANNEL == release
+#else
 pref("extensions.formautofill.available", "detect");
 pref("extensions.formautofill.creditCards.available", false);
-#else
-pref("extensions.formautofill.available", "detect");
-pref("extensions.formautofill.creditCards.available", true);
 #endif
 pref("extensions.formautofill.addresses.enabled", true);
 pref("extensions.formautofill.creditCards.enabled", true);
 // Pref for shield/heartbeat to recognize users who have used Credit Card
 // Autofill. The valid values can be:
 // 0: none
 // 1: submitted a manually-filled credit card form (but didn't see the doorhanger
 //    because of a duplicate profile in the storage)
 // 2: saw the doorhanger
 // 3: submitted an autofill'ed credit card form
 pref("extensions.formautofill.creditCards.used", 0);
 pref("extensions.formautofill.firstTimeUse", true);
 pref("extensions.formautofill.heuristics.enabled", true);
 pref("extensions.formautofill.section.enabled", true);
 pref("extensions.formautofill.loglevel", "Warn");
-// Comma separated list of countries Form Autofill supports
-#if MOZ_UPDATE_CHANNEL == release
+
+#ifdef NIGHTLY_BUILD
+// Comma separated list of countries Form Autofill supports.
+// This affects feature availability and the address edit form country picker.
+pref("extensions.formautofill.supportedCountries", "US,CA,DE");
+pref("extensions.formautofill.supportRTL", true);
+#else
 pref("extensions.formautofill.supportedCountries", "US");
 pref("extensions.formautofill.supportRTL", false);
-#else
-pref("extensions.formautofill.supportedCountries", "US,CA,DE");
-pref("extensions.formautofill.supportRTL", true);
 #endif
 
 // Whether or not to restore a session with lazy-browser tabs.
 pref("browser.sessionstore.restore_tabs_lazily", true);
 
 pref("browser.suppress_first_window_animation", true);
 
 // Preferences for Photon onboarding system extension
--- a/browser/base/content/browser-addons.js
+++ b/browser/base/content/browser-addons.js
@@ -640,17 +640,18 @@ var LightWeightThemeWebInstaller = {
     }
 
     if (this._isAllowed(baseURI)) {
       this._install(data, notify);
       return;
     }
 
     let strings = {
-      header: gNavigatorBundle.getFormattedString("webextPerms.header", [data.name]),
+      header: gNavigatorBundle.getFormattedString("webextPerms.header", ["<>"]),
+      addonName: data.name,
       text: gNavigatorBundle.getFormattedString("lwthemeInstallRequest.message2",
                                                 [uri.host]),
       acceptText: gNavigatorBundle.getString("lwthemeInstallRequest.allowButton2"),
       acceptKey: gNavigatorBundle.getString("lwthemeInstallRequest.allowButton.accesskey2"),
       cancelText: gNavigatorBundle.getString("webextPerms.cancel.label"),
       cancelKey: gNavigatorBundle.getString("webextPerms.cancel.accessKey"),
       msgs: []
     };
--- a/browser/base/content/browser-menubar.inc
+++ b/browser/base/content/browser-menubar.inc
@@ -524,16 +524,22 @@
                         command="View:PageInfo"/>
 #ifndef XP_UNIX
               <menuseparator id="prefSep"/>
               <menuitem id="menu_preferences"
                         label="&preferencesCmd2.label;"
                         accesskey="&preferencesCmd2.accesskey;"
                         oncommand="openPreferences(undefined, {origin: 'menubar'});"/>
 #endif
+#ifdef MOZ_DEBUG
+              <menuitem label="&ldbCmd.label;"
+                        accesskey="&ldbCmd.accesskey;"
+                        oncommand="toOpenWindowByType('mozapp:layoutdebug',
+                                          'chrome://layoutdebug/content/');"/>
+#endif
               </menupopup>
             </menu>
 
 #ifdef XP_MACOSX
           <menu id="windowMenu" />
 #endif
           <menu id="helpMenu" />
         </menubar>
--- a/browser/base/content/browser-sync.js
+++ b/browser/base/content/browser-sync.js
@@ -109,31 +109,36 @@ var gSync = {
     }
   },
 
   init() {
     if (this._initialized) {
       return;
     }
 
+    // initial label for the sync buttons.
+    let statusBroadcaster = document.getElementById("sync-status");
+    if (!statusBroadcaster) {
+      // We are in a window without our elements - just abort now, without
+      // setting this._initialized, so we don't attempt to remove observers.
+      return;
+    }
+    statusBroadcaster.setAttribute("label", this.syncStrings.GetStringFromName("syncnow.label"));
+    // We start with every broadcasters hidden, so that we don't need to init
+    // the sync UI on windows like pageInfo.xul (see bug 1384856).
+    let setupBroadcaster = document.getElementById("sync-setup-state");
+    setupBroadcaster.hidden = false;
+
     for (let topic of this._obs) {
       Services.obs.addObserver(this, topic, true);
     }
 
     this._generateNodeGetters();
     this._definePrefGetters();
 
-    // initial label for the sync buttons.
-    let statusBroadcaster = document.getElementById("sync-status");
-    statusBroadcaster.setAttribute("label", this.syncStrings.GetStringFromName("syncnow.label"));
-    // We start with every broadcasters hidden, so that we don't need to init
-    // the sync UI on windows like pageInfo.xul (see bug 1384856).
-    let setupBroadcaster = document.getElementById("sync-setup-state");
-    setupBroadcaster.hidden = false;
-
     this._maybeUpdateUIState();
 
     EnsureFxAccountsWebChannel();
 
     this._initialized = true;
   },
 
   uninit() {
@@ -175,21 +180,16 @@ var gSync = {
   updateAllUI(state) {
     this.updatePanelPopup(state);
     this.updateStateBroadcasters(state);
     this.updateSyncButtonsTooltip(state);
     this.updateSyncStatus(state);
   },
 
   updatePanelPopup(state) {
-    // Some windows (e.g. places.xul) won't contain the panel UI, so we can
-    // abort immediately for those (bug 1384856).
-    if (!this.appMenuContainer) {
-      return;
-    }
     let defaultLabel = this.appMenuStatus.getAttribute("defaultlabel");
     // The localization string is for the signed in text, but it's the default text as well
     let defaultTooltiptext = this.appMenuStatus.getAttribute("signedinTooltiptext");
 
     const status = state.status;
     // Reset the status bar to its original state.
     this.appMenuLabel.setAttribute("label", defaultLabel);
     this.appMenuStatus.setAttribute("tooltiptext", defaultTooltiptext);
--- a/browser/base/content/pageinfo/security.js
+++ b/browser/base/content/pageinfo/security.js
@@ -164,17 +164,17 @@ var security = {
 
   _cert: null
 };
 
 function securityOnLoad(uri, windowInfo) {
   security.init(uri, windowInfo);
 
   var info = security._getSecurityInfo();
-  if (!info) {
+  if (!info || uri.scheme === "about") {
     document.getElementById("securityTab").hidden = true;
     return;
   }
   document.getElementById("securityTab").hidden = false;
 
   const pageInfoBundle = document.getElementById("pageinfobundle");
 
   /* Set Identity section text */
--- a/browser/base/content/popup-notifications.inc
+++ b/browser/base/content/popup-notifications.inc
@@ -69,27 +69,19 @@
     </popupnotification>
 
     <popupnotification id="addon-install-confirmation-notification" hidden="true">
       <popupnotificationcontent id="addon-install-confirmation-content" orient="vertical"/>
     </popupnotification>
 
     <popupnotification id="addon-webext-permissions-notification" hidden="true">
       <popupnotificationcontent class="addon-webext-perm-notification-content" orient="vertical">
-        <description id="addon-webext-perm-header" class="addon-webext-perm-header"/>
         <description id="addon-webext-perm-text" class="addon-webext-perm-text"/>
         <label id="addon-webext-perm-intro" class="addon-webext-perm-text"/>
         <html:ul id="addon-webext-perm-list" class="addon-webext-perm-list"/>
       </popupnotificationcontent>
     </popupnotification>
 
-    <popupnotification id="addon-webext-defaultsearch-notification" hidden="true">
-      <popupnotificationcontent class="addon-webext-defaultsearch-notification-content" orient="vertical">
-        <description id="addon-webext-defaultsearch-text" class="addon-webext-perm-header"/>
-      </popupnotificationcontent>
-    </popupnotification>
-
     <popupnotification id="addon-installed-notification" hidden="true">
       <popupnotificationcontent class="addon-installed-notification-content" orient="vertical">
-        <description id="addon-installed-notification-header"/>
         <description id="addon-installed-notification-message"/>
       </popupnotificationcontent>
     </popupnotification>
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -3892,37 +3892,42 @@
 
             this.tabContainer._updateCloseButtons();
 
             this.tabContainer._setPositionalAttributes();
 
             let event = document.createEvent("Events");
             event.initEvent("TabShow", true, false);
             aTab.dispatchEvent(event);
+            SessionStore.deleteTabValue(aTab, "hiddenBy");
           }
         ]]>
         </body>
       </method>
 
       <method name="hideTab">
         <parameter name="aTab"/>
+        <parameter name="aSource"/>
         <body>
         <![CDATA[
           if (!aTab.hidden && !aTab.pinned && !aTab.selected &&
               !aTab.closing && !aTab._sharingState) {
             aTab.setAttribute("hidden", "true");
             this._visibleTabs = null; // invalidate cache
 
             this.tabContainer._updateCloseButtons();
 
             this.tabContainer._setPositionalAttributes();
 
             let event = document.createEvent("Events");
             event.initEvent("TabHide", true, false);
             aTab.dispatchEvent(event);
+            if (aSource) {
+              SessionStore.setTabValue(aTab, "hiddenBy", aSource);
+            }
           }
         ]]>
         </body>
       </method>
 
       <method name="selectTabAtIndex">
         <parameter name="aIndex"/>
         <parameter name="aEvent"/>
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -136,16 +136,21 @@ file, You can obtain one at http://mozil
 
         // The autocomplete controller uses heuristic on some internal caches
         // to handle cases like backspace, autofill or repeated searches.
         // Ensure to clear those internal caches when switching tabs.
         gBrowser.tabContainer.addEventListener("TabSelect", this);
       ]]></constructor>
 
       <destructor><![CDATA[
+        // Somehow, it's possible for the XBL destructor to fire without the
+        // constructor ever having fired. Fix:
+        if (!this._prefs) {
+          return;
+        }
         this._prefs.removeObserver("", this);
         this._prefs = null;
         Services.prefs.removeObserver("browser.search.suggest.enabled", this);
         this.inputField.controllers.removeController(this._copyCutController);
         this.inputField.removeEventListener("paste", this);
         this.inputField.removeEventListener("mousedown", this);
         this.inputField.removeEventListener("mousemove", this);
         this.inputField.removeEventListener("mouseout", this);
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -434,28 +434,28 @@
             <hbox id="PanelUI-remotetabs-tabsdisabledpane" pack="center" flex="1">
               <vbox class="PanelUI-remotetabs-instruction-box" align="center">
                 <hbox pack="center">
                   <image class="fxaSyncIllustrationIssue"/>
                 </hbox>
                 <label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.tabsnotsyncing.label;</label>
                 <hbox pack="center">
                   <toolbarbutton class="PanelUI-remotetabs-button"
-                                 label="&appMenuRemoteTabs.openprefs.label;"
+                                 label="&appMenuRemoteTabs.opensyncprefs.label;"
                                  oncommand="gSync.openPrefs('synced-tabs');"/>
                 </hbox>
               </vbox>
             </hbox>
             <!-- Sync is ready to Sync but we are still fetching the tabs to show -->
             <vbox id="PanelUI-remotetabs-fetching">
               <!-- Show intentionally blank panel, see bug 1239845 -->
             </vbox>
             <!-- Sync has only 1 (ie, this) device connected -->
             <hbox id="PanelUI-remotetabs-nodevicespane" pack="center" flex="1">
-              <vbox class="PanelUI-remotetabs-instruction-box">
+              <vbox class="PanelUI-remotetabs-instruction-box" align="center">
                 <hbox pack="center">
                   <image class="fxaSyncIllustrationIssue"/>
                 </hbox>
                 <label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.noclients.subtitle;</label>
                 <toolbarbutton id="PanelUI-remotetabs-connect-device-button"
                                class="PanelUI-remotetabs-button"
                                label="&appMenuRemoteTabs.connectdevice.label;"
                                oncommand="gSync.openConnectAnotherDevice('synced-tabs');"/>
@@ -494,17 +494,17 @@
           <vbox id="PanelUI-remotetabs-unverified"
                 flex="1"
                 align="center"
                 class="PanelUI-remotetabs-instruction-box"
                 observes="sync-unverified-state">
             <image class="fxaSyncIllustrationIssue"/>
             <label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.unverified.label;</label>
             <toolbarbutton class="PanelUI-remotetabs-button"
-                           label="&appMenuRemoteTabs.openprefs.label;"
+                           label="&appMenuRemoteTabs.opensyncprefs.label;"
                            oncommand="gSync.openPrefs('synced-tabs');"/>
           </vbox>
         </hbox>
       </vbox>
     </panelview>
 
     <panelview id="PanelUI-bookmarks" flex="1" class="PanelUI-subView">
       <vbox class="panel-subview-body">
--- a/browser/components/extensions/ext-c-browser.js
+++ b/browser/components/extensions/ext-c-browser.js
@@ -17,16 +17,23 @@ extensions.registerModules({
   },
   devtools_panels: {
     url: "chrome://browser/content/ext-c-devtools-panels.js",
     scopes: ["devtools_child"],
     paths: [
       ["devtools", "panels"],
     ],
   },
+  devtools_network: {
+    url: "chrome://browser/content/ext-c-devtools-network.js",
+    scopes: ["devtools_child"],
+    paths: [
+      ["devtools", "network"],
+    ],
+  },
   // Because of permissions, the module name must differ from both namespaces.
   menusInternal: {
     url: "chrome://browser/content/ext-c-menus.js",
     scopes: ["addon_child"],
     paths: [
       ["contextMenus"],
       ["menus"],
     ],
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/ext-c-devtools-network.js
@@ -0,0 +1,59 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+// The ext-* files are imported into the same scopes.
+/* import-globals-from ../../../toolkit/components/extensions/ext-c-toolkit.js */
+
+/**
+ * Responsible for fetching HTTP response content from the backend.
+ *
+ * @param {DevtoolsExtensionContext}
+ *   A devtools extension context running in a child process.
+ * @param {object} options
+ */
+class ChildNetworkResponseLoader {
+  constructor(context, requestId) {
+    this.context = context;
+    this.requestId = requestId;
+  }
+
+  api() {
+    const {context, requestId} = this;
+    return {
+      getContent(callback) {
+        return context.childManager.callParentAsyncFunction(
+          "devtools.network.Request.getContent",
+          [requestId],
+          callback);
+      },
+    };
+  }
+}
+
+this.devtools_network = class extends ExtensionAPI {
+  getAPI(context) {
+    return {
+      devtools: {
+        network: {
+          onRequestFinished: new EventManager(context, "devtools.network.onRequestFinished", fire => {
+            let onFinished = (data) => {
+              const loader = new ChildNetworkResponseLoader(context, data.requestId);
+              const harEntry = {...data.harEntry, ...loader.api()};
+              const result = Cu.cloneInto(harEntry, context.cloneScope, {
+                cloneFunctions: true,
+              });
+              fire.asyncWithoutClone(result);
+            };
+
+            let parent = context.childManager.getParentEvent("devtools.network.onRequestFinished");
+            parent.addListener(onFinished);
+            return () => {
+              parent.removeListener(onFinished);
+            };
+          }).api(),
+        },
+      },
+    };
+  }
+};
--- a/browser/components/extensions/ext-devtools-network.js
+++ b/browser/components/extensions/ext-devtools-network.js
@@ -1,15 +1,19 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 // The ext-* files are imported into the same scopes.
 /* import-globals-from ext-devtools.js */
 
+var {
+  SpreadArgs,
+} = ExtensionCommon;
+
 this.devtools_network = class extends ExtensionAPI {
   getAPI(context) {
     return {
       devtools: {
         network: {
           onNavigated: new EventManager(context, "devtools.onNavigated", fire => {
             let listener = (event, data) => {
               fire.async(data.url);
@@ -24,13 +28,42 @@ this.devtools_network = class extends Ex
                 target.off("navigate", listener);
               });
             };
           }).api(),
 
           getHAR: function() {
             return context.devToolsToolbox.getHARFromNetMonitor();
           },
+
+          onRequestFinished: new EventManager(context, "devtools.network.onRequestFinished", fire => {
+            const listener = (data) => {
+              fire.async(data);
+            };
+
+            const toolbox = context.devToolsToolbox;
+            toolbox.addRequestFinishedListener(listener);
+
+            return () => {
+              toolbox.removeRequestFinishedListener(listener);
+            };
+          }).api(),
+
+          // The following method is used internally to allow the request API
+          // piece that is running in the child process to ask the parent process
+          // to fetch response content from the back-end.
+          Request: {
+            async getContent(requestId) {
+              return context.devToolsToolbox.fetchResponseContent(requestId)
+                .then(({content}) => new SpreadArgs([content.text, content.mimeType]))
+                .catch(err => {
+                  const debugName = context.extension.policy.debugName;
+                  const errorMsg = "Unexpected error while fetching response content";
+                  Cu.reportError(`${debugName}: ${errorMsg} for ${requestId}: ${err}`);
+                  throw new ExtensionError(errorMsg);
+                });
+            },
+          },
         },
       },
     };
   }
 };
--- a/browser/components/extensions/ext-tabs.js
+++ b/browser/components/extensions/ext-tabs.js
@@ -6,30 +6,29 @@
 /* import-globals-from ext-browser.js */
 
 ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
                                "resource://gre/modules/PrivateBrowsingUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "PromiseUtils",
                                "resource://gre/modules/PromiseUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "Services",
                                "resource://gre/modules/Services.jsm");
+ChromeUtils.defineModuleGetter(this, "SessionStore",
+                               "resource:///modules/sessionstore/SessionStore.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "strBundle", function() {
   return Services.strings.createBundle("chrome://global/locale/extensions.properties");
 });
 
 var {
   ExtensionError,
 } = ExtensionUtils;
 
 const TABHIDE_PREFNAME = "extensions.webextensions.tabhide.enabled";
 
-// WeakMap[Tab -> ExtensionID]
-let hiddenTabs = new WeakMap();
-
 let tabListener = {
   tabReadyInitialized: false,
   tabReadyPromises: new WeakMap(),
   initializingTabs: new WeakSet(),
 
   initTabReady() {
     if (!this.tabReadyInitialized) {
       windowTracker.addListener("progress", this);
@@ -88,21 +87,19 @@ this.tabs = class extends ExtensionAPI {
     }
     if (reason == "ADDON_DISABLE" ||
         reason == "ADDON_UNINSTALL") {
       // Show all hidden tabs if a tab managing extension is uninstalled or
       // disabled.  If a user has more than one, the extensions will need to
       // self-manage re-hiding tabs.
       for (let tab of this.extension.tabManager.query()) {
         let nativeTab = tabTracker.getTab(tab.id);
-        if (hiddenTabs.get(nativeTab) === this.extension.id) {
-          hiddenTabs.delete(nativeTab);
-          if (nativeTab.ownerGlobal) {
-            nativeTab.ownerGlobal.gBrowser.showTab(nativeTab);
-          }
+        if (nativeTab.hidden && nativeTab.ownerGlobal &&
+            SessionStore.getTabValue(nativeTab, "hiddenBy") === this.extension.id) {
+          nativeTab.ownerGlobal.gBrowser.showTab(nativeTab);
         }
       }
     }
   }
 
   getAPI(context) {
     let {extension} = context;
 
@@ -296,18 +293,16 @@ this.tabs = class extends ExtensionAPI {
               needed.push("pinned");
             } else if (event.type == "TabBrowserInserted" &&
                        !event.detail.insertedOnTabCreation) {
               needed.push("discarded");
             } else if (event.type == "TabBrowserDiscarded") {
               needed.push("discarded");
             } else if (event.type == "TabShow") {
               needed.push("hidden");
-              // Always remove the tab from the hiddenTabs map.
-              hiddenTabs.delete(event.originalTarget);
             } else if (event.type == "TabHide") {
               needed.push("hidden");
             }
 
             let tab = tabManager.getWrapper(event.originalTarget);
             let changeInfo = {};
             for (let prop of needed) {
               changeInfo[prop] = tab[prop];
@@ -1059,17 +1054,16 @@ this.tabs = class extends ExtensionAPI {
 
           if (!Array.isArray(tabIds)) {
             tabIds = [tabIds];
           }
 
           for (let tabId of tabIds) {
             let tab = tabTracker.getTab(tabId);
             if (tab.ownerGlobal) {
-              hiddenTabs.delete(tab);
               tab.ownerGlobal.gBrowser.showTab(tab);
             }
           }
         },
 
         hide(tabIds) {
           if (!Services.prefs.getBoolPref(TABHIDE_PREFNAME, false)) {
             throw new ExtensionError(`tabs.hide is currently experimental and must be enabled with the ${TABHIDE_PREFNAME} preference.`);
@@ -1078,19 +1072,18 @@ this.tabs = class extends ExtensionAPI {
           if (!Array.isArray(tabIds)) {
             tabIds = [tabIds];
           }
 
           let hidden = [];
           let tabs = tabIds.map(tabId => tabTracker.getTab(tabId));
           for (let tab of tabs) {
             if (tab.ownerGlobal && !tab.hidden) {
-              tab.ownerGlobal.gBrowser.hideTab(tab);
+              tab.ownerGlobal.gBrowser.hideTab(tab, extension.id);
               if (tab.hidden) {
-                hiddenTabs.set(tab, extension.id);
                 hidden.push(tabTracker.getId(tab));
               }
             }
           }
           return hidden;
         },
       },
     };
--- a/browser/components/extensions/jar.mn
+++ b/browser/components/extensions/jar.mn
@@ -32,13 +32,14 @@ browser.jar:
     content/browser/ext-pkcs11.js
     content/browser/ext-sessions.js
     content/browser/ext-sidebarAction.js
     content/browser/ext-tabs.js
     content/browser/ext-url-overrides.js
     content/browser/ext-windows.js
     content/browser/ext-c-browser.js
     content/browser/ext-c-devtools-inspectedWindow.js
+    content/browser/ext-c-devtools-network.js
     content/browser/ext-c-devtools-panels.js
     content/browser/ext-c-devtools.js
     content/browser/ext-c-menus.js
     content/browser/ext-c-omnibox.js
     content/browser/ext-c-tabs.js
--- a/browser/components/extensions/schemas/devtools_network.json
+++ b/browser/components/extensions/schemas/devtools_network.json
@@ -63,21 +63,24 @@
             ]
           }
         ]
       }
     ],
     "events": [
       {
         "name": "onRequestFinished",
-        "unsupported": true,
         "type": "function",
         "description": "Fired when a network request is finished and all request data are available.",
         "parameters": [
-          { "name": "request", "$ref": "Request", "description": "Description of a network request in the form of a HAR entry. See HAR specification for details." }
+          {
+            "name": "request",
+            "$ref": "Request",
+            "description": "Description of a network request in the form of a HAR entry. See HAR specification for details."
+          }
         ]
       },
       {
         "name": "onNavigated",
         "type": "function",
         "description": "Fired when the inspected window navigates to a new page.",
         "parameters": [
           {
--- a/browser/components/extensions/test/browser/browser_ext_devtools_network.js
+++ b/browser/components/extensions/test/browser/browser_ext_devtools_network.js
@@ -50,16 +50,38 @@ function devtools_page() {
     browser.test.sendMessage("getHAR-result", harLog);
 
     if (harLogCount === 2) {
       harLogCount = 0;
       browser.test.onMessage.removeListener(harListener);
     }
   };
   browser.test.onMessage.addListener(harListener);
+
+  let requestFinishedListener = async request => {
+    browser.test.assertTrue(request.request, "Request entry must exist");
+    browser.test.assertTrue(request.response, "Response entry must exist");
+
+    browser.test.sendMessage("onRequestFinished");
+
+    // Get response content using callback
+    request.getContent((content, encoding) => {
+      browser.test.sendMessage("onRequestFinished-callbackExecuted",
+                               [content, encoding]);
+    });
+
+    // Get response content using returned promise
+    request.getContent().then(([content, encoding]) => {
+      browser.test.sendMessage("onRequestFinished-promiseResolved",
+                               [content, encoding]);
+    });
+
+    browser.devtools.network.onRequestFinished.removeListener(requestFinishedListener);
+  };
+  browser.devtools.network.onRequestFinished.addListener(requestFinishedListener);
 }
 
 function waitForRequestAdded(toolbox) {
   return new Promise(resolve => {
     let netPanel = toolbox.getPanel("netmonitor");
     netPanel.panelWin.once("NetMonitor:RequestAdded", () => {
       resolve();
     });
@@ -152,16 +174,17 @@ add_task(async function test_devtools_ne
   // Reload the page to collect some HTTP requests.
   extension.sendMessage("navigate");
 
   // Wait till the navigation is complete and request
   // added into the net panel.
   await Promise.all([
     extension.awaitMessage("tabUpdated"),
     extension.awaitMessage("onNavigatedFired"),
+    extension.awaitMessage("onRequestFinished"),
     waitForRequestAdded(toolbox),
   ]);
 
   // Get HAR, it should not be empty now.
   const getHARPromise = extension.awaitMessage("getHAR-result");
   extension.sendMessage("getHAR");
   const getHARResult = await getHARPromise;
   is(getHARResult.log.entries.length, 1, "HAR log should not be empty");
@@ -170,8 +193,61 @@ add_task(async function test_devtools_ne
   await gDevTools.closeToolbox(target);
 
   await target.destroy();
 
   await extension.unload();
 
   await BrowserTestUtils.removeTab(tab);
 });
+
+/**
+ * Test for `chrome.devtools.network.onRequestFinished()` API
+ */
+add_task(async function test_devtools_network_on_request_finished() {
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/");
+  let extension = ExtensionTestUtils.loadExtension(extData);
+
+  await extension.startup();
+  await extension.awaitMessage("ready");
+
+  let target = gDevTools.getTargetForTab(tab);
+
+  // Open the Toolbox
+  let toolbox = await gDevTools.showToolbox(target, "netmonitor");
+  info("Developer toolbox opened.");
+
+  // Reload and wait for onRequestFinished event.
+  extension.sendMessage("navigate");
+
+  await Promise.all([
+    extension.awaitMessage("tabUpdated"),
+    extension.awaitMessage("onNavigatedFired"),
+    waitForRequestAdded(toolbox),
+  ]);
+
+  await extension.awaitMessage("onRequestFinished");
+
+  // Wait for response content being fetched.
+  let [callbackRes, promiseRes] = await Promise.all([
+    extension.awaitMessage("onRequestFinished-callbackExecuted"),
+    extension.awaitMessage("onRequestFinished-promiseResolved"),
+  ]);
+
+  ok(callbackRes[0].startsWith("<html>"),
+     "The expected content has been retrieved.");
+  is(callbackRes[1], "text/html; charset=utf-8",
+     "The expected content has been retrieved.");
+
+  is(promiseRes[0], callbackRes[0],
+     "The resolved value is equal to the one received in the callback API mode");
+  is(promiseRes[1], callbackRes[1],
+     "The resolved value is equal to the one received in the callback API mode");
+
+  // Shutdown
+  await gDevTools.closeToolbox(target);
+
+  await target.destroy();
+
+  await extension.unload();
+
+  await BrowserTestUtils.removeTab(tab);
+});
--- a/browser/components/extensions/test/browser/browser_ext_tabs_hide.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_hide.js
@@ -1,10 +1,14 @@
 "use strict";
 
+ChromeUtils.defineModuleGetter(this, "SessionStore",
+                               "resource:///modules/sessionstore/SessionStore.jsm");
+ChromeUtils.defineModuleGetter(this, "TabStateFlusher",
+                               "resource:///modules/sessionstore/TabStateFlusher.jsm");
 const {Utils} = ChromeUtils.import("resource://gre/modules/sessionstore/Utils.jsm", {});
 const triggeringPrincipal_base64 = Utils.SERIALIZED_SYSTEMPRINCIPAL;
 
 // Ensure the pref prevents API use when the extension has the tabHide permission.
 add_task(async function test_pref_disabled() {
   async function background() {
     let tabs = await browser.tabs.query({hidden: false});
     let ids = tabs.map(tab => tab.id);
@@ -111,19 +115,36 @@ add_task(async function test_tabs_showhi
   for (let win of BrowserWindowIterator()) {
     if (win != window) {
       otherwin = win;
     }
     let tabs = Array.from(win.gBrowser.tabs.values());
     ok(!tabs[0].hidden, "first tab not hidden");
     for (let i = 1; i < tabs.length; i++) {
       ok(tabs[i].hidden, "tab hidden value is correct");
+      let id = SessionStore.getTabValue(tabs[i], "hiddenBy");
+      is(id, extension.id, "tab hiddenBy value is correct");
+      await TabStateFlusher.flush(tabs[i].linkedBrowser);
     }
   }
 
+  // Close the other window then restore it to test that the tabs are
+  // restored with proper hidden state, and the correct extension id.
+  await BrowserTestUtils.closeWindow(otherwin);
+
+  otherwin = SessionStore.undoCloseWindow(0);
+  await BrowserTestUtils.waitForEvent(otherwin, "load");
+  let tabs = Array.from(otherwin.gBrowser.tabs.values());
+  ok(!tabs[0].hidden, "first tab not hidden");
+  for (let i = 1; i < tabs.length; i++) {
+    ok(tabs[i].hidden, "tab hidden value is correct");
+    let id = SessionStore.getTabValue(tabs[i], "hiddenBy");
+    is(id, extension.id, "tab hiddenBy value is correct");
+  }
+
   // Test closing the last visible tab, the next tab which is hidden should become
   // the selectedTab and will be visible.
   ok(!otherwin.gBrowser.selectedTab.hidden, "selected tab is not hidden");
   await BrowserTestUtils.removeTab(otherwin.gBrowser.selectedTab);
   ok(!otherwin.gBrowser.selectedTab.hidden, "tab was unhidden");
 
   // Showall will unhide any remaining hidden tabs.
   extension.sendMessage("showall");
--- a/browser/components/places/content/places.js
+++ b/browser/components/places/content/places.js
@@ -1,17 +1,16 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* 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/. */
 
 /* import-globals-from editBookmarkOverlay.js */
 // Via downloadsViewOverlay.xul -> allDownloadsViewOverlay.xul
 /* import-globals-from ../../../../toolkit/content/contentAreaUtils.js */
-/* import-globals-from ../../../base/content/browser-sync.js */
 
 ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/TelemetryStopwatch.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.defineModuleGetter(this, "MigrationUtils",
                                "resource:///modules/MigrationUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "BookmarkJSONUtils",
@@ -172,17 +171,16 @@ var PlacesOrganizer = {
       }
     }
 
     // remove the "Properties" context-menu item, we've our own details pane
     document.getElementById("placesContext")
             .removeChild(document.getElementById("placesContext_show:info"));
 
     ContentArea.focus();
-    gSync.init();
   },
 
   QueryInterface: function PO_QueryInterface(aIID) {
     if (aIID.equals(Components.interfaces.nsIDOMEventListener) ||
         aIID.equals(Components.interfaces.nsISupports))
       return this;
 
     throw Components.results.NS_NOINTERFACE;
--- a/browser/components/places/content/places.xul
+++ b/browser/components/places/content/places.xul
@@ -43,18 +43,16 @@
         persist="width height screenX screenY sizemode">
 
   <script type="application/javascript"
           src="chrome://browser/content/places/places.js"/>
 #ifndef XP_MACOSX
   <!-- On Mac, these are included via macBrowserOverlay.xul -> browser.js -> defineLazyScriptGetter -->
   <script type="application/javascript"
           src="chrome://browser/content/places/editBookmarkOverlay.js"/>
-  <script type="application/javascript"
-          src="chrome://browser/content/browser-sync.js"/>
 #endif
 
   <stringbundleset id="placesStringSet">
     <stringbundle id="brandStrings" src="chrome://branding/locale/brand.properties"/>
   </stringbundleset>
 
 
 #ifdef XP_MACOSX
--- a/browser/components/preferences/in-content/extensionControlled.js
+++ b/browser/components/preferences/in-content/extensionControlled.js
@@ -91,70 +91,70 @@ function getControllingExtensionEls(sett
 
 async function getControllingExtension(type, settingName) {
   let info = await getControllingExtensionInfo(type, settingName);
   let addon = info && info.id
     && await AddonManager.getAddonByID(info.id);
   return addon;
 }
 
-async function handleControllingExtension(type, settingName) {
+async function handleControllingExtension(type, settingName, stringId) {
   let addon = await getControllingExtension(type, settingName);
 
   // Sometimes the ExtensionSettingsStore gets in a bad state where it thinks
   // an extension is controlling a setting but the extension has been uninstalled
   // outside of the regular lifecycle. If the extension isn't currently installed
   // then we should treat the setting as not being controlled.
   // See https://bugzilla.mozilla.org/show_bug.cgi?id=1411046 for an example.
   if (addon) {
     extensionControlledIds[settingName] = addon.id;
-    showControllingExtension(settingName, addon);
+    showControllingExtension(settingName, addon, stringId);
   } else {
     let elements = getControllingExtensionEls(settingName);
     if (extensionControlledIds[settingName]
         && !document.hidden
         && elements.button) {
       showEnableExtensionMessage(settingName);
     } else {
       hideControllingExtension(settingName);
     }
     delete extensionControlledIds[settingName];
   }
 
   return !!addon;
 }
 
-function getControllingExtensionFragment(settingName, addon, ...extraArgs) {
-  let msg = document.getElementById("bundlePreferences")
-                    .getString(`extensionControlled.${settingName}`);
+function getControllingExtensionFragment(stringId, addon, ...extraArgs) {
+  let msg = document.getElementById("bundlePreferences").getString(stringId);
   let image = document.createElement("image");
   const defaultIcon = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
   image.setAttribute("src", addon.iconURL || defaultIcon);
   image.classList.add("extension-controlled-icon");
   let addonBit = document.createDocumentFragment();
   addonBit.appendChild(image);
   addonBit.appendChild(document.createTextNode(" " + addon.name));
   return BrowserUtils.getLocalizedFragment(document, msg, addonBit, ...extraArgs);
 }
 
-async function showControllingExtension(settingName, addon) {
+async function showControllingExtension(
+  settingName, addon, stringId = `extensionControlled.${settingName}`) {
   // Tell the user what extension is controlling the setting.
   let elements = getControllingExtensionEls(settingName);
   let extraArgs = getExtensionControlledArgs(settingName);
 
   elements.section.classList.remove("extension-controlled-disabled");
   let description = elements.description;
 
   // Remove the old content from the description.
   while (description.firstChild) {
     description.firstChild.remove();
   }
 
   let fragment = getControllingExtensionFragment(
-    settingName, addon, ...extraArgs);
+    stringId, addon, ...extraArgs);
   description.appendChild(fragment);
 
   if (elements.button) {
     elements.button.hidden = false;
   }
 
   // Show the controlling extension row and hide the old label.
   elements.section.hidden = false;
--- a/browser/components/preferences/in-content/main.js
+++ b/browser/components/preferences/in-content/main.js
@@ -36,16 +36,17 @@ const PREF_SHOW_PLUGINS_IN_LIST = "brows
 const PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS =
   "browser.download.hide_plugins_without_extensions";
 
 // Strings to identify ExtensionSettingsStore overrides
 const CONTAINERS_KEY = "privacy.containers";
 const HOMEPAGE_OVERRIDE_KEY = "homepage_override";
 const URL_OVERRIDES_TYPE = "url_overrides";
 const NEW_TAB_KEY = "newTabURL";
+const NEW_TAB_STRING_ID = "extensionControlled.newTabURL2";
 
 /*
  * Preferences where we store handling information about the feed type.
  *
  * browser.feeds.handler
  * - "bookmarks", "reader" (clarified further using the .default preference),
  *   or "ask" -- indicates the default handler being used to process feeds;
  *   "bookmarks" is obsolete; to specify that the handler is bookmarks,
@@ -354,20 +355,22 @@ var gMainPane = {
     this.updatePerformanceSettingsBox({ duringChangeEvent: false });
 
     // set up the "use current page" label-changing listener
     this._updateUseCurrentButton();
     window.addEventListener("focus", this._updateUseCurrentButton.bind(this));
 
     this.updateBrowserStartupLastSession();
 
-    handleControllingExtension(URL_OVERRIDES_TYPE, NEW_TAB_KEY);
+    handleControllingExtension(
+      URL_OVERRIDES_TYPE, NEW_TAB_KEY, NEW_TAB_STRING_ID);
     let newTabObserver = {
       observe(subject, topic, data) {
-          handleControllingExtension(URL_OVERRIDES_TYPE, NEW_TAB_KEY);
+        handleControllingExtension(
+          URL_OVERRIDES_TYPE, NEW_TAB_KEY, NEW_TAB_STRING_ID);
       },
     };
     Services.obs.addObserver(newTabObserver, "newtab-url-changed");
     window.addEventListener("unload", () => {
       Services.obs.removeObserver(newTabObserver, "newtab-url-changed");
     });
 
     let connectionSettingsLink = document.getElementById("connectionSettingsLearnMore");
@@ -658,17 +661,17 @@ var gMainPane = {
 
     const link = document.getElementById("browserContainersLearnMore");
     link.href = Services.urlFormatter.formatURLPref("app.support.baseURL") + "containers";
 
     document.getElementById("browserContainersbox").hidden = false;
     this.readBrowserContainersCheckbox();
   },
 
-  separateProfileModeChange() {
+  async separateProfileModeChange() {
     if (AppConstants.MOZ_DEV_EDITION) {
       function quitApp() {
         Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestartNotSameProfile);
       }
       function revertCheckbox(error) {
         separateProfileModeCheckbox.checked = !separateProfileModeCheckbox.checked;
         if (error) {
           Cu.reportError("Failed to toggle separate profile mode: " + error);
@@ -681,17 +684,17 @@ var gMainPane = {
         if (separateProfileModeCheckbox.checked) {
           OS.File.remove(ignoreSeparateProfile).then(onSuccess, revertCheckbox);
         } else {
           OS.File.writeAtomic(ignoreSeparateProfile, new Uint8Array()).then(onSuccess, revertCheckbox);
         }
       }
 
       let separateProfileModeCheckbox = document.getElementById("separateProfileMode");
-      let button_index = confirmRestartPrompt(separateProfileModeCheckbox.checked,
+      let button_index = await confirmRestartPrompt(separateProfileModeCheckbox.checked,
         0, false, true);
       switch (button_index) {
         case CONFIRM_RESTART_PROMPT_CANCEL:
           revertCheckbox();
           return;
         case CONFIRM_RESTART_PROMPT_RESTART_NOW:
           let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
             .createInstance(Ci.nsISupportsPRBool);
@@ -767,17 +770,18 @@ var gMainPane = {
     }
 
     if (homePref.locked) {
       // An extension can't control these settings if they're locked.
       hideControllingExtension(HOMEPAGE_OVERRIDE_KEY);
       setInputDisabledStates(false);
     } else {
       // Asynchronously update the extension controlled UI.
-      handleControllingExtension(PREF_SETTING_TYPE, HOMEPAGE_OVERRIDE_KEY)
+      handleControllingExtension(
+        PREF_SETTING_TYPE, HOMEPAGE_OVERRIDE_KEY, "extensionControlled.homepage_override2")
         .then(setInputDisabledStates);
     }
 
     // If the pref is set to about:home or about:newtab, set the value to ""
     // to show the placeholder text (about:home title) rather than
     // exposing those URLs to users.
     let defaultBranch = Services.prefs.getDefaultBranch("");
     let defaultValue = defaultBranch.getComplexValue("browser.startup.homepage",
@@ -1100,17 +1104,18 @@ var gMainPane = {
                     null, null, this.updateProxySettingsUI.bind(this));
   },
 
   // Update the UI to show the proper description depending on whether an
   // extension is in control or not.
   async updateProxySettingsUI() {
     let controllingExtension = await getControllingExtension(PREF_SETTING_TYPE, PROXY_KEY);
     let fragment = controllingExtension ?
-      getControllingExtensionFragment(PROXY_KEY, controllingExtension, this._brandShortName) :
+      getControllingExtensionFragment(
+        "extensionControlled.proxyConfig", controllingExtension, this._brandShortName) :
       BrowserUtils.getLocalizedFragment(
         document,
         this._prefsBundle.getString("connectionDesc.label"),
         this._brandShortName);
     let description = document.getElementById("connectionSettingsDescription");
 
     // Remove the old content from the description.
     while (description.firstChild) {
--- a/browser/components/preferences/in-content/preferences.js
+++ b/browser/components/preferences/in-content/preferences.js
@@ -324,50 +324,51 @@ function internalPrefCategoryNameToFrien
 // and "restart later" buttons and returns the index of the button chosen.
 // We can choose not to display the "restart later", or "revert" buttons,
 // altough the later still lets us revert by using the escape key.
 //
 // The constants are useful to interpret the return value of the function.
 const CONFIRM_RESTART_PROMPT_RESTART_NOW = 0;
 const CONFIRM_RESTART_PROMPT_CANCEL = 1;
 const CONFIRM_RESTART_PROMPT_RESTART_LATER = 2;
-function confirmRestartPrompt(aRestartToEnable, aDefaultButtonIndex,
-                              aWantRevertAsCancelButton,
-                              aWantRestartLaterButton) {
-  let brandName = document.getElementById("bundleBrand").getString("brandShortName");
-  let bundle = document.getElementById("bundlePreferences");
-  let msg = bundle.getFormattedString(aRestartToEnable ?
-                                      "featureEnableRequiresRestart" :
-                                      "featureDisableRequiresRestart",
-                                      [brandName]);
-  let title = bundle.getFormattedString("shouldRestartTitle", [brandName]);
+async function confirmRestartPrompt(aRestartToEnable, aDefaultButtonIndex,
+                                    aWantRevertAsCancelButton,
+                                    aWantRestartLaterButton) {
+  let [
+    msg, title, restartButtonText, noRestartButtonText, restartLaterButtonText
+  ] = await document.l10n.formatValues([
+    [aRestartToEnable ?
+      "feature-enable-requires-restart" : "feature-disable-requires-restart"],
+    ["should-restart-title"],
+    ["should-restart-ok"],
+    ["revert-no-restart-button"],
+    ["restart-later"],
+  ]);
 
   // Set up the first (index 0) button:
-  let button0Text = bundle.getFormattedString("okToRestartButton", [brandName]);
   let buttonFlags = (Services.prompt.BUTTON_POS_0 *
                      Services.prompt.BUTTON_TITLE_IS_STRING);
 
 
   // Set up the second (index 1) button:
-  let button1Text = null;
   if (aWantRevertAsCancelButton) {
-    button1Text = bundle.getString("revertNoRestartButton");
     buttonFlags += (Services.prompt.BUTTON_POS_1 *
                     Services.prompt.BUTTON_TITLE_IS_STRING);
   } else {
+    noRestartButtonText = null;
     buttonFlags += (Services.prompt.BUTTON_POS_1 *
                     Services.prompt.BUTTON_TITLE_CANCEL);
   }
 
   // Set up the third (index 2) button:
-  let button2Text = null;
   if (aWantRestartLaterButton) {
-    button2Text = bundle.getString("restartLater");
     buttonFlags += (Services.prompt.BUTTON_POS_2 *
                     Services.prompt.BUTTON_TITLE_IS_STRING);
+  } else {
+    restartLaterButtonText = null;
   }
 
   switch (aDefaultButtonIndex) {
     case 0:
       buttonFlags += Services.prompt.BUTTON_POS_0_DEFAULT;
       break;
     case 1:
       buttonFlags += Services.prompt.BUTTON_POS_1_DEFAULT;
@@ -375,18 +376,18 @@ function confirmRestartPrompt(aRestartTo
     case 2:
       buttonFlags += Services.prompt.BUTTON_POS_2_DEFAULT;
       break;
     default:
       break;
   }
 
   let buttonIndex = Services.prompt.confirmEx(window, title, msg, buttonFlags,
-                                              button0Text, button1Text, button2Text,
-                                              null, {});
+                                              restartButtonText, noRestartButtonText,
+                                              restartLaterButtonText, null, {});
 
   // If we have the second confirmation dialog for restart, see if the user
   // cancels out at that point.
   if (buttonIndex == CONFIRM_RESTART_PROMPT_RESTART_NOW) {
     let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
                        .createInstance(Ci.nsISupportsPRBool);
     Services.obs.notifyObservers(cancelQuit, "quit-application-requested",
                                   "restart");
--- a/browser/components/preferences/in-content/preferences.xul
+++ b/browser/components/preferences/in-content/preferences.xul
@@ -84,22 +84,20 @@
 
 #ifdef XP_WIN
 #define USE_WIN_TITLE_STYLE
 #endif
 
 <page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
       xmlns:html="http://www.w3.org/1999/xhtml"
       disablefastfind="true"
-#ifdef USE_WIN_TITLE_STYLE
-      title="&prefWindow.titleWin;">
-#else
-      title="&prefWindow.title;">
-#endif
+      data-l10n-id="pref-page"
+      data-l10n-attrs="title">
 
+  <link rel="localization" href="branding/brand.ftl"/>
   <link rel="localization" href="browser/preferences/main.ftl"/>
   <link rel="localization" href="browser/preferences/preferences.ftl"/>
   <script type="text/javascript" src="chrome://global/content/l10n.js"></script>
 
   <html:link rel="shortcut icon"
               href="chrome://browser/skin/settings.svg"/>
 
   <script type="application/javascript"
@@ -132,78 +130,87 @@
 
     <vbox class="navigation">
       <!-- category list -->
       <richlistbox id="categories">
         <richlistitem id="category-general"
                       class="category"
                       value="paneGeneral"
                       helpTopic="prefs-main"
-                      tooltiptext="&paneGeneral.title;"
+                      data-l10n-id="category-general"
+                      data-l10n-attrs="tooltiptext"
                       align="center">
           <image class="category-icon"/>
-          <label class="category-name" flex="1">&paneGeneral.title;</label>
+          <label class="category-name" flex="1" data-l10n-id="pane-general-title"></label>
         </richlistitem>
 
         <richlistitem id="category-search"
                       class="category"
                       value="paneSearch"
                       helpTopic="prefs-search"
-                      tooltiptext="&paneSearch.title;"
+                      data-l10n-id="category-search"
+                      data-l10n-attrs="tooltiptext"
                       align="center">
           <image class="category-icon"/>
-          <label class="category-name" flex="1">&paneSearch.title;</label>
+          <label class="category-name" flex="1" data-l10n-id="pane-search-title"></label>
         </richlistitem>
 
         <richlistitem id="category-containers"
                       class="category"
                       value="paneContainers"
                       helpTopic="prefs-containers"
                       hidden="true"/>
 
         <richlistitem id="category-privacy"
                       class="category"
                       value="panePrivacy"
                       helpTopic="prefs-privacy"
-                      tooltiptext="&panePrivacySecurity.title;"
+                      data-l10n-id="category-privacy"
+                      data-l10n-attrs="tooltiptext"
                       align="center">
           <image class="category-icon"/>
-          <label class="category-name" flex="1">&panePrivacySecurity.title;</label>
+          <label class="category-name" flex="1" data-l10n-id="pane-privacy-title"></label>
         </richlistitem>
 
         <richlistitem id="category-sync"
                       class="category"
                       value="paneSync"
                       helpTopic="prefs-weave"
-                      tooltiptext="&paneSync1.title;"
+                      data-l10n-id="category-sync"
+                      data-l10n-attrs="tooltiptext"
                       align="center">
           <image class="category-icon"/>
-          <label class="category-name" flex="1">&paneSync1.title;</label>
+          <label class="category-name" flex="1" data-l10n-id="pane-sync-title"></label>
         </richlistitem>
       </richlistbox>
 
       <spacer flex="1"/>
 
       <hbox class="help-button" pack="center">
         <label class="text-link">
           <hbox align="center">
-            <image class="help-icon"/><label class="help-label" flex="1">&helpButton2.label;</label>
+            <image class="help-icon"/>
+              <label class="help-label" flex="1" data-l10n-id="help-button-label"></label>
           </hbox>
         </label>
       </hbox>
     </vbox>
 
     <keyset>
-      <key key="&focusSearch1.key;" modifiers="accel" id="focusSearch1" oncommand="gSearchResultsPane.searchInput.focus();"/>
+      <key data-l10n-id="focus-search" modifiers="accel" id="focusSearch1" oncommand="gSearchResultsPane.searchInput.focus();"/>
     </keyset>
 
     <vbox class="main-content" flex="1" align="start">
       <vbox class="pane-container">
         <hbox class="search-container" pack="end">
-          <textbox type="search" id="searchInput" style="width: &searchField.width;" hidden="true" clickSelectsAll="true"/>
+          <textbox
+            type="search" id="searchInput"
+            data-l10n-id="search-field"
+            data-l10n-attrs="style"
+            hidden="true" clickSelectsAll="true"/>
         </hbox>
         <vbox id="mainPrefPane" class="prefpane prefwindow">
 #include searchResults.xul
 #include main.xul
 #include search.xul
 #include privacy.xul
 #include containers.xul
 #include sync.xul
@@ -217,17 +224,17 @@
     <groupbox class="dialogBox"
               orient="vertical"
               pack="end"
               role="dialog"
               aria-labelledby="dialogTitle">
       <caption flex="1" align="center">
         <label class="dialogTitle" flex="1"></label>
         <button class="dialogClose close-icon"
-                aria-label="&preferencesCloseButton.label;"/>
+                data-l10n-id="close-button"/>
       </caption>
       <browser class="dialogFrame"
                autoscroll="false"
                disablehistory="true"/>
     </groupbox>
   </vbox>
   </stack>
 </page>
--- a/browser/components/preferences/in-content/privacy.js
+++ b/browser/components/preferences/in-content/privacy.js
@@ -725,17 +725,17 @@ var gPrivacyPane = {
     let mode = document.getElementById("historyMode");
     let autoStart = document.getElementById("privateBrowsingAutoStart");
     this._lastMode = mode.selectedIndex;
     this._lastCheckState = autoStart.hasAttribute("checked");
   },
 
   _lastMode: null,
   _lastCheckState: null,
-  updateAutostart() {
+  async updateAutostart() {
     let mode = document.getElementById("historyMode");
     let autoStart = document.getElementById("privateBrowsingAutoStart");
     let pref = Preferences.get("browser.privatebrowsing.autostart");
     if ((mode.value == "custom" && this._lastCheckState == autoStart.checked) ||
       (mode.value == "remember" && !this._lastCheckState) ||
       (mode.value == "dontremember" && this._lastCheckState)) {
       // These are all no-op changes, so we don't need to prompt.
       this._lastMode = mode.selectedIndex;
@@ -743,17 +743,17 @@ var gPrivacyPane = {
       return;
     }
 
     if (!this._shouldPromptForRestart) {
       // We're performing a revert. Just let it happen.
       return;
     }
 
-    let buttonIndex = confirmRestartPrompt(autoStart.checked, 1,
+    let buttonIndex = await confirmRestartPrompt(autoStart.checked, 1,
       true, false);
     if (buttonIndex == CONFIRM_RESTART_PROMPT_RESTART_NOW) {
       pref.value = autoStart.hasAttribute("checked");
       Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
       return;
     }
 
     this._shouldPromptForRestart = false;
@@ -1621,18 +1621,18 @@ var gPrivacyPane = {
 
   _initA11yString() {
     let a11yLearnMoreLink =
       Services.urlFormatter.formatURLPref("accessibility.support.url");
     document.getElementById("a11yLearnMoreLink")
       .setAttribute("href", a11yLearnMoreLink);
   },
 
-  updateA11yPrefs(checked) {
-    let buttonIndex = confirmRestartPrompt(checked, 0, true, false);
+  async updateA11yPrefs(checked) {
+    let buttonIndex = await confirmRestartPrompt(checked, 0, true, false);
     if (buttonIndex == CONFIRM_RESTART_PROMPT_RESTART_NOW) {
       Services.prefs.setIntPref("accessibility.force_disabled", checked ? 1 : 0);
       Services.telemetry.scalarSet("preferences.prevent_accessibility_services", true);
       Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
     }
 
     // Revert the checkbox in case we didn't quit
     document.getElementById("a11yPrivacyCheckbox").checked = !checked;
--- a/browser/components/preferences/in-content/tests/browser_extension_controlled.js
+++ b/browser/components/preferences/in-content/tests/browser_extension_controlled.js
@@ -101,17 +101,17 @@ add_task(async function testExtensionCon
   // Install an extension that will set the homepage.
   await installAddon("set_homepage.xpi");
   await waitForMessageShown("browserHomePageExtensionContent");
 
   // The homepage has been set by the extension, the user is notified and it isn't editable.
   let controlledLabel = controlledContent.querySelector("description");
   is(homepagePref(), extensionHomepage, "homepage is set by extension");
   // There are two spaces before "set_homepage" because it's " <image /> set_homepage".
-  is(controlledLabel.textContent, "An extension,  set_homepage, controls your home page.",
+  is(controlledLabel.textContent, "An extension,  set_homepage, is controlling your home page.",
      "The user is notified that an extension is controlling the homepage");
   is(controlledContent.hidden, false, "The extension controlled row is hidden");
   is(doc.getElementById("browserHomePage").disabled, true, "The homepage input is disabled");
 
   // Disable the extension.
   let enableMessageShown = waitForEnableMessage(controlledContent.id);
   doc.getElementById("disableHomePageExtension").click();
   await enableMessageShown;
@@ -291,17 +291,17 @@ add_task(async function testExtensionCon
   await installAddon("set_newtab.xpi");
 
   await waitForMessageShown("browserNewTabExtensionContent");
 
   // The new tab page has been set by the extension and the user is notified.
   let controlledLabel = controlledContent.querySelector("description");
   ok(aboutNewTabService.newTabURL.startsWith("moz-extension:"), "new tab url is set by extension");
   // There are two spaces before "set_newtab" because it's " <image /> set_newtab".
-  is(controlledLabel.textContent, "An extension,  set_newtab, controls your New Tab page.",
+  is(controlledLabel.textContent, "An extension,  set_newtab, is controlling your New Tab page.",
      "The user is notified that an extension is controlling the new tab page");
   is(controlledContent.hidden, false, "The extension controlled row is hidden");
 
   // Disable the extension.
   doc.getElementById("disableNewTabExtension").click();
 
   // Verify the user is notified how to enable the extension.
   await waitForEnableMessage(controlledContent.id);
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -3431,17 +3431,17 @@ var SessionStoreInternal = {
           tabbrowser.selectedTab = tab;
           tabbrowser.removeTab(leftoverTab);
         }
       }
 
       tabs.push(tab);
 
       if (tabData.hidden) {
-        tabbrowser.hideTab(tab);
+        tabbrowser.hideTab(tab, tabData.extData && tabData.extData.hiddenBy);
       }
 
       if (tabData.pinned) {
         tabbrowser.pinTab(tab);
       }
     }
 
     // Move the originally open tabs to the end.
--- a/browser/extensions/formautofill/bootstrap.js
+++ b/browser/extensions/formautofill/bootstrap.js
@@ -51,24 +51,25 @@ function addUpgradeListener(instanceID) 
   });
 }
 
 function isAvailable() {
   let availablePref = Services.prefs.getCharPref("extensions.formautofill.available");
   if (availablePref == "on") {
     return true;
   } else if (availablePref == "detect") {
+    let locale = Services.locale.getRequestedLocale();
     let region = Services.prefs.getCharPref("browser.search.region", "");
     let supportedCountries = Services.prefs.getCharPref("extensions.formautofill.supportedCountries")
                                            .split(",");
     if (!Services.prefs.getBoolPref("extensions.formautofill.supportRTL") &&
         Services.locale.isAppLocaleRTL) {
       return false;
     }
-    return supportedCountries.includes(region);
+    return locale == "en-US" && supportedCountries.includes(region);
   }
   return false;
 }
 
 function startup(data) {
   if (!isAvailable()) {
     Services.prefs.clearUserPref("dom.forms.autocomplete.formautofill");
     // reset the sync related prefs incase the feature was previously available
--- a/browser/extensions/formautofill/test/browser/browser_editAddressDialog.js
+++ b/browser/extensions/formautofill/test/browser/browser_editAddressDialog.js
@@ -1,10 +1,17 @@
 "use strict";
 
+add_task(async function setup_supportedCountries() {
+  await SpecialPowers.pushPrefEnv({set: [
+    [SUPPORTED_COUNTRIES_PREF, "US,CA,DE"],
+  ]});
+});
+
+
 add_task(async function test_cancelEditAddressDialog() {
   await testDialog(EDIT_ADDRESS_DIALOG_URL, win => {
     win.document.querySelector("#cancel").click();
   });
 });
 
 add_task(async function test_cancelEditAddressDialogWithESC() {
   await testDialog(EDIT_ADDRESS_DIALOG_URL, win => {
--- a/browser/extensions/formautofill/test/browser/head.js
+++ b/browser/extensions/formautofill/test/browser/head.js
@@ -1,12 +1,13 @@
 /* exported MANAGE_ADDRESSES_DIALOG_URL, MANAGE_CREDIT_CARDS_DIALOG_URL, EDIT_ADDRESS_DIALOG_URL, EDIT_CREDIT_CARD_DIALOG_URL,
             BASE_URL, TEST_ADDRESS_1, TEST_ADDRESS_2, TEST_ADDRESS_3, TEST_ADDRESS_4, TEST_ADDRESS_5, TEST_ADDRESS_CA_1, TEST_ADDRESS_DE_1,
             TEST_CREDIT_CARD_1, TEST_CREDIT_CARD_2, TEST_CREDIT_CARD_3, FORM_URL, CREDITCARD_FORM_URL,
             FTU_PREF, ENABLED_AUTOFILL_ADDRESSES_PREF, AUTOFILL_CREDITCARDS_AVAILABLE_PREF, ENABLED_AUTOFILL_CREDITCARDS_PREF,
+            SUPPORTED_COUNTRIES_PREF,
             SYNC_USERNAME_PREF, SYNC_ADDRESSES_PREF, SYNC_CREDITCARDS_PREF, SYNC_CREDITCARDS_AVAILABLE_PREF, CREDITCARDS_USED_STATUS_PREF,
             DEFAULT_REGION_PREF,
             sleep, expectPopupOpen, openPopupOn, expectPopupClose, closePopup, clickDoorhangerButton,
             getAddresses, saveAddress, removeAddresses, saveCreditCard,
             getDisplayedPopupItems, getDoorhangerCheckbox, waitForMasterPasswordDialog,
             getNotification, getDoorhangerButton, removeAllRecords, testDialog */
 
 "use strict";
@@ -21,16 +22,17 @@ const BASE_URL = "http://mochi.test:8888
 const FORM_URL = "http://mochi.test:8888/browser/browser/extensions/formautofill/test/browser/autocomplete_basic.html";
 const CREDITCARD_FORM_URL =
   "https://example.org/browser/browser/extensions/formautofill/test/browser/autocomplete_creditcard_basic.html";
 const FTU_PREF = "extensions.formautofill.firstTimeUse";
 const CREDITCARDS_USED_STATUS_PREF = "extensions.formautofill.creditCards.used";
 const ENABLED_AUTOFILL_ADDRESSES_PREF = "extensions.formautofill.addresses.enabled";
 const AUTOFILL_CREDITCARDS_AVAILABLE_PREF = "extensions.formautofill.creditCards.available";
 const ENABLED_AUTOFILL_CREDITCARDS_PREF = "extensions.formautofill.creditCards.enabled";
+const SUPPORTED_COUNTRIES_PREF = "extensions.formautofill.supportedCountries";
 const SYNC_USERNAME_PREF = "services.sync.username";
 const SYNC_ADDRESSES_PREF = "services.sync.engine.addresses";
 const SYNC_CREDITCARDS_PREF = "services.sync.engine.creditcards";
 const SYNC_CREDITCARDS_AVAILABLE_PREF = "services.sync.engine.creditcards.available";
 const DEFAULT_REGION_PREF = "browser.search.region";
 
 const TEST_ADDRESS_1 = {
   "given-name": "John",
--- a/browser/extensions/formautofill/test/mochitest/test_multi_locale_CA_address_form.html
+++ b/browser/extensions/formautofill/test/mochitest/test_multi_locale_CA_address_form.html
@@ -96,20 +96,26 @@ function checkFormFilled(selector, addre
 async function setupAddressStorage() {
   for (let address of MOCK_STORAGE) {
     await addAddress(address);
   }
 }
 
 initPopupListener();
 
+add_task(async function setup() {
+  // This test relies on being able to fill a Canadian address which isn't possible
+  // without `supportedCountries` allowing Canada
+  await SpecialPowers.pushPrefEnv({"set": [["extensions.formautofill.supportedCountries", "US,CA"]]});
+
+  await setupAddressStorage();
+});
+
 // Autofill the address with address level 1 code.
 add_task(async function autofill_with_level1_code() {
-  await setupAddressStorage();
-
   await setInput("#organization-en", "Mozilla Toronto");
   synthesizeKey("KEY_ArrowDown", {});
   await expectPopup();
 
   synthesizeKey("KEY_ArrowDown", {});
   // Replace address level 1 code with full name in English for test result
   let result = Object.assign({}, MOCK_STORAGE[1], {"address-level1": "Ontario"});
   await checkFormFilled("#form-en", result);
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -216,17 +216,16 @@
 @RESPATH@/components/dom_range.xpt
 @RESPATH@/components/dom_security.xpt
 @RESPATH@/components/dom_sidebar.xpt
 @RESPATH@/components/dom_storage.xpt
 #ifdef MOZ_WEBSPEECH
 @RESPATH@/components/dom_webspeechrecognition.xpt
 #endif
 @RESPATH@/components/dom_workers.xpt
-@RESPATH@/components/dom_xbl.xpt
 @RESPATH@/components/dom_xhr.xpt
 @RESPATH@/components/dom_xul.xpt
 @RESPATH@/components/dom_presentation.xpt
 @RESPATH@/components/downloads.xpt
 @RESPATH@/components/editor.xpt
 @RESPATH@/components/enterprisepolicies.xpt
 @RESPATH@/components/extensions.xpt
 @RESPATH@/components/exthandler.xpt
--- a/browser/locales/en-US/browser/preferences/preferences.ftl
+++ b/browser/locales/en-US/browser/preferences/preferences.ftl
@@ -1,10 +1,59 @@
-// 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/.
+# 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/.
 
 do-not-track-description = Send websites a “Do Not Track” signal that you don’t want to be tracked
 do-not-track-learn-more = Learn more
-do-not-track-option-default
+do-not-track-option-default =
     .label = Only when using Tracking Protection
-do-not-track-option-always
+do-not-track-option-always =
     .label = Always
+
+pref-page =
+    .title = { PLATFORM() ->
+            [windows] Options
+           *[other] Preferences
+        }
+
+# This is used to determine the width of the search field in about:preferences,
+# in order to make the entire placeholder string visible
+#
+# Notice: The value of the `.style` attribute is a CSS string, and the `min-width`
+# is the name of the CSS property. It is intended only to adjust the element's width.
+# Do not translate.
+search-field =
+    .style = min-width: 15.4em
+
+pane-general-title = General
+category-general =
+    .tooltiptext = { pane-general-title }
+
+pane-search-title = Search
+category-search =
+    .tooltiptext = { pane-search-title }
+
+pane-privacy-title = Privacy & Security
+category-privacy =
+    .tooltiptext = { pane-privacy-title }
+
+# The word "account" can be translated, do not translate or transliterate "Firefox".
+pane-sync-title = Firefox Account
+category-sync =
+    .tooltiptext = { pane-sync-title }
+
+help-button-label = { -brand-short-name } Support
+
+focus-search =
+    .key = f
+
+close-button =
+    .aria-label = Close
+
+## Browser Restart Dialog
+
+feature-enable-requires-restart = { -brand-short-name } must restart to enable this feature.
+feature-disable-requires-restart = { -brand-short-name } must restart to disable this feature.
+should-restart-title = Restart { -brand-short-name }
+should-restart-ok = Restart { -brand-short-name } now
+revert-no-restart-button = Revert
+restart-later = Restart Later
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -106,16 +106,18 @@ Option+Command keyboard shortcut letter 
 Source" on macOS. pageSourceCmd.commandkey above is Firefox's official keyboard
 shortcut shown in the GUI. SafariCommandKey is an alias provided for the
 convenience of Safari and Chrome users on macOS. See bug 1398988. -->
 <!ENTITY pageSourceCmd.SafariCommandKey "u">
 
 <!ENTITY pageInfoCmd.label "Page Info">
 <!ENTITY pageInfoCmd.accesskey "I">
 <!ENTITY pageInfoCmd.commandkey "i">
+<!ENTITY ldbCmd.label "Layout Debugger">
+<!ENTITY ldbCmd.accesskey "L">
 <!ENTITY mirrorTabCmd.label "Mirror Tab">
 <!ENTITY mirrorTabCmd.accesskey "m">
 <!-- LOCALIZATION NOTE (enterFullScreenCmd.label, exitFullScreenCmd.label):
 These should match what Safari and other Apple applications use on OS X Lion. -->
 <!ENTITY enterFullScreenCmd.label "Enter Full Screen">
 <!ENTITY enterFullScreenCmd.accesskey "F">
 <!ENTITY exitFullScreenCmd.label "Exit Full Screen">
 <!ENTITY exitFullScreenCmd.accesskey "F">
@@ -178,20 +180,16 @@ These should match what Safari and other
 <!ENTITY subscribeToPageMenupopup.label "Subscribe to This Page">
 <!ENTITY subscribeToPageMenuitem.label "Subscribe to This Page…">
 <!ENTITY addCurPagesCmd.label "Bookmark All Tabs…">
 <!ENTITY showAllBookmarks2.label "Show All Bookmarks">
 <!ENTITY recentBookmarks.label "Recently Bookmarked">
 <!ENTITY otherBookmarksCmd.label "Other Bookmarks">
 <!ENTITY mobileBookmarksCmd.label "Mobile Bookmarks">
 <!ENTITY bookmarksToolbarChevron.tooltip "Show more bookmarks">
-<!ENTITY showRecentlyBookmarked.label     "Show Recently Bookmarked">
-<!ENTITY showRecentlyBookmarked.accesskey "h">
-<!ENTITY hideRecentlyBookmarked.label     "Hide Recently Bookmarked">
-<!ENTITY hideRecentlyBookmarked.accesskey "H">
 
 <!ENTITY backCmd.label                "Back">
 <!ENTITY backCmd.accesskey            "B">
 <!ENTITY backButton.tooltip           "Go back one page">
 <!ENTITY forwardCmd.label             "Forward">
 <!ENTITY forwardCmd.accesskey         "F">
 <!ENTITY forwardButton.tooltip        "Go forward one page">
 <!ENTITY backForwardButtonMenu.tooltip "Right-click or pull down to show history">
@@ -375,17 +373,17 @@ These should match what Safari and other
 <!ENTITY appMenuRemoteTabs.showAll.tooltip "Show all tabs from this device">
 <!-- LOCALIZATION NOTE (appMenuRemoteTabs.tabsnotsyncing.label): This is shown
      when Sync is configured but syncing tabs is disabled. -->
 <!ENTITY appMenuRemoteTabs.tabsnotsyncing.label "Turn on tab syncing to view a list of tabs from your other devices.">
 <!-- LOCALIZATION NOTE (appMenuRemoteTabs.noclients.label): This is shown
      when Sync is configured but this appears to be the only device attached to
      the account. We also show links to download Firefox for android/ios. -->
 <!ENTITY appMenuRemoteTabs.noclients.subtitle "Want to see your tabs from other devices here?">
-<!ENTITY appMenuRemoteTabs.openprefs.label "Sync Preferences">
+<!ENTITY appMenuRemoteTabs.opensyncprefs.label "Open Sync Preferences">
 <!ENTITY appMenuRemoteTabs.notsignedin.label "Sign in to view a list of tabs from your other devices.">
 <!ENTITY appMenuRemoteTabs.unverified.label "Your account needs to be verified.">
 <!ENTITY appMenuRemoteTabs.signin.label "Sign in to Sync">
 <!ENTITY appMenuRemoteTabs.managedevices.label "Manage Devices…">
 <!ENTITY appMenuRemoteTabs.sidebar.label "View Synced Tabs Sidebar">
 <!ENTITY appMenuRemoteTabs.connectdevice.label "Connect Another Device">
 
 <!ENTITY appMenuRecentHighlights.label "Recent Highlights">
--- a/browser/locales/en-US/chrome/browser/preferences/preferences.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/preferences.dtd
@@ -1,36 +1,15 @@
 <!-- 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/. -->
 
-
-<!ENTITY  prefWindow.titleWin     "Options">
-<!ENTITY  prefWindow.title        "Preferences">
-<!-- LOCALIZATION NOTE (prefWindow.titleGNOME): This is not used for in-content preferences -->
-<!ENTITY  prefWindow.titleGNOME   "&brandShortName; Preferences">
-<!-- When making changes to prefWindow.styleWin test both Windows Classic and
-     Luna since widget heights are different based on the OS theme -->
-<!ENTITY  prefWinMinSize.styleWin2      "width: 42em; min-height: 37.5em;">
-<!ENTITY  prefWinMinSize.styleMac       "width: 47em; min-height: 40em;">
-<!ENTITY  prefWinMinSize.styleGNOME     "width: 45.5em; min-height: 40.5em;">
-
-<!-- LOCALIZATION NOTE: (searchField.width): This is used to determine the width
-     of the search field in about:preferences, in order to make entire placeholder
-     string visible -->
-<!ENTITY  searchField.width             "15.4em">
-
 <!ENTITY  paneSearchResults.title       "Search Results">
 <!ENTITY  paneGeneral.title             "General">
 <!ENTITY  paneSearch.title              "Search">
-<!ENTITY  paneFilesApplications.title   "Files &amp; Applications">
-<!ENTITY  panePrivacySecurity.title     "Privacy &amp; Security">
 <!ENTITY  paneContainers.title          "Container Tabs">
-<!ENTITY  paneUpdates.title             "Updates">
 
 <!ENTITY  languageAndAppearance.label   "Language and Appearance">
 <!ENTITY  filesAndApplications.label    "Files and Applications">
 <!ENTITY  browserPrivacy.label          "Browser Privacy">
 
 <!-- LOCALIZATION NOTE (paneSync1.title): This should match syncBrand.fxAccount.label in ../syncBrand.dtd -->
 <!ENTITY  paneSync1.title          "Firefox Account">
-
-<!ENTITY  helpButton2.label        "&brandShortName; Support">
--- a/browser/locales/en-US/chrome/browser/preferences/preferences.properties
+++ b/browser/locales/en-US/chrome/browser/preferences/preferences.properties
@@ -216,26 +216,16 @@ spaceAlert.over5GB.message=%S is running
 # - On Windows Preferences is called Options
 # - %S = brandShortName
 spaceAlert.over5GB.messageWin=%S is running out of disk space. Website contents may not display properly. You can clear stored site data in Options > Advanced > Site Data.
 spaceAlert.under5GB.okButton.label=OK, Got it
 spaceAlert.under5GB.okButton.accesskey=K
 # LOCALIZATION NOTE (spaceAlert.under5GB.message): %S = brandShortName
 spaceAlert.under5GB.message=%S is running out of disk space. Website contents may not display properly. Visit “Learn More” to optimize your disk usage for better browsing experience.
 
-# LOCALIZATION NOTE (featureEnableRequiresRestart, featureDisableRequiresRestart, restartTitle): %S = brandShortName
-featureEnableRequiresRestart=%S must restart to enable this feature.
-featureDisableRequiresRestart=%S must restart to disable this feature.
-shouldRestartTitle=Restart %S
-okToRestartButton=Restart %S now
-revertNoRestartButton=Revert
-
-restartNow=Restart Now
-restartLater=Restart Later
-
 disableContainersAlertTitle=Close All Container Tabs?
 
 # LOCALIZATION NOTE (disableContainersMsg): Semi-colon list of plural forms.
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # #S is the number of container tabs
 disableContainersMsg=If you disable Container Tabs now, #S container tab will be closed. Are you sure you want to disable Container Tabs?;If you disable Container Tabs now, #S container tabs will be closed. Are you sure you want to disable Container Tabs?
 
 # LOCALIZATION NOTE (disableContainersOkButton): Semi-colon list of plural forms.
@@ -270,21 +260,21 @@ searchResults.needHelp3=Need help? Visit
 # LOCALIZATION NOTE (searchResults.needHelpSupportLink): %S will be replaced with the browser name.
 searchResults.needHelpSupportLink=%S Support
 
 # LOCALIZATION NOTE %S is the default value of the `dom.ipc.processCount` pref.
 defaultContentProcessCount=%S (default)
 
 # LOCALIZATION NOTE (extensionControlled.homepage_override):
 # This string is shown to notify the user that their home page is being controlled by an extension.
-extensionControlled.homepage_override = An extension, %S, controls your home page.
+extensionControlled.homepage_override2 = An extension, %S, is controlling your home page.
 
 # LOCALIZATION NOTE (extensionControlled.newTabURL):
 # This string is shown to notify the user that their new tab page is being controlled by an extension.
-extensionControlled.newTabURL = An extension, %S, controls your New Tab page.
+extensionControlled.newTabURL2 = An extension, %S, is controlling your New Tab page.
 
 # LOCALIZATION NOTE (extensionControlled.defaultSearch):
 # This string is shown to notify the user that the default search engine is being controlled
 # by an extension. %S is the icon and name of the extension.
 extensionControlled.defaultSearch = An extension, %S, has set your default search engine.
 
 # LOCALIZATION NOTE (extensionControlled.privacy.containers):
 # This string is shown to notify the user that Container Tabs are being enabled by an extension
--- a/browser/locales/search/list.json
+++ b/browser/locales/search/list.json
@@ -798,14 +798,14 @@
         "visibleDefaultEngines": [
           "baidu", "google", "bing", "ddg", "wikipedia-zh-CN", "amazondotcn"
         ]
       }
     },
     "zh-TW": {
       "default": {
         "visibleDefaultEngines": [
-          "yahoo-zh-TW", "google", "ddg", "wikipedia-zh-TW", "yahoo-zh-TW-HK", "yahoo-bid-zh-TW", "yahoo-answer-zh-TW"
+          "yahoo-zh-TW", "google", "ddg", "readmoo", "wikipedia-zh-TW", "yahoo-zh-TW-HK", "yahoo-bid-zh-TW", "yahoo-answer-zh-TW"
         ]
       }
     }
   }
 }
new file mode 100644
--- /dev/null
+++ b/browser/locales/searchplugins/readmoo.xml
@@ -0,0 +1,16 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Readmoo 讀墨電子書</ShortName>
+<Description>Readmoo 讀墨電子書</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEAIADAAgAAJgAAACAgAAABACAAvgYAAOYCAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAKHSURBVDiNpZJNiJZVGIavc973e79vfnKmxBHHRTrgIkkEURdCoIi40EUS46JF06ImIkoKwuX4E2iLCFsE1oQguNGFQeAycTFS5sJIEyMkF2oz4ziO3/e955zn/LUIx0Dd2LW8b+5r8fDA/0SNXs2VLsN25ymci9Rt+enCtiX31p6e7pfB1qs5FgMd274xvWfFX08V7LiS+wYa8YRLxWgtmY6pdy3UaTijP5YQXwm5UMaHOVPLoe6bQ189IQDYNrWwJRfNqVpiqiVeC1mvk1ziJOCs4FFYSZJC2OzfWv7rfwUaoB1VoysJ45P2Sa+zUWNq85s4GbPWvWMlpqSKipTXcvxy4wlBsIFaEkYSNii6Ri60xWyffeOlk92kX06q0NELPufPqFb/wjczE0zkCqAE6ARIKuE8mOAeSNu+3xkbnmXyzkal9b7oPSmDLvpHUgB0uZ6l9zvAFxrAWjA+4XOBSDjXGRu+3vf13SHVKCeTKpYk1QBhNhl7hq6/hgBBv82HufmvAPA+42LChfgjx/5o1r36u9yo1uMT2PgtLmzivWV78ep7Ugm+GGTlfKsEsCagmk0kglfqFj3VcFZ6t8oNsrgvGV/+CQCnc8Gtha2IgqDuIC92SwBs+N2mfN5nFOrhJcbXtDk+82lWbim3hw4unvzm/Afo1ha8gqDOclAFtVhOnC+rlWtGpOrdjNfneHfw/qNqxfjt3rsjAx+BPgBVEyPXcf41jg7MPRYATE7/wAtDu5m7d4SFOEmii+oJKHWKsmcnsQAnfxPi6xzu+3nxDxaJeoq2+ZOkr9BsnaLZ2k/Mg6AuYfxNnL+IC7sejZ/OxNV+AD6f2cDh+dWPc7OK0dzz7OFz8g/+uVh1UIjnRQAAAABJRU5ErkJggolQTkcNChoKAAAADUlIRFIAAAAgAAAAIAgGAAAAc3p69AAABoVJREFUWIXllnuIXVcVxn97n3PPvXeSGZsXYxqapNNQIsYUKvh+UsQWX1GaaFrTBmljoGnatASLFIdQ0lRjMFjFBFojarURrIgKgpZUC5JiaUvRlKaRNO3EJJPJTB4zd845e6+1/OPcmcwktyZ/9L8u2HDuPWud79vft9Y+B97p4QBuP2wNP0L3ScUAzrVvSuucGz7efebAKld2rN59tGtp38ymjfWUr65w5zrmXA6B1QfibT5LdhR5DGUURCGoEXFoEdY889HupycK3vPH4UUuq33BTD5Tqrs2ivYYLo/Cq7npk7OPDO09dM+1xeUSSAHUKCTq2TLaQnNpKhiCYSQUMTYArvvr0IJI4z6DNZbU5okoZgLOIQrmfV9idtOpK+euqf/62J3F6vmvX7YCn9xn6cz0TLd2NZ5Vn743lIGghpgnhnDDaClZ4ms/trTWF4uCGBVRIyqIGmpGVMMUNG0Q8tb+0eg+y5q5Zy9FwAP87dMuLmgeGi3ENIgRxIgRihC1FHeXd/636lxfGB+njFrZo1aRMIgC1iYjxThaa3wodbr2chTwExdvDPb4ECBEq5YoIuYF/5WorqssA0FApNp59BkRT4gxjwalQmj3jkZB1W6m3/z/A59G4M9ANCWIUbZXFCOGSJTq/yBKdClRLC/K4ldlKFcEC+/PVTYLDjXDmaEqmNLL4tezSxFIp/4IYqir5I1SSSwT0qqhPqMMxfMhct/plbOfnairPzH0QVKPWKwaMnGAnWWokfDowBya9ZI75nUc0+kEomK+Ag9TwKMa+IyyLP4yPj5+y+itC4Ymi3a80XTIhqgJouAMLEYw+pjDPyCdCzLO40P7CbaT9fOe72jBktcqDyd3P2WpS8mL/OXWaPH1aeCAn5vdLj67XmLAGagZqGHezybNluPTKyG7hqR+Ky55ml2DX+1IACC2G7BUqxquPWpFkALTe8dumz84Tb9f/ndR4v2Dqnoe3EAB1HBBqMZEIS+ApAet7eaHw9d1JjAhfdv/Ugz1KRLjU6dunrdvGni/pV6SH7g0W2AxomboBLg5nFYL9aBU10FwvvEulG/TPoOmESgmwSsrVCGUQXOJj3Nh9J34VlrLvhxD9ZpQAwxQB6RY0kRJQSw6SzB1YA7KAOpuYseJa+CCJkSNSEUimoFLCDEcKgZs/7S8Pce+5n3aH2MAtfauAZdWewrxFdA/kOszJLRM/V5c2utUMTXw9W4Cy4FD0whEBcMmj1dLPaL2HJvnj03kpD8b/IQm/icGNTcJ7iDJoIyH0bAN757kjrnnx27n8Iuk6Y2mofLIe5Bk1kUKlKo4S1Ct/IwKQXjh/M4PLxbs5zh3hcVYvbvNgc+gCL/D8o2su2pgulfmkOEZeEDAmcfMgWp+UQ+ogpgRbOJIVZzj2GRC0viiqzcXtee88tvXcXn5BAOjt1wMDmwfWYilywgC6qteCNGQ5Og0AtlwaSLmghqqVo2VKirxvJTC2MSoVbI3oCj/bsHWs+Xq/CJwAOfXktZnIbTrEoh6nJb9exqBA1uWlUHtJUvriDmiT5EQzkjhXpni0e/J8+d80gVpF+Tlm4Swng29ox3Bt55civq7q907EAfUQP0+tvWchAt6oGnxwbHxsV4xPozoSaJ9h3XzD08mfHPBEI8OfI56sQLoRsf/xF1XvdYRvH/kCmrpbnw2hxDalnmQIKCPTQp0UeFvLKm3BhcXoRjhzoXDHR9+qfje6Lsx20Nav5GyxKnDlMqyfPwp3IyVbHHamcDU2DO4Euc+RrR/LXpz7BdH3srnqfH9kRvQ2k58toyyxJmrGs+lEGWQ3H2cRxoHJ9LTt3zQT4/f45PaTkjRzHFkvlsOtrF633WIh45dTWPGJiRZh0/qlCWoq0bOPCiRYBt5pHlwalnnL5bdVsPcN1RBixyKAMpqth+fB8COE0v4kc2czN82sphsxi58825cUndlrJrOXNX16pUibGZr194LoTorMAvltJ3Dp6ACzoO6Fk3fYueph0ia91Kefpn+o1+Cg6fBvw98D2X4D6JmLl2CKrgMoraIcj8Pz9jVCaqzAqucYH4rZRgiaYBoC3Vb2dA7iiWrcMlMkuZHaDaXcvZTNcy6ELYQ42OYV4QRfBOivojJ53m42REcLtWEO04sod51PaF1kE29LwGwfWQTtcb9lOU/UVnLA7PPTOb3n/4APmsitg6SFyhaj/HdKffftnhgYA4rLXn7H/xOjP8B3KUpxzDuUm4AAAAASUVORK5CYII=</Image>
+<Url type="text/html" method="GET" template="https://readmoo.com/search/keyword"
+     resultdomain="readmoo.com" rel="searchform">
+  <Param name="pi" value="0"/>
+  <Param name="q" value="{searchTerms}"/>
+  <Param name="st" value="true"/>
+</Url>
+</SearchPlugin>
--- a/browser/modules/ExtensionsUI.jsm
+++ b/browser/modules/ExtensionsUI.jsm
@@ -242,57 +242,54 @@ this.ExtensionsUI = {
 
       let bundle = Services.strings.createBundle(BROWSER_PROPERTIES);
 
       let strings = {};
       strings.acceptText = bundle.GetStringFromName("webext.defaultSearchYes.label");
       strings.acceptKey = bundle.GetStringFromName("webext.defaultSearchYes.accessKey");
       strings.cancelText = bundle.GetStringFromName("webext.defaultSearchNo.label");
       strings.cancelKey = bundle.GetStringFromName("webext.defaultSearchNo.accessKey");
-      let addonName = `<span class="addon-webext-name">${this._sanitizeName(name)}</span>`;
+      strings.addonName = name;
       strings.text = bundle.formatStringFromName("webext.defaultSearch.description",
-                                               [addonName, currentEngine, newEngine], 3);
+                                                 ["<>", currentEngine, newEngine], 3);
       resolve(this.showDefaultSearchPrompt(browser, strings, icon));
     }
   },
 
-  // Escape &, <, and > characters in a string so that it may be
-  // injected as part of raw markup.
-  _sanitizeName(name) {
-    return name.replace(/&/g, "&amp;")
-               .replace(/</g, "&lt;")
-               .replace(/>/g, "&gt;");
-  },
-
   // Create a set of formatted strings for a permission prompt
   _buildStrings(info) {
     let bundle = Services.strings.createBundle(BROWSER_PROPERTIES);
-
     let brandBundle = Services.strings.createBundle(BRAND_PROPERTIES);
     let appName = brandBundle.GetStringFromName("brandShortName");
-    let addonName = `<span class="addon-webext-name">${this._sanitizeName(info.addon.name)}</span>`;
+    let info2 = Object.assign({appName}, info);
 
-    let info2 = Object.assign({appName, addonName}, info);
-
-    return ExtensionData.formatPermissionStrings(info2, bundle);
+    let strings = ExtensionData.formatPermissionStrings(info2, bundle);
+    strings.addonName = info.addon.name;
+    return strings;
   },
 
   showPermissionsPrompt(browser, strings, icon, histkey) {
+    let message = {};
+    // Create the notification header element.
+    let header = strings.header;
+    header = header.split("<>");
+    message.start = header[0];
+    // Use the host element to display addon name in addon permission prompts.
+    message.host = strings.addonName;
+    message.end = header[1];
+
     function eventCallback(topic) {
       let doc = this.browser.ownerDocument;
       if (topic == "showing") {
-        // eslint-disable-next-line no-unsanitized/property
-        doc.getElementById("addon-webext-perm-header").innerHTML = strings.header;
         let textEl = doc.getElementById("addon-webext-perm-text");
-        // eslint-disable-next-line no-unsanitized/property
-        textEl.innerHTML = strings.text;
+        textEl.textContent = strings.text;
         textEl.hidden = !strings.text;
 
         let listIntroEl = doc.getElementById("addon-webext-perm-intro");
-        listIntroEl.value = strings.listIntro;
+        listIntroEl.textContent = strings.listIntro;
         listIntroEl.hidden = (strings.msgs.length == 0);
 
         let list = doc.getElementById("addon-webext-perm-list");
         while (list.firstChild) {
           list.firstChild.remove();
         }
 
         for (let msg of strings.msgs) {
@@ -333,86 +330,94 @@ this.ExtensionsUI = {
             if (histkey) {
               this.histogram.add(histkey + "Rejected");
             }
             resolve(false);
           },
         },
       ];
 
-      win.PopupNotifications.show(browser, "addon-webext-permissions", "",
+      win.PopupNotifications.show(browser, "addon-webext-permissions", message,
                                   "addons-notification-icon",
                                   action, secondaryActions, popupOptions);
     });
   },
 
   showDefaultSearchPrompt(browser, strings, icon) {
-//    const kDefaultSearchHistKey = "defaultSearch";
+    let message = {};
+    // Create the notification header element.
+    let header = strings.text;
+    header = header.split("<>");
+    message.start = header[0];
+    // Use the host element to display addon name in addon notification prompts.
+    message.host = strings.addonName;
+    message.end = header[1];
+
     return new Promise(resolve => {
       let popupOptions = {
         hideClose: true,
         popupIconURL: icon || DEFAULT_EXTENSION_ICON,
         persistent: false,
         removeOnDismissal: true,
         eventCallback(topic) {
-          if (topic == "showing") {
-            let doc = this.browser.ownerDocument;
-            // eslint-disable-next-line no-unsanitized/property
-            doc.getElementById("addon-webext-defaultsearch-text").innerHTML = strings.text;
-          } else if (topic == "removed") {
+          if (topic == "removed") {
             resolve(false);
           }
         }
       };
 
       let action = {
         label: strings.acceptText,
         accessKey: strings.acceptKey,
         disableHighlight: true,
         callback: () => {
-//          this.histogram.add(kDefaultSearchHistKey + "Accepted");
           resolve(true);
         },
       };
       let secondaryActions = [
         {
           label: strings.cancelText,
           accessKey: strings.cancelKey,
           callback: () => {
-//            this.histogram.add(kDefaultSearchHistKey + "Rejected");
             resolve(false);
           },
         },
       ];
 
       let win = browser.ownerGlobal;
-      win.PopupNotifications.show(browser, "addon-webext-defaultsearch", "",
+      win.PopupNotifications.show(browser, "addon-webext-defaultsearch", message,
                                   "addons-notification-icon",
                                   action, secondaryActions, popupOptions);
     });
   },
 
   showInstallNotification(target, addon) {
     let win = target.ownerGlobal;
     let popups = win.PopupNotifications;
 
-    let name = this._sanitizeName(addon.name);
-    let addonName = `<span class="addon-webext-name">${name}</span>`;
     let addonIcon = '<image class="addon-addon-icon"/>';
     let toolbarIcon = '<image class="addon-toolbar-icon"/>';
 
     let brandBundle = win.document.getElementById("bundle_brand");
     let appName = brandBundle.getString("brandShortName");
 
     let bundle = win.gNavigatorBundle;
-    let msg1 = bundle.getFormattedString("addonPostInstall.message1",
-                                         [addonName, appName]);
     let msg2 = bundle.getFormattedString("addonPostInstall.messageDetail",
                                          [addonIcon, toolbarIcon]);
 
+    // Create the notification header element.
+    let message = {};
+    let header = bundle.getFormattedString("addonPostInstall.message1",
+                                          ["<>", appName]);
+    header = header.split("<>");
+    message.start = header[0];
+    // Use the host element to display addon name in addon permission prompts.
+    message.host = addon.name;
+    message.end = header[1];
+
     return new Promise(resolve => {
       let action = {
         label: bundle.getString("addonPostInstall.okay.label"),
         accessKey: bundle.getString("addonPostInstall.okay.key"),
         callback: resolve,
       };
 
       let icon = addon.isWebExtension ?
@@ -420,27 +425,24 @@ this.ExtensionsUI = {
                  "chrome://browser/skin/addons/addon-install-installed.svg";
       let options = {
         hideClose: true,
         timeout: Date.now() + 30000,
         popupIconURL: icon,
         eventCallback(topic) {
           if (topic == "showing") {
             let doc = this.browser.ownerDocument;
-        // eslint-disable-next-line no-unsanitized/property
-            doc.getElementById("addon-installed-notification-header")
-               .unsafeSetInnerHTML(msg1);
             // eslint-disable-next-line no-unsanitized/property
             doc.getElementById("addon-installed-notification-message")
                .unsafeSetInnerHTML(msg2);
           } else if (topic == "dismissed") {
             resolve();
           }
         }
       };
 
-      popups.show(target, "addon-installed", "", "addons-notification-icon",
+      popups.show(target, "addon-installed", message, "addons-notification-icon",
                   action, null, options);
     });
   },
 };
 
 EventEmitter.decorate(ExtensionsUI);
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -256,36 +256,25 @@ html|*.addon-webext-perm-list {
   padding-inline-start: 10px;
 }
 
 .addon-webext-perm-text {
   margin-inline-start: 0;
 }
 
 .popup-notification-description[popupid="addon-webext-permissions"],
-.popup-notification-description[popupid="addon-installed"] {
-  display: none;
+.popup-notification-description[popupid="addon-webext-permissions-notification"] {
+  margin-inline-start: -1px;
 }
 
 .addon-webext-perm-notification-content,
 .addon-installed-notification-content {
   margin-top: 0;
 }
 
-#addon-webext-perm-header {
-  /* Align the text more closely with the icon by clearing some top line height. */
-  margin-top: -1px;
-  margin-inline-start: 0;
-}
-
-#addon-installed-notification-header {
-  /* Align the text more closely with the icon by clearing some top line height. */
-  margin-top: -1px;
-}
-
 .addon-webext-name {
   display: inline;
   font-weight: bold;
   margin: 0;
 }
 
 .addon-addon-icon,
 .addon-toolbar-icon {
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -957,36 +957,25 @@ html|*.addon-webext-perm-list {
   padding-inline-start: 10px;
 }
 
 .addon-webext-perm-text {
   margin-inline-start: 0;
 }
 
 .popup-notification-description[popupid="addon-webext-permissions"],
-.popup-notification-description[popupid="addon-installed"] {
-  display: none;
+.popup-notification-description[popupid="addon-webext-permissions-notification"] {
+  margin-inline-start: -1px;
 }
 
 .addon-webext-perm-notification-content,
 .addon-installed-notification-content {
   margin-top: 0;
 }
 
-#addon-webext-perm-header {
-  /* Align the text more closely with the icon by clearing some top line height. */
-  margin-top: -1px;
-  margin-inline-start: 0;
-}
-
-#addon-installed-notification-header {
-  /* Align the text more closely with the icon by clearing some top line height. */
-  margin-top: -1px;
-}
-
 .addon-webext-name {
   display: inline;
   font-weight: bold;
   margin: 0;
 }
 
 .addon-addon-icon,
 .addon-toolbar-icon {
--- a/browser/themes/shared/customizableui/panelUI.inc.css
+++ b/browser/themes/shared/customizableui/panelUI.inc.css
@@ -617,35 +617,30 @@ toolbarbutton[constrain-size="true"][cui
 
 .addon-banner-item {
   flex: 1;
   padding-inline-start: 15px;
   border-inline-start-style: none;
 }
 
 #PanelUI-remotetabs {
-  --panel-ui-sync-illustration-height: 157.5px;
+  --panel-ui-sync-illustration-height: 91px;
 }
 
-.PanelUI-remotetabs-instruction-title,
 .PanelUI-remotetabs-instruction-label {
   /* If you change the margin here, the min-height of the synced tabs panel
     (e.g. #PanelUI-remotetabs[mainview] #PanelUI-remotetabs-setupsync, etc) may
     need adjusting (see bug 1248506) */
-  margin-bottom: 15px;
+  margin: 0;
   text-align: center;
   text-shadow: none;
   max-width: 15em;
   color: GrayText;
 }
 
-.PanelUI-remotetabs-instruction-title {
-  font-size: 1.3em;
-}
-
 /* The boxes with "instructions" get extra top and bottom padding for space
    around the illustration and buttons */
 .PanelUI-remotetabs-instruction-box {
   /* If you change the padding here, the min-height of the synced tabs panel
     (e.g. #PanelUI-remotetabs[mainview] #PanelUI-remotetabs-setupsync, etc) may
     need adjusting (see bug 1248506) */
   padding-bottom: 30px;
 }
@@ -655,18 +650,18 @@ toolbarbutton[constrain-size="true"][cui
   background-color: #0060df;
   /* !important for the color as an OSX specific rule when a lightweight theme
      is used for buttons in the toolbox overrides. See bug 1238531 for details */
   color: white !important;
   border-radius: 2px;
   /* If you change the margin or padding below, the min-height of the synced tabs
      panel (e.g. #PanelUI-remotetabs[mainview] #PanelUI-remotetabs-setupsync,
      etc) may need adjusting (see bug 1248506) */
-  margin-top: 10px;
-  margin-bottom: 10px;
+  margin-top: 15px;
+  margin-bottom: 15px;
   padding: 8px;
   text-shadow: none;
   min-width: 200px;
 }
 
 .PanelUI-remotetabs-button:hover {
   background-color: #003eaa;
 }
@@ -689,19 +684,19 @@ toolbarbutton[constrain-size="true"][cui
   margin-left: 32px;
 }
 
 .fxaSyncIllustration,
 .fxaSyncIllustrationIssue {
   /* If you change the margin here, the min-height of the synced tabs panel
     (e.g. #PanelUI-remotetabs[mainview] #PanelUI-remotetabs-setupsync, etc) may
     need adjusting (see bug 1248506) */
-  width: 180px;
+  width: 104px;
   height: var(--panel-ui-sync-illustration-height);
-  margin: 38px 0;
+  margin: 38px 0 15px;
   -moz-context-properties: fill;
   fill: #cdcdcd;
 }
 
 .fxaSyncIllustration {
   list-style-image: url(chrome://browser/skin/fxa/sync-illustration.svg);
 }
 
@@ -723,20 +718,19 @@ toolbarbutton[constrain-size="true"][cui
    the panel is anchored to a toolbar button.
 */
 #PanelUI-remotetabs[mainview] #PanelUI-remotetabs-setupsync,
 #PanelUI-remotetabs[mainview] #PanelUI-remotetabs-reauthsync,
 #PanelUI-remotetabs[mainview] #PanelUI-remotetabs-unverified,
 #PanelUI-remotetabs[mainview] #PanelUI-remotetabs-nodevicespane,
 #PanelUI-remotetabs[mainview] #PanelUI-remotetabs-tabsdisabledpane {
   min-height: calc(var(--panel-ui-sync-illustration-height) +
-                   76px + /* margin of .fxaSyncIllustration */
-                   20px + /* margin of .PanelUI-remotetabs-button */
+                   53px + /* margin of .fxaSyncIllustration */
+                   30px + /* margin of .PanelUI-remotetabs-button */
                    16px + /* padding of .PanelUI-remotetabs-button */
-                   15px + /* margin of .PanelUI-remotetabs-instruction-label */
                    30px + /* padding of .PanelUI-remotetabs-instruction-box */
                    11em);
 }
 
 #PanelUI-remotetabs-tabslist > label[itemtype="client"] {
   color: GrayText;
 }
 
--- a/browser/themes/shared/syncedtabs/sidebar.inc.css
+++ b/browser/themes/shared/syncedtabs/sidebar.inc.css
@@ -212,18 +212,18 @@ body {
 
 .deck .sync-state.selected {
   display: unset;
   opacity: 100;
 }
 
 .deck .syncIllustration,
 .deck .syncIllustrationIssue {
-  height: 150px;
-  margin: 38px 8px;
+  height: 91px;
+  margin: 38px 8px 15px;
   background-position: center;
   background-repeat: no-repeat;
 }
 
 .deck .syncIllustration {
   background-image: url(chrome://browser/skin/fxa/sync-illustration.svg);
 }
 
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -765,36 +765,25 @@ html|*.addon-webext-perm-list {
   padding-inline-start: 10px;
 }
 
 .addon-webext-perm-text {
   margin-inline-start: 0;
 }
 
 .popup-notification-description[popupid="addon-webext-permissions"],
-.popup-notification-description[popupid="addon-installed"] {
-  display: none;
+.popup-notification-description[popupid="addon-webext-permissions-notification"] {
+  margin-inline-start: -1px;
 }
 
 .addon-webext-perm-notification-content,
 .addon-installed-notification-content {
   margin-top: 0;
 }
 
-#addon-webext-perm-header {
-  /* Align the text more closely with the icon by clearing some top line height. */
-  margin-top: -1px;
-  margin-inline-start: 0;
-}
-
-#addon-installed-notification-header {
-  /* Align the text more closely with the icon by clearing some top line height. */
-  margin-top: -1px;
-}
-
 .addon-webext-name {
   display: inline;
   font-weight: bold;
   margin: 0;
 }
 
 .addon-addon-icon,
 .addon-toolbar-icon {
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -110,16 +110,19 @@ function Toolbox(target, selectedTool, h
   this._initInspector = null;
   this._inspector = null;
   this._styleSheets = null;
 
   // Map of frames (id => frame-info) and currently selected frame id.
   this.frameMap = new Map();
   this.selectedFrameId = null;
 
+  // List of listeners for `devtools.network.onRequestFinished` WebExt API
+  this._requestFinishedListeners = new Set();
+
   this._toolRegistered = this._toolRegistered.bind(this);
   this._toolUnregistered = this._toolUnregistered.bind(this);
   this._onWillNavigate = this._onWillNavigate.bind(this);
   this._refreshHostTitle = this._refreshHostTitle.bind(this);
   this._toggleNoAutohide = this._toggleNoAutohide.bind(this);
   this.showFramesMenu = this.showFramesMenu.bind(this);
   this.handleKeyDownOnFramesButton = this.handleKeyDownOnFramesButton.bind(this);
   this.showFramesMenuOnKeyDown = this.showFramesMenuOnKeyDown.bind(this);
@@ -3041,25 +3044,76 @@ Toolbox.prototype = {
   /**
    * Opens source in plain "view-source:".
    * @see devtools/client/shared/source-utils.js
    */
   viewSource: function (sourceURL, sourceLine) {
     return viewSource.viewSource(this, sourceURL, sourceLine);
   },
 
+  // Support for WebExtensions API (`devtools.network.*`)
+
   /**
    * Returns data (HAR) collected by the Network panel.
    */
   getHARFromNetMonitor: function () {
     let netPanel = this.getPanel("netmonitor");
 
     // The panel doesn't have to exist (it must be selected
     // by the user at least once to be created).
     // Return default empty HAR file in such case.
     if (!netPanel) {
       return Promise.resolve(buildHarLog(Services.appinfo));
     }
 
     // Use Netmonitor object to get the current HAR log.
     return netPanel.panelWin.Netmonitor.getHar();
+  },
+
+  /**
+   * Add listener for `onRequestFinished` events.
+   *
+   * @param {Object} listener
+   *        The listener to be called it's expected to be
+   *        a function that takes ({harEntry, requestId})
+   *        as first argument.
+   */
+  addRequestFinishedListener: function (listener) {
+    // Log console message informing the extension developer
+    // that the Network panel needs to be selected at least
+    // once in order to receive `onRequestFinished` events.
+    let message = "The Network panel needs to be selected at least" +
+      " once in order to receive 'onRequestFinished' events.";
+    this.target.logErrorInPage(message, "har");
+
+    // Add the listener into internal list.
+    this._requestFinishedListeners.add(listener);
+  },
+
+  removeRequestFinishedListener: function (listener) {
+    this._requestFinishedListeners.delete(listener);
+  },
+
+  getRequestFinishedListeners: function () {
+    return this._requestFinishedListeners;
+  },
+
+  /**
+   * Used to lazily fetch HTTP response content within
+   * `onRequestFinished` event listener.
+   *
+   * @param {String} requestId
+   *        Id of the request for which the response content
+   *        should be fetched.
+   */
+  fetchResponseContent: function (requestId) {
+    let netPanel = this.getPanel("netmonitor");
+
+    // The panel doesn't have to exist (it must be selected
+    // by the user at least once to be created).
+    // Return undefined content in such case.
+    if (!netPanel) {
+      return Promise.resolve({content: {}});
+    }
+
+    return netPanel.panelWin.Netmonitor.fetchResponseContent(requestId);
   }
 };
--- a/devtools/client/inspector/test/browser_inspector_highlighter-cssshape_04.js
+++ b/devtools/client/inspector/test/browser_inspector_highlighter-cssshape_04.js
@@ -82,17 +82,22 @@ function* testPolygonAddPoint(testActor,
   };
 
   info("Adding new polygon point");
   yield testActor.synthesizeMouse(options);
   yield testActor.reflow();
 
   let computedStyle = yield highlightedNode.getComputedStyle();
   let definition = computedStyle["clip-path"].value;
-  ok(definition.includes(`${newPointX * 100 / width}% ${newPointY * 100 / height}%`),
+  // Decimal precision for coordinates with percentage units is 2
+  let precision = 2;
+  // Round to the desired decimal precision and cast to Number to remove trailing zeroes.
+  newPointX = Number((newPointX * 100 / width).toFixed(precision));
+  newPointY = Number((newPointY * 100 / height).toFixed(precision));
+  ok(definition.includes(`${newPointX}% ${newPointY}%`),
      "Point successfuly added");
 }
 
 function* testPolygonRemovePoint(testActor, helper) {
   yield helper.show("#polygon", {mode: "cssClipPath"});
   let { highlightedNode } = helper;
 
   let points = yield helper.getElementAttribute("shapes-polygon", "points");
--- a/devtools/client/netmonitor/initializer.js
+++ b/devtools/client/netmonitor/initializer.js
@@ -45,48 +45,55 @@ window.connector = connector;
 /**
  * Global Netmonitor object in this panel. This object can be consumed
  * by other panels (e.g. Console is using inspectRequest), by the
  * Launchpad (bootstrap), WebExtension API (getHAR), etc.
  */
 window.Netmonitor = {
   bootstrap({ toolbox, panel }) {
     this.mount = document.querySelector("#mount");
+    this.toolbox = toolbox;
 
     const connection = {
       tabConnection: {
         tabTarget: toolbox.target,
       },
       toolbox,
       panel,
     };
 
     const openLink = (link) => {
       let parentDoc = toolbox.doc;
       let iframe = parentDoc.getElementById("toolbox-panel-iframe-netmonitor");
       let top = iframe.ownerDocument.defaultView.top;
       top.openUILinkIn(link, "tab");
     };
 
+    this.onRequestAdded = this.onRequestAdded.bind(this);
+    window.on(EVENTS.REQUEST_ADDED, this.onRequestAdded);
+
     // Render the root Application component.
     const sourceMapService = toolbox.sourceMapURLService;
     const app = App({ connector, openLink, sourceMapService });
     render(Provider({ store }, app), this.mount);
 
     // Connect to the Firefox backend by default.
     return connector.connectFirefox(connection, actions, store.getState);
   },
 
   destroy() {
     unmountComponentAtNode(this.mount);
+    window.off(EVENTS.REQUEST_ADDED, this.onRequestAdded);
     return connector.disconnect();
   },
 
+  // Support for WebExtensions API
+
   /**
-   * Returns list of requests currently available in the panel.
+   * Support for `devtools.network.getHAR` (get collected data as HAR)
    */
   getHar() {
     let { HarExporter } = require("devtools/client/netmonitor/src/har/har-exporter");
     let {
       getLongString,
       getTabTarget,
       getTimingMarker,
       requestData,
@@ -101,16 +108,56 @@ window.Netmonitor = {
       getTimingMarker,
       title: title || url,
     };
 
     return HarExporter.getHar(options);
   },
 
   /**
+   * Support for `devtools.network.onRequestFinished`. A hook for
+   * every finished HTTP request used by WebExtensions API.
+   */
+  onRequestAdded(event, requestId) {
+    let listeners = this.toolbox.getRequestFinishedListeners();
+    if (!listeners.size) {
+      return;
+    }
+
+    let { HarExporter } = require("devtools/client/netmonitor/src/har/har-exporter");
+    let { getLongString, getTabTarget, requestData } = connector;
+    let { form: { title, url } } = getTabTarget();
+
+    let options = {
+      getString: getLongString,
+      requestData,
+      title: title || url,
+      includeResponseBodies: false,
+      items: [getDisplayedRequestById(store.getState(), requestId)],
+    };
+
+    // Build HAR for specified request only.
+    HarExporter.getHar(options).then(har => {
+      let harEntry = har.log.entries[0];
+      delete harEntry.pageref;
+      listeners.forEach(listener => listener({
+        harEntry,
+        requestId,
+      }));
+    });
+  },
+
+  /**
+   * Support for `Request.getContent` WebExt API (lazy loading response body)
+   */
+  fetchResponseContent(requestId) {
+    return connector.requestData(requestId, "responseContent");
+  },
+
+  /**
    * Selects the specified request in the waterfall and opens the details view.
    * This is a firefox toolbox specific API, which providing an ability to inspect
    * a network request directly from other internal toolbox panel.
    *
    * @param {string} requestId The actor ID of the request to inspect.
    * @return {object} A promise resolved once the task finishes.
    */
   inspectRequest(requestId) {
--- a/devtools/client/netmonitor/package.json
+++ b/devtools/client/netmonitor/package.json
@@ -3,30 +3,30 @@
   "version": "0.0.1",
   "engines": {
     "node": ">=6.9.0"
   },
   "description": "Network monitor in developer tools",
   "dependencies": {
     "babel-plugin-transform-flow-strip-types": "^6.22.0",
     "babel-plugin-transform-react-jsx": "^6.24.1",
+    "babel-plugin-transform-object-rest-spread": "^6.26.0",
     "codemirror": "^5.24.2",
     "devtools-config": "=0.0.12",
     "devtools-contextmenu": "=0.0.3",
     "devtools-launchpad": "=0.0.114",
     "devtools-modules": "=0.0.32",
     "devtools-source-editor": "=0.0.3",
     "jszip": "^3.1.3",
-    "react": "=15.6.1",
-    "react-dom": "=15.6.1",
-    "react-redux": "=5.0.3",
-    "redux": "^3.6.0",
+    "react": "=16.2.0",
+    "react-dom": "=16.2.0",
+    "react-prop-types": "=0.4.0",
+    "react-redux": "=5.0.6",
+    "redux": "^3.7.2",
     "reselect": "^3.0.1"
   },
   "devDependencies": {
-    "babel-plugin-transform-object-rest-spread": "^6.26.0",
-    "babel-register": "^6.24.0",
     "file-loader": "^0.10.1"
   },
   "scripts": {
     "start": "node bin/dev-server"
   }
 }
--- a/devtools/client/netmonitor/src/components/RequestListContent.js
+++ b/devtools/client/netmonitor/src/components/RequestListContent.js
@@ -90,33 +90,40 @@ class RequestListContent extends Compone
   }
 
   componentDidMount() {
     // Install event handler for displaying a tooltip
     this.tooltip.startTogglingOnHover(this.refs.contentEl, this.onHover, {
       toggleDelay: REQUESTS_TOOLTIP_TOGGLE_DELAY,
       interactive: true
     });
+    // Everytime the list is created or pruned, re-enable scroll to bottom feature.
+    this.shouldScrollToBottom = true;
+
     // Install event handler to hide the tooltip on scroll
     this.refs.contentEl.addEventListener("scroll", this.onScroll, true);
     this.onResize();
   }
 
-  componentWillUpdate(nextProps) {
+  componentDidUpdate(prevProps) {
+    if (!this.shouldScrollToBottom) {
+      return;
+    }
     // Check if the list is scrolled to bottom before the UI update.
     // The scroll is ever needed only if new rows are added to the list.
-    const delta = nextProps.displayedRequests.size - this.props.displayedRequests.size;
-    this.shouldScrollBottom = delta > 0 && this.isScrolledToBottom();
-  }
+    const hasNewRequests = this.props.displayedRequests.size -
+      prevProps.displayedRequests.size > 0;
+    // Keep the list scrolled to bottom if a new row was added
+    if (hasNewRequests) {
+      // Set a boolean flag to help `scroll` listener to know that the next event
+      // is related to the scroll to bottom and can be ignored.
+      this.ignoreNextScroll = true;
 
-  componentDidUpdate(prevProps) {
-    let node = this.refs.contentEl;
-    // Keep the list scrolled to bottom if a new row was added
-    if (this.shouldScrollBottom && node.scrollTop !== MAX_SCROLL_HEIGHT) {
       // Using maximum scroll height rather than node.scrollHeight to avoid sync reflow.
+      let node = this.refs.contentEl;
       node.scrollTop = MAX_SCROLL_HEIGHT;
     }
   }
 
   componentWillUnmount() {
     this.refs.contentEl.removeEventListener("scroll", this.onScroll, true);
 
     // Uninstall the tooltip event handler
@@ -188,16 +195,24 @@ class RequestListContent extends Compone
     return itemEl.querySelector(".requests-list-file");
   }
 
   /**
    * Scroll listener for the requests menu view.
    */
   onScroll() {
     this.tooltip.hide();
+
+    // Ignore scroll related to new requests being displayed
+    // To prevent slow reflows done in isScrolledToBottom.
+    if (this.ignoreNextScroll) {
+      this.ignoreNextScroll = false;
+      return;
+    }
+    this.shouldScrollToBottom = this.isScrolledToBottom();
   }
 
   /**
    * Handler for keyboard events. For arrow up/down, page up/down, home/end,
    * move the selection up or down.
    */
   onKeyDown(evt) {
     let delta;
@@ -239,17 +254,17 @@ class RequestListContent extends Compone
     this.contextMenu.open(evt, selectedRequest, sortedRequests);
   }
 
   /**
    * If selection has just changed (by keyboard navigation), don't keep the list
    * scrolled to bottom, but allow scrolling up with the selection.
    */
   onFocusedNodeChange() {
-    this.shouldScrollBottom = false;
+    this.shouldScrollToBottom = false;
   }
 
   render() {
     const {
       connector,
       columns,
       displayedRequests,
       firstRequestStartedMillis,
@@ -265,17 +280,19 @@ class RequestListContent extends Compone
     return (
       div({ className: "requests-list-wrapper" },
         div({ className: "requests-list-table" },
           div({
             ref: "contentEl",
             className: "requests-list-contents",
             tabIndex: 0,
             onKeyDown: this.onKeyDown,
-            style: { "--timings-scale": scale, "--timings-rev-scale": 1 / scale }
+            // scale is null until the waterfall is initialized
+            style: { "--timings-scale": scale ? scale : 0,
+                     "--timings-rev-scale": (scale ? 1 / scale : 1) }
           },
             RequestListHeader(),
             displayedRequests.map((item, index) => RequestListItem({
               firstRequestStartedMillis,
               fromCache: item.status === "304" || item.fromCache,
               connector,
               columns,
               item,
--- a/devtools/client/netmonitor/src/har/har-exporter.js
+++ b/devtools/client/netmonitor/src/har/har-exporter.js
@@ -131,25 +131,32 @@ const HarExporter = {
 
   // Helpers
 
   fetchHarData: function (options) {
     // Generate page ID
     options.id = options.id || uid++;
 
     // Set default generic HAR export options.
-    options.jsonp = options.jsonp ||
-      Services.prefs.getBoolPref("devtools.netmonitor.har.jsonp");
-    options.includeResponseBodies = options.includeResponseBodies ||
-      Services.prefs.getBoolPref(
+    if (typeof options.jsonp != "boolean") {
+      options.jsonp = Services.prefs.getBoolPref(
+        "devtools.netmonitor.har.jsonp");
+    }
+    if (typeof options.includeResponseBodies != "boolean") {
+      options.includeResponseBodies = Services.prefs.getBoolPref(
         "devtools.netmonitor.har.includeResponseBodies");
-    options.jsonpCallback = options.jsonpCallback ||
-      Services.prefs.getCharPref("devtools.netmonitor.har.jsonpCallback");
-    options.forceExport = options.forceExport ||
-      Services.prefs.getBoolPref("devtools.netmonitor.har.forceExport");
+    }
+    if (typeof options.jsonpCallback != "boolean") {
+      options.jsonpCallback = Services.prefs.getCharPref(
+        "devtools.netmonitor.har.jsonpCallback");
+    }
+    if (typeof options.forceExport != "boolean") {
+      options.forceExport = Services.prefs.getBoolPref(
+        "devtools.netmonitor.har.forceExport");
+    }
 
     // Build HAR object.
     return this.buildHarData(options).then(har => {
       // Do not export an empty HAR file, unless the user
       // explicitly says so (using the forceExport option).
       if (!har.log.entries.length && !options.forceExport) {
         return Promise.resolve();
       }
--- a/devtools/client/netmonitor/test/browser_net_autoscroll.js
+++ b/devtools/client/netmonitor/test/browser_net_autoscroll.js
@@ -36,16 +36,21 @@ add_task(function* () {
   // save for comparison later
   let scrollTop = requestsContainer.scrollTop;
   yield waitForNetworkEvents(monitor, 8);
   yield waitSomeTime();
   is(requestsContainer.scrollTop, scrollTop, "Did not scroll.");
 
   // (3) Now set the scroll position back at the bottom and check that
   // additional requests *do* cause the container to scroll down.
+  // Wait for another request to be displayed in order to ensure that
+  // scrollTop is set just before next RequestListContent.componentWillUpdate fires.
+  // If scrollTop is set between componentWillUpdate and componentDidUpdate,
+  // the view won't be scrolled.
+  yield waitForNetworkEvents(monitor, 8);
   requestsContainer.scrollTop = requestsContainer.scrollHeight;
   ok(scrolledToBottom(requestsContainer), "Set scroll position to bottom.");
   yield waitForNetworkEvents(monitor, 8);
   yield waitForScroll();
   ok(true, "Still scrolled to bottom.");
 
   // (4) Now select the first item in the list
   // and check that additional requests do not change the scroll position
--- a/devtools/client/netmonitor/test/browser_net_status-bar.js
+++ b/devtools/client/netmonitor/test/browser_net_status-bar.js
@@ -29,16 +29,24 @@ add_task(async () => {
   let onLoad = statusBar.querySelector(".load");
 
   // All expected labels should be there
   ok(requestCount, "There must be request count label");
   ok(size, "There must be size label");
   ok(onContentLoad, "There must be DOMContentLoaded label");
   ok(onLoad, "There must be load label");
 
-  // The content should not be empty
-  ok(requestCount.textContent, "There must be request count label text");
-  ok(size.textContent, "There must be size label text");
-  ok(onContentLoad.textContent, "There must be DOMContentLoaded label text");
-  ok(onLoad.textContent, "There must be load label text");
+  // The content should not be empty. The UI update can also be async,
+  // so use waitUntil.
+  await waitUntil(() => requestCount.textContent);
+  ok(true, "There must be request count label text");
+
+  await waitUntil(() => size.textContent);
+  ok(true, "There must be size label text");
+
+  await waitUntil(() => onContentLoad.textContent);
+  ok(true, "There must be DOMContentLoaded label text");
+
+  await waitUntil(() => onLoad.textContent);
+  ok(true, "There must be load label text");
 
   return teardown(monitor);
 });
--- a/devtools/client/webconsole/.babelrc
+++ b/devtools/client/webconsole/.babelrc
@@ -1,12 +1,14 @@
 {
   "env": {
     "test": {
-      "presets": ["es2015", "es2017"]
+      "plugins": [
+        "./new-console-output/test/node_modules/babel-plugin-transform-object-rest-spread"
+      ]
     }
   },
   "plugins": [
+    "transform-flow-strip-types",
     "transform-react-jsx",
-    "transform-object-rest-spread",
-    "transform-flow-strip-types"
+    "transform-object-rest-spread"
   ]
 }
--- a/devtools/client/webconsole/local-dev/index.js
+++ b/devtools/client/webconsole/local-dev/index.js
@@ -7,23 +7,16 @@
 "use strict";
 
 const React = require("react");
 const ReactDOM = require("react-dom");
 const { EventEmitter } = require("devtools-modules");
 const { Services: { appinfo, pref } } = require("devtools-modules");
 const { bootstrap } = require("devtools-launchpad");
 
-try {
-  const Perf = require("react-addons-perf");
-  window.Perf = Perf;
-} catch (e) {
-  // Perf addon is only available in development builds
-}
-
 EventEmitter.decorate(window);
 
 require("../../themes/widgets.css");
 require("../../themes/webconsole.css");
 require("../../themes/components-frame.css");
 require("../../themes/light-theme.css");
 require("../../shared/components/reps/reps.css");
 require("../../shared/components/tabs/Tabs.css");
--- a/devtools/client/webconsole/new-console-output/components/GripMessageBody.js
+++ b/devtools/client/webconsole/new-console-output/components/GripMessageBody.js
@@ -1,22 +1,16 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-// If this is being run from Mocha, then the browser loader hasn't set up
-// define. We need to do that before loading Rep.
-if (typeof define === "undefined") {
-  require("amd-loader");
-}
-
 // React
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const {
   MESSAGE_TYPE,
   JSTERM_COMMANDS,
 } = require("../constants");
 const { getObjectInspector } = require("devtools/client/webconsole/new-console-output/utils/object-inspector");
 
@@ -97,17 +91,17 @@ const forbiddenValuesRegexs = [
 function cleanupStyle(userProvidedStyle, createElement) {
   // Use a dummy element to parse the style string.
   let dummy = createElement("div");
   dummy.style = userProvidedStyle;
 
   // Return a style object as expected by React DOM components, e.g.
   // {color: "red"}
   // without forbidden properties and values.
-  return [...dummy.style]
+  return Array.from(dummy.style)
     .filter(name => {
       return allowedStylesRegex.test(name)
         && !forbiddenValuesRegexs.some(regex => regex.test(dummy.style[name]));
     })
     .reduce((object, name) => {
       return Object.assign({
         [name]: dummy.style[name]
       }, object);
--- a/devtools/client/webconsole/new-console-output/components/message-types/NetworkEventMessage.js
+++ b/devtools/client/webconsole/new-console-output/components/message-types/NetworkEventMessage.js
@@ -72,17 +72,17 @@ function NetworkEventMessage({
     statusText,
   } = response;
 
   const topLevelClasses = [ "cm-s-mozilla" ];
   let statusCode, statusInfo;
 
   if (httpVersion && status && statusText !== undefined && totalTime !== undefined) {
     let statusCodeDocURL = getHTTPStatusCodeURL(status.toString(), "webconsole");
-    statusCode = dom.a({
+    statusCode = dom.span({
       className: "status-code",
       "data-code": status,
       title: LEARN_MORE,
       onClick: (e) => {
         e.stopPropagation();
         e.preventDefault();
         serviceContainer.openLink(statusCodeDocURL, e);
       }
--- a/devtools/client/webconsole/new-console-output/test/README.md
+++ b/devtools/client/webconsole/new-console-output/test/README.md
@@ -1,28 +1,34 @@
 # Console Tests
 The console panel uses currently two different frameworks for tests:
 
 * Mochitest - [Mochitest](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Mochitest) is an automated testing framework built on top of the MochiKit JavaScript libraries. It's just one of the automated regression testing frameworks used by Mozilla.
 
-Mochitests are located in `new-console-output/test/mochitest/` and can be run with the following command:
+Mochitests are located in `devtools/client/webconsole/new-console-output/test/mochitest/` and can be run with the following command:
 
 ```sh
 ./mach test devtools/client/webconsole/new-console-output/test/mochitest/
 ```
 
 These tests can be run on CI when pushing to TRY. Not all tests are enabled at the moment since they were copied over from the old frontend (See Bug 1400847).
 
 * Mocha + Enzyme - [mocha](https://mochajs.org/) Mocha is JavaScript test framework running on Node.js
 [Enzyme](http://airbnb.io/enzyme/) is a JavaScript Testing utility for React that makes it easier to assert, manipulate, and traverse your React Components' output.
 
 These tests are located in `new-console-output/test/components/` and `new-console-output/test/store/`, and can be run with the following command:
 
 ```sh
-npm test # Or yarn test
+devtools/client/webconsole/new-console-output/test/ && npm install && npm test
+```
+
+or using yarn with
+
+```sh
+devtools/client/webconsole/new-console-output/test/ && yarn && yarn test
 ```
 
 **⚠️️️️️️️️️️ These tests are not ran on CI at the moment. You need to run them manually when working on the console. (See Bug 1312823)**
 
 ---
 
 The team is leaning towards Enzyme since it's well known and suitable for React.
 It's also easier to contribute to tests written on top of Enzyme.
--- 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
@@ -22,36 +22,39 @@ const serviceContainer = require("devtoo
 
 describe("EvaluationResult component:", () => {
   it("renders a grip result", () => {
     const message = stubPreparedMessages.get("new Date(0)");
     const wrapper = render(EvaluationResult({ message, serviceContainer }));
 
     expect(wrapper.find(".message-body").text()).toBe("Date 1970-01-01T00:00:00.000Z");
 
-    expect(wrapper.find(".message.log").length).toBe(1);
+    expect(wrapper.hasClass("message")).toBe(true);
+    expect(wrapper.hasClass("log")).toBe(true);
   });
 
   it("renders an error", () => {
     const message = stubPreparedMessages.get("asdf()");
     const wrapper = render(EvaluationResult({ message, serviceContainer }));
 
     expect(wrapper.find(".message-body").text())
       .toBe("ReferenceError: asdf is not defined[Learn More]");
 
-    expect(wrapper.find(".message.error").length).toBe(1);
+    expect(wrapper.hasClass("message")).toBe(true);
+    expect(wrapper.hasClass("error")).toBe(true);
   });
 
   it("renders an error with a longString exception message", () => {
     const message = stubPreparedMessages.get("longString message Error");
     const wrapper = render(EvaluationResult({ message, serviceContainer }));
 
     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);
+    expect(wrapper.hasClass("message")).toBe(true);
+    expect(wrapper.hasClass("error")).toBe(true);
   });
 
   it("renders thrown empty string", () => {
     const message = stubPreparedMessages.get(`eval throw ""`);
     const wrapper = render(EvaluationResult({ message, serviceContainer }));
     const text = wrapper.find(".message-body").text();
     expect(text).toBe("Error");
     expect(wrapper.find(".message.error").length).toBe(1);
--- a/devtools/client/webconsole/new-console-output/test/components/filter-button.test.js
+++ b/devtools/client/webconsole/new-console-output/test/components/filter-button.test.js
@@ -14,22 +14,26 @@ describe("FilterButton component:", () =
   const props = {
     active: true,
     label: "Error",
     filterKey: MESSAGE_LEVEL.ERROR,
   };
 
   it("displays as active when turned on", () => {
     const wrapper = render(FilterButton(props));
-    expect(wrapper.html()).toBe(
-      "<button aria-pressed=\"true\" class=\"devtools-button error checked\">" +
-      "Error</button>"
-    );
+    expect(wrapper.is("button")).toBe(true);
+    expect(wrapper.hasClass("devtools-button")).toBe(true);
+    expect(wrapper.hasClass("error")).toBe(true);
+    expect(wrapper.hasClass("checked")).toBe(true);
+    expect(wrapper.attr("aria-pressed")).toBe("true");
+    expect(wrapper.text()).toBe("Error");
   });
 
   it("displays as inactive when turned off", () => {
-    const inactiveProps = Object.assign({}, props, { active: false });
-    const wrapper = render(FilterButton(inactiveProps));
-    expect(wrapper.html()).toBe(
-      "<button aria-pressed=\"false\" class=\"devtools-button error\">Error</button>"
-    );
+    const wrapper = render(FilterButton({...props, active: false}));
+    expect(wrapper.is("button")).toBe(true);
+    expect(wrapper.hasClass("devtools-button")).toBe(true);
+    expect(wrapper.hasClass("error")).toBe(true);
+    expect(wrapper.hasClass("checked")).toBe(false);
+    expect(wrapper.attr("aria-pressed")).toBe("false");
+    expect(wrapper.text()).toBe("Error");
   });
 });
--- a/devtools/client/webconsole/new-console-output/test/components/filter-checkbox.test.js
+++ b/devtools/client/webconsole/new-console-output/test/components/filter-checkbox.test.js
@@ -14,23 +14,22 @@ describe("FilterCheckbox component:", ()
     label: "test label",
     title: "test title",
     checked: true,
     onChange: () => {},
   };
 
   it("displays as checked", () => {
     const wrapper = render(FilterCheckbox(props));
-    expect(wrapper.html()).toBe(
-      '<label title="test title" class="filter-checkbox">' +
-      '<input type="checkbox" checked>test label</label>'
-    );
+    expect(wrapper.is("label")).toBe(true);
+    expect(wrapper.attr("title")).toBe("test title");
+    expect(wrapper.hasClass("filter-checkbox")).toBe(true);
+    expect(wrapper.html()).toBe('<input type="checkbox" checked>test label');
   });
 
   it("displays as unchecked", () => {
-    const uncheckedProps = Object.assign({}, props, { checked: false });
-    const wrapper = render(FilterCheckbox(uncheckedProps));
-    expect(wrapper.html()).toBe(
-      '<label title="test title" class="filter-checkbox">' +
-      '<input type="checkbox">test label</label>'
-    );
+    const wrapper = render(FilterCheckbox({...props, checked: false}));
+    expect(wrapper.is("label")).toBe(true);
+    expect(wrapper.attr("title")).toBe("test title");
+    expect(wrapper.hasClass("filter-checkbox")).toBe(true);
+    expect(wrapper.html()).toBe('<input type="checkbox">test label');
   });
 });
--- 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
@@ -173,17 +173,18 @@ p {
 `);
 
 // Evaluation Result
 const evaluationResultCommands = [
   "new Date(0)",
   "asdf()",
   "1 + @",
   "inspect({a: 1})",
-  "cd(document)"
+  "cd(document)",
+  "undefined"
 ];
 
 let evaluationResult = new Map(evaluationResultCommands.map(cmd => [cmd, cmd]));
 evaluationResult.set("longString message Error",
   `throw new Error("Long error ".repeat(10000))`);
 
 evaluationResult.set(`eval throw ""`, `throw ""`);
 evaluationResult.set(`eval throw "tomato"`, `throw "tomato"`);
--- a/devtools/client/webconsole/new-console-output/test/fixtures/stubs/evaluationResult.js
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stubs/evaluationResult.js
@@ -161,16 +161,38 @@ stubPreparedMessages.set(`cd(document)`,
   "stacktrace": null,
   "frame": null,
   "groupId": null,
   "userProvidedStyles": null,
   "notes": null,
   "indent": 0
 }));
 
+stubPreparedMessages.set(`undefined`, new ConsoleMessage({
+  "id": "1",
+  "allowRepeating": true,
+  "source": "javascript",
+  "timeStamp": 1518606917356,
+  "type": "result",
+  "helperType": null,
+  "level": "log",
+  "parameters": [
+    {
+      "type": "undefined"
+    }
+  ],
+  "repeatId": "{\"frame\":null,\"groupId\":null,\"indent\":0,\"level\":\"log\",\"parameters\":[{\"type\":\"undefined\"}],\"source\":\"javascript\",\"type\":\"result\",\"userProvidedStyles\":null}",
+  "stacktrace": null,
+  "frame": null,
+  "groupId": null,
+  "userProvidedStyles": null,
+  "notes": null,
+  "indent": 0
+}));
+
 stubPreparedMessages.set(`longString message Error`, new ConsoleMessage({
   "id": "1",
   "allowRepeating": true,
   "source": "javascript",
   "timeStamp": 1493108241073,
   "type": "result",
   "helperType": null,
   "level": "error",
@@ -385,16 +407,29 @@ stubPackets.set(`cd(document)`, {
   "frame": null,
   "helperResult": {
     "type": "error",
     "message": "cdFunctionInvalidArgument"
   },
   "notes": null
 });
 
+stubPackets.set(`undefined`, {
+  "from": "server1.conn0.child1/consoleActor2",
+  "input": "undefined",
+  "result": {
+    "type": "undefined"
+  },
+  "timestamp": 1518606917356,
+  "exception": null,
+  "frame": null,
+  "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": {
rename from devtools/client/webconsole/new-console-output/test/require-helper.js
rename to devtools/client/webconsole/new-console-output/test/mocha-test-setup.js
--- a/devtools/client/webconsole/new-console-output/test/require-helper.js
+++ b/devtools/client/webconsole/new-console-output/test/mocha-test-setup.js
@@ -1,14 +1,19 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
+// Configure enzyme with React 16 adapter.
+const Enzyme = require("enzyme");
+const Adapter = require("enzyme-adapter-react-16");
+Enzyme.configure({ adapter: new Adapter() });
+
+// Point to vendored-in files and mocks when needed.
 const requireHacker = require("require-hacker");
-
 requireHacker.global_hook("default", path => {
   switch (path) {
     // For Enzyme
     case "react-dom":
       return `const ReactDOM = require('devtools/client/shared/vendor/react-dom'); module.exports = ReactDOM`;
     case "react-dom/server":
       return `const ReactDOMServer = require('devtools/client/shared/vendor/react-dom-server'); module.exports = ReactDOMServer`;
     case "react-addons-test-utils":
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
@@ -139,17 +139,16 @@ support-files =
   test-mutation.html
   test-network-exceptions.html
   test-network-request.html
   test-network.html
   test-observe-http-ajax.html
   test-own-console.html
   test-property-provider.html
   test-reopen-closed-tab.html
-  test-repeated-messages.html
   test-result-format-as-string.html
   test-sourcemap-error-01.html
   test-sourcemap-error-01.js
   test-sourcemap-error-02.html
   test-sourcemap-error-02.js
   test-stacktrace-location-debugger-link.html
   test-subresource-security-error.html
   test-subresource-security-error.js
@@ -327,18 +326,16 @@ skip-if = true #	Bug 1403448
 [browser_webconsole_output_copy.js]
 subsuite = clipboard
 [browser_webconsole_output_copy_newlines.js]
 subsuite = clipboard
 [browser_webconsole_output_order.js]
 [browser_webconsole_persist.js]
 [browser_webconsole_reopen_closed_tab.js]
 [browser_webconsole_repeat_different_objects.js]
-[browser_webconsole_repeated_messages_accuracy.js]
-skip-if = true #	Bug 1403450
 [browser_webconsole_sandbox_update_after_navigation.js]
 [browser_webconsole_script_errordoc_urls.js]
 [browser_webconsole_scroll.js]
 [browser_webconsole_select_all.js]
 [browser_webconsole_show_subresource_security_errors.js]
 [browser_webconsole_shows_reqs_in_netmonitor.js]
 [browser_webconsole_sourcemap_css.js]
 [browser_webconsole_sourcemap_error.js]
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_repeat_different_objects.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_repeat_different_objects.js
@@ -3,31 +3,33 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Test that makes sure messages are not considered repeated when console.log()
 // is invoked with different objects, see bug 865288.
 
 "use strict";
 
-const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
-                 "new-console-output/test/mochitest/test-repeated-messages.html";
+const TEST_URI = "data:text/html,Test repeated objects";
 
 add_task(async function () {
-  let hud = await openNewTabAndConsole(TEST_URI);
-  hud.jsterm.clearOutput();
+  const hud = await openNewTabAndConsole(TEST_URI);
 
-  let onMessages = waitForMessages({
+  const onMessages = waitForMessages({
     hud,
     messages: [
       { text: "abba" },
       { text: "abba" },
       { text: "abba" },
     ],
   });
 
-  hud.jsterm.execute("window.testConsoleObjects()");
+  ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
+    for (var i = 0; i < 3; i++) {
+      const o = { id: "abba" };
+      content.console.log("abba", o);
+    }
+  });
 
   info("waiting for 3 console.log objects, with the exact same text content");
   let messages = await onMessages;
-
-  is(messages.length, 3, "3 message elements");
+  is(messages.length, 3, "There are 3 messages, as expected.");
 });
deleted file mode 100644
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_repeated_messages_accuracy.js
+++ /dev/null
@@ -1,178 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-// Test that makes sure messages are not considered repeated when coming from
-// different lines of code, or from different severities, etc.
-// See bugs 720180 and 800510.
-
-"use strict";
-
-const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
-                 "test/test-repeated-messages.html";
-const PREF = "devtools.webconsole.persistlog";
-
-add_task(function* () {
-  Services.prefs.setBoolPref(PREF, true);
-
-  let { browser } = yield loadTab(TEST_URI);
-
-  let hud = yield openConsole();
-
-  yield consoleOpened(hud);
-
-  let loaded = loadBrowser(browser);
-  BrowserReload();
-  yield loaded;
-
-  yield testCSSRepeats(hud);
-  yield testCSSRepeatsAfterReload(hud);
-  yield testConsoleRepeats(hud);
-  yield testConsoleFalsyValues(hud);
-
-  Services.prefs.clearUserPref(PREF);
-});
-
-function consoleOpened(hud) {
-  // Check that css warnings are not coalesced if they come from different
-  // lines.
-  info("waiting for 2 css warnings");
-
-  return waitForMessages({
-    webconsole: hud,
-    messages: [{
-      name: "two css warnings",
-      category: CATEGORY_CSS,
-      count: 2,
-      repeats: 1,
-    }],
-  });
-}
-
-function testCSSRepeats(hud) {
-  info("wait for repeats after page reload");
-
-  return waitForMessages({
-    webconsole: hud,
-    messages: [{
-      name: "two css warnings, repeated twice",
-      category: CATEGORY_CSS,
-      repeats: 2,
-      count: 2,
-    }],
-  });
-}
-
-function testCSSRepeatsAfterReload(hud) {
-  hud.jsterm.clearOutput(true);
-  hud.jsterm.execute("testConsole()");
-
-  info("wait for repeats with the console API");
-
-  return waitForMessages({
-    webconsole: hud,
-    messages: [
-      {
-        name: "console.log 'foo repeat' repeated twice",
-        category: CATEGORY_WEBDEV,
-        severity: SEVERITY_LOG,
-        repeats: 2,
-      },
-      {
-        name: "console.log 'foo repeat' repeated once",
-        category: CATEGORY_WEBDEV,
-        severity: SEVERITY_LOG,
-        repeats: 1,
-      },
-      {
-        name: "console.error 'foo repeat' repeated once",
-        category: CATEGORY_WEBDEV,
-        severity: SEVERITY_ERROR,
-        repeats: 1,
-      },
-    ],
-  });
-}
-
-function testConsoleRepeats(hud) {
-  hud.jsterm.clearOutput(true);
-  hud.jsterm.execute("undefined");
-
-  ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
-    content.console.log("undefined");
-  });
-
-  info("make sure console API messages are not coalesced with jsterm output");
-
-  return waitForMessages({
-    webconsole: hud,
-    messages: [
-      {
-        name: "'undefined' jsterm input message",
-        text: "undefined",
-        category: CATEGORY_INPUT,
-      },
-      {
-        name: "'undefined' jsterm output message",
-        text: "undefined",
-        category: CATEGORY_OUTPUT,
-      },
-      {
-        name: "'undefined' console.log message",
-        text: "undefined",
-        category: CATEGORY_WEBDEV,
-        repeats: 1,
-      },
-    ],
-  });
-}
-
-function testConsoleFalsyValues(hud) {
-  hud.jsterm.clearOutput(true);
-  hud.jsterm.execute("testConsoleFalsyValues()");
-
-  info("wait for repeats of falsy values with the console API");
-
-  return waitForMessages({
-    webconsole: hud,
-    messages: [
-      {
-        name: "console.log 'NaN' repeated once",
-        category: CATEGORY_WEBDEV,
-        severity: SEVERITY_LOG,
-        repeats: 1,
-      },
-      {
-        name: "console.log 'undefined' repeated once",
-        category: CATEGORY_WEBDEV,
-        severity: SEVERITY_LOG,
-        repeats: 1,
-      },
-      {
-        name: "console.log 'null' repeated once",
-        category: CATEGORY_WEBDEV,
-        severity: SEVERITY_LOG,
-        repeats: 1,
-      },
-      {
-        name: "console.log 'NaN' repeated twice",
-        category: CATEGORY_WEBDEV,
-        severity: SEVERITY_LOG,
-        repeats: 2,
-      },
-      {
-        name: "console.log 'undefined' repeated twice",
-        category: CATEGORY_WEBDEV,
-        severity: SEVERITY_LOG,
-        repeats: 2,
-      },
-      {
-        name: "console.log 'null' repeated twice",
-        category: CATEGORY_WEBDEV,
-        severity: SEVERITY_LOG,
-        repeats: 2,
-      },
-    ],
-  });
-}
deleted file mode 100644
--- a/devtools/client/webconsole/new-console-output/test/mochitest/test-repeated-messages.html
+++ /dev/null
@@ -1,53 +0,0 @@
-<!DOCTYPE HTML>
-<html dir="ltr" xml:lang="en-US" lang="en-US">
-  <head>
-    <meta charset="utf8">
-    <title>Test for bugs 720180, 800510, 865288 and 1218089</title>
-    <script>
-      function testConsole() {
-        // same line and column number
-        for(var i = 0; i < 2; i++) {
-          console.log("foo repeat");
-        }
-        console.log("foo repeat");
-        console.error("foo repeat");
-      }
-      function testConsoleObjects() {
-        for (var i = 0; i < 3; i++) {
-          var o = { id: "abba" };
-          console.log("abba", o);
-        }
-      }
-      function testConsoleFalsyValues(){
-        [NaN, undefined, null].forEach(function(item, index){
-          console.log(item);
-        });
-        [NaN, NaN].forEach(function(item, index){
-          console.log(item);
-        });
-        [undefined, undefined].forEach(function(item, index){
-          console.log(item);
-        });
-        [null, null].forEach(function(item, index){
-          console.log(item);
-        });
-      }
-    </script>
-    <style>
-      body {
-        background-image: foobarz;
-      }
-      p {
-        background-image: foobarz;
-      }
-    </style>
-    <!--
-    - Any copyright is dedicated to the Public Domain.
-    - http://creativecommons.org/publicdomain/zero/1.0/
-    -->
-  </head>
-  <body>
-    <p>Hello world!</p>
-  </body>
-</html>
-
copy from devtools/client/webconsole/package.json
copy to devtools/client/webconsole/new-console-output/test/package.json
--- a/devtools/client/webconsole/package.json
+++ b/devtools/client/webconsole/new-console-output/test/package.json
@@ -1,44 +1,30 @@
 {
-  "name": "webconsole",
+  "name": "webconsole-tests",
   "version": "0.0.1",
   "engines": {
     "node": ">=6.9.0"
   },
   "scripts": {
-    "preinstall": "cd ../netmonitor && npm install && cd ../webconsole",
-    "start": "cross-env NODE_ENV=production node bin/dev-server",
-    "dev": "node bin/dev-server",
-    "test": "cross-env NODE_ENV=test NODE_PATH=../../../ mocha new-console-output/test/**/*.test.js --compilers js:babel-register -r jsdom-global/register -r ./new-console-output/test/require-helper.js"
+    "test": "cross-env NODE_ENV=test NODE_PATH=../../../../../ mocha \"./**/*.test.js\" -r babel-register  -r jsdom-global/register -r ./mocha-test-setup.js"
   },
   "dependencies": {
-    "amd-loader": "0.0.5",
-    "babel-plugin-transform-flow-strip-types": "^6.22.0",
-    "babel-plugin-transform-react-jsx": "^6.24.1",
     "babel-plugin-transform-object-rest-spread": "^6.26.0",
-    "babel-preset-es2015": "^6.6.0",
-    "babel-preset-es2017": "^6.24.1",
-    "babel-register": "^6.24.0",
+    "babel-register": "^6.26.0",
     "cross-env": "^3.1.3",
-    "devtools-config": "0.0.12",
-    "devtools-launchpad": "0.0.115",
-    "devtools-modules": "0.0.31",
-    "devtools-source-editor": "=0.0.3",
-    "enzyme": "^2.4.1",
+    "enzyme": "^3.3.0",
+    "enzyme-adapter-react-16": "^1.1.1",
     "expect": "^1.16.0",
-    "file-loader": "^0.10.1",
-    "immutable": "^3.8.1",
     "jsdom": "^9.4.1",
     "jsdom-global": "^2.0.0",
-    "json-loader": "^0.5.4",
-    "mocha": "^2.5.3",
-    "raw-loader": "^0.5.1",
-    "react": "=15.3.2",
-    "react-addons-perf": "=15.3.2",
-    "react-dom": "=15.3.2",
-    "react-redux": "=5.0.3",
-    "redux": "^3.6.0",
+    "mocha": "^5.0.0",
+    "netmonitor": "file:../../../netmonitor",
     "require-hacker": "^2.1.4",
-    "reselect": "^3.0.1",
-    "sinon": "^1.17.5"
+    "sinon": "^1.17.5",
+    "chai": "^4.1.2",
+    "es6-promise": "^4.2.4",
+    "nock": "^9.1.6",
+    "tap": "^11.1.0",
+    "react": "^16.2.0",
+    "react-dom": "^16.2.0"
   }
 }
--- a/devtools/client/webconsole/new-console-output/test/store/messages.test.js
+++ b/devtools/client/webconsole/new-console-output/test/store/messages.test.js
@@ -59,16 +59,41 @@ describe("Message reducer:", () => {
 
       const messages = getAllMessagesById(getState());
 
       expect(messages.size).toBe(1);
       const repeat = getAllRepeatById(getState());
       expect(repeat[getFirstMessage(getState()).id]).toBe(4);
     });
 
+    it("doesn't increment repeat on same log message with different locations", () => {
+      const key1 = "console.log('foobar', 'test')";
+      const { dispatch, getState } = setupStore();
+
+      const packet = clonePacket(stubPackets.get(key1));
+
+      // Dispatch original packet.
+      dispatch(actions.messagesAdd([packet]));
+
+      // Dispatch same packet with modified column number.
+      packet.message.columnNumber = packet.message.columnNumber + 1;
+      dispatch(actions.messagesAdd([packet]));
+
+      // Dispatch same packet with modified line number.
+      packet.message.lineNumber = packet.message.lineNumber + 1;
+      dispatch(actions.messagesAdd([packet]));
+
+      const messages = getAllMessagesById(getState());
+
+      expect(messages.size).toBe(3);
+
+      const repeat = getAllRepeatById(getState());
+      expect(Object.keys(repeat).length).toBe(0);
+    });
+
     it("increments repeat on a repeating css message", () => {
       const key1 = "Unknown property ‘such-unknown-property’.  Declaration dropped.";
       const { dispatch, getState } = setupStore([key1, key1]);
 
       const packet = clonePacket(stubPackets.get(key1));
 
       // Repeat ID must be the same even if the timestamp is different.
       packet.pageError.timeStamp = 1;
@@ -79,16 +104,41 @@ describe("Message reducer:", () => {
       const messages = getAllMessagesById(getState());
 
       expect(messages.size).toBe(1);
 
       const repeat = getAllRepeatById(getState());
       expect(repeat[getFirstMessage(getState()).id]).toBe(4);
     });
 
+    it("doesn't increment repeat on same css message with different locations", () => {
+      const key1 = "Unknown property ‘such-unknown-property’.  Declaration dropped.";
+      const { dispatch, getState } = setupStore();
+
+      const packet = clonePacket(stubPackets.get(key1));
+
+      // Dispatch original packet.
+      dispatch(actions.messagesAdd([packet]));
+
+      // Dispatch same packet with modified column number.
+      packet.pageError.columnNumber = packet.pageError.columnNumber + 1;
+      dispatch(actions.messagesAdd([packet]));
+
+      // Dispatch same packet with modified line number.
+      packet.pageError.lineNumber = packet.pageError.lineNumber + 1;
+      dispatch(actions.messagesAdd([packet]));
+
+      const messages = getAllMessagesById(getState());
+
+      expect(messages.size).toBe(3);
+
+      const repeat = getAllRepeatById(getState());
+      expect(Object.keys(repeat).length).toBe(0);
+    });
+
     it("increments repeat on a repeating error message", () => {
       const key1 = "ReferenceError: asdf is not defined";
       const { dispatch, getState } = setupStore([key1, key1]);
 
       const packet = clonePacket(stubPackets.get(key1));
 
       // Repeat ID must be the same even if the timestamp is different.
       packet.pageError.timeStamp = 1;
@@ -121,16 +171,67 @@ describe("Message reducer:", () => {
 
       expect(messages.size).toBe(4);
       const repeat = getAllRepeatById(getState());
       expect(repeat[getFirstMessage(getState()).id]).toBe(2);
       expect(repeat[getMessageAt(getState(), 2).id]).toBe(3);
       expect(repeat[getLastMessage(getState()).id]).toBe(undefined);
     });
 
+    it("doesn't increment undefined messages coming from different places", () => {
+      const { getState } = setupStore([
+        "console.log(undefined)",
+        "undefined",
+      ]);
+
+      const messages = getAllMessagesById(getState());
+      expect(messages.size).toBe(2);
+
+      const repeat = getAllRepeatById(getState());
+      expect(Object.keys(repeat).length).toBe(0);
+    });
+
+    it("doesn't increment successive falsy but different messages", () => {
+      const { getState } = setupStore([
+        "console.log(NaN)",
+        "console.log(undefined)",
+        "console.log(null)",
+      ], {actions});
+
+      let messages = getAllMessagesById(getState());
+      expect(messages.size).toBe(3);
+      let repeat = getAllRepeatById(getState());
+      expect(Object.keys(repeat).length).toBe(0);
+    });
+
+    it("increment falsy messages when expected", () => {
+      const { dispatch, getState } = setupStore();
+
+      const nanPacket = stubPackets.get("console.log(NaN)");
+      dispatch(actions.messagesAdd([nanPacket, nanPacket]));
+      let messages = getAllMessagesById(getState());
+      expect(messages.size).toBe(1);
+      let repeat = getAllRepeatById(getState());
+      expect(repeat[getLastMessage(getState()).id]).toBe(2);
+
+      const undefinedPacket = stubPackets.get("console.log(undefined)");
+      dispatch(actions.messagesAdd([undefinedPacket, undefinedPacket]));
+      messages = getAllMessagesById(getState());
+      expect(messages.size).toBe(2);
+      repeat = getAllRepeatById(getState());
+      expect(repeat[getLastMessage(getState()).id]).toBe(2);
+
+      const nullPacket = stubPackets.get("console.log(null)");
+      dispatch(actions.messagesAdd([nullPacket, nullPacket]));
+      messages = getAllMessagesById(getState());
+      expect(messages.size).toBe(3);
+      repeat = getAllRepeatById(getState());
+      expect(repeat[getLastMessage(getState()).id]).toBe(2);
+    });
+
     it("does not clobber a unique message", () => {
       const key1 = "console.log('foobar', 'test')";
       const { dispatch, getState } = setupStore([key1, key1]);
 
       const packet = stubPackets.get(key1);
       dispatch(actions.messagesAdd([packet]));
 
       const packet2 = stubPackets.get("console.log(undefined)");
--- a/devtools/client/webconsole/package.json
+++ b/devtools/client/webconsole/package.json
@@ -1,44 +1,29 @@
 {
   "name": "webconsole",
   "version": "0.0.1",
   "engines": {
     "node": ">=6.9.0"
   },
   "scripts": {
-    "preinstall": "cd ../netmonitor && npm install && cd ../webconsole",
     "start": "cross-env NODE_ENV=production node bin/dev-server",
-    "dev": "node bin/dev-server",
-    "test": "cross-env NODE_ENV=test NODE_PATH=../../../ mocha new-console-output/test/**/*.test.js --compilers js:babel-register -r jsdom-global/register -r ./new-console-output/test/require-helper.js"
+    "dev": "node bin/dev-server"
   },
   "dependencies": {
-    "amd-loader": "0.0.5",
     "babel-plugin-transform-flow-strip-types": "^6.22.0",
     "babel-plugin-transform-react-jsx": "^6.24.1",
     "babel-plugin-transform-object-rest-spread": "^6.26.0",
-    "babel-preset-es2015": "^6.6.0",
-    "babel-preset-es2017": "^6.24.1",
-    "babel-register": "^6.24.0",
     "cross-env": "^3.1.3",
     "devtools-config": "0.0.12",
     "devtools-launchpad": "0.0.115",
     "devtools-modules": "0.0.31",
-    "devtools-source-editor": "=0.0.3",
-    "enzyme": "^2.4.1",
-    "expect": "^1.16.0",
-    "file-loader": "^0.10.1",
-    "immutable": "^3.8.1",
-    "jsdom": "^9.4.1",
-    "jsdom-global": "^2.0.0",
-    "json-loader": "^0.5.4",
-    "mocha": "^2.5.3",
+    "file-loader": "^1.1.6",
+    "netmonitor": "file:../netmonitor",
     "raw-loader": "^0.5.1",
-    "react": "=15.3.2",
-    "react-addons-perf": "=15.3.2",
-    "react-dom": "=15.3.2",
-    "react-redux": "=5.0.3",
-    "redux": "^3.6.0",
-    "require-hacker": "^2.1.4",
-    "reselect": "^3.0.1",
-    "sinon": "^1.17.5"
+    "react": "=16.2.0",
+    "react-dom": "=16.2.0",
+    "react-prop-types": "=0.4.0",
+    "react-redux": "=5.0.6",
+    "redux": "^3.7.2",
+    "require-hacker": "^2.1.4"
   }
 }
--- a/devtools/server/actors/animation.js
+++ b/devtools/server/actors/animation.js
@@ -385,24 +385,26 @@ var AnimationPlayerActor = protocol.Acto
         // Reset the local copy of the state on removal, since the animation can
         // be kept on the client and re-added, its state needs to be sent in
         // full.
         this.currentState = null;
       }
 
       if (hasCurrentAnimation(changedAnimations)) {
         // Only consider the state has having changed if any of delay, duration,
-        // iterationcount or iterationStart has changed (for now at least).
+        // iterationCount, iterationStart, or playbackRate has changed (for now
+        // at least).
         let newState = this.getState();
         let oldState = this.currentState;
         hasChanged = newState.delay !== oldState.delay ||
                      newState.iterationCount !== oldState.iterationCount ||
                      newState.iterationStart !== oldState.iterationStart ||
                      newState.duration !== oldState.duration ||
-                     newState.endDelay !== oldState.endDelay;
+                     newState.endDelay !== oldState.endDelay ||
+                     newState.playbackRate !== oldState.playbackRate;
         break;
       }
     }
 
     if (hasChanged) {
       this.emit("changed", this.getCurrentState());
     }
   },
@@ -459,17 +461,18 @@ var AnimationPlayerActor = protocol.Acto
     }
     this.player.currentTime = currentTime * this.player.playbackRate;
   },
 
   /**
    * Set the playback rate of the animation player.
    */
   setPlaybackRate: function (playbackRate) {
-    this.player.playbackRate = playbackRate;
+    this.player.updatePlaybackRate(playbackRate);
+    return this.player.ready;
   },
 
   /**
    * Get data about the keyframes of this animation player.
    * @return {Object} Returns a list of frames, each frame containing the list
    * animated properties as well as the frame's offset.
    */
   getFrames: function () {
@@ -865,13 +868,13 @@ exports.AnimationsActor = protocol.Actor
   },
 
   /**
    * Set the playback rate of several animations at the same time.
    * @param {Array} players A list of AnimationPlayerActor.
    * @param {Number} rate The new rate.
    */
   setPlaybackRates: function (players, rate) {
-    for (let player of players) {
-      player.setPlaybackRate(rate);
-    }
+    return Promise.all(
+      players.map(player => player.setPlaybackRate(rate))
+    );
   }
 });
--- a/devtools/server/actors/highlighters/shapes.js
+++ b/devtools/server/actors/highlighters/shapes.js
@@ -829,17 +829,22 @@ class ShapesHighlighter extends AutoRefr
   _transformPolygon() {
     let { pointsInfo } = this[_dragging];
 
     let polygonDef = (this.fillRule) ? `${this.fillRule}, ` : "";
     polygonDef += pointsInfo.map(point => {
       let { unitX, unitY, valueX, valueY, ratioX, ratioY } = point;
       let vector = [valueX / ratioX, valueY / ratioY];
       let [newX, newY] = apply(this.transformMatrix, vector);
-      return `${newX * ratioX}${unitX} ${newY * ratioY}${unitY}`;
+      let precisionX = getDecimalPrecision(unitX);
+      let precisionY = getDecimalPrecision(unitY);
+      newX = (newX * ratioX).toFixed(precisionX);
+      newY = (newY * ratioY).toFixed(precisionY);
+
+      return `${newX}${unitX} ${newY}${unitY}`;
     }).join(", ");
     polygonDef = (this.geometryBox) ? `polygon(${polygonDef}) ${this.geometryBox}` :
                                       `polygon(${polygonDef})`;
 
     this.currentNode.style.setProperty(this.property, polygonDef, "important");
   }
 
   /**
@@ -960,31 +965,37 @@ class ShapesHighlighter extends AutoRefr
    * coords.
    * @param {Number} pageX the new x coordinate of the point
    * @param {Number} pageY the new y coordinate of the point
    */
   _handlePolygonMove(pageX, pageY) {
     let { point, unitX, unitY, valueX, valueY, ratioX, ratioY, x, y } = this[_dragging];
     let deltaX = (pageX - x) * ratioX;
     let deltaY = (pageY - y) * ratioY;
-    let newX = `${valueX + deltaX}${unitX}`;
-    let newY = `${valueY + deltaY}${unitY}`;
+    let precisionX = getDecimalPrecision(unitX);
+    let precisionY = getDecimalPrecision(unitY);
+    let newX = (valueX + deltaX).toFixed(precisionX);
+    let newY = (valueY + deltaY).toFixed(precisionY);
 
     let polygonDef = (this.fillRule) ? `${this.fillRule}, ` : "";
     polygonDef += this.coordUnits.map((coords, i) => {
-      return (i === point) ? `${newX} ${newY}` : `${coords[0]} ${coords[1]}`;
+      return (i === point) ?
+        `${newX}${unitX} ${newY}${unitY}` : `${coords[0]} ${coords[1]}`;
     }).join(", ");
     polygonDef = (this.geometryBox) ? `polygon(${polygonDef}) ${this.geometryBox}` :
                                       `polygon(${polygonDef})`;
 
     this.currentNode.style.setProperty(this.property, polygonDef, "important");
   }
 
   /**
    * Set the inline style of the polygon, adding a new point.
+   * TODO: Bug 1436054 - Do not default to percentage unit when inserting new point.
+   * https://bugzilla.mozilla.org/show_bug.cgi?id=1436054
+   *
    * @param {Number} after the index of the point that the new point should be added after
    * @param {Number} x the x coordinate of the new point
    * @param {Number} y the y coordinate of the new point
    */
   _addPolygonPoint(after, x, y) {
     let polygonDef = (this.fillRule) ? `${this.fillRule}, ` : "";
     polygonDef += this.coordUnits.map((coords, i) => {
       return (i === after) ? `${coords[0]} ${coords[1]}, ${x}% ${y}%` :
@@ -1562,17 +1573,19 @@ class ShapesHighlighter extends AutoRefr
       let distance = distanceToLine(x1, y1, x2, y2, pageX, pageY);
       if (distance <= clickWidth &&
           Math.min(x1, x2) - clickWidth <= pageX &&
           pageX <= Math.max(x1, x2) + clickWidth &&
           Math.min(y1, y2) - clickWidth <= pageY &&
           pageY <= Math.max(y1, y2) + clickWidth) {
         // Get the point on the line closest to the clicked point.
         let [newX, newY] = projection(x1, y1, x2, y2, pageX, pageY);
-        this._addPolygonPoint(i, newX, newY);
+        // Default unit for new points is percentages
+        let precision = getDecimalPrecision("%");
+        this._addPolygonPoint(i, newX.toFixed(precision), newY.toFixed(precision));
         return;
       }
     }
   }
 
   /**
    * Check if the center point or radius of the circle highlighter is at given coords
    * @param {Number} pageX the x coordinate on the page, in % relative to the element
@@ -2723,9 +2736,32 @@ const getAnchorPoint = (type) => {
     anchor = "n" + anchor;
   } else if (anchor === "n" || anchor === "s") {
     anchor = anchor + "w";
   }
 
   return anchor;
 };
 
+/**
+* Get the decimal point precision for values depending on unit type.
+* Used as argument for `toFixed()` on coordinate values when:
+* - transforming shapes
+* - inserting new points on a polygon.
+* Only handle pixels and falsy values for now. Round them to the nearest integer value.
+* All other unit types round to two decimal points.
+*
+* @param {String|undefined} unitType any one of the accepted CSS unit types for position.
+* @return {Number} decimal precision when rounding a value
+*/
+function getDecimalPrecision(unitType) {
+  switch (unitType) {
+    case "px":
+    case "":
+    case undefined:
+      return 0;
+    default:
+      return 2;
+  }
+}
+exports.getDecimalPrecision = getDecimalPrecision;
+
 exports.ShapesHighlighter = ShapesHighlighter;
--- a/devtools/server/tests/unit/test_shapes_highlighter_helpers.js
+++ b/devtools/server/tests/unit/test_shapes_highlighter_helpers.js
@@ -8,25 +8,27 @@
 "use strict";
 
 const {
   splitCoords,
   coordToPercent,
   evalCalcExpression,
   shapeModeToCssPropertyName,
   getCirclePath,
+  getDecimalPrecision,
   getUnit
 } = require("devtools/server/actors/highlighters/shapes");
 
 function run_test() {
   test_split_coords();
   test_coord_to_percent();
   test_eval_calc_expression();
   test_shape_mode_to_css_property_name();
   test_get_circle_path();
+  test_get_decimal_precision();
   test_get_unit();
   run_next_test();
 }
 
 function test_split_coords() {
   const tests = [{
     desc: "splitCoords for basic coordinate pair",
     expr: "30% 20%",
@@ -123,16 +125,39 @@ function test_get_circle_path() {
     expected: "M-2.5,0a2.5,1.25 0 1,0 5,0a2.5,1.25 0 1,0 -5,0"
   }];
 
   for (let { desc, size, cx, cy, width, height, zoom, expected } of tests) {
     equal(getCirclePath(size, cx, cy, width, height, zoom), expected, desc);
   }
 }
 
+function test_get_decimal_precision() {
+  const tests = [{
+    desc: "getDecimalPrecision with px",
+    expr: "px", expected: 0
+  }, {
+    desc: "getDecimalPrecision with %",
+    expr: "%", expected: 2
+  }, {
+    desc: "getDecimalPrecision with em",
+    expr: "em", expected: 2
+  }, {
+    desc: "getDecimalPrecision with undefined",
+    expr: undefined, expected: 0
+  }, {
+    desc: "getDecimalPrecision with empty string",
+    expr: "", expected: 0
+  }];
+
+  for (let { desc, expr, expected } of tests) {
+    equal(getDecimalPrecision(expr), expected, desc);
+  }
+}
+
 function test_get_unit() {
   const tests = [{
     desc: "getUnit with %",
     expr: "30%", expected: "%"
   }, {
     desc: "getUnit with px",
     expr: "400px", expected: "px"
   }, {
--- a/docshell/base/moz.build
+++ b/docshell/base/moz.build
@@ -35,17 +35,16 @@ DIRS += [
     'timeline',
 ]
 
 XPIDL_SOURCES += [
     'nsCDefaultURIFixup.idl',
     'nsIClipboardCommands.idl',
     'nsIContentViewer.idl',
     'nsIContentViewerEdit.idl',
-    'nsIDocCharset.idl',
     'nsIDocShell.idl',
     'nsIDocShellLoadInfo.idl',
     'nsIDocShellTreeItem.idl',
     'nsIDocShellTreeOwner.idl',
     'nsIDocumentLoaderFactory.idl',
     'nsIDownloadHistory.idl',
     'nsIGlobalHistory2.idl',
     'nsILoadContext.idl',
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -513,17 +513,16 @@ NS_IMPL_RELEASE_INHERITED(nsDocShell, ns
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDocShell)
   NS_INTERFACE_MAP_ENTRY(nsIDocShell)
   NS_INTERFACE_MAP_ENTRY(nsIDocShellTreeItem)
   NS_INTERFACE_MAP_ENTRY(nsIWebNavigation)
   NS_INTERFACE_MAP_ENTRY(nsIBaseWindow)
   NS_INTERFACE_MAP_ENTRY(nsIScrollable)
   NS_INTERFACE_MAP_ENTRY(nsITextScroll)
-  NS_INTERFACE_MAP_ENTRY(nsIDocCharset)
   NS_INTERFACE_MAP_ENTRY(nsIRefreshURI)
   NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener)
   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
   NS_INTERFACE_MAP_ENTRY(nsIWebPageDescriptor)
   NS_INTERFACE_MAP_ENTRY(nsIAuthPromptProvider)
   NS_INTERFACE_MAP_ENTRY(nsILoadContext)
   NS_INTERFACE_MAP_ENTRY(nsIWebShellServices)
   NS_INTERFACE_MAP_ENTRY(nsILinkHandler)
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -16,17 +16,16 @@
 #include "mozilla/WeakPtr.h"
 
 #include "mozilla/dom/ProfileTimelineMarkerBinding.h"
 
 #include "nsIAuthPromptProvider.h"
 #include "nsIBaseWindow.h"
 #include "nsIClipboardCommands.h"
 #include "nsIDeprecationWarner.h"
-#include "nsIDocCharset.h"
 #include "nsIDocShell.h"
 #include "nsIDocShellLoadInfo.h"
 #include "nsIDocShellTreeItem.h"
 #include "nsIDOMStorageManager.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsILinkHandler.h"
 #include "nsILoadContext.h"
 #include "nsILoadURIDelegate.h"
@@ -120,17 +119,16 @@ enum eCharsetReloadState
 
 class nsDocShell final
   : public nsDocLoader
   , public nsIDocShell
   , public nsIWebNavigation
   , public nsIBaseWindow
   , public nsIScrollable
   , public nsITextScroll
-  , public nsIDocCharset
   , public nsIRefreshURI
   , public nsIWebProgressListener
   , public nsIWebPageDescriptor
   , public nsIAuthPromptProvider
   , public nsILoadContext
   , public nsIWebShellServices
   , public nsILinkHandler
   , public nsIClipboardCommands
@@ -172,17 +170,16 @@ public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsDocShell, nsDocLoader)
   NS_DECL_NSIDOCSHELL
   NS_DECL_NSIDOCSHELLTREEITEM
   NS_DECL_NSIWEBNAVIGATION
   NS_DECL_NSIBASEWINDOW
   NS_DECL_NSISCROLLABLE
   NS_DECL_NSITEXTSCROLL
-  NS_DECL_NSIDOCCHARSET
   NS_DECL_NSIINTERFACEREQUESTOR
   NS_DECL_NSIWEBPROGRESSLISTENER
   NS_DECL_NSIREFRESHURI
   NS_DECL_NSIWEBPAGEDESCRIPTOR
   NS_DECL_NSIAUTHPROMPTPROVIDER
   NS_DECL_NSICLIPBOARDCOMMANDS
   NS_DECL_NSIWEBSHELLSERVICES
   NS_DECL_NSINETWORKINTERCEPTCONTROLLER
deleted file mode 100644
--- a/docshell/base/nsIDocCharset.idl
+++ /dev/null
@@ -1,19 +0,0 @@
-/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "nsISupports.idl"
-
-/**
- * The functionality of the nsIDocCharset interface has been incorporated into
- * nsIDocShell.
- *
- * This is an empty interface for backwards compatibility that will go away at
- * some point in the future
- *
- */
-
-[scriptable, uuid(c3faaf6e-40f0-11e1-95fc-6c626d69675c)]
-interface nsIDocCharset : nsISupports
-{};
--- a/docshell/test/mochitest.ini
+++ b/docshell/test/mochitest.ini
@@ -93,17 +93,16 @@ skip-if = toolkit == 'android'
 support-files = file_bug668513.html
 [test_bug669671.html]
 [test_bug675587.html]
 support-files = file_bug675587.html
 [test_bug680257.html]
 [test_bug691547.html]
 [test_bug694612.html]
 [test_bug703855.html]
-[test_bug713825.html]
 [test_bug728939.html]
 [test_bug797909.html]
 [test_bug1045096.html]
 [test_bug1121701.html]
 [test_bug1151421.html]
 [test_bug1186774.html]
 [test_forceinheritprincipal_overrule_owner.html]
 [test_framedhistoryframes.html]
--- a/docshell/test/moz.build
+++ b/docshell/test/moz.build
@@ -131,16 +131,13 @@ with Files('*637644*'):
     BUG_COMPONENT = ('Core', 'DOM: Core & HTML')
 
 with Files('*640387*'):
     BUG_COMPONENT = ('Core', 'DOM: Events')
 
 with Files('*668513*'):
     BUG_COMPONENT = ('Core', 'DOM')
 
-with Files('*713825*'):
-    BUG_COMPONENT = ('Core', 'Internationalization')
-
 with Files('*797909*'):
     BUG_COMPONENT = ('Core', 'DOM: Core & HTML')
 
 with Files('*forceinheritprincipal*'):
     BUG_COMPONENT = ('Core', 'DOM: Security')
deleted file mode 100644
--- a/docshell/test/test_bug713825.html
+++ /dev/null
@@ -1,42 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=713825
--->
-<head>
-  <meta charset="utf-8">
-  <title>Test for Bug 713825</title>
-  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-</head>
-<body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=713825">Mozilla Bug 713825</a>
-<p id="display"></p>
-<div id="content" style="display: none">
-  
-</div>
-<pre id="test">
-<script type="application/javascript">
-
-/** 
- * Test for Bug 713825 
- *  test that nsIDocCharset still works backward compatibly
- */
-var Ci = SpecialPowers.Ci;
-var docShell = SpecialPowers.wrap(window).QueryInterface(Ci.nsIInterfaceRequestor).
-                      getInterface(Ci.nsIWebNavigation).
-                      QueryInterface(Ci.nsIDocShell);
-
-var charset1 = docShell.charset;
-var charset2 = docShell.QueryInterface(Ci.nsIDocCharset).charset;
-var charset3 = SpecialPowers.wrap(window).QueryInterface(Ci.nsIInterfaceRequestor).
-                      getInterface(Ci.nsIDocCharset).charset;
-
-/* if we get here without throwing and the three charsets are equal, all is OK */
-is(charset1, charset2, "QI'd nsIDocCharset.charset should equal nsIDocShell.charset");
-is(charset1, charset3, "getInterface'd nsIDocCharset.charset should equal nsIDocShell.charset");
-
-</script>
-</pre>
-</body>
-</html>
--- a/dom/animation/Animation.cpp
+++ b/dom/animation/Animation.cpp
@@ -268,17 +268,20 @@ Animation::SetStartTime(const Nullable<T
     // the already null time to null.
     timelineTime = mTimeline->GetCurrentTime();
   }
   if (timelineTime.IsNull() && !aNewStartTime.IsNull()) {
     mHoldTime.SetNull();
   }
 
   Nullable<TimeDuration> previousCurrentTime = GetCurrentTime();
+
+  ApplyPendingPlaybackRate();
   mStartTime = aNewStartTime;
+
   if (!aNewStartTime.IsNull()) {
     if (mPlaybackRate != 0.0) {
       mHoldTime.SetNull();
     }
   } else {
     mHoldTime = previousCurrentTime;
   }
 
@@ -293,29 +296,30 @@ Animation::SetStartTime(const Nullable<T
   if (IsRelevant()) {
     nsNodeUtils::AnimationChanged(this);
   }
   PostUpdate();
 }
 
 // https://drafts.csswg.org/web-animations/#current-time
 Nullable<TimeDuration>
-Animation::GetCurrentTime() const
+Animation::GetCurrentTimeForHoldTime(
+  const Nullable<TimeDuration>& aHoldTime) const
 {
   Nullable<TimeDuration> result;
-  if (!mHoldTime.IsNull()) {
-    result = mHoldTime;
+  if (!aHoldTime.IsNull()) {
+    result = aHoldTime;
     return result;
   }
 
   if (mTimeline && !mStartTime.IsNull()) {
     Nullable<TimeDuration> timelineTime = mTimeline->GetCurrentTime();
     if (!timelineTime.IsNull()) {
-      result.SetValue((timelineTime.Value() - mStartTime.Value())
-                        .MultDouble(mPlaybackRate));
+      result = CurrentTimeFromTimelineTime(
+        timelineTime.Value(), mStartTime.Value(), mPlaybackRate);
     }
   }
   return result;
 }
 
 // https://drafts.csswg.org/web-animations/#set-the-current-time
 void
 Animation::SetCurrentTime(const TimeDuration& aSeekTime)
@@ -331,35 +335,39 @@ Animation::SetCurrentTime(const TimeDura
 
   AutoMutationBatchForAnimation mb(*this);
 
   SilentlySetCurrentTime(aSeekTime);
 
   if (mPendingState == PendingState::PausePending) {
     // Finish the pause operation
     mHoldTime.SetValue(aSeekTime);
+
+    ApplyPendingPlaybackRate();
     mStartTime.SetNull();
 
     if (mReady) {
       mReady->MaybeResolve(this);
     }
     CancelPendingTasks();
   }
 
   UpdateTiming(SeekFlag::DidSeek, SyncNotifyFlag::Async);
   if (IsRelevant()) {
     nsNodeUtils::AnimationChanged(this);
   }
   PostUpdate();
 }
 
-// https://drafts.csswg.org/web-animations/#set-the-animation-playback-rate
+// https://drafts.csswg.org/web-animations/#set-the-playback-rate
 void
 Animation::SetPlaybackRate(double aPlaybackRate)
 {
+  mPendingPlaybackRate.reset();
+
   if (aPlaybackRate == mPlaybackRate) {
     return;
   }
 
   AutoMutationBatchForAnimation mb(*this);
 
   Nullable<TimeDuration> previousTime = GetCurrentTime();
   mPlaybackRate = aPlaybackRate;
@@ -377,16 +385,93 @@ Animation::SetPlaybackRate(double aPlayb
   // - update the playback rate on animations on layers.
   UpdateTiming(SeekFlag::DidSeek, SyncNotifyFlag::Async);
   if (IsRelevant()) {
     nsNodeUtils::AnimationChanged(this);
   }
   PostUpdate();
 }
 
+// https://drafts.csswg.org/web-animations/#seamlessly-update-the-playback-rate
+void
+Animation::UpdatePlaybackRate(double aPlaybackRate)
+{
+  if (mPendingPlaybackRate && mPendingPlaybackRate.value() == aPlaybackRate) {
+    return;
+  }
+
+  mPendingPlaybackRate = Some(aPlaybackRate);
+
+  // If we already have a pending task, there is nothing more to do since the
+  // playback rate will be applied then.
+  if (Pending()) {
+    return;
+  }
+
+  AutoMutationBatchForAnimation mb(*this);
+
+  AnimationPlayState playState = PlayState();
+  if (playState == AnimationPlayState::Idle ||
+      playState == AnimationPlayState::Paused) {
+    // We are either idle or paused. In either case we can apply the pending
+    // playback rate immediately.
+    ApplyPendingPlaybackRate();
+
+    // We don't need to update timing or post an update here because:
+    //
+    // * the current time hasn't changed -- it's either unresolved or fixed
+    //   with a hold time -- so the output won't have changed
+    // * the finished state won't have changed even if the sign of the
+    //   playback rate changed since we're not finished (we're paused or idle)
+    // * the playback rate on layers doesn't need to be updated since we're not
+    //   moving. Once we get a start time etc. we'll update the playback rate
+    //   then.
+    //
+    // All we need to do is update observers so that, e.g. DevTools, report the
+    // right information.
+    if (IsRelevant()) {
+      nsNodeUtils::AnimationChanged(this);
+    }
+  } else if (playState == AnimationPlayState::Finished) {
+    MOZ_ASSERT(mTimeline && !mTimeline->GetCurrentTime().IsNull(),
+               "If we have no active timeline, we should be idle or paused");
+    if (aPlaybackRate != 0) {
+      // The unconstrained current time can only be unresolved if either we
+      // don't have an active timeline (and we already asserted that is not
+      // true) or we have an unresolved start time (in which case we should be
+      // paused).
+      MOZ_ASSERT(!GetUnconstrainedCurrentTime().IsNull(),
+                 "Unconstrained current time should be resolved");
+      TimeDuration unconstrainedCurrentTime =
+        GetUnconstrainedCurrentTime().Value();
+      TimeDuration timelineTime = mTimeline->GetCurrentTime().Value();
+      mStartTime = StartTimeFromTimelineTime(
+        timelineTime, unconstrainedCurrentTime, aPlaybackRate);
+    } else {
+      mStartTime = mTimeline->GetCurrentTime();
+    }
+
+    ApplyPendingPlaybackRate();
+
+    // Even though we preserve the current time, we might now leave the finished
+    // state (e.g. if the playback rate changes sign) so we need to update
+    // timing.
+    UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
+    if (IsRelevant()) {
+      nsNodeUtils::AnimationChanged(this);
+    }
+    PostUpdate();
+  } else {
+    ErrorResult rv;
+    Play(rv, LimitBehavior::Continue);
+    MOZ_ASSERT(!rv.Failed(),
+               "We should only fail to play when using auto-rewind behavior");
+  }
+}
+
 // https://drafts.csswg.org/web-animations/#play-state
 AnimationPlayState
 Animation::PlayState() const
 {
   if (!nsContentUtils::AnimationsAPIPendingMemberEnabled() && Pending()) {
     return AnimationPlayState::Pending;
   }
 
@@ -449,42 +534,46 @@ Animation::Cancel()
   CancelNoUpdate();
   PostUpdate();
 }
 
 // https://drafts.csswg.org/web-animations/#finish-an-animation
 void
 Animation::Finish(ErrorResult& aRv)
 {
-  if (mPlaybackRate == 0 ||
-      (mPlaybackRate > 0 && EffectEnd() == TimeDuration::Forever())) {
+  double effectivePlaybackRate = CurrentOrPendingPlaybackRate();
+
+  if (effectivePlaybackRate == 0 ||
+      (effectivePlaybackRate > 0 && EffectEnd() == TimeDuration::Forever())) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   AutoMutationBatchForAnimation mb(*this);
 
+  ApplyPendingPlaybackRate();
+
   // Seek to the end
   TimeDuration limit =
     mPlaybackRate > 0 ? TimeDuration(EffectEnd()) : TimeDuration(0);
   bool didChange = GetCurrentTime() != Nullable<TimeDuration>(limit);
   SilentlySetCurrentTime(limit);
 
   // If we are paused or play-pending we need to fill in the start time in
   // order to transition to the finished state.
   //
   // We only do this, however, if we have an active timeline. If we have an
   // inactive timeline we can't transition into the finished state just like
   // we can't transition to the running state (this finished state is really
   // a substate of the running state).
   if (mStartTime.IsNull() &&
       mTimeline &&
       !mTimeline->GetCurrentTime().IsNull()) {
-    mStartTime.SetValue(mTimeline->GetCurrentTime().Value() -
-                        limit.MultDouble(1.0 / mPlaybackRate));
+    mStartTime = StartTimeFromTimelineTime(
+      mTimeline->GetCurrentTime().Value(), limit, mPlaybackRate);
     didChange = true;
   }
 
   // If we just resolved the start time for a pause or play-pending
   // animation, we need to clear the task. We don't do this as a branch of
   // the above however since we can have a play-pending animation with a
   // resolved start time if we aborted a pause operation.
   if (!mStartTime.IsNull() &&
@@ -524,35 +613,34 @@ Animation::Pause(ErrorResult& aRv)
 void
 Animation::Reverse(ErrorResult& aRv)
 {
   if (!mTimeline || mTimeline->GetCurrentTime().IsNull()) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
-  if (mPlaybackRate == 0.0) {
+  double effectivePlaybackRate = CurrentOrPendingPlaybackRate();
+
+  if (effectivePlaybackRate == 0.0) {
     return;
   }
 
-  AutoMutationBatchForAnimation mb(*this);
+  Maybe<double> originalPendingPlaybackRate = mPendingPlaybackRate;
 
-  SilentlySetPlaybackRate(-mPlaybackRate);
+  mPendingPlaybackRate = Some(-effectivePlaybackRate);
+
   Play(aRv, LimitBehavior::AutoRewind);
 
   // If Play() threw, restore state and don't report anything to mutation
   // observers.
   if (aRv.Failed()) {
-    SilentlySetPlaybackRate(-mPlaybackRate);
-    return;
+    mPendingPlaybackRate = originalPendingPlaybackRate;
   }
 
-  if (IsRelevant()) {
-    nsNodeUtils::AnimationChanged(this);
-  }
   // Play(), above, unconditionally calls PostUpdate so we don't need to do
   // it here.
 }
 
 // ---------------------------------------------------------------------------
 //
 // JS wrappers for Animation interface:
 //
@@ -672,42 +760,53 @@ Animation::TriggerNow()
   FinishPendingAt(mTimeline->GetCurrentTime().Value());
 }
 
 Nullable<TimeDuration>
 Animation::GetCurrentOrPendingStartTime() const
 {
   Nullable<TimeDuration> result;
 
+  // If we have a pending playback rate, work out what start time we will use
+  // when we come to updating that playback rate.
+  //
+  // This logic roughly shadows that in ResumeAt but is just different enough
+  // that it is difficult to extract out the common functionality (and
+  // extracting that functionality out would make it harder to match ResumeAt up
+  // against the spec).
+  if (mPendingPlaybackRate && !mPendingReadyTime.IsNull() &&
+      !mStartTime.IsNull()) {
+    // If we have a hold time, use it as the current time to match.
+    TimeDuration currentTimeToMatch =
+      !mHoldTime.IsNull()
+        ? mHoldTime.Value()
+        : CurrentTimeFromTimelineTime(
+            mPendingReadyTime.Value(), mStartTime.Value(), mPlaybackRate);
+
+    result = StartTimeFromTimelineTime(
+      mPendingReadyTime.Value(), currentTimeToMatch, *mPendingPlaybackRate);
+    return result;
+  }
+
   if (!mStartTime.IsNull()) {
     result = mStartTime;
     return result;
   }
 
   if (mPendingReadyTime.IsNull() || mHoldTime.IsNull()) {
     return result;
   }
 
   // Calculate the equivalent start time from the pending ready time.
-  result = StartTimeFromReadyTime(mPendingReadyTime.Value());
+  result = StartTimeFromTimelineTime(
+    mPendingReadyTime.Value(), mHoldTime.Value(), mPlaybackRate);
 
   return result;
 }
 
-TimeDuration
-Animation::StartTimeFromReadyTime(const TimeDuration& aReadyTime) const
-{
-  MOZ_ASSERT(!mHoldTime.IsNull(), "Hold time should be set in order to"
-                                  " convert a ready time to a start time");
-  if (mPlaybackRate == 0) {
-    return aReadyTime;
-  }
-  return aReadyTime - mHoldTime.Value().MultDouble(1 / mPlaybackRate);
-}
-
 TimeStamp
 Animation::AnimationTimeToTimeStamp(const StickyTimeDuration& aTime) const
 {
   // Initializes to null. Return the same object every time to benefit from
   // return-value-optimization.
   TimeStamp result;
 
   // We *don't* check for mTimeline->TracksWallclockTime() here because that
@@ -728,17 +827,17 @@ Animation::AnimationTimeToTimeStamp(cons
   // Check the time is convertible to a timestamp
   if (aTime == TimeDuration::Forever() ||
       mPlaybackRate == 0.0 ||
       mStartTime.IsNull()) {
     return result;
   }
 
   // Invert the standard relation:
-  //   animation time = (timeline time - start time) * playback rate
+  //   current time = (timeline time - start time) * playback rate
   TimeDuration timelineTime =
     TimeDuration(aTime).MultDouble(1.0 / mPlaybackRate) + mStartTime.Value();
 
   result = mTimeline->ToTimeStamp(timelineTime);
   return result;
 }
 
 TimeStamp
@@ -760,33 +859,23 @@ Animation::SilentlySetCurrentTime(const 
       !mTimeline ||
       mTimeline->GetCurrentTime().IsNull() ||
       mPlaybackRate == 0.0) {
     mHoldTime.SetValue(aSeekTime);
     if (!mTimeline || mTimeline->GetCurrentTime().IsNull()) {
       mStartTime.SetNull();
     }
   } else {
-    mStartTime.SetValue(mTimeline->GetCurrentTime().Value() -
-                          (aSeekTime.MultDouble(1 / mPlaybackRate)));
+    mStartTime = StartTimeFromTimelineTime(
+      mTimeline->GetCurrentTime().Value(), aSeekTime, mPlaybackRate);
   }
 
   mPreviousCurrentTime.SetNull();
 }
 
-void
-Animation::SilentlySetPlaybackRate(double aPlaybackRate)
-{
-  Nullable<TimeDuration> previousTime = GetCurrentTime();
-  mPlaybackRate = aPlaybackRate;
-  if (!previousTime.IsNull()) {
-    SilentlySetCurrentTime(previousTime.Value());
-  }
-}
-
 // https://drafts.csswg.org/web-animations/#cancel-an-animation
 void
 Animation::CancelNoUpdate()
 {
   if (PlayState() != AnimationPlayState::Idle) {
     ResetPendingTasks();
 
     if (mFinished) {
@@ -996,18 +1085,18 @@ Animation::ComposeStyle(ComposeAnimation
     if (pending && mHoldTime.IsNull() && !mStartTime.IsNull()) {
       Nullable<TimeDuration> timeToUse = mPendingReadyTime;
       if (timeToUse.IsNull() &&
           mTimeline &&
           mTimeline->TracksWallclockTime()) {
         timeToUse = mTimeline->ToTimelineTime(TimeStamp::Now());
       }
       if (!timeToUse.IsNull()) {
-        mHoldTime.SetValue((timeToUse.Value() - mStartTime.Value())
-                            .MultDouble(mPlaybackRate));
+        mHoldTime = CurrentTimeFromTimelineTime(
+          timeToUse.Value(), mStartTime.Value(), mPlaybackRate);
       }
     }
 
     KeyframeEffectReadOnly* keyframeEffect = mEffect->AsKeyframeEffect();
     if (keyframeEffect) {
       keyframeEffect->ComposeStyle(Forward<ComposeAnimationResult>(aComposeResult),
                                    aPropertiesToSkip);
     }
@@ -1040,48 +1129,57 @@ Animation::NotifyGeometricAnimationsStar
 // https://drafts.csswg.org/web-animations/#play-an-animation
 void
 Animation::PlayNoUpdate(ErrorResult& aRv, LimitBehavior aLimitBehavior)
 {
   AutoMutationBatchForAnimation mb(*this);
 
   bool abortedPause = mPendingState == PendingState::PausePending;
 
+  double effectivePlaybackRate = CurrentOrPendingPlaybackRate();
+
   Nullable<TimeDuration> currentTime = GetCurrentTime();
-  if (mPlaybackRate > 0.0 &&
+  if (effectivePlaybackRate > 0.0 &&
       (currentTime.IsNull() ||
        (aLimitBehavior == LimitBehavior::AutoRewind &&
         (currentTime.Value() < TimeDuration() ||
          currentTime.Value() >= EffectEnd())))) {
     mHoldTime.SetValue(TimeDuration(0));
-  } else if (mPlaybackRate < 0.0 &&
+  } else if (effectivePlaybackRate < 0.0 &&
              (currentTime.IsNull() ||
               (aLimitBehavior == LimitBehavior::AutoRewind &&
                (currentTime.Value() <= TimeDuration() ||
                 currentTime.Value() > EffectEnd())))) {
     if (EffectEnd() == TimeDuration::Forever()) {
       aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
       return;
     }
     mHoldTime.SetValue(TimeDuration(EffectEnd()));
-  } else if (mPlaybackRate == 0.0 && currentTime.IsNull()) {
+  } else if (effectivePlaybackRate == 0.0 && currentTime.IsNull()) {
     mHoldTime.SetValue(TimeDuration(0));
   }
 
   bool reuseReadyPromise = false;
   if (mPendingState != PendingState::NotPending) {
     CancelPendingTasks();
     reuseReadyPromise = true;
   }
 
-  // If the hold time is null then we're either already playing normally (and
-  // we can ignore this call) or we aborted a pending pause operation (in which
-  // case, for consistency, we need to go through the motions of doing an
-  // asynchronous start even though we already have a resolved start time).
-  if (mHoldTime.IsNull() && !abortedPause) {
+  // If the hold time is null then we're already playing normally and,
+  // typically, we can bail out here.
+  //
+  // However, there are two cases where we can't do that:
+  //
+  // (a) If we just aborted a pause. In this case, for consistency, we need to
+  //     go through the motions of doing an asynchronous start.
+  //
+  // (b) If we have timing changes (specifically a change to the playbackRate)
+  //     that should be applied asynchronously.
+  //
+  if (mHoldTime.IsNull() && !abortedPause && !mPendingPlaybackRate) {
     return;
   }
 
   // Clear the start time until we resolve a new one. We do this except
   // for the case where we are aborting a pause and don't have a hold time.
   //
   // If we're aborting a pause and *do* have a hold time (e.g. because
   // the animation is finished or we just applied the auto-rewind behavior
@@ -1168,55 +1266,79 @@ Animation::PauseNoUpdate(ErrorResult& aR
   }
 
   UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
   if (IsRelevant()) {
     nsNodeUtils::AnimationChanged(this);
   }
 }
 
+// https://drafts.csswg.org/web-animations/#play-an-animation
 void
 Animation::ResumeAt(const TimeDuration& aReadyTime)
 {
   // This method is only expected to be called for an animation that is
   // waiting to play. We can easily adapt it to handle other states
   // but it's currently not necessary.
   MOZ_ASSERT(mPendingState == PendingState::PlayPending,
              "Expected to resume a play-pending animation");
   MOZ_ASSERT(!mHoldTime.IsNull() || !mStartTime.IsNull(),
              "An animation in the play-pending state should have either a"
              " resolved hold time or resolved start time");
 
-  // If we aborted a pending pause operation we will already have a start time
-  // we should use. In all other cases, we resolve it from the ready time.
-  if (mStartTime.IsNull()) {
-    mStartTime = StartTimeFromReadyTime(aReadyTime);
+  AutoMutationBatchForAnimation mb(*this);
+  bool hadPendingPlaybackRate = mPendingPlaybackRate.isSome();
+
+  if (!mHoldTime.IsNull()) {
+    // The hold time is set, so we don't need any special handling to preserve
+    // the current time.
+    ApplyPendingPlaybackRate();
+    mStartTime =
+      StartTimeFromTimelineTime(aReadyTime, mHoldTime.Value(), mPlaybackRate);
     if (mPlaybackRate != 0) {
       mHoldTime.SetNull();
     }
+  } else if (!mStartTime.IsNull() && mPendingPlaybackRate) {
+    // Apply any pending playback rate, preserving the current time.
+    TimeDuration currentTimeToMatch = CurrentTimeFromTimelineTime(
+      aReadyTime, mStartTime.Value(), mPlaybackRate);
+    ApplyPendingPlaybackRate();
+    mStartTime =
+      StartTimeFromTimelineTime(aReadyTime, currentTimeToMatch, mPlaybackRate);
+    if (mPlaybackRate == 0) {
+      mHoldTime.SetValue(currentTimeToMatch);
+    }
   }
+
   mPendingState = PendingState::NotPending;
 
   UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
 
+  // If we had a pending playback rate, we will have now applied it so we need
+  // to notify observers.
+  if (hadPendingPlaybackRate && IsRelevant()) {
+    nsNodeUtils::AnimationChanged(this);
+  }
+
   if (mReady) {
     mReady->MaybeResolve(this);
   }
 }
 
 void
 Animation::PauseAt(const TimeDuration& aReadyTime)
 {
   MOZ_ASSERT(mPendingState == PendingState::PausePending,
              "Expected to pause a pause-pending animation");
 
   if (!mStartTime.IsNull() && mHoldTime.IsNull()) {
-    mHoldTime.SetValue((aReadyTime - mStartTime.Value())
-                        .MultDouble(mPlaybackRate));
+    mHoldTime = CurrentTimeFromTimelineTime(
+      aReadyTime, mStartTime.Value(), mPlaybackRate);
   }
+  ApplyPendingPlaybackRate();
   mStartTime.SetNull();
   mPendingState = PendingState::NotPending;
 
   UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
 
   if (mReady) {
     mReady->MaybeResolve(this);
   }
@@ -1266,18 +1388,20 @@ Animation::UpdateFinishedState(SeekFlag 
       } else {
         mHoldTime.SetValue(0);
       }
     } else if (mPlaybackRate != 0.0 &&
                !currentTime.IsNull() &&
                mTimeline &&
                !mTimeline->GetCurrentTime().IsNull()) {
       if (aSeekFlag == SeekFlag::DidSeek && !mHoldTime.IsNull()) {
-        mStartTime.SetValue(mTimeline->GetCurrentTime().Value() -
-                             (mHoldTime.Value().MultDouble(1 / mPlaybackRate)));
+        mStartTime =
+          StartTimeFromTimelineTime(mTimeline->GetCurrentTime().Value(),
+                                    mHoldTime.Value(),
+                                    mPlaybackRate);
       }
       mHoldTime.SetNull();
     }
   }
 
   bool currentFinishedState = PlayState() == AnimationPlayState::Finished;
   if (currentFinishedState && !mFinishedIsResolved) {
     DoFinishNotification(aSyncNotifyFlag);
@@ -1352,16 +1476,18 @@ Animation::CancelPendingTasks()
 void
 Animation::ResetPendingTasks()
 {
   if (mPendingState == PendingState::NotPending) {
     return;
   }
 
   CancelPendingTasks();
+  ApplyPendingPlaybackRate();
+
   if (mReady) {
     mReady->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
     mReady = nullptr;
   }
 }
 
 bool
 Animation::IsPossiblyOrphanedPendingAnimation() const
--- a/dom/animation/Animation.h
+++ b/dom/animation/Animation.h
@@ -100,29 +100,32 @@ public:
   void GetId(nsAString& aResult) const { aResult = mId; }
   void SetId(const nsAString& aId);
   AnimationEffectReadOnly* GetEffect() const { return mEffect; }
   void SetEffect(AnimationEffectReadOnly* aEffect);
   AnimationTimeline* GetTimeline() const { return mTimeline; }
   void SetTimeline(AnimationTimeline* aTimeline);
   Nullable<TimeDuration> GetStartTime() const { return mStartTime; }
   void SetStartTime(const Nullable<TimeDuration>& aNewStartTime);
-  Nullable<TimeDuration> GetCurrentTime() const;
+  Nullable<TimeDuration> GetCurrentTime() const {
+    return GetCurrentTimeForHoldTime(mHoldTime);
+  }
   void SetCurrentTime(const TimeDuration& aNewCurrentTime);
   double PlaybackRate() const { return mPlaybackRate; }
   void SetPlaybackRate(double aPlaybackRate);
   AnimationPlayState PlayState() const;
   bool Pending() const { return mPendingState != PendingState::NotPending; }
   virtual Promise* GetReady(ErrorResult& aRv);
   Promise* GetFinished(ErrorResult& aRv);
   void Cancel();
   void Finish(ErrorResult& aRv);
   virtual void Play(ErrorResult& aRv, LimitBehavior aLimitBehavior);
   virtual void Pause(ErrorResult& aRv);
   void Reverse(ErrorResult& aRv);
+  void UpdatePlaybackRate(double aPlaybackRate);
   bool IsRunningOnCompositor() const;
   IMPL_EVENT_HANDLER(finish);
   IMPL_EVENT_HANDLER(cancel);
 
   // Wrapper functions for Animation DOM methods when called
   // from script.
   //
   // We often use the same methods internally and from script but when called
@@ -237,21 +240,64 @@ public:
    *
    * This method returns the start time, if resolved. Otherwise, if we have
    * a pending ready time, it returns the corresponding start time. If neither
    * of those are available, it returns null.
    */
   Nullable<TimeDuration> GetCurrentOrPendingStartTime() const;
 
   /**
-   * Calculates the corresponding start time to use for an animation that is
-   * currently pending with current time |mHoldTime| but should behave
-   * as if it began or resumed playback at timeline time |aReadyTime|.
+   * As with the start time, we should use the pending playback rate when
+   * producing layer animations.
+   */
+  double CurrentOrPendingPlaybackRate() const
+  {
+    return mPendingPlaybackRate.valueOr(mPlaybackRate);
+  }
+  bool HasPendingPlaybackRate() const { return mPendingPlaybackRate.isSome(); }
+
+  /**
+   * The following relationship from the definition of the 'current time' is
+   * re-used in many algorithms so we extract it here into a static method that
+   * can be re-used:
+   *
+   *   current time = (timeline time - start time) * playback rate
+   *
+   * As per https://drafts.csswg.org/web-animations-1/#current-time
    */
-  TimeDuration StartTimeFromReadyTime(const TimeDuration& aReadyTime) const;
+  static TimeDuration CurrentTimeFromTimelineTime(
+    const TimeDuration& aTimelineTime,
+    const TimeDuration& aStartTime,
+    float aPlaybackRate)
+  {
+    return (aTimelineTime - aStartTime).MultDouble(aPlaybackRate);
+  }
+
+  /**
+   * As with calculating the current time, we often need to calculate a start
+   * time from a current time. The following method simply inverts the current
+   * time relationship.
+   *
+   * In each case where this is used, the desired behavior for playbackRate ==
+   * 0 is to return the specified timeline time (often referred to as the ready
+   * time).
+   */
+  static TimeDuration StartTimeFromTimelineTime(
+    const TimeDuration& aTimelineTime,
+    const TimeDuration& aCurrentTime,
+    float aPlaybackRate)
+  {
+    TimeDuration result = aTimelineTime;
+    if (aPlaybackRate == 0) {
+      return result;
+    }
+
+    result -= aCurrentTime.MultDouble(1.0 / aPlaybackRate);
+    return result;
+  }
 
   /**
    * Converts a time in the timescale of this Animation's currentTime, to a
    * TimeStamp. Returns a null TimeStamp if the conversion cannot be performed
    * because of the current state of this Animation (e.g. it has no timeline, a
    * zero playbackRate, an unresolved start time etc.) or the value of the time
    * passed-in (e.g. an infinite time).
    */
@@ -351,32 +397,38 @@ public:
    * We need to do this synchronously because after a CSS animation/transition
    * is canceled, it will be released by its owning element and may not still
    * exist when we would normally go to queue events on the next tick.
    */
   virtual void MaybeQueueCancelEvent(const StickyTimeDuration& aActiveTime) {};
 
 protected:
   void SilentlySetCurrentTime(const TimeDuration& aNewCurrentTime);
-  void SilentlySetPlaybackRate(double aPlaybackRate);
   void CancelNoUpdate();
   void PlayNoUpdate(ErrorResult& aRv, LimitBehavior aLimitBehavior);
   void PauseNoUpdate(ErrorResult& aRv);
   void ResumeAt(const TimeDuration& aReadyTime);
   void PauseAt(const TimeDuration& aReadyTime);
   void FinishPendingAt(const TimeDuration& aReadyTime)
   {
     if (mPendingState == PendingState::PlayPending) {
       ResumeAt(aReadyTime);
     } else if (mPendingState == PendingState::PausePending) {
       PauseAt(aReadyTime);
     } else {
       NS_NOTREACHED("Can't finish pending if we're not in a pending state");
     }
   }
+  void ApplyPendingPlaybackRate()
+  {
+    if (mPendingPlaybackRate) {
+      mPlaybackRate = *mPendingPlaybackRate;
+      mPendingPlaybackRate.reset();
+    }
+  }
 
   /**
    * Finishing behavior depends on if changes to timing occurred due
    * to a seek or regular playback.
    */
   enum class SeekFlag {
     NoSeek,
     DidSeek
@@ -415,34 +467,47 @@ protected:
 
   /**
    * Returns true if this animation is not only play-pending, but has
    * yet to be given a pending ready time. This roughly corresponds to
    * animations that are waiting to be painted (since we set the pending
    * ready time at the end of painting). Identifying such animations is
    * useful because in some cases animations that are painted together
    * may need to be synchronized.
+   *
+   * We don't, however, want to include animations with a fixed start time such
+   * as animations that are simply having their playbackRate updated or which
+   * are resuming from an aborted pause.
    */
   bool IsNewlyStarted() const {
     return mPendingState == PendingState::PlayPending &&
-           mPendingReadyTime.IsNull();
+           mPendingReadyTime.IsNull() &&
+           mStartTime.IsNull();
   }
   bool IsPossiblyOrphanedPendingAnimation() const;
   StickyTimeDuration EffectEnd() const;
 
+  Nullable<TimeDuration> GetCurrentTimeForHoldTime(
+    const Nullable<TimeDuration>& aHoldTime) const;
+  Nullable<TimeDuration> GetUnconstrainedCurrentTime() const
+  {
+    return GetCurrentTimeForHoldTime(Nullable<TimeDuration>());
+  }
+
   nsIDocument* GetRenderedDocument() const;
 
   RefPtr<AnimationTimeline> mTimeline;
   RefPtr<AnimationEffectReadOnly> mEffect;
   // The beginning of the delay period.
   Nullable<TimeDuration> mStartTime; // Timeline timescale
   Nullable<TimeDuration> mHoldTime;  // Animation timescale
   Nullable<TimeDuration> mPendingReadyTime; // Timeline timescale
   Nullable<TimeDuration> mPreviousCurrentTime; // Animation timescale
   double mPlaybackRate;
+  Maybe<double> mPendingPlaybackRate;
 
   // A Promise that is replaced on each call to Play()
   // and fulfilled when Play() is successfully completed.
   // This object is lazily created by GetReady.
   // See http://drafts.csswg.org/web-animations/#current-ready-promise
   RefPtr<Promise> mReady;
 
   // A Promise that is resolved when we reach the end of the effect, or
--- a/dom/base/ShadowRoot.cpp
+++ b/dom/base/ShadowRoot.cpp
@@ -476,35 +476,16 @@ ShadowRoot::Host()
 {
   nsIContent* host = GetHost();
   MOZ_ASSERT(host && host->IsElement(),
              "ShadowRoot host should always be an element, "
              "how else did we create this ShadowRoot?");
   return host->AsElement();
 }
 
-bool
-ShadowRoot::ApplyAuthorStyles()
-{
-  return mProtoBinding->InheritsStyle();
-}
-
-void
-ShadowRoot::SetApplyAuthorStyles(bool aApplyAuthorStyles)
-{
-  mProtoBinding->SetInheritsStyle(aApplyAuthorStyles);
-
-  nsIPresShell* shell = OwnerDoc()->GetShell();
-  if (shell) {
-    OwnerDoc()->BeginUpdate(UPDATE_STYLE);
-    shell->RecordShadowStyleChange(this);
-    OwnerDoc()->EndUpdate(UPDATE_STYLE);
-  }
-}
-
 void
 ShadowRoot::AttributeChanged(nsIDocument* aDocument,
                              Element* aElement,
                              int32_t aNameSpaceID,
                              nsAtom* aAttribute,
                              int32_t aModType,
                              const nsAttrValue* aOldValue)
 {
--- a/dom/base/ShadowRoot.h
+++ b/dom/base/ShadowRoot.h
@@ -73,18 +73,16 @@ public:
   bool IsClosed() const
   {
     return mMode == ShadowRootMode::Closed;
   }
 
   // [deprecated] Shadow DOM v0
   void InsertSheet(StyleSheet* aSheet, nsIContent* aLinkingContent);
   void RemoveSheet(StyleSheet* aSheet);
-  bool ApplyAuthorStyles();
-  void SetApplyAuthorStyles(bool aApplyAuthorStyles);
   StyleSheetList* StyleSheets()
   {
     return &DocumentOrShadowRoot::EnsureDOMStyleSheets();
   }
 
   /**
    * Distributes all the explicit children of the pool host to the content
    * insertion points in this ShadowRoot.
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -54,17 +54,16 @@
 
 #include "mozilla/AsyncEventDispatcher.h"
 #include "mozilla/BasicEvents.h"
 #include "mozilla/EventListenerManager.h"
 #include "mozilla/EventStateManager.h"
 
 #include "mozilla/dom/Attr.h"
 #include "mozilla/dom/BindingDeclarations.h"
-#include "nsIDOMDocumentXBL.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/FramingChecker.h"
 #include "nsGenericHTMLElement.h"
 #include "mozilla/dom/CDATASection.h"
 #include "mozilla/dom/ProcessingInstruction.h"
 #include "nsDOMString.h"
 #include "nsNodeUtils.h"
 #include "nsLayoutUtils.h" // for GetFrameForPoint
@@ -1797,17 +1796,16 @@ nsDocument::~nsDocument()
 NS_INTERFACE_TABLE_HEAD(nsDocument)
   NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
   NS_INTERFACE_TABLE_BEGIN
     NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsDocument, nsISupports, nsINode)
     NS_INTERFACE_TABLE_ENTRY(nsDocument, nsINode)
     NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIDocument)
     NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIDOMDocument)
     NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIDOMNode)
-    NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIDOMDocumentXBL)
     NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIScriptObjectPrincipal)
     NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIDOMEventTarget)
     NS_INTERFACE_TABLE_ENTRY(nsDocument, mozilla::dom::EventTarget)
     NS_INTERFACE_TABLE_ENTRY(nsDocument, nsISupportsWeakReference)
     NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIRadioGroupContainer)
     NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIMutationObserver)
     NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIApplicationCacheContainer)
   NS_INTERFACE_TABLE_END
@@ -6209,65 +6207,28 @@ nsIDocument::ImportNode(nsINode& aNode, 
       NS_WARNING("Don't know how to clone this nodetype for importNode.");
     }
   }
 
   rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
   return nullptr;
 }
 
-NS_IMETHODIMP
-nsDocument::LoadBindingDocument(const nsAString& aURI)
-{
-  ErrorResult rv;
-  nsIDocument::LoadBindingDocument(aURI,
-                                   nsContentUtils::GetCurrentJSContext()
-                                     ? Some(nsContentUtils::SubjectPrincipal())
-                                     : Nothing(),
-                                   rv);
-  return rv.StealNSResult();
-}
-
 void
 nsIDocument::LoadBindingDocument(const nsAString& aURI,
                                  nsIPrincipal& aSubjectPrincipal,
                                  ErrorResult& rv)
 {
-  LoadBindingDocument(aURI, Some(&aSubjectPrincipal), rv);
-}
-
-void
-nsIDocument::LoadBindingDocument(const nsAString& aURI,
-                                 const Maybe<nsIPrincipal*>& aSubjectPrincipal,
-                                 ErrorResult& rv)
-{
   nsCOMPtr<nsIURI> uri;
   rv = NS_NewURI(getter_AddRefs(uri), aURI, mCharacterSet, GetDocBaseURI());
   if (rv.Failed()) {
     return;
   }
 
-  // Note - This computation of subjectPrincipal isn't necessarily sensical.
-  // It's just designed to preserve the old semantics during a mass-conversion
-  // patch.
-  nsCOMPtr<nsIPrincipal> subjectPrincipal =
-    aSubjectPrincipal.isSome() ? aSubjectPrincipal.value() : NodePrincipal();
-  BindingManager()->LoadBindingDocument(this, uri, subjectPrincipal);
-}
-
-NS_IMETHODIMP
-nsDocument::GetBindingParent(nsIDOMNode* aNode, nsIDOMElement** aResult)
-{
-  nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
-  NS_ENSURE_ARG_POINTER(node);
-
-  Element* bindingParent = nsIDocument::GetBindingParent(*node);
-  nsCOMPtr<nsIDOMElement> retval = do_QueryInterface(bindingParent);
-  retval.forget(aResult);
-  return NS_OK;
+  BindingManager()->LoadBindingDocument(this, uri, &aSubjectPrincipal);
 }
 
 Element*
 nsIDocument::GetBindingParent(nsINode& aNode)
 {
   nsCOMPtr<nsIContent> content(do_QueryInterface(&aNode));
   if (!content)
     return nullptr;
@@ -6328,54 +6289,26 @@ nsDocument::GetAnonymousElementByAttribu
                             universalMatch);
     if (matchedElm)
       return matchedElm;
   }
 
   return nullptr;
 }
 
-NS_IMETHODIMP
-nsDocument::GetAnonymousElementByAttribute(nsIDOMElement* aElement,
-                                           const nsAString& aAttrName,
-                                           const nsAString& aAttrValue,
-                                           nsIDOMElement** aResult)
-{
-  nsCOMPtr<Element> element = do_QueryInterface(aElement);
-  NS_ENSURE_ARG_POINTER(element);
-
-  Element* anonEl =
-    nsIDocument::GetAnonymousElementByAttribute(*element, aAttrName,
-                                                aAttrValue);
-  nsCOMPtr<nsIDOMElement> retval = do_QueryInterface(anonEl);
-  retval.forget(aResult);
-  return NS_OK;
-}
-
 Element*
 nsIDocument::GetAnonymousElementByAttribute(Element& aElement,
                                             const nsAString& aAttrName,
                                             const nsAString& aAttrValue)
 {
   RefPtr<nsAtom> attribute = NS_Atomize(aAttrName);
 
   return GetAnonymousElementByAttribute(&aElement, attribute, aAttrValue);
 }
 
-
-NS_IMETHODIMP
-nsDocument::GetAnonymousNodes(nsIDOMElement* aElement,
-                              nsIDOMNodeList** aResult)
-{
-  *aResult = nullptr;
-
-  nsCOMPtr<nsIContent> content(do_QueryInterface(aElement));
-  return BindingManager()->GetAnonymousNodesFor(content, aResult);
-}
-
 nsINodeList*
 nsIDocument::GetAnonymousNodes(Element& aElement)
 {
   return BindingManager()->GetAnonymousNodesFor(&aElement);
 }
 
 already_AddRefed<nsRange>
 nsIDocument::CreateRange(ErrorResult& rv)
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -16,17 +16,16 @@
 #include "nsCOMPtr.h"
 #include "nsAutoPtr.h"
 #include "nsCRT.h"
 #include "nsWeakReference.h"
 #include "nsWeakPtr.h"
 #include "nsTArray.h"
 #include "nsIdentifierMapEntry.h"
 #include "nsIDOMDocument.h"
-#include "nsIDOMDocumentXBL.h"
 #include "nsStubDocumentObserver.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsIContent.h"
 #include "nsIPrincipal.h"
 #include "nsIParser.h"
 #include "nsBindingManager.h"
 #include "nsRefPtrHashtable.h"
 #include "nsJSThingHashtable.h"
@@ -317,17 +316,16 @@ protected:
 };
 
 // For classifying a flash document based on its principal.
 class PrincipalFlashClassifier;
 
 // Base class for our document implementations.
 class nsDocument : public nsIDocument,
                    public nsIDOMDocument,
-                   public nsIDOMDocumentXBL,
                    public nsSupportsWeakReference,
                    public nsIScriptObjectPrincipal,
                    public nsIRadioGroupContainer,
                    public nsIApplicationCacheContainer,
                    public nsStubMutationObserver
 {
   friend class nsIDocument;
 
@@ -631,19 +629,16 @@ public:
 private:
   void AddOnDemandBuiltInUASheet(mozilla::StyleSheet* aSheet);
   void SendToConsole(nsCOMArray<nsISecurityConsoleMessage>& aMessages);
 
 public:
   // nsIDOMDocument
   NS_DECL_NSIDOMDOCUMENT
 
-  // nsIDOMDocumentXBL
-  NS_DECL_NSIDOMDOCUMENTXBL
-
   using mozilla::dom::DocumentOrShadowRoot::GetElementById;
   using mozilla::dom::DocumentOrShadowRoot::GetElementsByTagName;
   using mozilla::dom::DocumentOrShadowRoot::GetElementsByTagNameNS;
   using mozilla::dom::DocumentOrShadowRoot::GetElementsByClassName;
 
   // nsIDOMEventTarget
   virtual nsresult GetEventTargetParent(
                      mozilla::EventChainPreVisitor& aVisitor) override;
--- a/dom/base/nsGlobalWindowInner.cpp
+++ b/dom/base/nsGlobalWindowInner.cpp
@@ -103,17 +103,16 @@
 #include "nsIFrame.h"
 #include "nsCanvasFrame.h"
 #include "nsIWidget.h"
 #include "nsIWidgetListener.h"
 #include "nsIBaseWindow.h"
 #include "nsIDeviceSensors.h"
 #include "nsIContent.h"
 #include "nsIDocShell.h"
-#include "nsIDocCharset.h"
 #include "nsIDocument.h"
 #include "Crypto.h"
 #include "nsIDOMDocument.h"
 #include "nsIDOMElement.h"
 #include "nsIDOMEvent.h"
 #include "nsIDOMOfflineResourceList.h"
 #include "nsDOMString.h"
 #include "nsIEmbeddingSiteWindow.h"
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -101,17 +101,16 @@
 #include "nsIFrame.h"
 #include "nsCanvasFrame.h"
 #include "nsIWidget.h"
 #include "nsIWidgetListener.h"
 #include "nsIBaseWindow.h"
 #include "nsIDeviceSensors.h"
 #include "nsIContent.h"
 #include "nsIDocShell.h"
-#include "nsIDocCharset.h"
 #include "nsIDocument.h"
 #include "Crypto.h"
 #include "nsIDOMDocument.h"
 #include "nsIDOMElement.h"
 #include "nsIDOMEvent.h"
 #include "nsIDOMOfflineResourceList.h"
 #include "nsDOMString.h"
 #include "nsIEmbeddingSiteWindow.h"
@@ -6903,25 +6902,17 @@ nsGlobalWindowOuter::GetComputedStyleHel
 //*****************************************************************************
 
 nsresult
 nsGlobalWindowOuter::GetInterfaceInternal(const nsIID& aIID, void** aSink)
 {
   NS_ENSURE_ARG_POINTER(aSink);
   *aSink = nullptr;
 
-  if (aIID.Equals(NS_GET_IID(nsIDocCharset))) {
-    nsGlobalWindowOuter* outer = GetOuterWindowInternal();
-    NS_ENSURE_TRUE(outer, NS_ERROR_NOT_INITIALIZED);
-
-    NS_WARNING("Using deprecated nsIDocCharset: use nsIDocShell.GetCharset() instead ");
-    nsCOMPtr<nsIDocCharset> docCharset(do_QueryInterface(outer->mDocShell));
-    docCharset.forget(aSink);
-  }
-  else if (aIID.Equals(NS_GET_IID(nsIWebNavigation))) {
+  if (aIID.Equals(NS_GET_IID(nsIWebNavigation))) {
     nsGlobalWindowOuter* outer = GetOuterWindowInternal();
     NS_ENSURE_TRUE(outer, NS_ERROR_NOT_INITIALIZED);
 
     nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(outer->mDocShell));
     webNav.forget(aSink);
   }
   else if (aIID.Equals(NS_GET_IID(nsIDocShell))) {
     nsGlobalWindowOuter* outer = GetOuterWindowInternal();
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -2086,19 +2086,16 @@ public:
   /**
    * Check whether we've ever fired a DOMTitleChanged event for this
    * document.
    */
   bool HaveFiredDOMTitleChange() const {
     return mHaveFiredTitleChange;
   }
 
-  /**
-   * See GetAnonymousElementByAttribute on nsIDOMDocumentXBL.
-   */
   virtual Element*
     GetAnonymousElementByAttribute(nsIContent* aElement,
                                    nsAtom* aAttrName,
                                    const nsAString& aAttrValue) const = 0;
 
   /**
    * Helper for nsIDOMDocument::elementFromPoint implementation that allows
    * ignoring the scroll frame and/or avoiding layout flushes.
@@ -2983,19 +2980,16 @@ public:
   nsINodeList* GetAnonymousNodes(Element& aElement);
   Element* GetAnonymousElementByAttribute(Element& aElement,
                                           const nsAString& aAttrName,
                                           const nsAString& aAttrValue);
   Element* GetBindingParent(nsINode& aNode);
   void LoadBindingDocument(const nsAString& aURI,
                            nsIPrincipal& aSubjectPrincipal,
                            mozilla::ErrorResult& rv);
-  void LoadBindingDocument(const nsAString& aURI,
-                           const mozilla::Maybe<nsIPrincipal*>& aSubjectPrincipal,
-                           mozilla::ErrorResult& rv);
   mozilla::dom::XPathExpression*
     CreateExpression(const nsAString& aExpression,
                      mozilla::dom::XPathNSResolver* aResolver,
                      mozilla::ErrorResult& rv);
   nsINode* CreateNSResolver(nsINode& aNodeResolver);
   already_AddRefed<mozilla::dom::XPathResult>
     Evaluate(JSContext* aCx, const nsAString& aExpression, nsINode& aContextNode,
              mozilla::dom::XPathNSResolver* aResolver, uint16_t aType,
deleted file mode 100644
--- a/dom/interfaces/xbl/moz.build
+++ /dev/null
@@ -1,15 +0,0 @@
-# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# 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/.
-
-with Files("**"):
-    BUG_COMPONENT = ("Core", "XBL")
-
-XPIDL_SOURCES += [
-    'nsIDOMDocumentXBL.idl',
-]
-
-XPIDL_MODULE = 'dom_xbl'
-
deleted file mode 100644
--- a/dom/interfaces/xbl/nsIDOMDocumentXBL.idl
+++ /dev/null
@@ -1,21 +0,0 @@
-/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "domstubs.idl"
-
-[uuid(af628000-e3fa-40d2-9118-fbaa9f3ec6b9)]
-interface nsIDOMDocumentXBL : nsISupports
-{
-  /**
-   * See the MDC documentation for what these methods do.
-   */
-  nsIDOMNodeList        getAnonymousNodes(in nsIDOMElement elt);
-  nsIDOMElement         getAnonymousElementByAttribute(in nsIDOMElement elt,
-                                                       in DOMString attrName,
-                                                       in DOMString attrValue);
-
-  nsIDOMElement         getBindingParent(in nsIDOMNode node);
-  void                  loadBindingDocument(in DOMString documentURL);
-};
--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -1196,19 +1196,16 @@ class RTCPeerConnection {
     let transceiver =
       this._transceivers.find(transceiver => transceiver.sender == sender);
 
     // If the transceiver was removed due to rollback, let it slide.
     if (!transceiver || !sender.track) {
       return;
     }
 
-    // TODO(bug 1401983): Move to TransceiverImpl?
-    this._impl.removeTrack(sender.track);
-
     sender.setTrack(null);
     if (transceiver.direction == "sendrecv") {
       transceiver.setDirectionInternal("recvonly");
     } else if (transceiver.direction == "sendonly") {
       transceiver.setDirectionInternal("inactive");
     }
 
     transceiver.sync();
@@ -1371,18 +1368,17 @@ class RTCPeerConnection {
   _insertDTMF(transceiverImpl, tones, duration, interToneGap) {
     return this._impl.insertDTMF(transceiverImpl, tones, duration, interToneGap);
   }
 
   _getDTMFToneBuffer(sender) {
     return this._impl.getDTMFToneBuffer(sender.__DOM_IMPL__);
   }
 
-  _replaceTrack(transceiverImpl, withTrack) {
-    this._checkClosed();
+  _replaceTrackNoRenegotiation(transceiverImpl, withTrack) {
     this._impl.replaceTrackNoRenegotiation(transceiverImpl, withTrack);
   }
 
   close() {
     if (this._closed) {
       return;
     }
     this._closed = true;
@@ -1960,17 +1956,17 @@ class RTCRtpSender {
       throw new this._pc._win.DOMException(
           "Cannot replaceTrack with a different kind!",
           "TypeError");
     }
 
     // Updates the track on the MediaPipeline; this is needed whether or not
     // we've associated this transceiver, the spec language notwithstanding.
     // Synchronous, and will throw on failure.
-    this._pc._replaceTrack(this._transceiverImpl, withTrack);
+    this._pc._replaceTrackNoRenegotiation(this._transceiverImpl, withTrack);
 
     let setTrack = () => {
       this.track = withTrack;
       this._transceiver.sync();
     };
 
     // Spec is a little weird here; we only queue if the transceiver was
     // associated, otherwise we update the track synchronously.
@@ -2034,16 +2030,17 @@ class RTCRtpSender {
     this._streams = streams;
   }
 
   getStreams() {
     return this._streams;
   }
 
   setTrack(track) {
+    this._pc._replaceTrackNoRenegotiation(this._transceiverImpl, track);
     this.track = track;
   }
 
   getStats() {
     return this._pc._async(
       async () => this._pc._getStats(this.track));
   }
 
--- a/dom/media/mp4/MP4Metadata.cpp
+++ b/dom/media/mp4/MP4Metadata.cpp
@@ -103,20 +103,16 @@ MP4Metadata::MP4Metadata(ByteStream* aSo
   : mSource(aSource)
   , mSourceAdaptor(aSource)
 {
   DDLINKCHILD("source", aSource);
 
   Mp4parseIo io = { read_source, &mSourceAdaptor };
   mParser.reset(mp4parse_new(&io));
   MOZ_ASSERT(mParser);
-
-  if (MOZ_LOG_TEST(gMP4MetadataLog, LogLevel::Debug)) {
-    mp4parse_log(true);
-  }
 }
 
 MP4Metadata::~MP4Metadata()
 {
 }
 
 nsresult
 MP4Metadata::Parse()
@@ -251,16 +247,17 @@ MP4Metadata::GetTrackInfo(mozilla::Track
   }
 #ifdef DEBUG
   const char* codec_string = "unrecognized";
   switch (info.codec) {
     case MP4PARSE_CODEC_UNKNOWN: codec_string = "unknown"; break;
     case MP4PARSE_CODEC_AAC: codec_string = "aac"; break;
     case MP4PARSE_CODEC_OPUS: codec_string = "opus"; break;
     case MP4PARSE_CODEC_FLAC: codec_string = "flac"; break;
+    case MP4PARSE_CODEC_ALAC: codec_string = "alac"; break;
     case MP4PARSE_CODEC_AVC: codec_string = "h.264"; break;
     case MP4PARSE_CODEC_VP9: codec_string = "vp9"; break;
     case MP4PARSE_CODEC_MP3: codec_string = "mp3"; break;
     case MP4PARSE_CODEC_MP4V: codec_string = "mp4v"; break;
     case MP4PARSE_CODEC_JPEG: codec_string = "jpeg"; break;
     case MP4PARSE_CODEC_AC3: codec_string = "ac-3"; break;
     case MP4PARSE_CODEC_EC3: codec_string = "ec-3"; break;
   }
--- a/dom/media/tests/mochitest/test_peerConnection_replaceTrack.html
+++ b/dom/media/tests/mochitest/test_peerConnection_replaceTrack.html
@@ -34,18 +34,17 @@
     ok(sender, "We have a sender for video");
     ok(allLocalStreamsHaveSender(pc),
        "Shouldn't have any local streams without a corresponding sender");
     ok(allRemoteStreamsHaveReceiver(pc),
        "Shouldn't have any remote streams without a corresponding receiver");
 
     var newTrack;
     var audiotrack;
-    // Remove fake constraints with Bug 1431056
-    return getUserMedia({fake:true, video:true, audio:true})
+    return getUserMedia({video:true, audio:true})
       .then(newStream => {
         window.grip = newStream;
         newTrack = newStream.getVideoTracks()[0];
         audiotrack = newStream.getAudioTracks()[0];
         isnot(newTrack, sender.track, "replacing with a different track");
         ok(!pc.getLocalStreams().some(s => s == newStream),
            "from a different stream");
         // Use wrapper function, since it updates expected tracks
@@ -65,18 +64,17 @@
                 e => is(e.name, "TypeError",
                         "replacing with different kind should fail"));
       });
   }
 
   runNetworkTest(function () {
     test = new PeerConnectionTest();
     test.audioCtx = new AudioContext();
-    // Remove fake constraints with Bug 1431056
-    test.setMediaConstraints([{fake: true, video: true, audio: true}], [{fake: true, video: true}]);
+    test.setMediaConstraints([{video: true, audio: true}], [{video: true}]);
     test.chain.removeAfter("PC_REMOTE_WAIT_FOR_MEDIA_FLOW");
 
     // Test replaceTrack on pcRemote separately since it's video only.
     test.chain.append([
       function PC_REMOTE_VIDEOONLY_REPLACE_VIDEOTRACK(test) {
         return replacetest(test.pcRemote);
       },
       function PC_LOCAL_NEW_VIDEOTRACK_WAIT_FOR_MEDIA_FLOW(test) {
--- a/dom/media/tests/mochitest/test_peerConnection_transceivers.html
+++ b/dom/media/tests/mochitest/test_peerConnection_transceivers.html
@@ -780,16 +780,35 @@
         {currentDirection: "sendrecv"}
       ]);
 
     pc1.close();
     pc2.close();
     stopTracks(stream);
   };
 
+  let checkAddTrackExistingTransceiverThenRemove = async () => {
+    let pc = new RTCPeerConnection();
+    pc.addTransceiver("audio");
+    let stream = await getUserMedia({audio: true});
+    let audio = stream.getAudioTracks()[0];
+    let sender = pc.addTrack(audio, stream);
+    pc.removeTrack(sender);
+
+    // Cause transceiver to be associated
+    await pc.setLocalDescription(await pc.createOffer());
+
+    // Make sure add/remove works still
+    sender = pc.addTrack(audio, stream);
+    pc.removeTrack(sender);
+
+    pc.close();
+    stopTracks(stream);
+  };
+
   let checkRemoveTrackNegotiation = async () => {
     let pc1 = new RTCPeerConnection();
     let pc2 = new RTCPeerConnection();
     let stream = await getUserMedia({audio: true, video: true});
     let audio = stream.getAudioTracks()[0];
     pc1.addTrack(audio, stream);
     let video = stream.getVideoTracks()[0];
     pc1.addTrack(video, stream);
@@ -2193,16 +2212,17 @@
     await checkSendrecvWithTracklessStream();
     await checkAddTransceiverNoTrackDoesntPair();
     await checkAddTransceiverWithTrackDoesntPair();
     await checkAddTransceiverThenReplaceTrackDoesntPair();
     await checkAddTransceiverThenAddTrackPairs();
     await checkAddTrackPairs();
     await checkReplaceTrackNullDoesntPreventPairing();
     await checkRemoveAndReadd();
+    await checkAddTrackExistingTransceiverThenRemove();
     await checkRemoveTrackNegotiation();
     await checkMute();
     await checkOnAddStream();
     await checkStop();
     await checkStopAfterCreateOffer();
     await checkStopAfterSetLocalOffer();
     await checkStopAfterSetRemoteOffer();
     await checkStopAfterCreateAnswer();
--- a/dom/moz.build
+++ b/dom/moz.build
@@ -16,17 +16,16 @@ JAR_MANIFESTS += ['jar.mn']
 interfaces = [
     'base',
     'canvas',
     'core',
     'html',
     'events',
     'sidebar',
     'range',
-    'xbl',
     'xul',
     'security',
     'storage',
     'offline',
     'geolocation',
     'notification',
     'push',
     'payments',
--- a/dom/tests/mochitest/webcomponents/test_shadowroot_style.html
+++ b/dom/tests/mochitest/webcomponents/test_shadowroot_style.html
@@ -55,29 +55,21 @@ setShadowDOMPrefAndCreateIframe(content)
     is(root.styleSheets[0].ownerNode, shadowStyle, "The style sheet remaining in the ShadowRoot should be shadowStyle.");
 
     // Make sure that elements outside of the ShadowRoot are not affected by the ShadowRoot style.
     isnot(iframeWin.getComputedStyle(aDocument.getElementById("bodydiv"), null).getPropertyValue("height"), "100px", "Style sheets in ShadowRoot should not apply to elements no in the ShadowRoot.");
 
     // Make sure that elements in the ShadowRoot are styled according to the ShadowRoot style.
     is(iframeWin.getComputedStyle(divToStyle, null).getPropertyValue("height"), "100px", "ShadowRoot style sheets should apply to elements in ShadowRoot.");
 
-    // Tests for applyAuthorStyles.
+    // Tests for author styles not applying in a ShadowRoot.
     var authorStyle = aDocument.createElement("style");
     authorStyle.innerHTML = ".fat { padding-right: 20px; padding-left: 30px; }";
     aDocument.body.appendChild(authorStyle);
-
-    is(root.applyAuthorStyles, false, "applyAuthorStyles defaults to false.");
-    isnot(iframeWin.getComputedStyle(divToStyle, null).getPropertyValue("padding-right"), "20px", "Author styles should not apply to ShadowRoot when ShadowRoot.applyAuthorStyles is false.");
-    root.applyAuthorStyles = true;
-    is(root.applyAuthorStyles, true, "applyAuthorStyles was set to true.");
-    is(iframeWin.getComputedStyle(divToStyle, null).getPropertyValue("padding-right"), "20px", "Author styles should apply to ShadowRoot when ShadowRoot.applyAuthorStyles is true.");
-    root.applyAuthorStyles = false;
-    is(root.applyAuthorStyles, false, "applyAuthorStyles was set to false.");
-    isnot(iframeWin.getComputedStyle(divToStyle, null).getPropertyValue("padding-right"), "20px", "Author styles should not apply to ShadowRoot when ShadowRoot.applyAuthorStyles is false.");
+    isnot(iframeWin.getComputedStyle(divToStyle, null).getPropertyValue("padding-right"), "20px", "Author styles should not apply to ShadowRoot.");
 
     // Test dynamic changes to style in ShadowRoot.
     root.innerHTML = '<div id="divtostyle" class="dummy"></div>';
     divToStyle = root.getElementById("divtostyle");
     var dummyShadowStyle = aDocument.createElement("style");
     dummyShadowStyle.innerHTML = ".dummy { height: 300px; }";
     root.appendChild(dummyShadowStyle);
     is(iframeWin.getComputedStyle(divToStyle, null).getPropertyValue("height"), "300px", "Dummy element in ShadowRoot should be styled by style in ShadowRoot.");
--- a/dom/webidl/Animation.webidl
+++ b/dom/webidl/Animation.webidl
@@ -39,16 +39,17 @@ interface Animation : EventTarget {
            attribute EventHandler       oncancel;
   void cancel ();
   [Throws]
   void finish ();
   [Throws, BinaryName="playFromJS"]
   void play ();
   [Throws, BinaryName="pauseFromJS"]
   void pause ();
+  void updatePlaybackRate (double playbackRate);
   [Throws]
   void reverse ();
 };
 
 // Non-standard extensions
 partial interface Animation {
   [ChromeOnly] readonly attribute boolean isRunningOnCompositor;
 };
--- a/dom/webidl/Document.webidl
+++ b/dom/webidl/Document.webidl
@@ -314,17 +314,17 @@ partial interface Document {
 // https://svgwg.org/svg2-draft/struct.html#InterfaceDocumentExtensions
 partial interface Document {
   [BinaryName="SVGRootElement"]
   readonly attribute SVGSVGElement? rootElement;
 };
 
 //  Mozilla extensions of various sorts
 partial interface Document {
-  // nsIDOMDocumentXBL.  Wish we could make these [ChromeOnly], but
+  // XBL support.  Wish we could make these [ChromeOnly], but
   // that would likely break bindings running with the page principal.
   [Func="IsChromeOrXBL"]
   NodeList? getAnonymousNodes(Element elt);
   [Func="IsChromeOrXBL"]
   Element? getAnonymousElementByAttribute(Element elt, DOMString attrName,
                                           DOMString attrValue);
   [Func="IsChromeOrXBL"]
   Element? getBindingParent(Node node);
--- a/dom/webidl/PeerConnectionImpl.webidl
+++ b/dom/webidl/PeerConnectionImpl.webidl
@@ -36,18 +36,16 @@ interface PeerConnectionImpl  {
 
   /* Stats call, calls either |onGetStatsSuccess| or |onGetStatsError| on our
      observer. (see the |PeerConnectionObserver| interface) */
   [Throws]
   void getStats(MediaStreamTrack? selector);
 
   /* Adds the tracks created by GetUserMedia */
   [Throws]
-  void removeTrack(MediaStreamTrack track);
-  [Throws]
   TransceiverImpl createTransceiverImpl(DOMString kind,
                                         MediaStreamTrack? track);
   [Throws]
   boolean checkNegotiationNeeded();
   [Throws]
   void insertDTMF(TransceiverImpl transceiver, DOMString tones,
                   optional unsigned long duration = 100,
                   optional unsigned long interToneGap = 70);
--- a/dom/webidl/ShadowRoot.webidl
+++ b/dom/webidl/ShadowRoot.webidl
@@ -26,12 +26,11 @@ interface ShadowRoot : DocumentFragment
 
   // [deprecated] Shadow DOM v0
   Element? getElementById(DOMString elementId);
   HTMLCollection getElementsByTagName(DOMString localName);
   HTMLCollection getElementsByTagNameNS(DOMString? namespace, DOMString localName);
   HTMLCollection getElementsByClassName(DOMString classNames);
   [CEReactions, SetterThrows, TreatNullAs=EmptyString]
   attribute DOMString innerHTML;
-  attribute boolean applyAuthorStyles;
 };
 
 ShadowRoot implements DocumentOrShadowRoot;
--- a/dom/xml/nsXMLPrettyPrinter.cpp
+++ b/dom/xml/nsXMLPrettyPrinter.cpp
@@ -1,17 +1,16 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsXMLPrettyPrinter.h"
 #include "nsContentUtils.h"
 #include "nsICSSDeclaration.h"
-#include "nsIDOMDocumentXBL.h"
 #include "nsIObserver.h"
 #include "nsSyncLoadService.h"
 #include "nsPIDOMWindow.h"
 #include "nsIDOMElement.h"
 #include "nsIServiceManager.h"
 #include "nsNetUtil.h"
 #include "mozilla/dom/Element.h"
 #include "nsBindingManager.h"
--- a/dom/xul/nsXULPopupListener.cpp
+++ b/dom/xul/nsXULPopupListener.cpp
@@ -7,25 +7,20 @@
   This file provides the implementation for xul popup listener which
   tracks xul popups and context menus
  */
 
 #include "nsXULPopupListener.h"
 #include "nsCOMPtr.h"
 #include "nsGkAtoms.h"
 #include "nsIDOMElement.h"
-#include "nsIDOMXULElement.h"
-#include "nsIDOMNodeList.h"
-#include "nsIDOMDocument.h"
-#include "nsIDOMDocumentXBL.h"
 #include "nsContentCID.h"
 #include "nsContentUtils.h"
 #include "nsXULPopupManager.h"
 #include "nsIScriptContext.h"
-#include "nsIDOMWindow.h"
 #include "nsIDocument.h"
 #include "nsServiceManagerUtils.h"
 #include "nsIPrincipal.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsLayoutUtils.h"
 #include "mozilla/ReflowInput.h"
 #include "nsIObjectLoadingContent.h"
 #include "mozilla/EventStateManager.h"
@@ -363,28 +358,21 @@ nsXULPopupListener::LaunchPopup(nsIDOMEv
     return NS_ERROR_FAILURE;
   }
 
   // Handle the _child case for popups and context menus
   RefPtr<Element> popup;
   if (identifier.EqualsLiteral("_child")) {
     popup = GetImmediateChild(mElement, nsGkAtoms::menupopup);
     if (!popup) {
-      nsCOMPtr<nsIDOMDocumentXBL> nsDoc(do_QueryInterface(document));
-      nsCOMPtr<nsIDOMNodeList> list;
-      nsCOMPtr<nsIDOMElement> el = do_QueryInterface(mElement);
-      nsDoc->GetAnonymousNodes(el, getter_AddRefs(list));
+      nsINodeList* list = document->GetAnonymousNodes(*mElement);
       if (list) {
-        uint32_t ctr,listLength;
-        nsCOMPtr<nsIDOMNode> node;
-        list->GetLength(&listLength);
-        for (ctr = 0; ctr < listLength; ctr++) {
-          list->Item(ctr, getter_AddRefs(node));
-          nsCOMPtr<nsIContent> childContent(do_QueryInterface(node));
-
+        uint32_t listLength = list->Length();
+        for (uint32_t ctr = 0; ctr < listLength; ctr++) {
+          nsIContent* childContent = list->Item(ctr);
           if (childContent->NodeInfo()->Equals(nsGkAtoms::menupopup,
                                                kNameSpaceID_XUL)) {
             popup = childContent->AsElement();
             break;
           }
         }
       }
     }
--- a/editor/libeditor/EditorBase.cpp
+++ b/editor/libeditor/EditorBase.cpp
@@ -3566,82 +3566,92 @@ EditorBase::GetNodeLocation(nsINode* aCh
     *aOffset = -1;
   }
   return parent;
 }
 
 nsIContent*
 EditorBase::GetPreviousNodeInternal(nsINode& aNode,
                                     bool aFindEditableNode,
+                                    bool aFindAnyDataNode,
                                     bool aNoBlockCrossing)
 {
   if (!IsDescendantOfEditorRoot(&aNode)) {
     return nullptr;
   }
-  return FindNode(&aNode, false, aFindEditableNode, aNoBlockCrossing);
+  return FindNode(&aNode, false,
+                  aFindEditableNode, aFindAnyDataNode, aNoBlockCrossing);
 }
 
 nsIContent*
 EditorBase::GetPreviousNodeInternal(const EditorRawDOMPoint& aPoint,
                                     bool aFindEditableNode,
+                                    bool aFindAnyDataNode,
                                     bool aNoBlockCrossing)
 {
   MOZ_ASSERT(aPoint.IsSetAndValid());
   NS_WARNING_ASSERTION(!aPoint.IsInDataNode() || aPoint.IsInTextNode(),
     "GetPreviousNodeInternal() doesn't assume that the start point is a "
     "data node except text node");
 
   // If we are at the beginning of the node, or it is a text node, then just
   // look before it.
   if (aPoint.IsStartOfContainer() || aPoint.IsInTextNode()) {
     if (aNoBlockCrossing && IsBlockNode(aPoint.GetContainer())) {
       // If we aren't allowed to cross blocks, don't look before this block.
       return nullptr;
     }
     return GetPreviousNodeInternal(*aPoint.GetContainer(),
-                                   aFindEditableNode, aNoBlockCrossing);
+                                   aFindEditableNode, aFindAnyDataNode,
+                                   aNoBlockCrossing);
   }
 
   // else look before the child at 'aOffset'
   if (aPoint.GetChild()) {
     return GetPreviousNodeInternal(*aPoint.GetChild(),
-                                   aFindEditableNode, aNoBlockCrossing);
+                                   aFindEditableNode, aFindAnyDataNode,
+                                   aNoBlockCrossing);
   }
 
   // unless there isn't one, in which case we are at the end of the node
   // and want the deep-right child.
   nsIContent* rightMostNode =
     GetRightmostChild(aPoint.GetContainer(), aNoBlockCrossing);
   if (!rightMostNode) {
     return nullptr;
   }
 
-  if (!aFindEditableNode || IsEditable(rightMostNode)) {
+  if ((!aFindEditableNode || IsEditable(rightMostNode)) &&
+      (aFindAnyDataNode || IsElementOrText(*rightMostNode))) {
     return rightMostNode;
   }
 
   // restart the search from the non-editable node we just found
   return GetPreviousNodeInternal(*rightMostNode,
-                                 aFindEditableNode, aNoBlockCrossing);
+                                 aFindEditableNode, aFindAnyDataNode,
+                                 aNoBlockCrossing);
 }
 
 nsIContent*
 EditorBase::GetNextNodeInternal(nsINode& aNode,
                                 bool aFindEditableNode,
+                                bool aFindAnyDataNode,
                                 bool aNoBlockCrossing)
 {
   if (!IsDescendantOfEditorRoot(&aNode)) {
     return nullptr;
   }
-  return FindNode(&aNode, true, aFindEditableNode, aNoBlockCrossing);
+  return FindNode(&aNode, true,
+                  aFindEditableNode, aFindAnyDataNode, aNoBlockCrossing);
 }
 
 nsIContent*
 EditorBase::GetNextNodeInternal(const EditorRawDOMPoint& aPoint,
                                 bool aFindEditableNode,
+                                bool aFindAnyDataNode,
                                 bool aNoBlockCrossing)
 {
   MOZ_ASSERT(aPoint.IsSetAndValid());
   NS_WARNING_ASSERTION(!aPoint.IsInDataNode() || aPoint.IsInTextNode(),
     "GetNextNodeInternal() doesn't assume that the start point is a "
     "data node except text node");
 
   EditorRawDOMPoint point(aPoint);
@@ -3665,34 +3675,37 @@ EditorBase::GetNextNodeInternal(const Ed
     if (!leftMostNode) {
       return point.GetChild();
     }
 
     if (!IsDescendantOfEditorRoot(leftMostNode)) {
       return nullptr;
     }
 
-    if (!aFindEditableNode || IsEditable(leftMostNode)) {
+    if ((!aFindEditableNode || IsEditable(leftMostNode)) &&
+        (aFindAnyDataNode || IsElementOrText(*leftMostNode))) {
       return leftMostNode;
     }
 
     // restart the search from the non-editable node we just found
     return GetNextNodeInternal(*leftMostNode,
-                               aFindEditableNode, aNoBlockCrossing);
+                               aFindEditableNode, aFindAnyDataNode,
+                               aNoBlockCrossing);
   }
 
   // unless there isn't one, in which case we are at the end of the node
   // and want the next one.
   if (aNoBlockCrossing && IsBlockNode(point.GetContainer())) {
     // don't cross out of parent block
     return nullptr;
   }
 
   return GetNextNodeInternal(*point.GetContainer(),
-                             aFindEditableNode, aNoBlockCrossing);
+                             aFindEditableNode, aFindAnyDataNode,
+                             aNoBlockCrossing);
 }
 
 nsIContent*
 EditorBase::FindNextLeafNode(nsINode* aCurrentNode,
                              bool aGoForward,
                              bool bNoBlockCrossing)
 {
   // called only by GetPriorNode so we don't need to check params.
@@ -3741,16 +3754,17 @@ EditorBase::FindNextLeafNode(nsINode* aC
   NS_NOTREACHED("What part of for(;;) do you not understand?");
   return nullptr;
 }
 
 nsIContent*
 EditorBase::FindNode(nsINode* aCurrentNode,
                      bool aGoForward,
                      bool aEditableNode,
+                     bool aFindAnyDataNode,
                      bool bNoBlockCrossing)
 {
   if (IsEditorRoot(aCurrentNode)) {
     // Don't allow traversal above the root node! This helps
     // prevent us from accidentally editing browser content
     // when the editor is in a text widget.
 
     return nullptr;
@@ -3758,21 +3772,23 @@ EditorBase::FindNode(nsINode* aCurrentNo
 
   nsCOMPtr<nsIContent> candidate =
     FindNextLeafNode(aCurrentNode, aGoForward, bNoBlockCrossing);
 
   if (!candidate) {
     return nullptr;
   }
 
-  if (!aEditableNode || IsEditable(candidate)) {
+  if ((!aEditableNode || IsEditable(candidate)) &&
+      (aFindAnyDataNode || IsElementOrText(*candidate))) {
     return candidate;
   }
 
-  return FindNode(candidate, aGoForward, aEditableNode, bNoBlockCrossing);
+  return FindNode(candidate, aGoForward,
+                  aEditableNode, aFindAnyDataNode, bNoBlockCrossing);
 }
 
 nsIContent*
 EditorBase::GetRightmostChild(nsINode* aCurrentNode,
                               bool bNoBlockCrossing)
 {
   NS_ENSURE_TRUE(aCurrentNode, nullptr);
   nsIContent *cur = aCurrentNode->GetLastChild();
@@ -5227,16 +5243,21 @@ public:
   explicit RepaintSelectionRunner(nsISelectionController* aSelectionController)
     : Runnable("RepaintSelectionRunner")
     , mSelectionController(aSelectionController)
   {
   }
 
   NS_IMETHOD Run() override
   {
+    nsCOMPtr<nsIPresShell> shell = do_QueryInterface(mSelectionController);
+    if (!shell || shell->IsDestroying()) {
+      return NS_OK;
+    }
+
     mSelectionController->RepaintSelection(
                             nsISelectionController::SELECTION_NORMAL);
     return NS_OK;
   }
 
 private:
   nsCOMPtr<nsISelectionController> mSelectionController;
 };
--- a/editor/libeditor/EditorBase.h
+++ b/editor/libeditor/EditorBase.h
@@ -627,60 +627,69 @@ protected:
    * Helper for GetPreviousNodeInternal() and GetNextNodeInternal().
    */
   nsIContent* FindNextLeafNode(nsINode* aCurrentNode,
                                bool aGoForward,
                                bool bNoBlockCrossing);
   nsIContent* FindNode(nsINode* aCurrentNode,
                        bool aGoForward,
                        bool aEditableNode,
+                       bool aFindAnyDataNode,
                        bool bNoBlockCrossing);
 
   /**
    * Get the node immediately previous node of aNode.
    * @param atNode               The node from which we start the search.
    * @param aFindEditableNode    If true, only return an editable node.
+   * @param aFindAnyDataNode     If true, may return invisible data node
+   *                             like Comment.
    * @param aNoBlockCrossing     If true, don't move across "block" nodes,
    *                             whatever that means.
    * @return                     The node that occurs before aNode in
    *                             the tree, skipping non-editable nodes if
    *                             aFindEditableNode is true.  If there is no
    *                             previous node, returns nullptr.
    */
   nsIContent* GetPreviousNodeInternal(nsINode& aNode,
                                       bool aFindEditableNode,
+                                      bool aFindAnyDataNode,
                                       bool aNoBlockCrossing);
 
   /**
    * And another version that takes a point in DOM tree rather than a node.
    */
   nsIContent* GetPreviousNodeInternal(const EditorRawDOMPoint& aPoint,
                                       bool aFindEditableNode,
+                                      bool aFindAnyDataNode,
                                       bool aNoBlockCrossing);
 
   /**
    * Get the node immediately next node of aNode.
    * @param aNode                The node from which we start the search.
    * @param aFindEditableNode    If true, only return an editable node.
+   * @param aFindAnyDataNode     If true, may return invisible data node
+   *                             like Comment.
    * @param aNoBlockCrossing     If true, don't move across "block" nodes,
    *                             whatever that means.
    * @return                     The node that occurs after aNode in the
    *                             tree, skipping non-editable nodes if
    *                             aFindEditableNode is true.  If there is no
    *                             next node, returns nullptr.
    */
   nsIContent* GetNextNodeInternal(nsINode& aNode,
                                   bool aFindEditableNode,
+                                  bool aFindAnyDataNode,
                                   bool bNoBlockCrossing);
 
   /**
    * And another version that takes a point in DOM tree rather than a node.
    */
   nsIContent* GetNextNodeInternal(const EditorRawDOMPoint& aPoint,
                                   bool aFindEditableNode,
+                                  bool aFindAnyDataNode,
                                   bool aNoBlockCrossing);
 
 
   virtual nsresult InstallEventListeners();
   virtual void CreateEventListeners();
   virtual void RemoveEventListeners();
 
   /**
@@ -802,46 +811,62 @@ public:
                                                       int32_t* outOffset);
   static nsINode* GetNodeLocation(nsINode* aChild, int32_t* aOffset);
 
   /**
    * Get the previous node.
    */
   nsIContent* GetPreviousNode(const EditorRawDOMPoint& aPoint)
   {
-    return GetPreviousNodeInternal(aPoint, false, false);
+    return GetPreviousNodeInternal(aPoint, false, true, false);
+  }
+  nsIContent* GetPreviousElementOrText(const EditorRawDOMPoint& aPoint)
+  {
+    return GetPreviousNodeInternal(aPoint, false, false, false);
   }
   nsIContent* GetPreviousEditableNode(const EditorRawDOMPoint& aPoint)
   {
-    return GetPreviousNodeInternal(aPoint, true, false);
+    return GetPreviousNodeInternal(aPoint, true, true, false);
   }
   nsIContent* GetPreviousNodeInBlock(const EditorRawDOMPoint& aPoint)
   {
-    return GetPreviousNodeInternal(aPoint, false, true);
+    return GetPreviousNodeInternal(aPoint, false, true, true);
+  }
+  nsIContent* GetPreviousElementOrTextInBlock(const EditorRawDOMPoint& aPoint)
+  {
+    return GetPreviousNodeInternal(aPoint, false, false, true);
   }
   nsIContent* GetPreviousEditableNodeInBlock(
                 const EditorRawDOMPoint& aPoint)
   {
-    return GetPreviousNodeInternal(aPoint, true, true);
+    return GetPreviousNodeInternal(aPoint, true, true, true);
   }
   nsIContent* GetPreviousNode(nsINode& aNode)
   {
-    return GetPreviousNodeInternal(aNode, false, false);
+    return GetPreviousNodeInternal(aNode, false, true, false);
+  }
+  nsIContent* GetPreviousElementOrText(nsINode& aNode)
+  {
+    return GetPreviousNodeInternal(aNode, false, false, false);
   }
   nsIContent* GetPreviousEditableNode(nsINode& aNode)
   {
-    return GetPreviousNodeInternal(aNode, true, false);
+    return GetPreviousNodeInternal(aNode, true, true, false);
   }
   nsIContent* GetPreviousNodeInBlock(nsINode& aNode)
   {
-    return GetPreviousNodeInternal(aNode, false, true);
+    return GetPreviousNodeInternal(aNode, false, true, true);
+  }
+  nsIContent* GetPreviousElementOrTextInBlock(nsINode& aNode)
+  {
+    return GetPreviousNodeInternal(aNode, false, false, true);
   }
   nsIContent* GetPreviousEditableNodeInBlock(nsINode& aNode)
   {
-    return GetPreviousNodeInternal(aNode, true, true);
+    return GetPreviousNodeInternal(aNode, true, true, true);
   }
 
   /**
    * Get the next node.
    *
    * Note that methods taking EditorRawDOMPoint behavior includes the
    * child at offset as search target.  E.g., following code causes infinite
    * loop.
@@ -862,46 +887,62 @@ public:
    * }
    *
    * On the other hand, the methods taking nsINode behavior must be what
    * you want.  They start to search the result from next node of the given
    * node.
    */
   nsIContent* GetNextNode(const EditorRawDOMPoint& aPoint)
   {
-    return GetNextNodeInternal(aPoint, false, false);
+    return GetNextNodeInternal(aPoint, false, true, false);
+  }
+  nsIContent* GetNextElementOrText(const EditorRawDOMPoint& aPoint)
+  {
+    return GetNextNodeInternal(aPoint, false, false, false);
   }
   nsIContent* GetNextEditableNode(const EditorRawDOMPoint& aPoint)
   {
-    return GetNextNodeInternal(aPoint, true, false);
+    return GetNextNodeInternal(aPoint, true, true, false);
   }
   nsIContent* GetNextNodeInBlock(const EditorRawDOMPoint& aPoint)
   {
-    return GetNextNodeInternal(aPoint, false, true);
+    return GetNextNodeInternal(aPoint, false, true, true);
+  }
+  nsIContent* GetNextElementOrTextInBlock(const EditorRawDOMPoint& aPoint)
+  {
+    return GetNextNodeInternal(aPoint, false, false, true);
   }
   nsIContent* GetNextEditableNodeInBlock(
                 const EditorRawDOMPoint& aPoint)
   {
-    return GetNextNodeInternal(aPoint, true, true);
+    return GetNextNodeInternal(aPoint, true, true, true);
   }
   nsIContent* GetNextNode(nsINode& aNode)
   {
-    return GetNextNodeInternal(aNode, false, false);
+    return GetNextNodeInternal(aNode, false, true, false);
+  }
+  nsIContent* GetNextElementOrText(nsINode& aNode)
+  {
+    return GetNextNodeInternal(aNode, false, false, false);
   }
   nsIContent* GetNextEditableNode(nsINode& aNode)
   {
-    return GetNextNodeInternal(aNode, true, false);
+    return GetNextNodeInternal(aNode, true, true, false);
   }
   nsIContent* GetNextNodeInBlock(nsINode& aNode)
   {
-    return GetNextNodeInternal(aNode, false, true);
+    return GetNextNodeInternal(aNode, false, true, true);
+  }
+  nsIContent* GetNextElementOrTextInBlock(nsINode& aNode)
+  {
+    return GetNextNodeInternal(aNode, false, false, true);
   }
   nsIContent* GetNextEditableNodeInBlock(nsINode& aNode)
   {
-    return GetNextNodeInternal(aNode, true, true);
+    return GetNextNodeInternal(aNode, true, true, true);
   }
 
   /**
    * Get the rightmost child of aCurrentNode;
    * return nullptr if aCurrentNode has no children.
    */
   nsIContent* GetRightmostChild(nsINode* aCurrentNode,
                                 bool bNoBlockCrossing = false);
@@ -970,27 +1011,41 @@ public:
         // Text nodes are considered to be editable by both typed of editors.
         return true;
       default:
         return false;
     }
   }
 
   /**
+   * Returns true if aNode is a usual element node (not bogus node) or
+   * a text node.  In other words, returns true if aNode is a usual element
+   * node or visible data node.
+   */
+  bool IsElementOrText(const nsINode& aNode) const
+  {
+    if (!aNode.IsContent() || IsMozEditorBogusNode(&aNode)) {
+      return false;
+    }
+    return aNode.NodeType() == nsINode::ELEMENT_NODE ||
+           aNode.NodeType() == nsINode::TEXT_NODE;
+  }
+
+  /**
    * Returns true if selection is in an editable element and both the range
    * start and the range end are editable.  E.g., even if the selection range
    * includes non-editable elements, returns true when one of common ancestors
    * of the range start and the range end is editable.  Otherwise, false.
    */
   bool IsSelectionEditable();
 
   /**
    * Returns true if aNode is a MozEditorBogus node.
    */
-  bool IsMozEditorBogusNode(nsINode* aNode)
+  bool IsMozEditorBogusNode(const nsINode* aNode) const
   {
     return aNode && aNode->IsElement() &&
            aNode->AsElement()->AttrValueIs(kNameSpaceID_None,
                kMOZEditorBogusNodeAttrAtom, kMOZEditorBogusNodeValue,
                eCaseMatters);
   }
 
   /**
--- a/editor/libeditor/HTMLEditRules.cpp
+++ b/editor/libeditor/HTMLEditRules.cpp
@@ -1747,16 +1747,24 @@ HTMLEditRules::WillInsertBreak(Selection
       // Didn't create a new block for some reason, fall back to <br>
       rv = InsertBRElement(aSelection, atStartOfSelection);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
       *aHandled = true;
       return NS_OK;
     }
+    // Now, mNewBlock is last created block element for wrapping inline
+    // elements around the caret position and AfterEditInner() will move
+    // caret into it.  However, it may be different from block parent of
+    // the caret position.  E.g., MakeBasicBlock() may wrap following
+    // inline elements of a <br> element which is next sibling of container
+    // of the caret.  So, we need to adjust mNewBlock here for avoiding
+    // jumping caret to odd position.
+    mNewBlock = blockParent;
   }
 
   // If block is empty, populate with br.  (For example, imagine a div that
   // contains the word "text".  The user selects "text" and types return.
   // "Text" is deleted leaving an empty block.  We want to put in one br to
   // make block have a line.  Then code further below will put in a second br.)
   if (IsEmptyBlockElement(*blockParent, IgnoreSingleBR::eNo)) {
     AutoEditorDOMPointChildInvalidator lockOffset(atStartOfSelection);
@@ -5920,16 +5928,28 @@ HTMLEditRules::GetPromotedPoint(RulesEnd
     point.Set(point.GetContainer());
     DebugOnly<bool> advanced = point.AdvanceOffset();
     NS_WARNING_ASSERTION(advanced,
       "Failed to advance offset to after the text node");
   }
 
   // look ahead through any further inline nodes that aren't across a <br> from
   // us, and that are enclosed in the same block.
+  // XXX Currently, we stop block-extending when finding visible <br> element.
+  //     This might be different from "block-extend" of execCommand spec.
+  //     However, the spec is really unclear.
+  // XXX Probably, scanning only editable nodes is wrong for
+  //     EditAction::makeBasicBlock because it might be better to wrap existing
+  //     inline elements even if it's non-editable.  For example, following
+  //     examples with insertParagraph causes different result:
+  //     * <div contenteditable>foo[]<b contenteditable="false">bar</b></div>
+  //     * <div contenteditable>foo[]<b>bar</b></div>
+  //     * <div contenteditable>foo[]<b contenteditable="false">bar</b>baz</div>
+  //     Only in the first case, after the caret position isn't wrapped with
+  //     new <div> element.
   nsCOMPtr<nsIContent> nextNode =
     htmlEditor->GetNextEditableHTMLNodeInBlock(point.AsRaw());
 
   while (nextNode && !IsBlockNode(*nextNode) && nextNode->GetParentNode()) {
     point.Set(nextNode);
     if (NS_WARN_IF(!point.AdvanceOffset())) {
       break;
     }
--- a/editor/libeditor/HTMLEditRules.h
+++ b/editor/libeditor/HTMLEditRules.h
@@ -415,18 +415,36 @@ protected:
    * GetHiestInlineParent() returns the highest inline node parent between
    * aNode and the editing host.  Even if the editing host is an inline
    * element, this method never returns the editing host as the result.
    */
   nsIContent* GetHighestInlineParent(nsINode& aNode);
   void MakeTransitionList(nsTArray<OwningNonNull<nsINode>>& aNodeArray,
                           nsTArray<bool>& aTransitionArray);
   nsresult RemoveBlockStyle(nsTArray<OwningNonNull<nsINode>>& aNodeArray);
+
+  /**
+   * ApplyBlockStyle() formats all nodes in aNodeArray with block elements
+   * whose name is aBlockTag.
+   * If aNodeArray has an inline element, a block element is created and the
+   * inline element and following inline elements are moved into the new block
+   * element.
+   * If aNodeArray has <br> elements, they'll be removed from the DOM tree and
+   * new block element will be created when there are some remaining inline
+   * elements.
+   * If aNodeArray has a block element, this calls itself with children of
+   * the block element.  Then, new block element will be created when there
+   * are some remaining inline elements.
+   *
+   * @param aNodeArray      Must be descendants of a node.
+   * @param aBlockTag       The element name of new block elements.
+   */
   nsresult ApplyBlockStyle(nsTArray<OwningNonNull<nsINode>>& aNodeArray,
                            nsAtom& aBlockTag);
+
   nsresult MakeBlockquote(nsTArray<OwningNonNull<nsINode>>& aNodeArray);
 
   /**
    * MaybeSplitAncestorsForInsert() does nothing if container of
    * aStartOfDeepestRightNode can have an element whose tag name is aTag.
    * Otherwise, looks for an ancestor node which is or is in active editing
    * host and can have an element whose name is aTag.  If there is such
    * ancestor, its descendants are split.
--- a/editor/libeditor/HTMLEditor.cpp
+++ b/editor/libeditor/HTMLEditor.cpp
@@ -865,18 +865,23 @@ HTMLEditor::IsPrevCharInNodeWhitespace(n
 
 bool
 HTMLEditor::IsVisibleBRElement(nsINode* aNode)
 {
   MOZ_ASSERT(aNode);
   if (!TextEditUtils::IsBreak(aNode)) {
     return false;
   }
-  // Check if there is a later node in block after br
-  nsCOMPtr<nsINode> nextNode = GetNextEditableHTMLNodeInBlock(*aNode);
+  // Check if there is another element or text node in block after current
+  // <br> element.
+  // Note that even if following node is non-editable, it may make the
+  // <br> element visible if it just exists.
+  // E.g., foo<br><button contenteditable="false">button</button>
+  // However, we need to ignore invisible data nodes like comment node.
+  nsCOMPtr<nsINode> nextNode = GetNextHTMLElementOrTextInBlock(*aNode);
   if (nextNode && TextEditUtils::IsBreak(nextNode)) {
     return true;
   }
 
   // A single line break before a block boundary is not displayed, so e.g.
   // foo<p>bar<br></p> and foo<br><p>bar</p> display the same as foo<p>bar</p>.
   // But if there are multiple <br>s in a row, all but the last are visible.
   if (!nextNode) {
@@ -885,17 +890,21 @@ HTMLEditor::IsVisibleBRElement(nsINode* 
   }
   if (IsBlockNode(nextNode)) {
     // Break is right before a block, it's not visible
     return false;
   }
 
   // If there's an inline node after this one that's not a break, and also a
   // prior break, this break must be visible.
-  nsCOMPtr<nsINode> priorNode = GetPreviousEditableHTMLNodeInBlock(*aNode);
+  // Note that even if previous node is non-editable, it may make the
+  // <br> element visible if it just exists.
+  // E.g., <button contenteditable="false"><br>foo
+  // However, we need to ignore invisible data nodes like comment node.
+  nsCOMPtr<nsINode> priorNode = GetPreviousHTMLElementOrTextInBlock(*aNode);
   if (priorNode && TextEditUtils::IsBreak(priorNode)) {
     return true;
   }
 
   // Sigh.  We have to use expensive whitespace calculation code to
   // determine what is going on
   int32_t selOffset;
   nsCOMPtr<nsINode> selNode = GetNodeLocation(aNode, &selOffset);
@@ -3699,16 +3708,39 @@ HTMLEditor::GetNextHTMLSibling(nsINode* 
   while (node && !IsEditable(node)) {
     node = node->GetNextSibling();
   }
 
   return node;
 }
 
 nsIContent*
+HTMLEditor::GetPreviousHTMLElementOrTextInternal(nsINode& aNode,
+                                                 bool aNoBlockCrossing)
+{
+  if (!GetActiveEditingHost()) {
+    return nullptr;
+  }
+  return aNoBlockCrossing ? GetPreviousElementOrTextInBlock(aNode) :
+                            GetPreviousElementOrText(aNode);
+}
+
+nsIContent*
+HTMLEditor::GetPreviousHTMLElementOrTextInternal(
+              const EditorRawDOMPoint& aPoint,
+              bool aNoBlockCrossing)
+{
+  if (!GetActiveEditingHost()) {
+    return nullptr;
+  }
+  return aNoBlockCrossing ? GetPreviousElementOrTextInBlock(aPoint) :
+                            GetPreviousElementOrText(aPoint);
+}
+
+nsIContent*
 HTMLEditor::GetPreviousEditableHTMLNodeInternal(nsINode& aNode,
                                                 bool aNoBlockCrossing)
 {
   if (!GetActiveEditingHost()) {
     return nullptr;
   }
   return aNoBlockCrossing ? GetPreviousEditableNodeInBlock(aNode) :
                             GetPreviousEditableNode(aNode);
@@ -3721,16 +3753,38 @@ HTMLEditor::GetPreviousEditableHTMLNodeI
   if (!GetActiveEditingHost()) {
     return nullptr;
   }
   return aNoBlockCrossing ? GetPreviousEditableNodeInBlock(aPoint) :
                             GetPreviousEditableNode(aPoint);
 }
 
 nsIContent*
+HTMLEditor::GetNextHTMLElementOrTextInternal(nsINode& aNode,
+                                             bool aNoBlockCrossing)
+{
+  if (!GetActiveEditingHost()) {
+    return nullptr;
+  }
+  return aNoBlockCrossing ? GetNextElementOrTextInBlock(aNode) :
+                            GetNextElementOrText(aNode);
+}
+
+nsIContent*
+HTMLEditor::GetNextHTMLElementOrTextInternal(const EditorRawDOMPoint& aPoint,
+                                             bool aNoBlockCrossing)
+{
+  if (!GetActiveEditingHost()) {
+    return nullptr;
+  }
+  return aNoBlockCrossing ? GetNextElementOrTextInBlock(aPoint) :
+                            GetNextElementOrText(aPoint);
+}
+
+nsIContent*
 HTMLEditor::GetNextEditableHTMLNodeInternal(nsINode& aNode,
                                             bool aNoBlockCrossing)
 {
   if (!GetActiveEditingHost()) {
     return nullptr;
   }
   return aNoBlockCrossing ? GetNextEditableNodeInBlock(aNode) :
                             GetNextEditableNode(aNode);
--- a/editor/libeditor/HTMLEditor.h
+++ b/editor/libeditor/HTMLEditor.h
@@ -887,16 +887,49 @@ protected:
 
   nsresult RemoveBlockContainer(nsIContent& aNode);
 
   nsIContent* GetPriorHTMLSibling(nsINode* aNode);
 
   nsIContent* GetNextHTMLSibling(nsINode* aNode);
 
   /**
+   * GetPreviousHTMLElementOrText*() methods are similar to
+   * EditorBase::GetPreviousElementOrText*() but this won't return nodes
+   * outside active editing host.
+   */
+  nsIContent* GetPreviousHTMLElementOrText(nsINode& aNode)
+  {
+    return GetPreviousHTMLElementOrTextInternal(aNode, false);
+  }
+  nsIContent* GetPreviousHTMLElementOrTextInBlock(nsINode& aNode)
+  {
+    return GetPreviousHTMLElementOrTextInternal(aNode, true);
+  }
+  nsIContent* GetPreviousHTMLElementOrText(const EditorRawDOMPoint& aPoint)
+  {
+    return GetPreviousHTMLElementOrTextInternal(aPoint, false);
+  }
+  nsIContent*
+  GetPreviousHTMLElementOrTextInBlock(const EditorRawDOMPoint& aPoint)
+  {
+    return GetPreviousHTMLElementOrTextInternal(aPoint, true);
+  }
+
+  /**
+   * GetPreviousHTMLElementOrTextInternal() methods are common implementation
+   * of above methods.  Please don't use this method directly.
+   */
+  nsIContent* GetPreviousHTMLElementOrTextInternal(nsINode& aNode,
+                                                   bool aNoBlockCrossing);
+  nsIContent*
+  GetPreviousHTMLElementOrTextInternal(const EditorRawDOMPoint& aPoint,
+                                       bool aNoBlockCrossing);
+
+  /**
    * GetPreviousEditableHTMLNode*() methods are similar to
    * EditorBase::GetPreviousEditableNode() but this won't return nodes outside
    * active editing host.
    */
   nsIContent* GetPreviousEditableHTMLNode(nsINode& aNode)
   {
     return GetPreviousEditableHTMLNodeInternal(aNode, false);
   }
@@ -920,21 +953,57 @@ protected:
    */
   nsIContent* GetPreviousEditableHTMLNodeInternal(nsINode& aNode,
                                                   bool aNoBlockCrossing);
   nsIContent* GetPreviousEditableHTMLNodeInternal(
                 const EditorRawDOMPoint& aPoint,
                 bool aNoBlockCrossing);
 
   /**
+   * GetNextHTMLElementOrText*() methods are similar to
+   * EditorBase::GetNextElementOrText*() but this won't return nodes outside
+   * active editing host.
+   *
+   * Note that same as EditorBase::GetTextEditableNode(), methods which take
+   * |const EditorRawDOMPoint&| start to search from the node pointed by it.
+   * On the other hand, methods which take |nsINode&| start to search from
+   * next node of aNode.
+   */
+  nsIContent* GetNextHTMLElementOrText(nsINode& aNode)
+  {
+    return GetNextHTMLElementOrTextInternal(aNode, false);
+  }
+  nsIContent* GetNextHTMLElementOrTextInBlock(nsINode& aNode)
+  {
+    return GetNextHTMLElementOrTextInternal(aNode, true);
+  }
+  nsIContent* GetNextHTMLElementOrText(const EditorRawDOMPoint& aPoint)
+  {
+    return GetNextHTMLElementOrTextInternal(aPoint, false);
+  }
+  nsIContent* GetNextHTMLElementOrTextInBlock(const EditorRawDOMPoint& aPoint)
+  {
+    return GetNextHTMLElementOrTextInternal(aPoint, true);
+  }
+
+  /**
+   * GetNextHTMLNodeInternal() methods are common implementation
+   * of above methods.  Please don't use this method directly.
+   */
+  nsIContent* GetNextHTMLElementOrTextInternal(nsINode& aNode,
+                                               bool aNoBlockCrossing);
+  nsIContent* GetNextHTMLElementOrTextInternal(const EditorRawDOMPoint& aPoint,
+                                               bool aNoBlockCrossing);
+
+  /**
    * GetNextEditableHTMLNode*() methods are similar to
    * EditorBase::GetNextEditableNode() but this won't return nodes outside
    * active editing host.
    *
-   * Note that same as EditorBaseGetTextEditableNode(), methods which take
+   * Note that same as EditorBase::GetTextEditableNode(), methods which take
    * |const EditorRawDOMPoint&| start to search from the node pointed by it.
    * On the other hand, methods which take |nsINode&| start to search from
    * next node of aNode.
    */
   nsIContent* GetNextEditableHTMLNode(nsINode& aNode)
   {
     return GetNextEditableHTMLNodeInternal(aNode, false);
   }
--- a/editor/libeditor/tests/mochitest.ini
+++ b/editor/libeditor/tests/mochitest.ini
@@ -250,16 +250,17 @@ skip-if = toolkit == 'android' # bug 131
 [test_bug1355792.html]
 [test_bug1358025.html]
 [test_bug1361008.html]
 [test_bug1368544.html]
 [test_bug1385905.html]
 [test_bug1390562.html]
 [test_bug1394758.html]
 [test_bug1399722.html]
+[test_bug1406726.html]
 [test_bug1409520.html]
 [test_bug1425997.html]
 
 [test_CF_HTML_clipboard.html]
 subsuite = clipboard
 [test_composition_event_created_in_chrome.html]
 [test_contenteditable_focus.html]
 [test_documentCharacterSet.html]
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1406726.html
@@ -0,0 +1,126 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1406726
+-->
+<head>
+  <title>Test for Bug 1406726</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1406726">Mozilla Bug 1406726</a>
+<p id="display"></p>
+<div id="editor" contenteditable></div>
+
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1406726 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(() => {
+  let editor = document.getElementById("editor");
+  let selection = window.getSelection();
+
+  editor.focus();
+  for (let paragraphSeparator of ["div", "p"]) {
+    document.execCommand("defaultParagraphSeparator", false, paragraphSeparator);
+
+    // The result of editor.innerHTML may be wrong in this tests.
+    // Currently, editor wraps following elements of <br> element with default
+    // paragraph separator only when there is only non-editable elements.
+    // This behavior should be standardized by execCommand spec.
+
+    editor.innerHTML = "foo<br>bar<br><span contenteditable=\"false\">baz</span>";
+    selection.collapse(editor.childNodes.item(2), "bar".length);
+    document.execCommand("insertParagraph", false);
+    is(editor.innerHTML, "foo<br>" +
+                         "<" + paragraphSeparator + ">bar</" + paragraphSeparator + ">" +
+                         "<" + paragraphSeparator + "><br></" + paragraphSeparator + ">" +
+                         "<" + paragraphSeparator + "><span contenteditable=\"false\">baz</span></" + paragraphSeparator + ">",
+       "All inline nodes including non-editable <span> element should be wrapped with default paragraph separator, <" + paragraphSeparator + ">");
+    ok(selection.isCollapsed, "Selection should be collapsed");
+    is(selection.anchorNode, editor.childNodes.item(3),
+       "Caret should be in the third line");
+    is(selection.anchorOffset, 0,
+       "Caret should be at start of the third line");
+
+    editor.innerHTML = "foo<br>bar<br><span>baz</span>";
+    selection.collapse(editor.childNodes.item(2), "bar".length);
+    document.execCommand("insertParagraph", false);
+    is(editor.innerHTML, "foo<br>" +
+                         "<" + paragraphSeparator + ">bar</" + paragraphSeparator + ">" +
+                         "<" + paragraphSeparator + "><br></" + paragraphSeparator + ">" +
+                         "<span>baz</span>",
+       "All inline nodes in the second line should be wrapped with default paragraph separator, <" + paragraphSeparator + ">");
+    ok(selection.isCollapsed, "Selection should be collapsed");
+    is(selection.anchorNode, editor.childNodes.item(3),
+       "Caret should be in the third line");
+    is(selection.anchorOffset, 0,
+       "Caret should be at start of the third line");
+
+    editor.innerHTML = "foo<br>bar<br><span contenteditable=\"false\">baz</span>qux";
+    selection.collapse(editor.childNodes.item(2), "bar".length);
+    document.execCommand("insertParagraph", false);
+    is(editor.innerHTML, "foo<br>" +
+                         "<" + paragraphSeparator + ">bar</" + paragraphSeparator + ">" +
+                         "<" + paragraphSeparator + "><br></" + paragraphSeparator + ">" +
+                         "<span contenteditable=\"false\">baz</span>qux",
+       "All inline nodes in the second line should be wrapped with default paragraph separator, <" + paragraphSeparator + ">");
+    ok(selection.isCollapsed, "Selection should be collapsed");
+    is(selection.anchorNode, editor.childNodes.item(3),
+       "Caret should be in the third line");
+    is(selection.anchorOffset, 0,
+       "Caret should be at start of the third line");
+
+    editor.innerHTML = "foo<br>bar<br><span contenteditable=\"false\">baz</span>";
+    selection.collapse(editor.childNodes.item(2), "ba".length);
+    document.execCommand("insertParagraph", false);
+    is(editor.innerHTML, "foo<br>" +
+                         "<" + paragraphSeparator + ">ba</" + paragraphSeparator + ">" +
+                         "<" + paragraphSeparator + ">r</" + paragraphSeparator + ">" +
+                         "<" + paragraphSeparator + "><span contenteditable=\"false\">baz</span></" + paragraphSeparator + ">",
+       "All inline nodes including non-editable <span> element should be wrapped with default paragraph separator, <" + paragraphSeparator + ">");
+    ok(selection.isCollapsed, "Selection should be collapsed");
+    is(selection.anchorNode, editor.childNodes.item(3).firstChild,
+       "Caret should be in the text node in the third line");
+    is(selection.anchorOffset, 0,
+       "Caret should be at start of the text node in the third line");
+
+    editor.innerHTML = "foo<br>bar<br><span>baz</span>";
+    selection.collapse(editor.childNodes.item(2), "ba".length);
+    document.execCommand("insertParagraph", false);
+    is(editor.innerHTML, "foo<br>" +
+                         "<" + paragraphSeparator + ">ba</" + paragraphSeparator + ">" +
+                         "<" + paragraphSeparator + ">r</" + paragraphSeparator + ">" +
+                         "<span>baz</span>",
+       "All inline nodes in the second line should be wrapped with default paragraph separator, <" + paragraphSeparator + ">");
+    ok(selection.isCollapsed, "Selection should be collapsed");
+    is(selection.anchorNode, editor.childNodes.item(3).firstChild,
+       "Caret should be in the text node in the third line");
+    is(selection.anchorOffset, 0,
+       "Caret should be at start of the text node in the third line");
+
+    editor.innerHTML = "foo<br>bar<br><span contenteditable=\"false\">baz</span>qux";
+    selection.collapse(editor.childNodes.item(2), "ba".length);
+    document.execCommand("insertParagraph", false);
+    is(editor.innerHTML, "foo<br>" +
+                         "<" + paragraphSeparator + ">ba</" + paragraphSeparator + ">" +
+                         "<" + paragraphSeparator + ">r</" + paragraphSeparator + ">" +
+                         "<span contenteditable=\"false\">baz</span>qux",
+       "All inline nodes in the second line should be wrapped with default paragraph separator, <" + paragraphSeparator + ">");
+    ok(selection.isCollapsed, "Selection should be collapsed");
+    is(selection.anchorNode, editor.childNodes.item(3).firstChild,
+       "Caret should be in the text node in the third line");
+    is(selection.anchorOffset, 0,
+       "Caret should be at start of the text node in the third line");
+  }
+
+  SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
--- a/gfx/layers/AnimationInfo.cpp
+++ b/gfx/layers/AnimationInfo.cpp
@@ -2,16 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "AnimationInfo.h"
 #include "mozilla/layers/WebRenderLayerManager.h"
 #include "mozilla/layers/AnimationHelper.h"
+#include "mozilla/dom/Animation.h"
 
 namespace mozilla {
 namespace layers {
 
 AnimationInfo::AnimationInfo(LayerManager* aManager) :
   mManager(aManager),
   mCompositorAnimationsId(0),
   mAnimationGeneration(0),
@@ -98,27 +99,37 @@ AnimationInfo::SetCompositorAnimations(c
 bool
 AnimationInfo::StartPendingAnimations(const TimeStamp& aReadyTime)
 {
   bool updated = false;
   for (size_t animIdx = 0, animEnd = mAnimations.Length();
        animIdx < animEnd; animIdx++) {
     Animation& anim = mAnimations[animIdx];
 
+    // If the animation is doing an async update of its playback rate, then we
+    // want to match whatever its current time would be at *aReadyTime*.
+    if (!std::isnan(anim.previousPlaybackRate()) &&
+        anim.startTime().type() == MaybeTimeDuration::TTimeDuration &&
+        !anim.originTime().IsNull() && !anim.isNotPlaying()) {
+      TimeDuration readyTime = aReadyTime - anim.originTime();
+      anim.holdTime() = dom::Animation::CurrentTimeFromTimelineTime(
+        readyTime,
+        anim.startTime().get_TimeDuration(),
+        anim.previousPlaybackRate());
+      // Make start time null so that we know to update it below.
+      anim.startTime() = null_t();
+    }
+
     // If the animation is play-pending, resolve the start time.
-    // This mirrors the calculation in Animation::StartTimeFromReadyTime.
     if (anim.startTime().type() == MaybeTimeDuration::Tnull_t &&
         !anim.originTime().IsNull() &&
         !anim.isNotPlaying()) {
       TimeDuration readyTime = aReadyTime - anim.originTime();
-      anim.startTime() =
-        anim.playbackRate() == 0
-        ? readyTime
-        : readyTime - anim.holdTime().MultDouble(1.0 /
-                                                 anim.playbackRate());
+      anim.startTime() = dom::Animation::StartTimeFromTimelineTime(
+        readyTime, anim.holdTime(), anim.playbackRate());
       updated = true;
     }
   }
   return updated;
 }
 
 void
 AnimationInfo::TransferMutatedFlagToLayer(Layer* aLayer)
--- a/gfx/layers/apz/test/mochitest/apz_test_utils.js
+++ b/gfx/layers/apz/test/mochitest/apz_test_utils.js
@@ -400,8 +400,142 @@ function injectScript(aScript, aWindow =
         dump('Script [' + aScript + '] errored out\n');
         reject();
       };
       e.src = aScript;
       aWindow.document.getElementsByTagName('head')[0].appendChild(e);
     });
   };
 }
+
+// Compute some configuration information used for hit testing.
+// The computed information is cached to avoid recomputing it
+// each time this function is called.
+// The computed information is an object with three fields:
+//   utils: the nsIDOMWindowUtils instance for this window
+//   isWebRender: true if WebRender is enabled
+//   isWindow: true if the platform is Windows
+function getHitTestConfig() {
+  if (!("hitTestConfig" in window)) {
+    var utils = SpecialPowers.getDOMWindowUtils(window);
+    var isWebRender = (utils.layerManagerType == 'WebRender');
+    var isWindows = (getPlatform() == 'windows');
+    window.hitTestConfig = { utils, isWebRender, isWindows };
+  }
+  return window.hitTestConfig;
+}
+
+// Peform a compositor hit test at the given point and return the result.
+// The returned object has two fields:
+//   hitInfo: a combination of APZHitResultFlags
+//   scrollId: the view-id of the scroll frame that was hit
+function hitTest(point) {
+  var utils = getHitTestConfig().utils;
+  dump("Hit-testing point (" + point.x + ", " + point.y + ")\n");
+  utils.sendMouseEvent("MozMouseHittest", point.x, point.y, 0, 0, 0, true, 0, 0, true, true);
+  var data = utils.getCompositorAPZTestData();
+  ok(data.hitResults.length >= 1, "Expected at least one hit result in the APZTestData");
+  var result = data.hitResults[data.hitResults.length - 1];
+  return { hitInfo: result.hitResult, scrollId: result.scrollId };
+}
+
+// Symbolic constants used by hitTestScrollbar().
+var ScrollbarTrackLocation = {
+    START: 1,
+    END: 2
+};
+var LayerState = {
+    ACTIVE: 1,
+    INACTIVE: 2
+};
+
+// Perform a hit test on the scrollbar(s) of a scroll frame.
+// This function takes a single argument which is expected to be
+// an object with the following fields:
+//   element: The scroll frame to perform the hit test on.
+//   directions: The direction(s) of scrollbars to test.
+//     If directions.vertical is true, the vertical scrollbar will be tested.
+//     If directions.horizontal is true, the horizontal scrollbar will be tested.
+//     Both may be true in a single call (in which case two tests are performed).
+//   expectedScrollId: The scroll id that is expected to be hit.
+//   trackLocation: One of ScrollbarTrackLocation.{START, END}.
+//     Determines which end of the scrollbar track is targeted.
+//   expectThumb: Whether the scrollbar thumb is expected to be present
+//     at the targeted end of the scrollbar track.
+//   layerState: Whether the scroll frame is active or inactive.
+// The function performs the hit tests and asserts that the returned
+// hit test information is consistent with the passed parameters.
+// There is no return value.
+// Tests that use this function must set the pref
+// "layout.scrollbars.always-layerize-track".
+function hitTestScrollbar(params) {
+  var config = getHitTestConfig();
+
+  var elem = params.element;
+
+  var boundingClientRect = elem.getBoundingClientRect();
+
+  var verticalScrollbarWidth = boundingClientRect.width - elem.clientWidth;
+  var horizontalScrollbarHeight = boundingClientRect.height - elem.clientHeight;
+
+  // On windows, the scrollbar tracks have buttons on the end. When computing
+  // coordinates for hit-testing we need to account for this. We assume the
+  // buttons are square, and so can use the scrollbar width/height to estimate
+  // the size of the buttons
+  var scrollbarArrowButtonHeight = config.isWindows ? verticalScrollbarWidth : 0;
+  var scrollbarArrowButtonWidth = config.isWindows ? horizontalScrollbarHeight : 0;
+
+  // Compute the expected hit result flags.
+  // The direction flag (APZHitResultFlags.SCROLLBAR_VERTICAL) is added in
+  // later, for the vertical test only.
+  // The APZHitResultFlags.SCROLLBAR flag will be present regardless of whether
+  // the layer is active or inactive because we force layerization of scrollbar
+  // tracks. Unfortunately not forcing the layerization results in different
+  // behaviour on different platforms which makes testing harder.
+  var expectedHitInfo = APZHitResultFlags.VISIBLE | APZHitResultFlags.SCROLLBAR;
+  if (params.expectThumb) {
+    // WebRender will hit-test scroll thumbs even inside inactive scrollframes,
+    // because the hit-test is based on display items and we do in fact generate
+    // the display items for the scroll thumb. The user-observed behaviour is
+    // going to be unaffected because the dispatch-to-content flag will also be
+    // set on these thumbs so it's not like APZ will allow async-scrolling them
+    // before the scrollframe has been activated/layerized. In non-WebRender we
+    // do not generate the layers for thumbs on inactive scrollframes, so the
+    // hit test will be accordingly different.
+    expectedHitInfo |= APZHitResultFlags.DISPATCH_TO_CONTENT;
+    if (config.isWebRender || params.layerState == LayerState.ACTIVE) {
+        expectedHitInfo |= APZHitResultFlags.SCROLLBAR_THUMB;
+    }
+  }
+
+  var scrollframeMsg = (params.layerState == LayerState.ACTIVE)
+    ? "active scrollframe" : "inactive scrollframe";
+
+  // Hit-test the targeted areas, assuming we don't have overlay scrollbars
+  // with zero dimensions.
+  if (params.directions.vertical && verticalScrollbarWidth > 0) {
+    var verticalScrollbarPoint = {
+        x: boundingClientRect.right - (verticalScrollbarWidth / 2),
+        y: (params.trackLocation == ScrollbarTrackLocation.START)
+             ? (boundingClientRect.y + scrollbarArrowButtonHeight + 5)
+             : (boundingClientRect.bottom - horizontalScrollbarHeight - scrollbarArrowButtonHeight - 5)
+    };
+    var {hitInfo, scrollId} = hitTest(verticalScrollbarPoint);
+    is(hitInfo, expectedHitInfo | APZHitResultFlags.SCROLLBAR_VERTICAL,
+       scrollframeMsg + " - vertical scrollbar hit info");
+    is(scrollId, params.expectedScrollId,
+       scrollframeMsg + " - vertical scrollbar scrollid");
+  }
+
+  if (params.directions.horizontal && horizontalScrollbarHeight > 0) {
+    var horizontalScrollbarPoint = {
+        x: (params.trackLocation == ScrollbarTrackLocation.START)
+             ? (boundingClientRect.x + scrollbarArrowButtonWidth + 5)
+             : (boundingClientRect.right - verticalScrollbarWidth - scrollbarArrowButtonWidth - 5),
+        y: boundingClientRect.bottom - (horizontalScrollbarHeight / 2),
+    };
+    var {hitInfo, scrollId} = hitTest(horizontalScrollbarPoint);
+    is(hitInfo, expectedHitInfo,
+       scrollframeMsg + " - horizontal scrollbar hit info");
+    is(scrollId, params.expectedScrollId,
+       scrollframeMsg + " - horizontal scrollbar scrollid");
+  }
+}
--- a/gfx/layers/apz/test/mochitest/helper_hittest_basic.html
+++ b/gfx/layers/apz/test/mochitest/helper_hittest_basic.html
@@ -12,186 +12,126 @@
   <div id="contents" style="width: 500px; height: 500px; background-image: linear-gradient(blue,red)">
    <div id="apzaware" style="position: relative; width: 100px; height: 100px; top: 300px; background-color: red" onwheel="return false;"></div>
   </div>
  </div>
  <div id="make_root_scrollable" style="height: 5000px"></div>
 </body>
 <script type="application/javascript">
 
-var utils = SpecialPowers.getDOMWindowUtils(window);
-var isWebRender = (utils.layerManagerType == 'WebRender');
-var isWindows = (getPlatform() == 'windows');
-
 function centerOf(element) {
   var bounds = element.getBoundingClientRect();
   return { x: bounds.x + (bounds.width / 2), y: bounds.y + (bounds.height / 2) };
 }
 
-function hitTest(point) {
-  dump("Hit-testing point (" + point.x + ", " + point.y + ")\n");
-  utils.sendMouseEvent("MozMouseHittest", point.x, point.y, 0, 0, 0, true, 0, 0, true, true);
-  var data = utils.getCompositorAPZTestData();
-  ok(data.hitResults.length >= 1, "Expected at least one hit result in the APZTestData");
-  var result = data.hitResults[data.hitResults.length - 1];
-  return { hitInfo: result.hitResult, scrollId: result.scrollId };
-}
+function* test(testDriver) {
+  var config = getHitTestConfig();
+  var utils = config.utils;
 
-function* test(testDriver) {
   var scroller = document.getElementById('scroller');
   var apzaware = document.getElementById('apzaware');
 
-  var verticalScrollbarWidth = scroller.getBoundingClientRect().width - scroller.clientWidth;
-  var horizontalScrollbarHeight = scroller.getBoundingClientRect().height - scroller.clientHeight;
-
-  // On windows, the scrollbar tracks have buttons on the end. When computing
-  // coordinates for hit-testing we need to account for this. We assume the
-  // buttons are square, and so can use the scrollbar width/height to estimate
-  // the size of the buttons
-  var scrollbarArrowButtonHeight = isWindows ? verticalScrollbarWidth : 0;
-  var scrollbarArrowButtonWidth = isWindows ? horizontalScrollbarHeight : 0;
-
-  // WebRender will hit-test scroll thumbs even inside inactive scrollframes,
-  // because the hit-test is based on display items and we do in fact generate
-  // the display items for the scroll thumb. The user-observed behaviour is
-  // going to be unaffected because the dispatch-to-content flag will also be
-  // set on these thumbs so it's not like APZ will allow async-scrolling them
-  // before the scrollframe has been activated/layerized. In non-WebRender we
-  // do not generate the layers for thumbs on inactive scrollframes, so the
-  // hit test will be accordingly different.
-  var inactiveScrollframeThumbFlag = isWebRender ? APZHitResultFlags.SCROLLBAR_THUMB : 0;
-
   var {hitInfo, scrollId} = hitTest(centerOf(scroller));
   is(hitInfo, APZHitResultFlags.VISIBLE | APZHitResultFlags.DISPATCH_TO_CONTENT,
      "inactive scrollframe hit info");
   is(scrollId, utils.getViewId(document.scrollingElement),
      "inactive scrollframe scrollid");
 
   // The apz-aware div (which has a non-passive wheel listener) is not visible
   // and so the hit-test should just return the root scrollframe area that's
   // covering it
   var {hitInfo, scrollId} = hitTest(centerOf(apzaware));
   is(hitInfo, APZHitResultFlags.VISIBLE,
      "inactive scrollframe - apzaware block hit info");
   is(scrollId, utils.getViewId(document.scrollingElement),
      "inactive scrollframe - apzaware block scrollid");
 
-  // Hit-test against where the scrollthumbs should be, assuming we don't have
-  // overlay scrollbars with zero dimensions. Note that the scrollframe is still
-  // inactive so the result should just be the same dispatch-to-content area
-  // as before, but because we force layerization of scrollbar tracks the hit
-  // result will have HITTEST_SCROLLBAR set. Unfortunately not forcing the
-  // layerization results in different behaviour on different platforms which
-  // makes testing harder.
-  if (verticalScrollbarWidth > 0) {
-    var verticalScrollbarPoint = {
-        x: scroller.getBoundingClientRect().right - (verticalScrollbarWidth / 2),
-        y: scroller.getBoundingClientRect().y + scrollbarArrowButtonHeight + 5,
-    };
-    var {hitInfo, scrollId} = hitTest(verticalScrollbarPoint);
-    is(hitInfo, APZHitResultFlags.VISIBLE | APZHitResultFlags.DISPATCH_TO_CONTENT
-              | APZHitResultFlags.SCROLLBAR | APZHitResultFlags.SCROLLBAR_VERTICAL
-              | inactiveScrollframeThumbFlag,
-       "inactive scrollframe - vertical scrollbar hit info");
-    is(scrollId, utils.getViewId(document.scrollingElement),
-       "inactive scrollframe - vertical scrollbar scrollid");
-  }
-
-  if (horizontalScrollbarHeight > 0) {
-    var horizontalScrollbarPoint = {
-        x: scroller.getBoundingClientRect().x + scrollbarArrowButtonWidth + 5,
-        y: scroller.getBoundingClientRect().bottom - (horizontalScrollbarHeight / 2),
-    };
-    var {hitInfo, scrollId} = hitTest(horizontalScrollbarPoint);
-    is(hitInfo, APZHitResultFlags.VISIBLE | APZHitResultFlags.DISPATCH_TO_CONTENT
-              | APZHitResultFlags.SCROLLBAR | inactiveScrollframeThumbFlag,
-       "inactive scrollframe - horizontal scrollbar hit info");
-    is(scrollId, utils.getViewId(document.scrollingElement),
-       "inactive scrollframe - horizontal scrollbar scrollid");
-  }
-
+  // Hit test where the scroll thumbs should be.
+  hitTestScrollbar({
+    element: scroller,
+    directions: { vertical: true, horizontal: true },
+    expectedScrollId: utils.getViewId(document.scrollingElement),
+    trackLocation: ScrollbarTrackLocation.START,
+    expectThumb: true,
+    layerState: LayerState.INACTIVE
+  });
 
   // activate the scrollframe but keep the main-thread scroll position at 0.
   // also apply a async scroll offset in the y-direction such that the
   // scrollframe scrolls to the bottom of its range.
   utils.setDisplayPortForElement(0, 0, 500, 500, scroller, 1);
   yield waitForAllPaints(testDriver);
   var scrollY = scroller.scrollTopMax;
   utils.setAsyncScrollOffset(scroller, 0, scrollY);
-  if (isWebRender) {
+  if (config.isWebRender) {
     // Tick the refresh driver once to make sure the compositor has applied the
     // async scroll offset (for APZ hit-testing this doesn't matter, but for
     // WebRender hit-testing we need to make sure WR has the latest info).
     utils.advanceTimeAndRefresh(16);
     utils.restoreNormalRefresh();
   }
 
+  var scrollerViewId = utils.getViewId(scroller);
+
   // Now we again test the middle of the scrollframe, which is now active
   var {hitInfo, scrollId} = hitTest(centerOf(scroller));
   is(hitInfo, APZHitResultFlags.VISIBLE,
      "active scrollframe hit info");
-  is(scrollId, utils.getViewId(scroller),
+  is(scrollId, scrollerViewId,
      "active scrollframe scrollid");
 
   // Test the apz-aware block
   var apzawarePosition = centerOf(apzaware); // main thread position
   apzawarePosition.y -= scrollY; // APZ position
   var {hitInfo, scrollId} = hitTest(apzawarePosition);
   is(hitInfo, APZHitResultFlags.VISIBLE | APZHitResultFlags.DISPATCH_TO_CONTENT,
      "active scrollframe - apzaware block hit info");
-  is(scrollId, utils.getViewId(scroller),
+  is(scrollId, scrollerViewId,
      "active scrollframe - apzaware block scrollid");
 
   // Test the scrollbars. Note that this time the vertical scrollthumb is
   // going to be at the bottom of the track. We'll test both the top and the
   // bottom.
-  if (verticalScrollbarWidth > 0) {
-    // top of scrollbar track
-    var verticalScrollbarPoint = {
-        x: scroller.getBoundingClientRect().right - (verticalScrollbarWidth / 2),
-        y: scroller.getBoundingClientRect().top + scrollbarArrowButtonHeight + 5,
-    };
-    var {hitInfo, scrollId} = hitTest(verticalScrollbarPoint);
-    is(hitInfo, APZHitResultFlags.VISIBLE | APZHitResultFlags.SCROLLBAR
-              | APZHitResultFlags.SCROLLBAR_VERTICAL,
-       "active scrollframe - vertical scrollbar hit info");
-    is(scrollId, utils.getViewId(scroller),
-       "active scrollframe - vertical scrollbar scrollid");
 
-    // bottom of scrollbar track (scrollthumb)
-    verticalScrollbarPoint.y = scroller.getBoundingClientRect().bottom - horizontalScrollbarHeight - scrollbarArrowButtonHeight - 5;
-    var {hitInfo, scrollId} = hitTest(verticalScrollbarPoint);
-    is(hitInfo, APZHitResultFlags.VISIBLE | APZHitResultFlags.DISPATCH_TO_CONTENT
-              | APZHitResultFlags.SCROLLBAR | APZHitResultFlags.SCROLLBAR_THUMB
-              | APZHitResultFlags.SCROLLBAR_VERTICAL,
-       "active scrollframe - vertical scrollthumb hit info");
-    is(scrollId, utils.getViewId(scroller),
-       "active scrollframe - vertical scrollthumb scrollid");
-  }
-  if (horizontalScrollbarHeight > 0) {
-    // left part of scrollbar track (has scrollthumb)
-    var horizontalScrollbarPoint = {
-        x: scroller.getBoundingClientRect().x + scrollbarArrowButtonWidth + 5,
-        y: scroller.getBoundingClientRect().bottom - (horizontalScrollbarHeight / 2),
-    };
-    var {hitInfo, scrollId} = hitTest(horizontalScrollbarPoint);
-    is(hitInfo, APZHitResultFlags.VISIBLE | APZHitResultFlags.DISPATCH_TO_CONTENT
-              | APZHitResultFlags.SCROLLBAR | APZHitResultFlags.SCROLLBAR_THUMB,
-       "active scrollframe - horizontal scrollthumb hit info");
-    is(scrollId, utils.getViewId(scroller),
-       "active scrollframe - horizontal scrollthumb scrollid");
-
-    // right part of scrollbar track
-    horizontalScrollbarPoint.x = scroller.getBoundingClientRect().right - verticalScrollbarWidth - scrollbarArrowButtonWidth - 5;
-    var {hitInfo, scrollId} = hitTest(horizontalScrollbarPoint);
-    is(hitInfo, APZHitResultFlags.VISIBLE | APZHitResultFlags.SCROLLBAR,
-       "active scrollframe - horizontal scrollbar hit info");
-    is(scrollId, utils.getViewId(scroller),
-       "active scrollframe - horizontal scrollbar scrollid");
-  }
+  // top of scrollbar track
+  hitTestScrollbar({
+    element: scroller,
+    directions: { vertical: true },
+    expectedScrollId: scrollerViewId,
+    trackLocation: ScrollbarTrackLocation.START,
+    expectThumb: false,
+    layerState: LayerState.ACTIVE
+  });
+  // bottom of scrollbar track (scrollthumb)
+  hitTestScrollbar({
+    element: scroller,
+    directions: { vertical: true },
+    expectedScrollId: scrollerViewId,
+    trackLocation: ScrollbarTrackLocation.END,
+    expectThumb: true,
+    layerState: LayerState.ACTIVE
+  });
+  // left part of scrollbar track (has scrollthumb)
+  hitTestScrollbar({
+    element: scroller,
+    directions: { horizontal: true },
+    expectedScrollId: scrollerViewId,
+    trackLocation: ScrollbarTrackLocation.START,
+    expectThumb: true,
+    layerState: LayerState.ACTIVE
+  });
+  // right part of scrollbar track
+  hitTestScrollbar({
+    element: scroller,
+    directions: { horizontal: true },
+    expectedScrollId: scrollerViewId,
+    trackLocation: ScrollbarTrackLocation.END,
+    expectThumb: false,
+    layerState: LayerState.ACTIVE
+  });
 
   subtestDone();
 }
 
 waitUntilApzStable().then(runContinuation(test));
 
 </script>
 </html>
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_hittest_subframe_float.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Various tests to exercise the APZ hit-testing codepaths</title>
+  <script type="application/javascript" src="apz_test_utils.js"></script>
+  <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
+  <meta name="viewport" content="width=device-width"/>
+  <style>
+    #float {
+      float: left;
+    }
+    #subframe {
+      overflow: scroll;
+      height: 300px;
+    }
+    #subframe-content {
+      width: 300px;
+      height: 2000px;
+      background: cyan;
+    }
+    #make-root-scrollable {
+      height: 5000px;
+    }
+  </style>
+</head>
+<body>
+  <div id="float">
+    <div id="subframe">
+      <div id="subframe-content"></div>
+    </div>
+  </div>
+  <div id="make-root-scrollable"></div>
+</body>
+<script type="application/javascript">
+
+function* test(testDriver) {
+  var utils = getHitTestConfig().utils;
+
+  hitTestScrollbar({
+    element: document.getElementById('subframe'),
+    directions: { vertical: true },
+    expectedScrollId: utils.getViewId(document.scrollingElement),
+    trackLocation: ScrollbarTrackLocation.START,
+    expectThumb: true,
+    layerState: LayerState.INACTIVE
+  });
+
+  subtestDone();
+}
+
+waitUntilApzStable().then(runContinuation(test));
+
+</script>
+</html>
--- a/gfx/layers/apz/test/mochitest/mochitest.ini
+++ b/gfx/layers/apz/test/mochitest/mochitest.ini
@@ -15,16 +15,17 @@
     helper_click.html
     helper_div_pan.html
     helper_drag_click.html
     helper_drag_scroll.html
     helper_iframe_pan.html
     helper_iframe1.html
     helper_iframe2.html
     helper_hittest_basic.html
+    helper_hittest_subframe_float.html
     helper_key_scroll.html
     helper_long_tap.html
     helper_override_root.html
     helper_override_subdoc.html
     helper_scroll_inactive_perspective.html
     helper_scroll_inactive_zindex.html
     helper_scroll_on_position_fixed.html
     helper_scroll_over_scrollbar.html
--- a/gfx/layers/apz/test/mochitest/test_group_hittest.html
+++ b/gfx/layers/apz/test/mochitest/test_group_hittest.html
@@ -23,16 +23,17 @@ var prefs = [
   // APZ as a MouseInput so the hit result is recorded.
   ["test.events.async.enabled", true],
   // Turns on APZTestData logging which we use to obtain the hit test results.
   ["apz.test.logging_enabled", true]
 ];
 
 var subtests = [
   {'file': 'helper_hittest_basic.html', 'prefs': prefs},
+  {'file': 'helper_hittest_subframe_float.html', 'prefs': prefs},
 ];
 
 if (isApzEnabled()) {
   SimpleTest.waitForExplicitFinish();
   window.onload = function() {
     runSubtestsSeriallyInFreshWindows(subtests)
     .then(SimpleTest.finish);
   };
--- a/gfx/layers/ipc/LayersMessages.ipdlh
+++ b/gfx/layers/ipc/LayersMessages.ipdlh
@@ -236,16 +236,25 @@ struct Animation {
   float iterationStart;
   // This uses the NS_STYLE_ANIMATION_DIRECTION_* constants.
   uint8_t direction;
   // This uses dom::FillMode.
   uint8_t fillMode;
   nsCSSPropertyID property;
   AnimationData data;
   float playbackRate;
+  // When performing an asynchronous update to the playbackRate, |playbackRate|
+  // above is the updated playbackRate while |previousPlaybackRate| is the
+  // existing playbackRate. This is used by AnimationInfo to update the
+  // startTime based on the 'readyTime' (timestamp at the end of painting)
+  // and is not used beyond that point.
+  //
+  // It is set to numeric_limits<float>::quiet_NaN() when no asynchronous update
+  // to the playbackRate is being performed.
+  float previousPlaybackRate;
   // This is used in the transformed progress calculation.
   TimingFunction easingFunction;
   uint8_t iterationComposite;
   // True if the animation has a fixed current time (e.g. paused and
   // forward-filling animations).
   bool isNotPlaying;
   // The base style that animations should composite with. This is only set for
   // animations with a composite mode of additive or accumulate, and only for
--- a/gfx/layers/wr/StackingContextHelper.cpp
+++ b/gfx/layers/wr/StackingContextHelper.cpp
@@ -30,20 +30,16 @@ StackingContextHelper::StackingContextHe
                                              gfx::Matrix4x4* aTransformPtr,
                                              gfx::Matrix4x4* aPerspectivePtr,
                                              const gfx::CompositionOp& aMixBlendMode,
                                              bool aBackfaceVisible,
                                              bool aIsPreserve3D)
   : mBuilder(&aBuilder)
   , mScale(1.0f, 1.0f)
 {
-  if (aTransformPtr) {
-    mTransform = *aTransformPtr;
-  }
-
   // Compute scale for fallback rendering.
   gfx::Matrix transform2d;
   if (aBoundTransform && aBoundTransform->CanDraw2D(&transform2d)) {
     mInheritedTransform = transform2d * aParentSC.mInheritedTransform;
     mScale = mInheritedTransform.ScaleFactors(true);
   }
 
   mBuilder->PushStackingContext(wr::ToLayoutRect(aBounds),
@@ -52,17 +48,17 @@ StackingContextHelper::StackingContextHe
                                 aTransformPtr,
                                 aIsPreserve3D ? wr::TransformStyle::Preserve3D : wr::TransformStyle::Flat,
                                 aPerspectivePtr,
                                 wr::ToMixBlendMode(aMixBlendMode),
                                 aFilters,
                                 aBackfaceVisible);
 
   mAffectsClipPositioning =
-      !mTransform.IsIdentity() ||
+      (aTransformPtr && !aTransformPtr->IsIdentity()) ||
       (aBounds.TopLeft() != LayoutDevicePoint());
 }
 
 StackingContextHelper::~StackingContextHelper()
 {
   if (mBuilder) {
     mBuilder->PopStackingContext();
   }
--- a/gfx/layers/wr/StackingContextHelper.h
+++ b/gfx/layers/wr/StackingContextHelper.h
@@ -69,22 +69,20 @@ public:
   // Export the inherited scale
   gfx::Size GetInheritedScale() const { return mScale; }
 
   const gfx::Matrix& GetInheritedTransform() const
   {
     return mInheritedTransform;
   }
 
-  bool IsBackfaceVisible() const { return mTransform.IsBackfaceVisible(); }
   bool AffectsClipPositioning() const { return mAffectsClipPositioning; }
 
 private:
   wr::DisplayListBuilder* mBuilder;
-  gfx::Matrix4x4 mTransform;
   gfx::Size mScale;
   gfx::Matrix mInheritedTransform;
   bool mAffectsClipPositioning;
 };
 
 } // namespace layers
 } // namespace mozilla
 
--- a/intl/l10n/Localization.jsm
+++ b/intl/l10n/Localization.jsm
@@ -16,16 +16,17 @@
  */
 
 
 /* fluent@0.6.0 */
 
 /* eslint no-console: ["error", { allow: ["warn", "error"] }] */
 /* global console */
 
+const { XPCOMUtils } = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm", {});
 const { L10nRegistry } = ChromeUtils.import("resource://gre/modules/L10nRegistry.jsm", {});
 const LocaleService = Cc["@mozilla.org/intl/localeservice;1"].getService(Ci.mozILocaleService);
 const ObserverService = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
 
 /*
  * CachedIterable caches the elements yielded by an iterable.
  *
  * It can be used to iterate over an iterable many times without depleting the
@@ -249,21 +250,21 @@ class Localization {
    * @returns {Promise<string>}
    */
   async formatValue(id, args) {
     const [val] = await this.formatValues([[id, args]]);
     return val;
   }
 
   /**
-   * Register observers on events that will trigger cache invalidation
+   * Register weak observers on events that will trigger cache invalidation
    */
   registerObservers() {
-    ObserverService.addObserver(this, 'l10n:available-locales-changed', false);
-    ObserverService.addObserver(this, 'intl:requested-locales-changed', false);
+    ObserverService.addObserver(this, 'l10n:available-locales-changed', true);
+    ObserverService.addObserver(this, 'intl:requested-locales-changed', true);
   }
 
   /**
    * Unregister observers on events that will trigger cache invalidation
    */
   unregisterObservers() {
     ObserverService.removeObserver(this, 'l10n:available-locales-changed');
     ObserverService.removeObserver(this, 'intl:requested-locales-changed');
@@ -291,16 +292,20 @@ class Localization {
    * This method should be called when there's a reason to believe
    * that language negotiation or available resources changed.
    */
   onLanguageChange() {
     this.ctxs = new CachedIterable(this.generateMessages(this.resourceIds));
   }
 }
 
+Localization.prototype.QueryInterface = XPCOMUtils.generateQI([
+  Ci.nsISupportsWeakReference
+]);
+
 /**
  * Format the value of a message into a string.
  *
  * This function is passed as a method to `keysFromContext` and resolve
  * a value of a single L10n Entity using provided `MessageContext`.
  *
  * If the function fails to retrieve the entity, it will return an ID of it.
  * If formatting fails, it will return a partially resolved entity.
--- a/intl/l10n/l10n.js
+++ b/intl/l10n/l10n.js
@@ -1,36 +1,45 @@
 {
   const { DOMLocalization } =
     ChromeUtils.import("resource://gre/modules/DOMLocalization.jsm");
 
   /**
    * Polyfill for document.ready polyfill.
    * See: https://github.com/whatwg/html/issues/127 for details.
    *
+   * XXX: The callback is a temporary workaround for bug 1193394. Once Promises in Gecko
+   *      start beeing a microtask and stop pushing translation post-layout, we can
+   *      remove it and start using the returned Promise again.
+   *
+   * @param {Function} callback - function to be called when the document is ready.
    * @returns {Promise}
    */
-  function documentReady() {
+  function documentReady(callback) {
     if (document.contentType === 'application/vnd.mozilla.xul+xml') {
       // XUL
       return new Promise(
         resolve => document.addEventListener(
-          'MozBeforeInitialXULLayout', resolve, { once: true }
+          'MozBeforeInitialXULLayout', () => {
+            resolve(callback());
+          }, { once: true }
         )
       );
     }
 
     // HTML
     const rs = document.readyState;
     if (rs === 'interactive' || rs === 'completed') {
-      return Promise.resolve();
+      return Promise.resolve(callback());
     }
     return new Promise(
       resolve => document.addEventListener(
-        'readystatechange', resolve, { once: true }
+        'readystatechange', () => {
+          resolve(callback());
+        }, { once: true }
       )
     );
   }
 
   /**
    * Scans the `elem` for links with localization resources.
    *
    * @param {Element} elem
@@ -44,17 +53,17 @@
 
   const resourceIds = getResourceLinks(document.head || document);
 
   document.l10n = new DOMLocalization(window, resourceIds);
 
   // trigger first context to be fetched eagerly
   document.l10n.ctxs.touchNext();
 
-  document.l10n.ready = documentReady().then(() => {
+  document.l10n.ready = documentReady(() => {
     document.l10n.registerObservers();
     window.addEventListener('unload', () => {
       document.l10n.unregisterObservers();
     });
     document.l10n.connectRoot(document.documentElement);
     return document.l10n.translateRoots();
   });
 }
--- a/ipc/mscom/Interceptor.cpp
+++ b/ipc/mscom/Interceptor.cpp
@@ -684,16 +684,22 @@ Interceptor::GetInterceptorForIID(REFIID
   // (5) Now that we have this new COM interceptor, insert it into the map.
 
   { // Scope for lock
     MutexAutoLock lock(mInterceptorMapMutex);
     // We might have raced with another thread, so first check that we don't
     // already have an entry for this
     MapEntry* entry = Lookup(interceptorIid);
     if (entry && entry->mInterceptor) {
+      // Bug 1433046: Because of aggregation, the QI for |interceptor|
+      // AddRefed |this|, not |unkInterceptor|. Thus, releasing |unkInterceptor|
+      // will destroy the object. Before we do that, we must first release
+      // |interceptor|. Otherwise, |interceptor| would be invalidated when
+      // |unkInterceptor| is destroyed.
+      interceptor = nullptr;
       unkInterceptor = entry->mInterceptor;
     } else {
       // MapEntry has a RefPtr to unkInterceptor, OTOH we must not touch the
       // refcount for the target interface because we are just moving it into
       // the map and its refcounting might not be thread-safe.
       IUnknown* rawTargetInterface = targetInterface.release();
       mInterceptorMap.AppendElement(MapEntry(interceptorIid,
                                              unkInterceptor,
--- a/js/public/MemoryMetrics.h
+++ b/js/public/MemoryMetrics.h
@@ -771,17 +771,18 @@ struct CompartmentStats
     macro(Other,   MallocHeap, lazyArrayBuffersTable) \
     macro(Other,   MallocHeap, objectMetadataTable) \
     macro(Other,   MallocHeap, crossCompartmentWrappersTable) \
     macro(Other,   MallocHeap, savedStacksSet) \
     macro(Other,   MallocHeap, varNamesSet) \
     macro(Other,   MallocHeap, nonSyntacticLexicalScopesTable) \
     macro(Other,   MallocHeap, templateLiteralMap) \
     macro(Other,   MallocHeap, jitCompartment) \
-    macro(Other,   MallocHeap, privateData)
+    macro(Other,   MallocHeap, privateData) \
+    macro(Other,   MallocHeap, scriptCountsMap)
 
     CompartmentStats()
       : FOR_EACH_SIZE(ZERO_SIZE)
         classInfo(),
         extra(),
         allClasses(nullptr),
         notableClasses(),
         isTotals(true)
--- a/js/src/jit/IonCode.h
+++ b/js/src/jit/IonCode.h
@@ -678,16 +678,21 @@ struct IonBlockCounts
             strcpy(ncode, code);
             code_ = ncode;
         }
     }
 
     const char* code() const {
         return code_;
     }
+
+    size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
+        return mallocSizeOf(description_) + mallocSizeOf(successors_) +
+            mallocSizeOf(code_);
+    }
 };
 
 // Execution information for a compiled script which may persist after the
 // IonScript is destroyed, for use during profiling.
 struct IonScriptCounts
 {
   private:
     // Any previous invalidated compilation(s) for the script.
@@ -738,16 +743,35 @@ struct IonScriptCounts
 
     void setPrevious(IonScriptCounts* previous) {
         previous_ = previous;
     }
 
     IonScriptCounts* previous() const {
         return previous_;
     }
+
+    size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
+        size_t size = 0;
+        auto currCounts = this;
+        while (currCounts) {
+            const IonScriptCounts* currCount = currCounts;
+            currCounts = currCount->previous_;
+            size += currCount->sizeOfOneIncludingThis(mallocSizeOf);
+        }
+        return size;
+    }
+
+    size_t sizeOfOneIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
+        size_t size = mallocSizeOf(this) + mallocSizeOf(blocks_);
+        for (size_t i = 0; i < numBlocks_; i++)
+            blocks_[i].sizeOfExcludingThis(mallocSizeOf);
+        return size;
+    }
+
 };
 
 struct VMFunction;
 
 struct AutoFlushICache
 {
   private:
 #if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
--- a/js/src/vm/JSCompartment.cpp
+++ b/js/src/vm/JSCompartment.cpp
@@ -1378,24 +1378,26 @@ JSCompartment::addSizeOfIncludingThis(mo
                                       size_t* lazyArrayBuffersArg,
                                       size_t* objectMetadataTablesArg,
                                       size_t* crossCompartmentWrappersArg,
                                       size_t* savedStacksSet,
                                       size_t* varNamesSet,
                                       size_t* nonSyntacticLexicalEnvironmentsArg,
                                       size_t* templateLiteralMap,
                                       size_t* jitCompartment,
-                                      size_t* privateData)
+                                      size_t* privateData,
+                                      size_t* scriptCountsMapArg)
 {
     *compartmentObject += mallocSizeOf(this);
     objectGroups.addSizeOfExcludingThis(mallocSizeOf, tiAllocationSiteTables,
                                         tiArrayTypeTables, tiObjectTypeTables,
                                         compartmentTables);
     wasm.addSizeOfExcludingThis(mallocSizeOf, compartmentTables);
     *innerViewsArg += innerViews.sizeOfExcludingThis(mallocSizeOf);
+
     if (lazyArrayBuffers)
         *lazyArrayBuffersArg += lazyArrayBuffers->sizeOfIncludingThis(mallocSizeOf);
     if (objectMetadataTable)
         *objectMetadataTablesArg += objectMetadataTable->sizeOfIncludingThis(mallocSizeOf);
     *crossCompartmentWrappersArg += crossCompartmentWrappers.sizeOfExcludingThis(mallocSizeOf);
     *savedStacksSet += savedStacks_.sizeOfExcludingThis(mallocSizeOf);
     *varNamesSet += varNames_.sizeOfExcludingThis(mallocSizeOf);
     if (nonSyntacticLexicalEnvironments_)
@@ -1403,16 +1405,23 @@ JSCompartment::addSizeOfIncludingThis(mo
             nonSyntacticLexicalEnvironments_->sizeOfIncludingThis(mallocSizeOf);
     *templateLiteralMap += templateLiteralMap_.sizeOfExcludingThis(mallocSizeOf);
     if (jitCompartment_)
         *jitCompartment += jitCompartment_->sizeOfIncludingThis(mallocSizeOf);
 
     auto callback = runtime_->sizeOfIncludingThisCompartmentCallback;
     if (callback)
         *privateData += callback(mallocSizeOf, this);
+
+    if (scriptCountsMap) {
+        *scriptCountsMapArg += scriptCountsMap->sizeOfIncludingThis(mallocSizeOf);
+        for (auto r = scriptCountsMap->all(); !r.empty(); r.popFront()) {
+            *scriptCountsMapArg += r.front().value()->sizeOfIncludingThis(mallocSizeOf);
+        }
+    }
 }
 
 void
 JSCompartment::reportTelemetry()
 {
     // Only report telemetry for web content, not add-ons or chrome JS.
     if (creationOptions_.addonIdOrNull() || isSystem_)
         return;
--- a/js/src/vm/JSCompartment.h
+++ b/js/src/vm/JSCompartment.h
@@ -776,17 +776,18 @@ struct JSCompartment
                                 size_t* lazyArrayBuffers,
                                 size_t* objectMetadataTables,
                                 size_t* crossCompartmentWrappers,
                                 size_t* savedStacksSet,
                                 size_t* varNamesSet,
                                 size_t* nonSyntacticLexicalScopes,
                                 size_t* templateLiteralMap,
                                 size_t* jitCompartment,
-                                size_t* privateData);
+                                size_t* privateData,
+                                size_t* scriptCountsMapArg);
 
     // Object group tables and other state in the compartment.
     js::ObjectGroupCompartment   objectGroups;
 
 #ifdef JSGC_HASH_TABLE_CHECKS
     void checkWrapperMapAfterMovingGC();
     void checkScriptMapsAfterMovingGC();
 #endif
--- a/js/src/vm/JSScript.cpp
+++ b/js/src/vm/JSScript.cpp
@@ -1256,16 +1256,24 @@ js::PCCounts*
 ScriptCounts::getThrowCounts(size_t offset) {
     PCCounts searched = PCCounts(offset);
     PCCounts* elem = std::lower_bound(throwCounts_.begin(), throwCounts_.end(), searched);
     if (elem == throwCounts_.end() || elem->pcOffset() != offset)
         elem = throwCounts_.insert(elem, searched);
     return elem;
 }
 
+size_t
+ScriptCounts::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) {
+    return mallocSizeOf(this) +
+        pcCounts_.sizeOfExcludingThis(mallocSizeOf) +
+        throwCounts_.sizeOfExcludingThis(mallocSizeOf) +
+        ionCounts_->sizeOfIncludingThis(mallocSizeOf);
+}
+
 void
 JSScript::setIonScript(JSRuntime* rt, js::jit::IonScript* ionScript)
 {
     MOZ_ASSERT_IF(ionScript != ION_DISABLED_SCRIPT, !baselineScript()->hasPendingIonBuilder());
     if (hasIonScript())
         js::jit::IonScript::writeBarrierPre(zone(), ion);
     ion = ionScript;
     MOZ_ASSERT_IF(hasIonScript(), hasBaselineScript());
--- a/js/src/vm/JSScript.h
+++ b/js/src/vm/JSScript.h
@@ -210,16 +210,18 @@ class ScriptCounts
     // the immediate preceding PCCount, then this throw happened in the same
     // basic block.
     const PCCounts* getImmediatePrecedingThrowCounts(size_t offset) const;
 
     // Return the counter used to count the number of throws. Allocate it if
     // none exists yet. Returns null if the allocation failed.
     PCCounts* getThrowCounts(size_t offset);
 
+    size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
+
   private:
     friend class ::JSScript;
     friend struct ScriptAndCounts;
 
     // This sorted array is used to map an offset to the number of times a
     // branch got visited.
     PCCountsVector pcCounts_;
 
--- a/js/src/vm/MemoryMetrics.cpp
+++ b/js/src/vm/MemoryMetrics.cpp
@@ -358,17 +358,18 @@ StatsCompartmentCallback(JSContext* cx, 
                                         &cStats.lazyArrayBuffersTable,
                                         &cStats.objectMetadataTable,
                                         &cStats.crossCompartmentWrappersTable,
                                         &cStats.savedStacksSet,
                                         &cStats.varNamesSet,
                                         &cStats.nonSyntacticLexicalScopesTable,
                                         &cStats.templateLiteralMap,
                                         &cStats.jitCompartment,
-                                        &cStats.privateData);
+                                        &cStats.privateData,
+                                        &cStats.scriptCountsMap);
 }
 
 static void
 StatsArenaCallback(JSRuntime* rt, void* data, gc::Arena* arena,
                    JS::TraceKind traceKind, size_t thingSize)
 {
     RuntimeStats* rtStats = static_cast<StatsClosure*>(data)->rtStats;
 
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -1838,16 +1838,20 @@ ReportCompartmentStats(const JS::Compart
         cStats.jitCompartment,
         "The JIT compartment.");
 
     ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("private-data"),
         cStats.privateData,
         "Extra data attached to the compartment by XPConnect, including "
         "its wrapped-js.");
 
+    ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("script-counts-map"),
+        cStats.scriptCountsMap,
+        "Profiling-related information for scripts.");
+
     if (sundriesGCHeap > 0) {
         // We deliberately don't use ZCREPORT_GC_BYTES here.
         REPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("sundries/gc-heap"),
             sundriesGCHeap,
             "The sum of all 'gc-heap' measurements that are too small to be "
             "worth showing individually.");
     }
 
--- a/layout/base/PositionedEventTargeting.cpp
+++ b/layout/base/PositionedEventTargeting.cpp
@@ -12,16 +12,17 @@
 #include "mozilla/Preferences.h"
 #include "nsLayoutUtils.h"
 #include "nsGkAtoms.h"
 #include "nsFontMetrics.h"
 #include "nsPrintfCString.h"
 #include "mozilla/dom/Element.h"
 #include "nsRegion.h"
 #include "nsDeviceContext.h"
+#include "nsIContentInlines.h"
 #include "nsIFrame.h"
 #include <algorithm>
 #include "LayersLogging.h"
 
 // If debugging this code you may wish to enable this logging, and also
 // uncomment the DumpFrameTree call near the bottom of the file.
 #define PET_LOG(...)
 // #define PET_LOG(...) printf_stderr("PET: " __VA_ARGS__);
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -9206,68 +9206,70 @@ PresShell::WindowSizeMoveDone()
 #ifdef MOZ_XUL
 /*
  * It's better to add stuff to the |DidSetStyleContext| method of the
  * relevant frames than adding it here.  These methods should (ideally,
  * anyway) go away.
  */
 
 // Return value says whether to walk children.
-typedef bool (* frameWalkerFn)(nsIFrame *aFrame, void *aClosure);
+typedef bool (*frameWalkerFn)(nsIFrame* aFrame);
 
 static bool
-ReResolveMenusAndTrees(nsIFrame *aFrame, void *aClosure)
+ReResolveMenusAndTrees(nsIFrame* aFrame)
 {
   // Trees have a special style cache that needs to be flushed when
   // the theme changes.
-  nsTreeBodyFrame *treeBody = do_QueryFrame(aFrame);
+  nsTreeBodyFrame* treeBody = do_QueryFrame(aFrame);
   if (treeBody)
     treeBody->ClearStyleAndImageCaches();
 
   // We deliberately don't re-resolve style on a menu's popup
   // sub-content, since doing so slows menus to a crawl.  That means we
   // have to special-case them on a skin switch, and ensure that the
   // popup frames just get destroyed completely.
   nsMenuFrame* menu = do_QueryFrame(aFrame);
   if (menu)
     menu->CloseMenu(true);
   return true;
 }
 
 static bool
-ReframeImageBoxes(nsIFrame *aFrame, void *aClosure)
-{
-  nsStyleChangeList *list = static_cast<nsStyleChangeList*>(aClosure);
+ReframeImageBoxes(nsIFrame* aFrame)
+{
   if (aFrame->IsImageBoxFrame()) {
-    list->AppendChange(aFrame, aFrame->GetContent(),
-                       nsChangeHint_ReconstructFrame);
+    aFrame->PresContext()->RestyleManager()->PostRestyleEvent(
+        aFrame->GetContent()->AsElement(),
+        nsRestyleHint(0),
+        nsChangeHint_ReconstructFrame);
     return false; // don't walk descendants
   }
   return true; // walk descendants
 }
 
 static void
-WalkFramesThroughPlaceholders(nsPresContext *aPresContext, nsIFrame *aFrame,
-                              frameWalkerFn aFunc, void *aClosure)
-{
-  bool walkChildren = (*aFunc)(aFrame, aClosure);
+WalkFramesThroughPlaceholders(nsPresContext* aPresContext,
+                              nsIFrame* aFrame,
+                              frameWalkerFn aFunc)
+{
+  bool walkChildren = (*aFunc)(aFrame);
   if (!walkChildren)
     return;
 
   nsIFrame::ChildListIterator lists(aFrame);
   for (; !lists.IsDone(); lists.Next()) {
     nsFrameList::Enumerator childFrames(lists.CurrentList());
     for (; !childFrames.AtEnd(); childFrames.Next()) {
       nsIFrame* child = childFrames.get();
       if (!(child->GetStateBits() & NS_FRAME_OUT_OF_FLOW)) {
         // only do frames that are in flow, and recur through the
         // out-of-flows of placeholders.
         WalkFramesThroughPlaceholders(aPresContext,
                                       nsPlaceholderFrame::GetRealFrameFor(child),
-                                      aFunc, aClosure);
+                                      aFunc);
       }
     }
   }
 }
 #endif
 
 NS_IMETHODIMP
 PresShell::Observe(nsISupports* aSubject,
@@ -9276,47 +9278,28 @@ PresShell::Observe(nsISupports* aSubject
 {
   if (mIsDestroying) {
     NS_WARNING("our observers should have been unregistered by now");
     return NS_OK;
   }
 
 #ifdef MOZ_XUL
   if (!nsCRT::strcmp(aTopic, "chrome-flush-skin-caches")) {
-    nsIFrame *rootFrame = mFrameConstructor->GetRootFrame();
     // Need to null-check because "chrome-flush-skin-caches" can happen
     // at interesting times during startup.
-    if (rootFrame) {
+    if (nsIFrame* rootFrame = mFrameConstructor->GetRootFrame()) {
       NS_ASSERTION(mViewManager, "View manager must exist");
 
-      AutoWeakFrame weakRoot(rootFrame);
-      // Have to make sure that the content notifications are flushed before we
-      // start messing with the frame model; otherwise we can get content doubling.
-      mDocument->FlushPendingNotifications(FlushType::ContentAndNotify);
-
-      if (weakRoot.IsAlive()) {
-        WalkFramesThroughPlaceholders(mPresContext, rootFrame,
-                                      &ReResolveMenusAndTrees, nullptr);
-
-        // Because "chrome:" URL equality is messy, reframe image box
-        // frames (hack!).
-        nsStyleChangeList changeList(mPresContext->StyleSet()->BackendType());
-        WalkFramesThroughPlaceholders(mPresContext, rootFrame,
-                                      ReframeImageBoxes, &changeList);
-        // Mark ourselves as not safe to flush while we're doing frame
-        // construction.
-        {
-          nsAutoScriptBlocker scriptBlocker;
-          ++mChangeNestCount;
-          RestyleManager* restyleManager = mPresContext->RestyleManager();
-          restyleManager->ProcessRestyledFrames(changeList);
-          restyleManager->FlushOverflowChangedTracker();
-          --mChangeNestCount;
-        }
-      }
+      WalkFramesThroughPlaceholders(
+          mPresContext, rootFrame, ReResolveMenusAndTrees);
+
+      // Because "chrome:" URL equality is messy, reframe image box
+      // frames (hack!).
+      WalkFramesThroughPlaceholders(
+          mPresContext, rootFrame, ReframeImageBoxes);
     }
     return NS_OK;
   }
 #endif
 
   if (!nsCRT::strcmp(aTopic, "memory-pressure")) {
     if (!AssumeAllFramesVisible() && mPresContext->IsRootContentDocument()) {
       DoUpdateApproximateFrameVisibility(/* aRemoveOnly = */ true);
--- a/layout/base/RestyleManager.cpp
+++ b/layout/base/RestyleManager.cpp
@@ -9,16 +9,17 @@
 
 #include "Layers.h"
 #include "LayerAnimationInfo.h" // For LayerAnimationInfo::sRecords
 #include "mozilla/StyleSetHandleInlines.h"
 #include "nsAnimationManager.h"
 #include "nsCSSFrameConstructor.h"
 #include "nsCSSRendering.h"
 #include "nsIFrame.h"
+#include "nsIFrameInlines.h"
 #include "nsIPresShellInlines.h"
 #include "nsPlaceholderFrame.h"
 #include "nsStyleChangeList.h"
 #include "nsStyleUtil.h"
 #include "StickyScrollContainer.h"
 #include "mozilla/EffectSet.h"
 #include "mozilla/ViewportFrame.h"
 #include "SVGObserverUtils.h"
--- a/layout/base/RestyleTracker.cpp
+++ b/layout/base/RestyleTracker.cpp
@@ -8,18 +8,20 @@
  * A class which manages pending restyles.  This handles keeping track
  * of what nodes restyles need to happen on and so forth.
  */
 
 #include "RestyleTracker.h"
 
 #include "GeckoProfiler.h"
 #include "nsFrameManager.h"
+#include "nsIContentInlines.h"
 #include "nsIDocument.h"
 #include "nsStyleChangeList.h"
+#include "mozilla/dom/ElementInlines.h"
 #include "mozilla/GeckoRestyleManager.h"
 #include "RestyleTrackerInlines.h"
 #include "nsTransitionManager.h"
 #include "mozilla/AutoRestyleTimelineMarker.h"
 
 namespace mozilla {
 
 #ifdef RESTYLE_LOGGING
--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -14,16 +14,17 @@
 #include "mozilla/ServoStyleContext.h"
 #include "mozilla/ServoStyleContextInlines.h"
 #include "mozilla/Unused.h"
 #include "mozilla/ViewportFrame.h"
 #include "mozilla/dom/ChildIterator.h"
 #include "mozilla/dom/ElementInlines.h"
 #include "nsBlockFrame.h"
 #include "nsBulletFrame.h"
+#include "nsIFrameInlines.h"
 #include "nsImageFrame.h"
 #include "nsPlaceholderFrame.h"
 #include "nsContentUtils.h"
 #include "nsCSSFrameConstructor.h"
 #include "nsPrintfCString.h"
 #include "nsRefreshDriver.h"
 #include "nsStyleChangeList.h"
 
new file mode 100644
--- /dev/null
+++ b/layout/base/crashtests/1429962.html
@@ -0,0 +1,12 @@
+<script>
+function go(){
+  let o=document.getElementById('a');
+  let n=document.createElement('li');
+  o.parentNode.replaceChild(n,o);
+}
+document.addEventListener("DOMContentLoaded", go);
+</script>
+<fieldset>
+<canvas id='a'></canvas>
+<footer>
+
--- a/layout/base/crashtests/crashtests.list
+++ b/layout/base/crashtests/crashtests.list
@@ -518,8 +518,9 @@ load 1411138.html
 load 1419762.html
 load 1420533.html
 load 1425959.html
 load 1425893.html
 load 1428353.html
 pref(dom.webcomponents.shadowdom.enabled,true) load 1429088.html
 load 1429961.html
 load 1435015.html
+load 1429962.html
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -7168,41 +7168,43 @@ MaybeGetListBoxBodyFrame(nsIContent* aCo
 void
 nsCSSFrameConstructor::AddTextItemIfNeeded(nsFrameConstructorState& aState,
                                            const InsertionPoint& aInsertion,
                                            nsIContent* aPossibleTextContent,
                                            FrameConstructionItemList& aItems)
 {
   NS_PRECONDITION(aPossibleTextContent, "Must have node");
   if (!aPossibleTextContent->IsNodeOfType(nsINode::eTEXT) ||
-      !aPossibleTextContent->HasFlag(NS_CREATE_FRAME_IF_NON_WHITESPACE)) {
-    // Not text, or not suppressed due to being all-whitespace (if it
-    // were being suppressed, it would have the
-    // NS_CREATE_FRAME_IF_NON_WHITESPACE flag)
+      !aPossibleTextContent->HasFlag(NS_CREATE_FRAME_IF_NON_WHITESPACE) ||
+      aPossibleTextContent->HasFlag(NODE_NEEDS_FRAME)) {
+    // Not text, or not suppressed due to being all-whitespace (if it were being
+    // suppressed, it would have the NS_CREATE_FRAME_IF_NON_WHITESPACE flag), or
+    // going to be reframed anyway.
     return;
   }
-  NS_ASSERTION(!aPossibleTextContent->GetPrimaryFrame(),
-               "Text node has a frame and NS_CREATE_FRAME_IF_NON_WHITESPACE");
+  MOZ_ASSERT(!aPossibleTextContent->GetPrimaryFrame(),
+             "Text node has a frame and NS_CREATE_FRAME_IF_NON_WHITESPACE");
   AddFrameConstructionItems(aState, aPossibleTextContent, false,
                             aInsertion, aItems);
 }
 
 void
 nsCSSFrameConstructor::ReframeTextIfNeeded(nsIContent* aParentContent,
                                            nsIContent* aContent)
 {
   if (!aContent->IsNodeOfType(nsINode::eTEXT) ||
-      !aContent->HasFlag(NS_CREATE_FRAME_IF_NON_WHITESPACE)) {
-    // Not text, or not suppressed due to being all-whitespace (if it
-    // were being suppressed, it would have the
-    // NS_CREATE_FRAME_IF_NON_WHITESPACE flag)
+      !aContent->HasFlag(NS_CREATE_FRAME_IF_NON_WHITESPACE) ||
+      aContent->HasFlag(NODE_NEEDS_FRAME)) {
+    // Not text, or not suppressed due to being all-whitespace (if it were being
+    // suppressed, it would have the NS_CREATE_FRAME_IF_NON_WHITESPACE flag), or
+    // going to be reframed anyway.
     return;
   }
-  NS_ASSERTION(!aContent->GetPrimaryFrame(),
-               "Text node has a frame and NS_CREATE_FRAME_IF_NON_WHITESPACE");
+  MOZ_ASSERT(!aContent->GetPrimaryFrame(),
+             "Text node has a frame and NS_CREATE_FRAME_IF_NON_WHITESPACE");
   ContentInserted(aParentContent, aContent, nullptr, InsertionKind::Async);
 }
 
 #ifdef DEBUG
 void
 nsCSSFrameConstructor::CheckBitsForLazyFrameConstruction(nsIContent* aParent)
 {
   // If we hit a node with no primary frame, or the NODE_NEEDS_FRAME bit set
--- a/layout/generic/MathMLTextRunFactory.cpp
+++ b/layout/generic/MathMLTextRunFactory.cpp
@@ -8,16 +8,18 @@
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/BinarySearch.h"
 
 #include "nsStyleConsts.h"
 #include "nsTextFrameUtils.h"
 #include "nsFontMetrics.h"
 #include "nsDeviceContext.h"
+#include "nsStyleContext.h"
+#include "nsStyleContextInlines.h"
 #include "nsUnicodeScriptCodes.h"
 
 using namespace mozilla;
 
 /*
   Entries for the mathvariant lookup tables.  mKey represents the Unicode
   character to be transformed and is used for searching the tables.
   mReplacement represents the mapped mathvariant Unicode character.
--- a/layout/generic/nsBlockFrame.cpp
+++ b/layout/generic/nsBlockFrame.cpp
@@ -48,16 +48,17 @@
 #include "nsCSSAnonBoxes.h"
 #include "nsCSSFrameConstructor.h"
 #include "TextOverflow.h"
 #include "nsIFrameInlines.h"
 #include "CounterStyleManager.h"
 #include "nsISelection.h"
 #include "mozilla/dom/HTMLDetailsElement.h"
 #include "mozilla/dom/HTMLSummaryElement.h"
+#include "mozilla/RestyleManagerInlines.h"
 #include "mozilla/ServoRestyleManager.h"
 #include "mozilla/ServoStyleSet.h"
 #include "mozilla/StyleSetHandle.h"
 #include "mozilla/StyleSetHandleInlines.h"
 #include "mozilla/Telemetry.h"
 
 #include "nsBidiPresUtils.h"
 
--- a/layout/generic/nsFrameSetFrame.cpp
+++ b/layout/generic/nsFrameSetFrame.cpp
@@ -16,16 +16,17 @@
 #include "mozilla/Likely.h"
 
 #include "nsGenericHTMLElement.h"
 #include "nsAttrValueInlines.h"
 #include "nsLeafFrame.h"
 #include "nsContainerFrame.h"
 #include "nsLayoutUtils.h"
 #include "nsPresContext.h"
+#include "nsIContentInlines.h"
 #include "nsIPresShell.h"
 #include "nsGkAtoms.h"
 #include "nsStyleConsts.h"
 #include "nsStyleContext.h"
 #include "nsHTMLParts.h"
 #include "nsNameSpaceManager.h"
 #include "nsCSSAnonBoxes.h"
 #include "mozilla/StyleSetHandle.h"
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -4947,17 +4947,17 @@ ScrollFrameHelper::PostScrollEvent()
   // The ScrollEvent constructor registers itself with the refresh driver.
   mScrollEvent = new ScrollEvent(this);
 }
 
 NS_IMETHODIMP
 ScrollFrameHelper::AsyncScrollPortEvent::Run()
 {
   if (mHelper) {
-    mHelper->mOuter->PresContext()->GetPresShell()->
+    mHelper->mOuter->PresContext()->Document()->
       FlushPendingNotifications(FlushType::InterruptibleLayout);
   }
   return mHelper ? mHelper->FireScrollPortEvent() : NS_OK;
 }
 
 bool
 nsXULScrollFrame::AddHorizontalScrollbar(nsBoxLayoutState& aState, bool aOnBottom)
 {
--- a/layout/generic/nsImageMap.cpp
+++ b/layout/generic/nsImageMap.cpp
@@ -15,16 +15,17 @@
 #include "mozilla/UniquePtr.h"
 #include "nsString.h"
 #include "nsReadableUtils.h"
 #include "nsPresContext.h"
 #include "nsNameSpaceManager.h"
 #include "nsGkAtoms.h"
 #include "nsImageFrame.h"
 #include "nsCoord.h"
+#include "nsIContentInlines.h"
 #include "nsIScriptError.h"
 #include "nsIStringBundle.h"
 #include "nsContentUtils.h"
 #include "ImageLayers.h"
 
 #ifdef ACCESSIBILITY
 #include "nsAccessibilityService.h"
 #endif
--- a/layout/generic/nsTextRunTransformations.cpp
+++ b/layout/generic/nsTextRunTransformations.cpp
@@ -6,16 +6,17 @@
 
 #include "nsTextRunTransformations.h"
 
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Move.h"
 
 #include "nsGkAtoms.h"
 #include "nsStyleConsts.h"
+#include "nsStyleContextInlines.h"
 #include "nsUnicharUtils.h"
 #include "nsUnicodeProperties.h"
 #include "nsSpecialCasingData.h"
 #include "mozilla/gfx/2D.h"
 #include "nsTextFrameUtils.h"
 #include "nsIPersistentProperties2.h"
 #include "GreekCasing.h"
 #include "IrishCasing.h"
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -566,17 +566,21 @@ AddAnimationForProperty(nsIFrame* aFrame
   animation->delay() = timing.Delay();
   animation->endDelay() = timing.EndDelay();
   animation->duration() = computedTiming.mDuration;
   animation->iterations() = computedTiming.mIterations;
   animation->iterationStart() = computedTiming.mIterationStart;
   animation->direction() = static_cast<uint8_t>(timing.Direction());
   animation->fillMode() = static_cast<uint8_t>(computedTiming.mFill);
   animation->property() = aProperty.mProperty;
-  animation->playbackRate() = aAnimation->PlaybackRate();
+  animation->playbackRate() = aAnimation->CurrentOrPendingPlaybackRate();
+  animation->previousPlaybackRate() =
+    aAnimation->HasPendingPlaybackRate()
+      ? aAnimation->PlaybackRate()
+      : std::numeric_limits<float>::quiet_NaN();
   animation->data() = aData;
   animation->easingFunction() = ToTimingFunction(timing.TimingFunction());
   animation->iterationComposite() =
     static_cast<uint8_t>(aAnimation->GetEffect()->
                          AsKeyframeEffect()->IterationComposite());
   animation->isNotPlaying() = !aAnimation->IsPlaying();
 
   TransformReferenceBox refBox(aFrame);
--- a/layout/style/GeckoStyleContext.cpp
+++ b/layout/style/GeckoStyleContext.cpp
@@ -11,16 +11,17 @@
 #include "nsStyleConsts.h"
 #include "nsStyleStruct.h"
 #include "nsPresContext.h"
 #include "nsRuleNode.h"
 #include "nsStyleContextInlines.h"
 #include "nsIFrame.h"
 #include "nsLayoutUtils.h"
 #include "mozilla/ReflowInput.h"
+#include "mozilla/StyleSetHandleInlines.h"
 #include "RubyUtils.h"
 
 using namespace mozilla;
 
 #ifdef DEBUG
 // Whether to perform expensive assertions in the nsStyleContext destructor.
 static bool sExpensiveStyleStructAssertionsEnabled;
 
--- a/layout/style/ServoKeyframeRule.cpp
+++ b/layout/style/ServoKeyframeRule.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/ServoKeyframeRule.h"
 
+#include "mozilla/DeclarationBlockInlines.h"
 #include "mozilla/ServoDeclarationBlock.h"
 #include "nsDOMCSSDeclaration.h"
 #include "mozAutoDocUpdate.h"
 
 namespace mozilla {
 
 // -------------------------------------------
 // ServoKeyframeDeclaration
--- a/layout/style/StyleSheet.cpp
+++ b/layout/style/StyleSheet.cpp
@@ -1,26 +1,31 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/StyleSheet.h"
 
+#include "nsStyleContext.h"
+#include "nsStyleContextInlines.h"
 #include "mozilla/css/GroupRule.h"
 #include "mozilla/dom/CSSImportRule.h"
 #include "mozilla/dom/CSSRuleList.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/MediaList.h"
 #include "mozilla/dom/ShadowRoot.h"
 #include "mozilla/dom/ShadowRootBinding.h"
+#include "mozilla/GeckoStyleContext.h"
 #include "mozilla/ServoCSSRuleList.h"
+#include "mozilla/ServoStyleContext.h"
 #include "mozilla/ServoStyleSet.h"
 #include "mozilla/ServoStyleSheet.h"
+#include "mozilla/StyleSetHandleInlines.h"
 #include "mozilla/StyleSheetInlines.h"
 #ifdef MOZ_OLD_STYLE
 #include "mozilla/CSSStyleSheet.h"
 #endif
 
 #include "mozAutoDocUpdate.h"
 #include "NullPrincipal.h"
 
--- a/layout/style/nsRuleData.cpp
+++ b/layout/style/nsRuleData.cpp
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsRuleData.h"
 
 #include "nsAttrValueInlines.h"
 #include "nsCSSParser.h"
 #include "nsPresContext.h"
+#include "nsStyleContextInlines.h"
 #include "mozilla/GeckoStyleContext.h"
 #include "mozilla/Poison.h"
 #include <stdint.h>
 
 using namespace mozilla;
 
 inline size_t
 nsRuleData::GetPoisonOffset()
deleted file mode 100644
--- a/layout/tools/layout-debug/ui/content/layoutdebug-overlay.xul
+++ /dev/null
@@ -1,24 +0,0 @@
-<?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/. -->
-
-
-<!DOCTYPE window SYSTEM "chrome://layoutdebug/locale/layoutdebug-overlay.dtd" >
-
-<overlay id="layoutdebugTaskMenuID"
-         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
-
-<!-- Firefox -->
-<menupopup id="menu_ToolsPopup">
-  <menuitem label="&ldbCmd.label;"
-            accesskey="&ldbCmd.accesskey;"
-            insertafter="javascriptConsole"
-            oncommand="toOpenWindowByType('mozapp:layoutdebug',
-                                          'chrome://layoutdebug/content/');"/>
-</menupopup>
-
-</overlay>
--- a/layout/tools/layout-debug/ui/jar.mn
+++ b/layout/tools/layout-debug/ui/jar.mn
@@ -1,13 +1,10 @@
 # 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/.
 
 layoutdebug.jar:
 % content layoutdebug %content/layoutdebug/
-% overlay chrome://browser/content/browser.xul chrome://layoutdebug/content/layoutdebug-overlay.xul
 % locale layoutdebug en-US %locale/en-US/layoutdebug/
   content/layoutdebug/layoutdebug.xul              (content/layoutdebug.xul)
   content/layoutdebug/layoutdebug.js               (content/layoutdebug.js)
-  content/layoutdebug/layoutdebug-overlay.xul      (content/layoutdebug-overlay.xul)
   locale/en-US/layoutdebug/layoutdebug.dtd         (locale/en-US/layoutdebug.dtd)
-  locale/en-US/layoutdebug/layoutdebug-overlay.dtd (locale/en-US/layoutdebug-overlay.dtd)
deleted file mode 100644
--- a/layout/tools/layout-debug/ui/locale/en-US/layoutdebug-overlay.dtd
+++ /dev/null
@@ -1,8 +0,0 @@
-<!--
-   -
-   - This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!ENTITY ldbCmd.label                   "Layout Debugger">
-<!ENTITY ldbCmd.accesskey               "L">
--- a/layout/xul/nsXULLabelFrame.h
+++ b/layout/xul/nsXULLabelFrame.h
@@ -7,20 +7,16 @@
 /* derived class of nsBlockFrame used for xul:label elements */
 
 #ifndef nsXULLabelFrame_h_
 #define nsXULLabelFrame_h_
 
 #include "mozilla/Attributes.h"
 #include "nsBlockFrame.h"
 
-#ifndef MOZ_XUL
-#error "This file should not be included"
-#endif
-
 class nsXULLabelFrame final : public nsBlockFrame
 {
 public:
   NS_DECL_FRAMEARENA_HELPERS(nsXULLabelFrame)
 
   friend nsIFrame* NS_NewXULLabelFrame(nsIPresShell* aPresShell,
                                        nsStyleContext *aContext);
 
--- a/media/mp4parse-rust/mp4parse-cargo.patch
+++ b/media/mp4parse-rust/mp4parse-cargo.patch
@@ -1,50 +1,55 @@
 diff --git a/media/libstagefright/binding/mp4parse/Cargo.toml b/media/libstagefright/binding/mp4parse/Cargo.toml
 index ff9422c..814c4c6 100644
 --- a/media/libstagefright/binding/mp4parse/Cargo.toml
 +++ b/media/libstagefright/binding/mp4parse/Cargo.toml
-@@ -20,18 +20,12 @@ exclude = [
+@@ -19,13 +19,9 @@
+   "*.mp4",
  ]
  
 -[badges]
 -travis-ci = { repository = "https://github.com/mozilla/mp4parse-rust" }
  
  [dependencies]
- byteorder = "1.0.0"
+ byteorder = "1.2.1"
 -afl = { version = "0.3", optional = true }
 -abort_on_panic = { version = "1.0.0", optional = true }
  bitreader = { version = "0.3.0" }
- num-traits = "0.1.37"
+ num-traits = "0.2.0"
  mp4parse_fallible = { version = "0.0.1", optional = true }
+@@ -33,6 +29,3 @@
  
  [dev-dependencies]
  test-assembler = "0.1.2"
- 
+-
 -[features]
 -fuzz = ["afl", "abort_on_panic"]
+
 diff --git a/media/libstagefright/binding/mp4parse_capi/Cargo.toml b/media/libstagefright/binding/mp4parse_capi/Cargo.toml
 index a30e045..a965f06 100644
 --- a/media/libstagefright/binding/mp4parse_capi/Cargo.toml
 +++ b/media/libstagefright/binding/mp4parse_capi/Cargo.toml
-@@ -18,20 +18,13 @@ exclude = [
+@@ -18,22 +18,13 @@
    "*.mp4",
  ]
  
 -[badges]
 -travis-ci = { repository = "https://github.com/mozilla/mp4parse-rust" }
 +build = false
  
  [dependencies]
- byteorder = "1.0.0"
+ byteorder = "1.2.1"
+-env_logger = "0.5.3"
+ log = "0.4"
  
  # To enable fallible memory allocation, add 'features = ["mp4parse_fallible"]'
  # in mp4parse brace.
--mp4parse = {version = "0.9.1", path = "../mp4parse"}
-+mp4parse = {version = "0.9.1", path = "../mp4parse", features = ["mp4parse_fallible"]}
- num-traits = "0.1.37"
- 
+-mp4parse = {version = "0.10.0", path = "../mp4parse"}
++mp4parse = {version = "0.10.0", path = "../mp4parse", features = ["mp4parse_fallible"]}
+ num-traits = "0.2.0"
+-
 -[build-dependencies]
--cbindgen = "0.3.1"
+-cbindgen = "0.4.3"
 -
 -[features]
 -fuzz = ["mp4parse/fuzz"]
 -
--- a/media/mp4parse-rust/mp4parse.h
+++ b/media/mp4parse-rust/mp4parse.h
@@ -11,27 +11,28 @@ extern "C" {
 
 // THIS FILE IS AUTOGENERATED BY mp4parse_capi/build.rs - DO NOT EDIT
 
 #include <stdint.h>
 #include <stdlib.h>
 #include <stdbool.h>
 
 typedef enum {
-  MP4PARSE_CODEC_UNKNOWN = 0,
-  MP4PARSE_CODEC_AAC = 1,
-  MP4PARSE_CODEC_FLAC = 2,
-  MP4PARSE_CODEC_OPUS = 3,
-  MP4PARSE_CODEC_AVC = 4,
-  MP4PARSE_CODEC_VP9 = 5,
-  MP4PARSE_CODEC_MP3 = 6,
-  MP4PARSE_CODEC_MP4V = 7,
-  MP4PARSE_CODEC_JPEG = 8,
-  MP4PARSE_CODEC_AC3 = 9,
-  MP4PARSE_CODEC_EC3 = 10,
+  MP4PARSE_CODEC_UNKNOWN,
+  MP4PARSE_CODEC_AAC,
+  MP4PARSE_CODEC_FLAC,
+  MP4PARSE_CODEC_OPUS,
+  MP4PARSE_CODEC_AVC,
+  MP4PARSE_CODEC_VP9,
+  MP4PARSE_CODEC_MP3,
+  MP4PARSE_CODEC_MP4V,
+  MP4PARSE_CODEC_JPEG,
+  MP4PARSE_CODEC_AC3,
+  MP4PARSE_CODEC_EC3,
+  MP4PARSE_CODEC_ALAC,
 } Mp4parseCodec;
 
 typedef enum {
   MP4PARSE_STATUS_OK = 0,
   MP4PARSE_STATUS_BAD_ARG = 1,
   MP4PARSE_STATUS_INVALID = 2,
   MP4PARSE_STATUS_UNSUPPORTED = 3,
   MP4PARSE_STATUS_EOF = 4,
@@ -164,21 +165,16 @@ Mp4parseStatus mp4parse_get_track_video_
 /*
  * A fragmented file needs mvex table and contains no data in stts, stsc, and stco boxes.
  */
 Mp4parseStatus mp4parse_is_fragmented(Mp4parseParser *parser,
                                       uint32_t track_id,
                                       uint8_t *fragmented);
 
 /*
- * Enable `mp4_parser` log.
- */
-void mp4parse_log(bool enable);
-
-/*
  * Allocate an `Mp4parseParser*` to read from the supplied `Mp4parseIo`.
  */
 Mp4parseParser *mp4parse_new(const Mp4parseIo *io);
 
 /*
  * Run the `Mp4parseParser*` allocated by `mp4parse_new()` until EOF or error.
  */
 Mp4parseStatus mp4parse_read(Mp4parseParser *parser);
--- a/media/mp4parse-rust/mp4parse/Cargo.toml
+++ b/media/mp4parse-rust/mp4parse/Cargo.toml
@@ -1,11 +1,11 @@
 [package]
 name = "mp4parse"
-version = "0.9.1"
+version = "0.10.0"
 authors = [
   "Ralph Giles <giles@mozilla.com>",
   "Matthew Gregan <kinetik@flim.org>",
   "Alfredo Yang <ayang@mozilla.com>",
 ]
 
 description = "Parser for ISO base media file format (mp4)"
 documentation = "https://docs.rs/mp4parse/"
@@ -16,16 +16,16 @@ repository = "https://github.com/mozilla
 
 # Avoid complaints about trying to package test files.
 exclude = [
   "*.mp4",
 ]
 
 
 [dependencies]
-byteorder = "1.0.0"
+byteorder = "1.2.1"
 bitreader = { version = "0.3.0" }
-num-traits = "0.1.37"
+num-traits = "0.2.0"
 mp4parse_fallible = { version = "0.0.1", optional = true }
+log = "0.4"
 
 [dev-dependencies]
 test-assembler = "0.1.2"
-
--- a/media/mp4parse-rust/mp4parse/src/boxes.rs
+++ b/media/mp4parse-rust/mp4parse/src/boxes.rs
@@ -133,9 +133,10 @@ box_database!(
     ProtectionSystemSpecificHeaderBox 0x70737368, // "pssh"
     SchemeInformationBox              0x73636869, // "schi"
     TrackEncryptionBox                0x74656e63, // "tenc"
     ProtectionSchemeInformationBox    0x73696e66, // "sinf"
     OriginalFormatBox                 0x66726d61, // "frma"
     MP3AudioSampleEntry               0x2e6d7033, // ".mp3" - from F4V.
     CompositionOffsetBox              0x63747473, // "ctts"
     LPCMAudioSampleEntry              0x6C70636D, // "lpcm" - quicktime atom
+    ALACSpecificBox                   0x616C6163, // "alac" - Also used by ALACSampleEntry
 );
--- a/media/mp4parse-rust/mp4parse/src/lib.rs
+++ b/media/mp4parse-rust/mp4parse/src/lib.rs
@@ -1,16 +1,19 @@
 //! Module for parsing ISO Base Media Format aka video/mp4 streams.
 
 // 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 https://mozilla.org/MPL/2.0/.
 #[cfg(feature = "fuzz")]
 extern crate afl;
 
+#[macro_use]
+extern crate log;
+
 extern crate byteorder;
 extern crate bitreader;
 extern crate num_traits;
 use byteorder::{ReadBytesExt, WriteBytesExt};
 use bitreader::{BitReader, ReadInto};
 use std::io::{Read, Take};
 use std::io::Cursor;
 use std::cmp;
@@ -31,35 +34,16 @@ mod tests;
 
 // Arbitrary buffer size limit used for raw read_bufs on a box.
 const BUF_SIZE_LIMIT: usize = 1024 * 1024;
 
 // Max table length. Calculating in worth case for one week long video, one
 // frame per table entry in 30 fps.
 const TABLE_SIZE_LIMIT: u32 = 30 * 60 * 60 * 24 * 7;
 
-static DEBUG_MODE: std::sync::atomic::AtomicBool = std::sync::atomic::ATOMIC_BOOL_INIT;
-
-pub fn set_debug_mode(mode: bool) {
-    DEBUG_MODE.store(mode, std::sync::atomic::Ordering::SeqCst);
-}
-
-#[inline(always)]
-fn get_debug_mode() -> bool {
-    DEBUG_MODE.load(std::sync::atomic::Ordering::Relaxed)
-}
-
-macro_rules! log {
-    ($($args:tt)*) => (
-        if get_debug_mode() {
-            println!( $( $args )* );
<