Merge m-c to graphics
authorKartikaya Gupta <kgupta@mozilla.com>
Thu, 12 Jan 2017 12:40:09 -0500
changeset 342119 26000628a40dc56024740663ccfc16dbf57cfb4f
parent 342118 571286200177ae7ddfa1893c6b42853b60f2e81e (current diff)
parent 329182 048240a074e841c425a4da4707cf8e353074ec1d (diff)
child 342120 d55de1bf995f76bfbad5c1d3afdc3112710deb47
push id86826
push userkwierso@gmail.com
push dateFri, 10 Feb 2017 23:33:17 +0000
treeherdermozilla-inbound@2c7816419218 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone53.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to graphics MozReview-Commit-ID: LPeWBwbK82h
browser/base/content/win6BrowserOverlay.xul
browser/components/extensions/test/browser/browser-common.ini
dom/base/nsDOMWindowUtils.cpp
gfx/layers/LayersTypes.h
gfx/layers/client/ClientLayerManager.cpp
gfx/layers/composite/CompositableHost.h
gfx/layers/composite/ImageHost.cpp
gfx/layers/ipc/PCompositable.ipdl
gfx/layers/ipc/PCompositorBridge.ipdl
gfx/layers/moz.build
gfx/moz.build
layout/reftests/svg/reftest.list
layout/reftests/text-overflow/reftest.list
memory/jemalloc/src/msvc/projects/vc2015/test_threads/test_threads.cpp
memory/jemalloc/src/src/stats.c
memory/jemalloc/src/src/tcache.c
memory/jemalloc/src/src/util.c
memory/jemalloc/src/test/integration/MALLOCX_ARENA.c
memory/jemalloc/src/test/integration/allocated.c
memory/jemalloc/src/test/integration/mallocx.c
memory/jemalloc/src/test/integration/overflow.c
memory/jemalloc/src/test/integration/rallocx.c
memory/jemalloc/src/test/integration/thread_arena.c
memory/jemalloc/src/test/integration/thread_tcache_enabled.c
memory/jemalloc/src/test/integration/xallocx.c
memory/jemalloc/src/test/unit/arena_reset.c
memory/jemalloc/src/test/unit/decay.c
memory/jemalloc/src/test/unit/mallctl.c
memory/jemalloc/src/test/unit/prof_accum.c
memory/jemalloc/src/test/unit/prof_active.c
memory/jemalloc/src/test/unit/prof_gdump.c
memory/jemalloc/src/test/unit/prof_idump.c
memory/jemalloc/src/test/unit/prof_reset.c
memory/jemalloc/src/test/unit/prof_thread_name.c
memory/jemalloc/src/test/unit/size_classes.c
memory/jemalloc/src/test/unit/stats.c
mobile/android/base/java/org/mozilla/gecko/home/activitystream/StreamItem.java
modules/libpref/init/all.js
netwerk/wifi/win_xp_wifiScanner.cpp
netwerk/wifi/win_xp_wifiScanner.h
rdf/base/rdfISerializer.idl
rdf/base/rdfTriplesSerializer.cpp
taskcluster/ci/test/test-sets.yml
testing/marionette/harness/marionette_harness/tests/unit/test_prefs.py
testing/web-platform/tests/service-workers/service-worker/resources/empty-but-slow-worker.js
third_party/rust/byteorder/Makefile
third_party/rust/byteorder/session.vim
toolkit/library/gtest/rust/Cargo.lock
toolkit/library/rust/Cargo.lock
toolkit/moz.configure
widget/GfxInfoBase.cpp
widget/nsBaseWidget.cpp
widget/windows/nsWindow.cpp
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -3,16 +3,17 @@
 module.exports = {
   // When adding items to this file please check for effects on sub-directories.
   "plugins": [
     "mozilla"
   ],
   "rules": {
     "mozilla/import-globals": "warn",
     "mozilla/no-import-into-var-and-global": "error",
+    "mozilla/no-useless-parameters": "error",
 
     // No (!foo in bar) or (!object instanceof Class)
     "no-unsafe-negation": "error",
   },
   "env": {
     "es6": true
   },
   "parserOptions": {
--- a/accessible/tests/crashtests/448064.xhtml
+++ b/accessible/tests/crashtests/448064.xhtml
@@ -27,40 +27,37 @@ function dumpAccessibleNode(aNode, level
 		msg += " noName ";
 	}
 	
 	dump(msg + '\n');
 }
 
 
 function dumpAccessibleTree(aNode, level) {
-netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
 	level = level || 0;
 
 	dumpAccessibleNode(aNode, level);
 	try {	
 		var child = aNode.firstChild;
 		while (child) {
 			dumpAccessibleTree(child, level + 1);
 			child = child.nextSibling;
 		}
 	} catch (e) {
 		dump("Error visiting child nodes: " + e + '\n');
 	}
 }
 
 function A(o) { 
-  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
-  var acc = Components.classes['@mozilla.org/accessibilityService;1']
-                         .getService(Components.interfaces.nsIAccessibilityService);
+  var acc = SpecialPowers.Cc['@mozilla.org/accessibilityService;1']
+                         .getService(SpecialPowers.Ci.nsIAccessibilityService);
   return acc.getAccessibleFor(o);
 }
 
 function beginAccessible() {
-  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
   dumpAccessibleTree(A(document),0);
 }
 setTimeout(beginAccessible, 100);
 
 
 setTimeout(doe, 200);
 function doe() {
    document.getElementById('mw_a').appendChild(document.getElementById('mw_b'));
--- a/addon-sdk/source/lib/sdk/preferences/event-target.js
+++ b/addon-sdk/source/lib/sdk/preferences/event-target.js
@@ -51,11 +51,11 @@ function onChange(subject, topic, name) 
   }
 }
 
 function destroy() {
   off(this);
 
   // stop listening to preference changes
   let branch = prefTargetNS(this).branch;
-  branch.removeObserver('', prefTargetNS(this).observer, false);
+  branch.removeObserver('', prefTargetNS(this).observer);
   prefTargetNS(this).observer = null;
 }
--- a/addon-sdk/source/test/leak/leak-utils.js
+++ b/addon-sdk/source/test/leak/leak-utils.js
@@ -55,17 +55,17 @@ exports.asyncWindowLeakTest = function*(
   }
   Services.obs.addObserver(windowObserver, "domwindowopened", false);
 
   // Execute the body of the test.
   let testLoader = yield asyncTestFunc(assert);
 
   // Stop tracking new windows and attempt to GC any resources allocated
   // by the test body.
-  Services.obs.removeObserver(windowObserver, "domwindowopened", false);
+  Services.obs.removeObserver(windowObserver, "domwindowopened");
   yield gc();
 
   // Check to see if any of the windows we saw survived the GC.  We consider
   // these leaks.
   assert.ok(weakWindows.length > 0, "should see at least one new window");
   for (let i = 0; i < weakWindows.length; ++i) {
     assert.equal(weakWindows[i].get(), null, "window " + i + " should be GC'd");
   }
--- a/b2g/chrome/content/test/mochitest/RecordingStatusChromeScript.js
+++ b/b2g/chrome/content/test/mochitest/RecordingStatusChromeScript.js
@@ -1,14 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 var { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
-const { Services } = Cu.import('resource://gre/modules/Services.jsm');
-const { SystemAppProxy } = Cu.import('resource://gre/modules/SystemAppProxy.jsm');
+const { Services } = Cu.import('resource://gre/modules/Services.jsm', {});
+const { SystemAppProxy } = Cu.import('resource://gre/modules/SystemAppProxy.jsm', {});
 
 var processId;
 
 function peekChildId(aSubject, aTopic, aData) {
   Services.obs.removeObserver(peekChildId, 'recording-device-events');
   Services.obs.removeObserver(peekChildId, 'recording-device-ipc-events');
   let props = aSubject.QueryInterface(Ci.nsIPropertyBag2);
   if (props.hasKey('childID')) {
--- a/b2g/components/SafeMode.jsm
+++ b/b2g/components/SafeMode.jsm
@@ -40,17 +40,17 @@ this.SafeMode = {
         return Promise.resolve();
       }
     } catch(e) { debug("No current mode available!"); }
 
     // Wait for the preference to toggle.
     return new Promise((aResolve, aReject) => {
       let observer = function(aSubject, aTopic, aData) {
         if (Services.prefs.getCharPref(kSafeModePref)) {
-          Services.prefs.removeObserver(kSafeModePref, observer, false);
+          Services.prefs.removeObserver(kSafeModePref, observer);
           aResolve();
         }
       }
 
       Services.prefs.addObserver(kSafeModePref, observer, false);
     });
   },
 
--- a/b2g/components/test/mochitest/permission_handler_chrome.js
+++ b/b2g/components/test/mochitest/permission_handler_chrome.js
@@ -7,18 +7,18 @@
 function debug(str) {
   dump("CHROME PERMISSON HANDLER -- " + str + "\n");
 }
 
 var Cc = Components.classes;
 var Ci = Components.interfaces;
 var Cu = Components.utils;
 
-const { Services } = Cu.import("resource://gre/modules/Services.jsm");
-const { SystemAppProxy } = Cu.import("resource://gre/modules/SystemAppProxy.jsm");
+const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+const { SystemAppProxy } = Cu.import("resource://gre/modules/SystemAppProxy.jsm", {});
 
 var eventHandler = function(evt) {
   if (!evt.detail || evt.detail.type !== "permission-prompt") {
     return;
   }
 
   sendAsyncMessage("permission-request", evt.detail);
 };
@@ -28,9 +28,8 @@ SystemAppProxy.addEventListener("mozChro
 // need to remove ChromeEvent listener after test finished.
 addMessageListener("teardown", function() {
   SystemAppProxy.removeEventListener("mozChromeEvent", eventHandler);
 });
 
 addMessageListener("permission-response", function(detail) {
   SystemAppProxy._sendCustomEvent('mozContentEvent', detail);
 });
-
--- a/b2g/components/test/mochitest/presentation_prompt_handler_chrome.js
+++ b/b2g/components/test/mochitest/presentation_prompt_handler_chrome.js
@@ -4,18 +4,18 @@
 
 'use strict';
 
  function debug(str) {
    dump('presentation_prompt_handler_chrome: ' + str + '\n');
  }
 
 var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
-const { XPCOMUtils } = Cu.import('resource://gre/modules/XPCOMUtils.jsm');
-const { SystemAppProxy } = Cu.import('resource://gre/modules/SystemAppProxy.jsm');
+const { XPCOMUtils } = Cu.import('resource://gre/modules/XPCOMUtils.jsm', {});
+const { SystemAppProxy } = Cu.import('resource://gre/modules/SystemAppProxy.jsm', {});
 
 const manager = Cc["@mozilla.org/presentation-device/manager;1"]
                   .getService(Ci.nsIPresentationDeviceManager);
 
 const prompt = Cc['@mozilla.org/presentation-device/prompt;1']
                  .getService(Ci.nsIPresentationDevicePrompt);
 
 function TestPresentationDevice(options) {
@@ -86,9 +86,8 @@ addMessageListener('teardown', function(
 addMessageListener('trigger-device-prompt', function(request_options) {
   let request = new TestPresentationRequest(request_options);
   prompt.promptDeviceSelection(request);
 });
 
 addMessageListener('presentation-select-response', function(detail) {
   SystemAppProxy._sendCustomEvent('mozContentEvent', detail);
 });
-
--- a/b2g/components/test/mochitest/presentation_ui_glue_handler_chrome.js
+++ b/b2g/components/test/mochitest/presentation_ui_glue_handler_chrome.js
@@ -1,17 +1,17 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 'use strict';
 
 var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
-const { XPCOMUtils } = Cu.import('resource://gre/modules/XPCOMUtils.jsm');
-const { SystemAppProxy } = Cu.import('resource://gre/modules/SystemAppProxy.jsm');
+const { XPCOMUtils } = Cu.import('resource://gre/modules/XPCOMUtils.jsm', {});
+const { SystemAppProxy } = Cu.import('resource://gre/modules/SystemAppProxy.jsm', {});
 
 const glue = Cc["@mozilla.org/presentation/requestuiglue;1"]
              .createInstance(Ci.nsIPresentationRequestUIGlue);
 
 SystemAppProxy.addEventListener('mozPresentationChromeEvent', function(aEvent) {
   if (!aEvent.detail || aEvent.detail.type !== 'presentation-launch-receiver') {
     return;
   }
--- a/b2g/components/test/mochitest/screenshot_helper.js
+++ b/b2g/components/test/mochitest/screenshot_helper.js
@@ -1,14 +1,14 @@
 var Cu = Components.utils;
 var Ci = Components.interfaces;
 
 Cu.importGlobalProperties(['File']);
 
-const { Services } = Cu.import("resource://gre/modules/Services.jsm");
+const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
 
 // Load a duplicated copy of the jsm to prevent messing with the currently running one
 var scope = {};
 Services.scriptloader.loadSubScript("resource://gre/modules/Screenshot.jsm", scope);
 const { Screenshot } = scope;
 
 var index = -1;
 function next() {
--- a/b2g/components/test/mochitest/systemapp_helper.js
+++ b/b2g/components/test/mochitest/systemapp_helper.js
@@ -1,11 +1,11 @@
 var Cu = Components.utils;
 
-const { Services } = Cu.import("resource://gre/modules/Services.jsm");
+const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
 
 // Load a duplicated copy of the jsm to prevent messing with the currently running one
 var scope = {};
 Services.scriptloader.loadSubScript("resource://gre/modules/SystemAppProxy.jsm", scope);
 const { SystemAppProxy } = scope;
 
 var frame;
 var customEventTarget;
--- a/b2g/components/test/unit/test_fxaccounts.js
+++ b/b2g/components/test/unit/test_fxaccounts.js
@@ -14,17 +14,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   "resource://gre/modules/FxAccountsMgmtService.jsm",
   "FxAccountsMgmtService");
 
 XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsManager",
   "resource://gre/modules/FxAccountsManager.jsm");
 
 // At end of test, restore original state
 const ORIGINAL_AUTH_URI = Services.prefs.getCharPref("identity.fxaccounts.auth.uri");
-var { SystemAppProxy } = Cu.import("resource://gre/modules/FxAccountsMgmtService.jsm");
+var { SystemAppProxy } = Cu.import("resource://gre/modules/FxAccountsMgmtService.jsm", {});
 const ORIGINAL_SENDCUSTOM = SystemAppProxy._sendCustomEvent;
 do_register_cleanup(function() {
   Services.prefs.setCharPref("identity.fxaccounts.auth.uri", ORIGINAL_AUTH_URI);
   SystemAppProxy._sendCustomEvent = ORIGINAL_SENDCUSTOM;
   Services.prefs.clearUserPref("identity.fxaccounts.skipDeviceRegistration");
 });
 
 // Make profile available so that fxaccounts can store user data
@@ -203,10 +203,8 @@ function httpd_setup (handlers, port=-1)
   }
 
   // Set the base URI for convenience.
   let i = server.identity;
   server.baseURI = i.primaryScheme + "://" + i.primaryHost + ":" + i.primaryPort;
 
   return server;
 }
-
-
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -956,25 +956,23 @@ pref("dom.ipc.plugins.sandbox-level.flas
 // See - security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp
 // SetSecurityLevelForContentProcess() for what the different settings mean.
 #if defined(NIGHTLY_BUILD)
 pref("security.sandbox.content.level", 2);
 #else
 pref("security.sandbox.content.level", 1);
 #endif
 
-#if defined(MOZ_STACKWALKING)
 // This controls the depth of stack trace that is logged when Windows sandbox
 // logging is turned on.  This is only currently available for the content
 // process because the only other sandbox (for GMP) has too strict a policy to
 // allow stack tracing.  This does not require a restart to take effect.
 pref("security.sandbox.windows.log.stackTraceDepth", 0);
 #endif
 #endif
-#endif
 
 #if defined(XP_MACOSX) && defined(MOZ_SANDBOX) && defined(MOZ_CONTENT_SANDBOX)
 // This pref is discussed in bug 1083344, the naming is inspired from its
 // Windows counterpart, but on Mac it's an integer which means:
 // 0 -> "no sandbox"
 // 1 -> "preliminary content sandboxing enabled: write access to
 //       home directory is prevented"
 // 2 -> "preliminary content sandboxing enabled with profile protection:
--- a/browser/base/content/browser-devedition.js
+++ b/browser/base/content/browser-devedition.js
@@ -119,18 +119,18 @@ var DevEdition = {
     } else if (!deveditionThemeEnabled && wasEnabled) {
       this.styleSheet.sheet.disabled = true;
       this.refreshBrowserDisplay();
     }
   },
 
   uninit() {
     Services.prefs.removeObserver(this._devtoolsThemePrefName, this);
-    Services.obs.removeObserver(this, "lightweight-theme-styling-update", false);
-    Services.obs.removeObserver(this, "lightweight-theme-window-updated", false);
+    Services.obs.removeObserver(this, "lightweight-theme-styling-update");
+    Services.obs.removeObserver(this, "lightweight-theme-window-updated");
     if (this.styleSheet) {
       this.styleSheet.removeEventListener("load", this);
     }
     this.styleSheet = null;
   }
 };
 
 // If the DevEdition theme is going to be applied in gBrowserInit.onLoad,
--- a/browser/base/content/browser-media.js
+++ b/browser/base/content/browser-media.js
@@ -202,28 +202,22 @@ const TELEMETRY_DDSTAT_SOLVED = 4;
 let gDecoderDoctorHandler = {
   getLabelForNotificationBox(type) {
     if (type == "adobe-cdm-not-found" &&
         AppConstants.platform == "win") {
       return gNavigatorBundle.getString("decoder.noCodecs.message");
     }
     if (type == "adobe-cdm-not-activated" &&
         AppConstants.platform == "win") {
-      if (!AppConstants.isPlatformAndVersionAtLeast("win", "6.1")) {
-        return gNavigatorBundle.getString("decoder.noCodecsVista.message");
-      }
       return gNavigatorBundle.getString("decoder.noCodecs.message");
     }
     if (type == "platform-decoder-not-found") {
-      if (AppConstants.isPlatformAndVersionAtLeast("win", "6.1")) {
+      if (AppConstants.platform == "win") {
         return gNavigatorBundle.getString("decoder.noHWAcceleration.message");
       }
-      if (AppConstants.isPlatformAndVersionAtLeast("win", "6")) {
-        return gNavigatorBundle.getString("decoder.noHWAccelerationVista.message");
-      }
       if (AppConstants.platform == "linux") {
         return gNavigatorBundle.getString("decoder.noCodecsLinux.message");
       }
     }
     if (type == "cannot-initialize-pulseaudio") {
       return gNavigatorBundle.getString("decoder.noPulseAudio.message");
     }
     if (type == "unsupported-libavcodec" &&
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -1407,17 +1407,17 @@ var BookmarkingUI = {
         updatePlacesContextMenu(shouldHidePrefUI);
       }
     };
 
     let onBookmarksMenuHidden = event => {
       if (event.target == event.currentTarget) {
         updatePlacesContextMenu(true);
 
-        Services.prefs.removeObserver(this.RECENTLY_BOOKMARKED_PREF, prefObserver, false);
+        Services.prefs.removeObserver(this.RECENTLY_BOOKMARKED_PREF, prefObserver);
         PlacesUtils.bookmarks.removeObserver(this._recentlyBookmarkedObserver);
         this._recentlyBookmarkedObserver = null;
         if (placesContextMenu) {
           placesContextMenu.removeEventListener("popupshowing", onPlacesContextMenuShowing);
         }
         bookmarksMenu.removeEventListener("popuphidden", onBookmarksMenuHidden);
       }
     };
--- a/browser/base/content/browser-syncui.js
+++ b/browser/base/content/browser-syncui.js
@@ -132,25 +132,25 @@ var gSyncUI = {
     // Otherwise we are configured for legacy Sync, which has no verification
     // concept.
     return Promise.resolve(false);
   },
 
   // Note that we don't show login errors in a notification bar here, but do
   // still need to track a login-failed state so the "Tools" menu updates
   // with the correct state.
-  _loginFailed() {
+  loginFailed() {
     // If Sync isn't already ready, we don't want to force it to initialize
     // by referencing Weave.Status - and it isn't going to be accurate before
     // Sync is ready anyway.
     if (!this.weaveService.ready) {
-      this.log.debug("_loginFailed has sync not ready, so returning false");
+      this.log.debug("loginFailed has sync not ready, so returning false");
       return false;
     }
-    this.log.debug("_loginFailed has sync state=${sync}",
+    this.log.debug("loginFailed has sync state=${sync}",
                    { sync: Weave.Status.login});
     return Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED;
   },
 
   // Kick off an update of the UI - does *not* return a promise.
   updateUI() {
     this._promiseUpdateUI().catch(err => {
       this.log.error("updateUI failed", err);
@@ -158,17 +158,17 @@ var gSyncUI = {
   },
 
   // Updates the UI - returns a promise.
   _promiseUpdateUI() {
     return this._needsSetup().then(needsSetup => {
       if (!gBrowser)
         return Promise.resolve();
 
-      let loginFailed = this._loginFailed();
+      let loginFailed = this.loginFailed();
 
       // Start off with a clean slate
       document.getElementById("sync-reauth-state").hidden = true;
       document.getElementById("sync-setup-state").hidden = true;
       document.getElementById("sync-syncnow-state").hidden = true;
 
       if (CloudSync && CloudSync.ready && CloudSync().adapters.count) {
         document.getElementById("sync-syncnow-state").hidden = false;
@@ -261,17 +261,17 @@ var gSyncUI = {
     });
   },
 
   // Handle clicking the toolbar button - which either opens the Sync setup
   // pages or forces a sync now. Does *not* return a promise as it is called
   // via the UI.
   handleToolbarButton() {
     this._needsSetup().then(needsSetup => {
-      if (needsSetup || this._loginFailed()) {
+      if (needsSetup || this.loginFailed()) {
         this.openSetup();
       } else {
         this.doSync();
       }
     }).catch(err => {
       this.log.error("Failed to handle toolbar button command", err);
     });
   },
@@ -375,17 +375,17 @@ var gSyncUI = {
 
     let email;
     try {
       email = Services.prefs.getCharPref("services.sync.username");
     } catch (ex) {}
 
     let needsSetup = yield this._needsSetup();
     let needsVerification = yield this._needsVerification();
-    let loginFailed = this._loginFailed();
+    let loginFailed = this.loginFailed();
     // This is a little messy as the Sync buttons are 1/2 Sync related and
     // 1/2 FxA related - so for some strings we use Sync strings, but for
     // others we reach into gFxAccounts for strings.
     let tooltiptext;
     if (needsVerification) {
       // "needs verification"
       tooltiptext = gFxAccounts.strings.formatStringFromName("verifyDescription", [email], 1);
     } else if (needsSetup) {
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -122,21 +122,16 @@ XPCOMUtils.defineLazyGetter(this, "gBrow
 });
 
 XPCOMUtils.defineLazyGetter(this, "gCustomizeMode", function() {
   let scope = {};
   Cu.import("resource:///modules/CustomizeMode.jsm", scope);
   return new scope.CustomizeMode(window);
 });
 
-XPCOMUtils.defineLazyGetter(window, "gShowPageResizers", function() {
-  // Only show resizers on Windows 2000 and XP
-  return AppConstants.isPlatformAndVersionAtMost("win", "5.9");
-});
-
 XPCOMUtils.defineLazyGetter(this, "gPrefService", function() {
   return Services.prefs;
 });
 
 XPCOMUtils.defineLazyGetter(this, "InlineSpellCheckerUI", function() {
   let tmp = {};
   Cu.import("resource://gre/modules/InlineSpellChecker.jsm", tmp);
   return new tmp.InlineSpellChecker();
@@ -3397,18 +3392,16 @@ var PrintPreviewListener = {
     this._chromeState.sidebarOpen = SidebarUI.isOpen;
     this._sidebarCommand = SidebarUI.currentID;
     SidebarUI.hide();
 
     var notificationBox = gBrowser.getNotificationBox();
     this._chromeState.notificationsOpen = !notificationBox.notificationsHidden;
     notificationBox.notificationsHidden = true;
 
-    gBrowser.updateWindowResizers();
-
     this._chromeState.findOpen = gFindBarInitialized && !gFindBar.hidden;
     if (gFindBarInitialized)
       gFindBar.close();
 
     var globalNotificationBox = document.getElementById("global-notificationbox");
     this._chromeState.globalNotificationsOpen = !globalNotificationBox.notificationsHidden;
     globalNotificationBox.notificationsHidden = true;
 
@@ -5235,17 +5228,16 @@ function setToolbarVisibility(toolbar, i
     },
     bubbles: true
   };
   let event = new CustomEvent("toolbarvisibilitychange", eventParams);
   toolbar.dispatchEvent(event);
 
   PlacesToolbarHelper.init();
   BookmarkingUI.onToolbarVisibilityChange();
-  gBrowser.updateWindowResizers();
   if (isVisible)
     ToolbarIconColor.inferFromText();
 }
 
 var TabletModeUpdater = {
   init() {
     if (AppConstants.isPlatformAndVersionAtLeast("win", "10")) {
       this.update(WindowsUIUtils.inTabletMode);
@@ -7779,16 +7771,19 @@ var TabContextMenu = {
     this.contextTab = aPopupMenu.triggerNode.localName == "tab" ?
                       aPopupMenu.triggerNode : gBrowser.selectedTab;
     let disabled = gBrowser.tabs.length == 1;
 
     var menuItems = aPopupMenu.getElementsByAttribute("tbattr", "tabbrowser-multiple");
     for (let menuItem of menuItems)
       menuItem.disabled = disabled;
 
+    if (this.contextTab.hasAttribute("customizemode"))
+      document.getElementById("context_openTabInWindow").disabled = true;
+
     if (AppConstants.E10S_TESTING_ONLY) {
       menuItems = aPopupMenu.getElementsByAttribute("tbattr", "tabbrowser-remote");
       for (let menuItem of menuItems) {
         menuItem.hidden = !gMultiProcessBrowser;
         if (menuItem.id == "context_openNonRemoteWindow") {
           menuItem.disabled = !!parseInt(this.contextTab.getAttribute("usercontextid"));
         }
       }
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -469,16 +469,19 @@
       <menuseparator/>
       <menuitem label="&syncedTabs.context.bookmarkSingleTab.label;"
                 accesskey="&syncedTabs.context.bookmarkSingleTab.accesskey;"
                 id="syncedTabsBookmarkSelected"/>
       <menuitem label="&syncedTabs.context.copy.label;"
                 accesskey="&syncedTabs.context.copy.accesskey;"
                 id="syncedTabsCopySelected"/>
       <menuseparator/>
+      <menuitem label="&syncedTabs.context.openAllInTabs.label;"
+                accesskey="&syncedTabs.context.openAllInTabs.accesskey;"
+                id="syncedTabsOpenAllInTabs"/>
       <menuitem label="&syncSyncNowItem.label;"
                 accesskey="&syncSyncNowItem.accesskey;"
                 id="syncedTabsRefresh"/>
     </menupopup>
     <menupopup id="SyncedTabsSidebarTabsFilterContext"
                class="textbox-contextmenu">
       <menuitem label="&undoCmd.label;"
                 accesskey="&undoCmd.accesskey;"
@@ -540,20 +543,18 @@
 
   <toolbox id="navigator-toolbox" mode="icons">
     <!-- Menu -->
     <toolbar type="menubar" id="toolbar-menubar" class="chromeclass-menubar" customizable="true"
              mode="icons" iconsize="small"
 #ifdef MENUBAR_CAN_AUTOHIDE
              toolbarname="&menubarCmd.label;"
              accesskey="&menubarCmd.accesskey;"
-#if defined(MOZ_WIDGET_GTK)
              autohide="true"
 #endif
-#endif
              context="toolbar-context-menu">
       <toolbaritem id="menubar-items" align="center">
 # The entire main menubar is placed into browser-menubar.inc, so that it can be shared by
 # hiddenWindow.xul.
 #include browser-menubar.inc
       </toolbaritem>
 
 #ifdef CAN_DRAW_IN_TITLEBAR
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -210,28 +210,16 @@
           if (this._statusPanel) {
             let browser = this.selectedBrowser;
             let browserContainer = this.getBrowserContainer(browser);
             browserContainer.insertBefore(this._statusPanel, browser.parentNode.nextSibling);
           }
         ]]></body>
       </method>
 
-      <method name="updateWindowResizers">
-        <body><![CDATA[
-          if (!window.gShowPageResizers)
-            return;
-
-          var show = window.windowState == window.STATE_NORMAL;
-          for (let i = 0; i < this.browsers.length; i++) {
-            this.browsers[i].showWindowResizer = show;
-          }
-        ]]></body>
-      </method>
-
       <method name="_setCloseKeyState">
         <parameter name="aEnabled"/>
         <body><![CDATA[
           let keyClose = document.getElementById("key_close");
           let closeKeyEnabled = keyClose.getAttribute("disabled") != "true";
           if (closeKeyEnabled == aEnabled)
             return;
 
@@ -533,19 +521,20 @@
       </method>
 
       <!-- A web progress listener object definition for a given tab. -->
       <method name="mTabProgressListener">
         <parameter name="aTab"/>
         <parameter name="aBrowser"/>
         <parameter name="aStartsBlank"/>
         <parameter name="aWasPreloadedBrowser"/>
+        <parameter name="aOrigStateFlags"/>
         <body>
         <![CDATA[
-          let stateFlags = 0;
+          let stateFlags = aOrigStateFlags || 0;
           // Initialize mStateFlags to non-zero e.g. when creating a progress
           // listener for preloaded browsers as there was no progress listener
           // around when the content started loading. If the content didn't
           // quite finish loading yet, mStateFlags will very soon be overridden
           // with the correct value and end up at STATE_STOP again.
           if (aWasPreloadedBrowser) {
             stateFlags = Ci.nsIWebProgressListener.STATE_STOP |
                          Ci.nsIWebProgressListener.STATE_IS_REQUEST;
@@ -1966,20 +1955,16 @@
 
             if (aParams.opener) {
               if (aParams.remoteType) {
                 throw new Exception("Cannot set opener window on a remote browser!");
               }
               b.QueryInterface(Ci.nsIFrameLoaderOwner).presetOpenerWindow(aParams.opener);
             }
 
-            if (window.gShowPageResizers && window.windowState == window.STATE_NORMAL) {
-              b.setAttribute("showresizer", "true");
-            }
-
             if (!aParams.isPreloadBrowser && this.hasAttribute("autocompletepopup")) {
               b.setAttribute("autocompletepopup", this.getAttribute("autocompletepopup"));
             }
 
             if (this.hasAttribute("selectmenulist"))
               b.setAttribute("selectmenulist", this.getAttribute("selectmenulist"));
 
             if (this.hasAttribute("datetimepicker")) {
@@ -2834,16 +2819,19 @@
             if (otherBrowser.hasAttribute("usercontextid")) {
               ourBrowser.setAttribute("usercontextid", otherBrowser.getAttribute("usercontextid"));
             }
 
             // That's gBrowser for the other window, not the tab's browser!
             var remoteBrowser = aOtherTab.ownerDocument.defaultView.gBrowser;
             var isPending = aOtherTab.hasAttribute("pending");
 
+            let otherTabListener = remoteBrowser._tabListeners.get(aOtherTab);
+            let stateFlags = otherTabListener.mStateFlags;
+
             // Expedite the removal of the icon if it was already scheduled.
             if (aOtherTab._soundPlayingAttrRemovalTimer) {
               clearTimeout(aOtherTab._soundPlayingAttrRemovalTimer);
               aOtherTab._soundPlayingAttrRemovalTimer = 0;
               aOtherTab.removeAttribute("soundplaying");
               remoteBrowser._tabAttrModified(aOtherTab, ["soundplaying"]);
             }
 
@@ -2891,17 +2879,17 @@
               var isBusy = aOtherTab.hasAttribute("busy");
               if (isBusy) {
                 aOurTab.setAttribute("busy", "true");
                 modifiedAttrs.push("busy");
                 if (aOurTab.selected)
                   this.mIsBusy = true;
               }
 
-              this._swapBrowserDocShells(aOurTab, otherBrowser, Ci.nsIBrowser.SWAP_DEFAULT);
+              this._swapBrowserDocShells(aOurTab, otherBrowser, Ci.nsIBrowser.SWAP_DEFAULT, stateFlags);
             }
 
             // Unregister the previously opened URI
             if (otherBrowser.registeredOpenURI) {
               this._unifiedComplete.unregisterOpenPage(otherBrowser.registeredOpenURI,
                                                        otherBrowser.getAttribute("usercontextid") || 0);
               delete otherBrowser.registeredOpenURI;
             }
@@ -2964,16 +2952,17 @@
           ]]>
         </body>
       </method>
 
       <method name="_swapBrowserDocShells">
         <parameter name="aOurTab"/>
         <parameter name="aOtherBrowser"/>
         <parameter name="aFlags"/>
+        <parameter name="aStateFlags"/>
         <body>
           <![CDATA[
             // Unhook our progress listener
             const filter = this._tabFilters.get(aOurTab);
             let tabListener = this._tabListeners.get(aOurTab);
             let ourBrowser = this.getBrowserForTab(aOurTab);
             ourBrowser.webProgress.removeProgressListener(filter);
             filter.removeProgressListener(tabListener);
@@ -3020,17 +3009,18 @@
                 let otherTab = remoteBrowser.getTabForBrowser(aOtherBrowser);
                 if (otherTab) {
                   otherTab.permanentKey = aOtherBrowser.permanentKey;
                 }
               }
             }
 
             // Restore the progress listener
-            tabListener = this.mTabProgressListener(aOurTab, ourBrowser, false, false);
+            tabListener = this.mTabProgressListener(aOurTab, ourBrowser, false, false,
+                                                    aStateFlags);
             this._tabListeners.set(aOurTab, tabListener);
 
             const notifyAll = Ci.nsIWebProgress.NOTIFY_ALL;
             filter.addProgressListener(tabListener, notifyAll);
             ourBrowser.webProgress.addProgressListener(filter, notifyAll);
           ]]>
         </body>
       </method>
@@ -4958,17 +4948,16 @@
           this._tabForBrowser.set(this.mCurrentBrowser, this.mCurrentTab);
 
           // set up the shared autoscroll popup
           this._autoScrollPopup = this.mCurrentBrowser._createAutoScrollPopup();
           this._autoScrollPopup.id = "autoscroller";
           this.appendChild(this._autoScrollPopup);
           this.mCurrentBrowser.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
           this.mCurrentBrowser.droppedLinkHandler = handleDroppedLink;
-          this.updateWindowResizers();
 
           // Hook up the event listeners to the first browser
           var tabListener = this.mTabProgressListener(this.mCurrentTab, this.mCurrentBrowser, true, false);
           const nsIWebProgress = Components.interfaces.nsIWebProgress;
           const filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
                                    .createInstance(nsIWebProgress);
           filter.addProgressListener(tabListener, nsIWebProgress.NOTIFY_ALL);
           this._tabListeners.set(this.mCurrentTab, tabListener);
@@ -5031,19 +5020,19 @@
           // window ID. We switched to a monotonic counter as Date.now() lead
           // to random failures because of colliding IDs.
           return "panel-" + outerID + "-" + (++this._uniquePanelIDCounter);
         ]]></body>
       </method>
 
       <destructor>
         <![CDATA[
-          Services.obs.removeObserver(this, "live-resize-start", false);
-          Services.obs.removeObserver(this, "live-resize-end", false);
-          Services.obs.removeObserver(this, "contextual-identity-updated", false);
+          Services.obs.removeObserver(this, "live-resize-start");
+          Services.obs.removeObserver(this, "live-resize-end");
+          Services.obs.removeObserver(this, "contextual-identity-updated");
 
           for (let tab of this.tabs) {
             let browser = tab.linkedBrowser;
             if (browser.registeredOpenURI) {
               this._unifiedComplete.unregisterOpenPage(browser.registeredOpenURI,
                                                        browser.getAttribute("usercontextid") || 0);
               delete browser.registeredOpenURI;
             }
@@ -6002,18 +5991,16 @@
 
               var width = this.mTabstrip.boxObject.width;
               if (width != this.mTabstripWidth) {
                 this.adjustTabstrip();
                 this._fillTrailingGap();
                 this._handleTabSelect();
                 this.mTabstripWidth = width;
               }
-
-              this.tabbrowser.updateWindowResizers();
               break;
             case "mouseout":
               // If the "related target" (the node to which the pointer went) is not
               // a child of the current document, the mouse just left the window.
               let relatedTarget = aEvent.relatedTarget;
               if (relatedTarget && relatedTarget.ownerDocument == document)
                 break;
             case "mousemove":
--- a/browser/base/content/test/general/browser_bug435325.js
+++ b/browser/base/content/test/general/browser_bug435325.js
@@ -47,17 +47,17 @@ function checkPage(data) {
 
   // Re-enable the proxy so example.com is resolved to localhost, rather than
   // the actual example.com.
   Services.prefs.setIntPref("network.proxy.type", proxyPrefValue);
 
   Services.obs.addObserver(function observer(aSubject, aTopic) {
     ok(!Services.io.offline, "After clicking the Try Again button, we're back " +
                              "online.");
-    Services.obs.removeObserver(observer, "network:offline-status-changed", false);
+    Services.obs.removeObserver(observer, "network:offline-status-changed");
     finish();
   }, "network:offline-status-changed", false);
 
   ContentTask.spawn(gBrowser.selectedBrowser, null, function* () {
     content.document.getElementById("errorTryAgain").click();
   });
 }
 
--- a/browser/base/content/test/general/browser_bug553455.js
+++ b/browser/base/content/test/general/browser_bug553455.js
@@ -5,17 +5,17 @@
 const TESTROOT = "http://example.com/browser/toolkit/mozapps/extensions/test/xpinstall/";
 const TESTROOT2 = "http://example.org/browser/toolkit/mozapps/extensions/test/xpinstall/";
 const SECUREROOT = "https://example.com/browser/toolkit/mozapps/extensions/test/xpinstall/";
 const XPINSTALL_URL = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul";
 const PREF_INSTALL_REQUIREBUILTINCERTS = "extensions.install.requireBuiltInCerts";
 const PROGRESS_NOTIFICATION = "addon-progress";
 
 const { REQUIRE_SIGNING } = Cu.import("resource://gre/modules/addons/AddonConstants.jsm", {});
-const { Task } = Cu.import("resource://gre/modules/Task.jsm");
+const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
 
 var rootDir = getRootDirectory(gTestPath);
 var rootPath = rootDir.split('/');
 var chromeName = rootPath[0] + '//' + rootPath[2];
 var croot = chromeName + "/content/browser/toolkit/mozapps/extensions/test/xpinstall/";
 var jar = getJar(croot);
 if (jar) {
   var tmpdir = extractJarToTmp(jar);
--- a/browser/base/content/test/general/browser_extension_permissions.js
+++ b/browser/base/content/test/general/browser_extension_permissions.js
@@ -5,25 +5,33 @@ const BASE = getRootDirectory(gTestPath)
 
 const PAGE = `${BASE}/file_install_extensions.html`;
 const PERMS_XPI = `${BASE}/browser_webext_permissions.xpi`;
 const NO_PERMS_XPI = `${BASE}/browser_webext_nopermissions.xpi`;
 const ID = "permissions@test.mozilla.org";
 
 const DEFAULT_EXTENSION_ICON = "chrome://browser/content/extension.svg";
 
+Services.perms.add(makeURI("https://example.com/"), "install",
+                   Services.perms.ALLOW_ACTION);
+
 function promisePopupNotificationShown(name) {
   return new Promise(resolve => {
-    PopupNotifications.panel.addEventListener("popupshown", () => {
+    function popupshown() {
       let notification = PopupNotifications.getNotification(name);
+      if (!notification) { return; }
+
       ok(notification, `${name} notification shown`);
       ok(PopupNotifications.isPanelOpen, "notification panel open");
 
+      PopupNotifications.panel.removeEventListener("popupshown", popupshown);
       resolve(PopupNotifications.panel.firstChild);
-    }, {once: true});
+    }
+
+    PopupNotifications.panel.addEventListener("popupshown", popupshown);
   });
 }
 
 function promiseGetAddonByID(id) {
   return new Promise(resolve => {
     AddonManager.getAddonByID(id, resolve);
   });
 }
@@ -55,52 +63,88 @@ function checkNotification(panel, url) {
 
     is(header.getAttribute("hidden"), "true", "Permission list header is hidden");
     is(ul.childElementCount, 0, "Permissions list has 0 entries");
   }
 }
 
 const INSTALL_FUNCTIONS = [
   function installMozAM(url) {
-    return ContentTask.spawn(gBrowser.selectedBrowser, url, function*(cUrl) {
-      return content.wrappedJSObject.installMozAM(cUrl);
+    ContentTask.spawn(gBrowser.selectedBrowser, url, function*(cUrl) {
+      content.wrappedJSObject.installMozAM(cUrl);
+    });
+  },
+
+  function installTrigger(url) {
+    ContentTask.spawn(gBrowser.selectedBrowser, url, function*(cUrl) {
+      content.wrappedJSObject.installTrigger(cUrl);
     });
   },
 ];
 
 add_task(function* () {
   yield SpecialPowers.pushPrefEnv({set: [
     ["extensions.webapi.testing", true],
     ["extensions.install.requireBuiltInCerts", false],
 
     // XXX remove this when prompts are enabled by default
     ["extensions.webextPermissionPrompts", true],
   ]});
 
   function* runOnce(installFn, url, cancel) {
     let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE);
 
-    let installPromise = installFn(url);
+    let installPromise = new Promise(resolve => {
+      let listener = {
+        onDownloadCancelled() {
+          AddonManager.removeInstallListener(listener);
+          resolve(false);
+        },
+
+        onDownloadFailed() {
+          AddonManager.removeInstallListener(listener);
+          resolve(false);
+        },
+
+        onInstallCancelled() {
+          AddonManager.removeInstallListener(listener);
+          resolve(false);
+        },
+
+        onInstallEnded() {
+          AddonManager.removeInstallListener(listener);
+          resolve(true);
+        },
+
+        onInstallFailed() {
+          AddonManager.removeInstallListener(listener);
+          resolve(false);
+        },
+      };
+      AddonManager.addInstallListener(listener);
+    });
+
+    installFn(url);
 
     let panel = yield promisePopupNotificationShown("addon-webext-permissions");
     checkNotification(panel, url);
 
     if (cancel) {
       panel.secondaryButton.click();
     } else {
       panel.button.click();
     }
 
     let result = yield installPromise;
     let addon = yield promiseGetAddonByID(ID);
     if (cancel) {
-      is(result, "onInstallCancelled", "Installation was cancelled");
+      ok(!result, "Installation was cancelled");
       is(addon, null, "Extension is not installed");
     } else {
-      is(result, "onInstallEnded", "Installation completed");
+      ok(result, "Installation completed");
       isnot(addon, null, "Extension is installed");
       addon.uninstall();
     }
 
     yield BrowserTestUtils.removeTab(tab);
   }
 
   for (let installFn of INSTALL_FUNCTIONS) {
--- a/browser/base/content/test/general/browser_sanitize-timespans.js
+++ b/browser/base/content/test/general/browser_sanitize-timespans.js
@@ -16,17 +16,17 @@ Cc["@mozilla.org/moz/jssubscript-loader;
 var Sanitizer = tempScope.Sanitizer;
 
 var FormHistory = (Components.utils.import("resource://gre/modules/FormHistory.jsm", {})).FormHistory;
 var Downloads = (Components.utils.import("resource://gre/modules/Downloads.jsm", {})).Downloads;
 
 function promiseFormHistoryRemoved() {
   let deferred = Promise.defer();
   Services.obs.addObserver(function onfh() {
-    Services.obs.removeObserver(onfh, "satchel-storage-changed", false);
+    Services.obs.removeObserver(onfh, "satchel-storage-changed");
     deferred.resolve();
   }, "satchel-storage-changed", false);
   return deferred.promise;
 }
 
 function promiseDownloadRemoved(list) {
   let deferred = Promise.defer();
 
--- a/browser/base/content/test/general/browser_tab_drag_drop_perwindow.js
+++ b/browser/base/content/test/general/browser_tab_drag_drop_perwindow.js
@@ -172,8 +172,45 @@ add_task(function* test_dragging_adoptio
   let openEvent = yield awaitOpenEvent;
 
   is(openEvent.detail.adoptedTab, tab1, "New tab adopted old tab");
   is(closeEvent.detail.adoptedBy, openEvent.target, "Old tab adopted by new tab");
 
   yield BrowserTestUtils.closeWindow(win1);
   yield BrowserTestUtils.closeWindow(win2);
 });
+
+
+/**
+ * Tests that per-site zoom settings remain active after a tab is
+ * dragged between windows.
+ */
+add_task(function* test_dragging_zoom_handling() {
+  const ZOOM_FACTOR = 1.62;
+
+  let win1 = yield BrowserTestUtils.openNewBrowserWindow();
+  let win2 = yield BrowserTestUtils.openNewBrowserWindow();
+
+  let tab1 = yield BrowserTestUtils.openNewForegroundTab(win1.gBrowser);
+  let tab2 = yield BrowserTestUtils.openNewForegroundTab(win2.gBrowser,
+                                                         "http://example.com/");
+
+  win2.FullZoom.setZoom(ZOOM_FACTOR);
+  FullZoomHelper.zoomTest(tab2, ZOOM_FACTOR,
+                          "Original tab should have correct zoom factor");
+
+  let effect = EventUtils.synthesizeDrop(tab2, tab1,
+    [[{type: TAB_DROP_TYPE, data: tab2}]],
+    null, win2, win1);
+  is(effect, "move", "Tab should be moved from win2 to win1.");
+
+  // Delay slightly to make sure we've finished executing any promise
+  // chains in the zoom code.
+  yield new Promise(resolve => setTimeout(resolve, 0));
+
+  FullZoomHelper.zoomTest(win1.gBrowser.selectedTab, ZOOM_FACTOR,
+                          "Dragged tab should have correct zoom factor");
+
+  win1.FullZoom.reset();
+
+  yield BrowserTestUtils.closeWindow(win1);
+  yield BrowserTestUtils.closeWindow(win2);
+});
--- a/browser/base/content/test/general/file_install_extensions.html
+++ b/browser/base/content/test/general/file_install_extensions.html
@@ -2,25 +2,19 @@
 
 <html>
 <head>
   <meta charset="utf-8">
 </head>
 <body>
 <script type="text/javascript">
 function installMozAM(url) {
-  return navigator.mozAddonManager.createInstall({url}).then(install => new Promise(resolve => {
-    const EVENTS = [
-      "onDownloadCancelled",
-      "onDownloadFailed",
-      "onInstallCancelled",
-      "onInstallEnded",
-      "onInstallFailed",
-    ];
-    for (let event of EVENTS) {
-      install.addEventListener(event, () => { resolve(event); });
-    }
+  return navigator.mozAddonManager.createInstall({url}).then(install => {
     install.install();
-  }));
+  });
+}
+
+function installTrigger(url) {
+  InstallTrigger.install({extension: url});
 }
 </script>
 </body>
 </html>
--- a/browser/base/content/test/general/test_offlineNotification.html
+++ b/browser/base/content/test/general/test_offlineNotification.html
@@ -35,18 +35,18 @@ window.addEventListener("message", funct
     is(event.data, "success", "Child was successfully cached.");
 
     if (++numFinished == 3) {
       // Clean up after ourself
       var pm = Cc["@mozilla.org/permissionmanager;1"].
                getService(SpecialPowers.Ci.nsIPermissionManager);
       var ioService = Cc["@mozilla.org/network/io-service;1"]
                         .getService(SpecialPowers.Ci.nsIIOService);
-      var uri1 = ioService.newURI(frames.testFrame.location, null, null);
-      var uri2 = ioService.newURI(frames.testFrame3.location, null, null);
+      var uri1 = ioService.newURI(frames.testFrame.location);
+      var uri2 = ioService.newURI(frames.testFrame3.location);
 
       var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
                   .getService(SpecialPowers.Ci.nsIScriptSecurityManager);
       var principal1 = ssm.createCodebasePrincipal(uri1, {});
       var principal2 = ssm.createCodebasePrincipal(uri2, {});
 
       pm.removeFromPrincipal(principal1, "offline-app");
       pm.removeFromPrincipal(principal2, "offline-app");
--- a/browser/base/content/test/urlbar/browser_urlbarCopying.js
+++ b/browser/base/content/test/urlbar/browser_urlbarCopying.js
@@ -39,16 +39,32 @@ var tests = [
     setURL: "http://example.com/",
     expectedURL: "example.com",
     copyExpected: "example.com"
   },
   {
     copyVal: "<e>xample.com",
     copyExpected: "e"
   },
+  {
+    copyVal: "<e>x<a>mple.com",
+    copyExpected: "ea"
+  },
+  {
+    copyVal: "<e><xa>mple.com",
+    copyExpected: "exa"
+  },
+  {
+    copyVal: "<e><xa>mple.co<m>",
+    copyExpected: "exam"
+  },
+  {
+    copyVal: "<e><xample.co><m>",
+    copyExpected: "example.com"
+  },
 
   // pageproxystate="valid" from this point on (due to the load)
   {
     loadURL: "http://example.com/",
     expectedURL: "example.com",
     copyExpected: "http://example.com/"
   },
   {
@@ -58,16 +74,24 @@ var tests = [
   {
     copyVal: "e<x>ample.com",
     copyExpected: "x"
   },
   {
     copyVal: "<e>xample.com",
     copyExpected: "e"
   },
+  {
+    copyVal: "<e>xample.co<m>",
+    copyExpected: "em"
+  },
+  {
+    copyVal: "<exam><ple.com>",
+    copyExpected: "example.com"
+  },
 
   {
     loadURL: "http://example.com/foo",
     expectedURL: "example.com/foo",
     copyExpected: "http://example.com/foo"
   },
   {
     copyVal: "<example.com>/foo",
@@ -195,25 +219,48 @@ function runTest(testCase, cb) {
   }
 }
 
 function testCopy(copyVal, targetValue, cb) {
   info("Expecting copy of: " + targetValue);
   waitForClipboard(targetValue, function() {
     gURLBar.focus();
     if (copyVal) {
-      let startBracket = copyVal.indexOf("<");
-      let endBracket = copyVal.indexOf(">");
-      if (startBracket == -1 || endBracket == -1 ||
-          startBracket > endBracket ||
-          copyVal.replace("<", "").replace(">", "") != gURLBar.textValue) {
+      let offsets = [];
+      while (true) {
+        let startBracket = copyVal.indexOf("<");
+        let endBracket = copyVal.indexOf(">");
+        if (startBracket == -1 && endBracket == -1) {
+          break;
+        }
+        if (startBracket > endBracket || startBracket == -1) {
+          offsets = [];
+          break;
+        }
+        offsets.push([startBracket, endBracket - 1]);
+        copyVal = copyVal.replace("<", "").replace(">", "");
+      }
+      if (offsets.length == 0 ||
+          copyVal != gURLBar.textValue) {
         ok(false, "invalid copyVal: " + copyVal);
       }
-      gURLBar.selectionStart = startBracket;
-      gURLBar.selectionEnd = endBracket - 1;
+      gURLBar.selectionStart = offsets[0][0];
+      gURLBar.selectionEnd = offsets[0][1];
+      if (offsets.length > 1) {
+        let sel = gURLBar.editor.selection;
+        let r0 = sel.getRangeAt(0);
+        let node0 = r0.startContainer;
+        sel.removeAllRanges();
+        offsets.map(function(startEnd) {
+          let range = r0.cloneRange();
+          range.setStart(node0, startEnd[0]);
+          range.setEnd(node0, startEnd[1]);
+          sel.addRange(range);
+        });
+      }
     } else {
       gURLBar.select();
     }
 
     goDoCommand("cmd_copy");
   }, cb, cb);
 }
 
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -753,17 +753,23 @@ file, You can obtain one at http://mozil
           }
         ]]></body>
       </method>
 
       <method name="_getSelectedValueForClipboard">
         <body><![CDATA[
           // Grab the actual input field's value, not our value, which could include moz-action:
           var inputVal = this.inputField.value;
-          var selectedVal = inputVal.substring(this.selectionStart, this.selectionEnd);
+          let selection = this.editor.selection;
+          var selectedVal = selection.toString();
+
+          // Handle multiple-range selection as a string for simplicity.
+          if (selection.rangeCount > 1) {
+             return selectedVal;
+          }
 
           // If the selection doesn't start at the beginning or doesn't span the full domain or
           // the URL bar is modified or there is no text at all, nothing else to do here.
           if (this.selectionStart > 0 || this.valueIsTyped || selectedVal == "")
             return selectedVal;
           // The selection doesn't span the full domain if it doesn't contain a slash and is
           // followed by some character other than a slash.
           if (!selectedVal.includes("/")) {
deleted file mode 100644
--- a/browser/base/content/win6BrowserOverlay.xul
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0"?>
-
-<!-- -*- Mode: HTML -*- -->
-<!-- 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/. -->
-
-<overlay id="win6-browser-overlay"
-         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
-  <toolbar id="toolbar-menubar"
-           autohide="true"/>
-</overlay>
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -1,19 +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/.
 browser.jar:
 %  content browser %content/browser/ contentaccessible=yes
 #ifdef XP_MACOSX
 %  overlay chrome://mozapps/content/update/updates.xul chrome://browser/content/softwareUpdateOverlay.xul
 #endif
-#ifdef XP_WIN
-%  overlay chrome://browser/content/browser.xul chrome://browser/content/win6BrowserOverlay.xul os=WINNT osversion>=6
-#endif
 %  overlay chrome://global/content/viewSource.xul chrome://browser/content/viewSourceOverlay.xul
 %  overlay chrome://global/content/viewPartialSource.xul chrome://browser/content/viewSourceOverlay.xul
 
         content/browser/aboutDialog-appUpdater.js     (content/aboutDialog-appUpdater.js)
 *       content/browser/aboutDialog.xul               (content/aboutDialog.xul)
         content/browser/aboutDialog.js                (content/aboutDialog.js)
         content/browser/aboutDialog.css               (content/aboutDialog.css)
         content/browser/aboutRobots.xhtml             (content/aboutRobots.xhtml)
@@ -174,19 +171,16 @@ browser.jar:
 *       content/browser/macBrowserOverlay.xul         (content/macBrowserOverlay.xul)
 *       content/browser/softwareUpdateOverlay.xul  (content/softwareUpdateOverlay.xul)
 #endif
 *       content/browser/viewSourceOverlay.xul         (content/viewSourceOverlay.xul)
 #ifndef XP_MACOSX
 *       content/browser/webrtcIndicator.xul           (content/webrtcIndicator.xul)
         content/browser/webrtcIndicator.js            (content/webrtcIndicator.js)
 #endif
-#ifdef XP_WIN
-        content/browser/win6BrowserOverlay.xul        (content/win6BrowserOverlay.xul)
-#endif
 # the following files are browser-specific overrides
 *       content/browser/license.html                  (/toolkit/content/license.html)
 % override chrome://global/content/license.html chrome://browser/content/license.html
         content/browser/report-phishing-overlay.xul     (content/report-phishing-overlay.xul)
         content/browser/blockedSite.xhtml               (content/blockedSite.xhtml)
 % overlay chrome://browser/content/browser.xul chrome://browser/content/report-phishing-overlay.xul
 
 % override chrome://global/content/netError.xhtml chrome://browser/content/aboutNetError.xhtml
--- a/browser/components/controlcenter/content/panel.inc.xul
+++ b/browser/components/controlcenter/content/panel.inc.xul
@@ -21,25 +21,21 @@
       <!-- Security Section -->
       <hbox id="identity-popup-security" class="identity-popup-section">
         <vbox id="identity-popup-security-content" flex="1">
           <label class="plain">
             <label class="identity-popup-headline host"></label>
             <label class="identity-popup-headline hostless" crop="end"/>
           </label>
           <description class="identity-popup-connection-not-secure"
-                       value="&identity.connectionNotSecure;"
-                       when-connection="not-secure secure-cert-user-overridden"/>
+                       when-connection="not-secure secure-cert-user-overridden">&identity.connectionNotSecure;</description>
           <description class="identity-popup-connection-secure"
-                       value="&identity.connectionSecure;"
-                       when-connection="secure secure-ev"/>
-          <description value="&identity.connectionInternal;"
-                       when-connection="chrome"/>
-          <description value="&identity.connectionFile;"
-                       when-connection="file"/>
+                       when-connection="secure secure-ev">&identity.connectionSecure;</description>
+          <description when-connection="chrome">&identity.connectionInternal;</description>
+          <description when-connection="file">&identity.connectionFile;</description>
 
           <vbox id="identity-popup-security-descriptions">
             <description class="identity-popup-warning-gray"
                          when-mixedcontent="active-blocked">&identity.activeBlocked;</description>
             <description class="identity-popup-warning-yellow"
                          when-mixedcontent="passive-loaded">&identity.passiveLoaded;</description>
             <description when-mixedcontent="active-loaded">&identity.activeLoaded;</description>
             <description class="identity-popup-warning-yellow"
@@ -100,21 +96,19 @@
     <!-- Security SubView -->
     <panelview id="identity-popup-securityView" flex="1">
       <vbox id="identity-popup-securityView-header">
         <label class="plain">
           <label class="identity-popup-headline host"></label>
           <label class="identity-popup-headline hostless" crop="end"/>
         </label>
         <description class="identity-popup-connection-not-secure"
-                     value="&identity.connectionNotSecure;"
-                     when-connection="not-secure secure-cert-user-overridden"/>
+                     when-connection="not-secure secure-cert-user-overridden">&identity.connectionNotSecure;</description>
         <description class="identity-popup-connection-secure"
-                     value="&identity.connectionSecure;"
-                     when-connection="secure secure-ev"/>
+                     when-connection="secure secure-ev">&identity.connectionSecure;</description>
       </vbox>
 
       <vbox id="identity-popup-securityView-body" flex="1">
         <!-- (EV) Certificate Information -->
         <description id="identity-popup-content-verified-by"
                      when-connection="secure-ev">&identity.connectionVerified2;</description>
         <description id="identity-popup-content-owner"
                      when-connection="secure-ev"
--- a/browser/components/customizableui/CustomizableUI.jsm
+++ b/browser/components/customizableui/CustomizableUI.jsm
@@ -33,16 +33,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 const kSpecialWidgetPfx = "customizableui-special-";
 
 const kPrefCustomizationState        = "browser.uiCustomization.state";
 const kPrefCustomizationAutoAdd      = "browser.uiCustomization.autoAdd";
 const kPrefCustomizationDebug        = "browser.uiCustomization.debug";
 const kPrefDrawInTitlebar            = "browser.tabs.drawInTitlebar";
+const kPrefSelectedThemeID           = "lightweightThemes.selectedThemeID";
 const kPrefWebIDEInNavbar            = "devtools.webide.widget.inNavbarByDefault";
 
 const kExpectedWindowURL = "chrome://browser/content/browser.xul";
 
 /**
  * The keys are the handlers that are fired when the event type (the value)
  * is fired on the subview. A widget that provides a subview has the option
  * of providing onViewShowing and onViewHiding event handlers.
@@ -257,34 +258,24 @@ var CustomizableUIInternal = {
     this.registerArea(CustomizableUI.AREA_NAVBAR, {
       legacy: true,
       type: CustomizableUI.TYPE_TOOLBAR,
       overflowable: true,
       defaultPlacements: navbarPlacements,
       defaultCollapsed: false,
     }, true);
 
-    if (AppConstants.platform != "macosx") {
+    if (AppConstants.MENUBAR_CAN_AUTOHIDE) {
       this.registerArea(CustomizableUI.AREA_MENUBAR, {
         legacy: true,
         type: CustomizableUI.TYPE_TOOLBAR,
         defaultPlacements: [
           "menubar-items",
         ],
-        get defaultCollapsed() {
-          if (AppConstants.MENUBAR_CAN_AUTOHIDE) {
-            if (AppConstants.platform == "linux") {
-              return true;
-            }
-            // This is duplicated logic from /browser/base/jar.mn
-            // for win6BrowserOverlay.xul.
-            return AppConstants.isPlatformAndVersionAtLeast("win", 6);
-          }
-          return false;
-        }
+        defaultCollapsed: true,
       }, true);
     }
 
     this.registerArea(CustomizableUI.AREA_TABSTRIP, {
       legacy: true,
       type: CustomizableUI.TYPE_TOOLBAR,
       defaultPlacements: [
         "tabbrowser-tabs",
@@ -1925,25 +1916,17 @@ var CustomizableUIInternal = {
   // Note that this does not populate gPlacements, which is done lazily so that
   // the legacy state can be migrated, which is only available once a browser
   // window is openned.
   // The panel area is an exception here, since it has no legacy state and is
   // built lazily - and therefore wouldn't otherwise result in restoring its
   // state immediately when a browser window opens, which is important for
   // other consumers of this API.
   loadSavedState() {
-    let state = null;
-    try {
-      state = Services.prefs.getCharPref(kPrefCustomizationState);
-    } catch (e) {
-      log.debug("No saved state found");
-      // This will fail if nothing has been customized, so silently fall back to
-      // the defaults.
-    }
-
+    let state = Services.prefs.getCharPref(kPrefCustomizationState);
     if (!state) {
       return;
     }
     try {
       gSavedState = JSON.parse(state);
       if (typeof gSavedState != "object" || gSavedState === null) {
         throw "Invalid saved state";
       }
@@ -2519,28 +2502,28 @@ var CustomizableUIInternal = {
     if (gSeenWidgets.size) {
       gDirty = true;
     }
 
     gResetting = false;
   },
 
   _resetUIState() {
-    try {
-      gUIStateBeforeReset.drawInTitlebar = Services.prefs.getBoolPref(kPrefDrawInTitlebar);
-      gUIStateBeforeReset.uiCustomizationState = Services.prefs.getCharPref(kPrefCustomizationState);
-      gUIStateBeforeReset.currentTheme = LightweightThemeManager.currentTheme;
-    } catch (e) { }
-
     this._resetExtraToolbars();
 
-    Services.prefs.clearUserPref(kPrefCustomizationState);
+    gUIStateBeforeReset.selectedThemeID = Services.prefs.getCharPref(kPrefSelectedThemeID);
+    let selectedThemeID = Services.prefs.getDefaultBranch("").getCharPref(kPrefSelectedThemeID);
+    LightweightThemeManager.currentTheme =
+      selectedThemeID ? LightweightThemeManager.getUsedTheme(selectedThemeID) : null;
+
+    gUIStateBeforeReset.drawInTitlebar = Services.prefs.getBoolPref(kPrefDrawInTitlebar);
     Services.prefs.clearUserPref(kPrefDrawInTitlebar);
-    LightweightThemeManager.currentTheme = null;
-    log.debug("State reset");
+
+    gUIStateBeforeReset.uiCustomizationState = Services.prefs.getCharPref(kPrefCustomizationState);
+    Services.prefs.clearUserPref(kPrefCustomizationState);
 
     // Reset placements to make restoring default placements possible.
     gPlacements = new Map();
     gDirtyAreaCache = new Set();
     gSeenWidgets = new Set();
     // Clear the saved state to ensure that defaults will be used.
     gSavedState = null;
     // Restore the state for each area to its defaults
@@ -2598,25 +2581,26 @@ var CustomizableUIInternal = {
     if (gUIStateBeforeReset.uiCustomizationState == null ||
         gUIStateBeforeReset.drawInTitlebar == null) {
       return;
     }
     gUndoResetting = true;
 
     let uiCustomizationState = gUIStateBeforeReset.uiCustomizationState;
     let drawInTitlebar = gUIStateBeforeReset.drawInTitlebar;
-    let currentTheme = gUIStateBeforeReset.currentTheme;
+    let selectedThemeID = gUIStateBeforeReset.selectedThemeID;
 
     // Need to clear the previous state before setting the prefs
     // because pref observers may check if there is a previous UI state.
     this._clearPreviousUIState();
 
     Services.prefs.setCharPref(kPrefCustomizationState, uiCustomizationState);
     Services.prefs.setBoolPref(kPrefDrawInTitlebar, drawInTitlebar);
-    LightweightThemeManager.currentTheme = currentTheme;
+    LightweightThemeManager.currentTheme =
+      selectedThemeID ? LightweightThemeManager.getUsedTheme(selectedThemeID) : null;
     this.loadSavedState();
     // If the user just customizes toolbar/titlebar visibility, gSavedState will be null
     // and we don't need to do anything else here:
     if (gSavedState) {
       for (let areaId of Object.keys(gSavedState.placements)) {
         let placements = gSavedState.placements[areaId];
         gPlacements.set(areaId, placements);
       }
@@ -2785,18 +2769,19 @@ var CustomizableUIInternal = {
       }
     }
 
     if (Services.prefs.prefHasUserValue(kPrefDrawInTitlebar)) {
       log.debug(kPrefDrawInTitlebar + " pref is non-default");
       return false;
     }
 
-    if (LightweightThemeManager.currentTheme) {
-      log.debug(LightweightThemeManager.currentTheme + " theme is non-default");
+    if (Services.prefs.getDefaultBranch("").getCharPref(kPrefSelectedThemeID) !=
+        Services.prefs.getCharPref(kPrefSelectedThemeID)) {
+      log.debug(kPrefSelectedThemeID + " pref is non-default");
       return false;
     }
 
     return true;
   },
 
   setToolbarVisibility(aToolbarId, aIsVisible) {
     // We only persist the attribute the first time.
--- a/browser/components/customizableui/CustomizeMode.jsm
+++ b/browser/components/customizableui/CustomizeMode.jsm
@@ -444,17 +444,17 @@ CustomizeMode.prototype = {
     this._transitioning = true;
 
     Task.spawn(function*() {
       yield this.depopulatePalette();
 
       yield this._doTransition(false);
       this.removeLWTStyling();
 
-      Services.obs.removeObserver(this, "lightweight-theme-window-updated", false);
+      Services.obs.removeObserver(this, "lightweight-theme-window-updated");
 
       if (this.browser.selectedTab == gTab) {
         if (gTab.linkedBrowser.currentURI.spec == "about:blank") {
           closeGlobalTab();
         } else {
           unregisterGlobalTab();
         }
       }
--- a/browser/components/customizableui/test/browser_934951_zoom_in_toolbar.js
+++ b/browser/components/customizableui/test/browser_934951_zoom_in_toolbar.js
@@ -55,22 +55,22 @@ add_task(function*() {
   });
   is(parseInt(zoomResetButton.label, 10), 110, "Zoom is still 110% for about:mozilla");
   FullZoom.reset();
 });
 
 function promiseObserverNotification(aObserver) {
   let deferred = Promise.defer();
   function notificationCallback(e) {
-    Services.obs.removeObserver(notificationCallback, aObserver, false);
+    Services.obs.removeObserver(notificationCallback, aObserver);
     clearTimeout(timeoutId);
     deferred.resolve();
   }
   let timeoutId = setTimeout(() => {
-    Services.obs.removeObserver(notificationCallback, aObserver, false);
+    Services.obs.removeObserver(notificationCallback, aObserver);
     deferred.reject("Notification '" + aObserver + "' did not happen within 20 seconds.");
   }, kTimeoutInMS);
   Services.obs.addObserver(notificationCallback, aObserver, false);
   return deferred.promise;
 }
 
 function promiseTabSelect() {
   let deferred = Promise.defer();
--- a/browser/components/distribution.js
+++ b/browser/components/distribution.js
@@ -431,18 +431,18 @@ DistributionCustomizer.prototype = {
         }
         try {
           let value = this._ini.getString("LocalizablePreferences", key);
           if (value) {
             value = parseValue(value);
             value = value.replace(/%LOCALE%/g, this._locale);
             value = value.replace(/%LANGUAGE%/g, this._language);
             localizedStr.data = "data:text/plain," + key + "=" + value;
+            defaults._prefBranch.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedStr);
           }
-          defaults._prefBranch.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedStr);
         } catch (e) { /* ignore bad prefs and move on */ }
       }
     }
 
     return this._checkCustomizationComplete();
   },
 
   _checkCustomizationComplete: function DIST__checkCustomizationComplete() {
--- a/browser/components/extensions/ext-contextMenus.js
+++ b/browser/components/extensions/ext-contextMenus.js
@@ -319,16 +319,17 @@ function getContexts(contextData) {
 function MenuItem(extension, createProperties, isRoot = false) {
   this.extension = extension;
   this.children = [];
   this.parent = null;
   this.tabManager = TabManager.for(extension);
 
   this.setDefaults();
   this.setProps(createProperties);
+
   if (!this.hasOwnProperty("_id")) {
     this.id = gNextMenuItemID++;
   }
   // If the item is not the root and has no parent
   // it must be a child of the root.
   if (!isRoot && !this.parent) {
     this.root.addChild(this);
   }
@@ -346,16 +347,22 @@ MenuItem.prototype = {
 
     if (createProperties.documentUrlPatterns != null) {
       this.documentUrlMatchPattern = new MatchPattern(this.documentUrlPatterns);
     }
 
     if (createProperties.targetUrlPatterns != null) {
       this.targetUrlMatchPattern = new MatchPattern(this.targetUrlPatterns);
     }
+
+    // If a child MenuItem does not specify any contexts, then it should
+    // inherit the contexts specified from its parent.
+    if (createProperties.parentId && !createProperties.contexts) {
+      this.contexts = this.parent.contexts;
+    }
   },
 
   setDefaults() {
     this.setProps({
       type: "normal",
       checked: false,
       contexts: ["all"],
       enabled: true,
--- a/browser/components/extensions/ext-sessions.js
+++ b/browser/components/extensions/ext-sessions.js
@@ -6,17 +6,17 @@ Cu.import("resource://gre/modules/Extens
 var {
   promiseObserved,
   SingletonEventManager,
 } = ExtensionUtils;
 
 XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
                                   "resource:///modules/sessionstore/SessionStore.jsm");
 
-const ssOnChangedTopic = "sessionstore-closed-objects-changed";
+const SS_ON_CLOSED_OBJECTS_CHANGED = "sessionstore-closed-objects-changed";
 
 function getRecentlyClosed(maxResults, extension) {
   let recentlyClosed = [];
 
   // Get closed windows
   let closedWindowData = SessionStore.getClosedWindowData(false);
   for (let window of closedWindowData) {
     recentlyClosed.push({
@@ -57,16 +57,17 @@ function createSession(restored, extensi
 extensions.registerSchemaAPI("sessions", "addon_parent", context => {
   let {extension} = context;
   return {
     sessions: {
       getRecentlyClosed: function(filter) {
         let maxResults = filter.maxResults == undefined ? this.MAX_SESSION_RESULTS : filter.maxResults;
         return Promise.resolve(getRecentlyClosed(maxResults, extension));
       },
+
       restore: function(sessionId) {
         let session, closedId;
         if (sessionId) {
           closedId = sessionId;
           session = SessionStore.undoCloseById(closedId);
         } else if (SessionStore.lastClosedObjectType == "window") {
           // If the most recently closed object is a window, just undo closing the most recent window.
           session = SessionStore.undoCloseWindow(0);
@@ -85,38 +86,22 @@ extensions.registerSchemaAPI("sessions",
           recentlyClosedTabs.sort((a, b) => b.closedAt - a.closedAt);
 
           // Use the closedId of the most recently closed tab to restore it.
           closedId = recentlyClosedTabs[0].closedId;
           session = SessionStore.undoCloseById(closedId);
         }
         return createSession(session, extension, closedId);
       },
+
       onChanged: new SingletonEventManager(context, "sessions.onChanged", fire => {
-        let listenerCount = 0;
-
-        let observer = {
-          observe: function() {
-            this.emit("changed");
-          },
-        };
-        EventEmitter.decorate(observer);
-
-        let listener = (event) => {
+        let observer = () => {
           context.runSafe(fire);
         };
 
-        observer.on("changed", listener);
-        listenerCount++;
-        if (listenerCount == 1) {
-          Services.obs.addObserver(observer, ssOnChangedTopic, false);
-        }
+        Services.obs.addObserver(observer, SS_ON_CLOSED_OBJECTS_CHANGED, false);
         return () => {
-          observer.off("changed", listener);
-          listenerCount -= 1;
-          if (!listenerCount) {
-            Services.obs.removeObserver(observer, ssOnChangedTopic);
-          }
+          Services.obs.removeObserver(observer, SS_ON_CLOSED_OBJECTS_CHANGED);
         };
       }).api(),
     },
   };
 });
--- a/browser/components/extensions/ext-utils.js
+++ b/browser/components/extensions/ext-utils.js
@@ -705,16 +705,25 @@ ExtensionTabManager.prototype = {
       windowId: WindowManager.getId(window),
       selected: false,
       highlighted: false,
       active: false,
       pinned: false,
       incognito: Boolean(tab.state && tab.state.isPrivate),
     };
 
+    if (this.hasTabPermission(tab)) {
+      let entries = tab.state ? tab.state.entries : tab.entries;
+      result.url = entries[0].url;
+      result.title = entries[0].title;
+      if (tab.image) {
+        result.favIconUrl = tab.image;
+      }
+    }
+
     return result;
   },
 
   getTabs(window) {
     return Array.from(window.gBrowser.tabs)
                 .filter(tab => !tab.closing)
                 .map(tab => this.convert(tab));
   },
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -58,16 +58,17 @@ support-files =
 [browser_ext_popup_corners.js]
 [browser_ext_popup_sendMessage.js]
 [browser_ext_popup_shutdown.js]
 [browser_ext_runtime_openOptionsPage.js]
 [browser_ext_runtime_openOptionsPage_uninstall.js]
 [browser_ext_runtime_setUninstallURL.js]
 [browser_ext_sessions_getRecentlyClosed.js]
 [browser_ext_sessions_getRecentlyClosed_private.js]
+[browser_ext_sessions_getRecentlyClosed_tabs.js]
 [browser_ext_sessions_restore.js]
 [browser_ext_simple.js]
 [browser_ext_tab_runtimeConnect.js]
 [browser_ext_tabs_audio.js]
 [browser_ext_tabs_captureVisibleTab.js]
 [browser_ext_tabs_create.js]
 [browser_ext_tabs_create_invalid_url.js]
 [browser_ext_tabs_detectLanguage.js]
--- a/browser/components/extensions/test/browser/browser_ext_contextMenus_chrome.js
+++ b/browser/components/extensions/test/browser/browser_ext_contextMenus_chrome.js
@@ -9,18 +9,18 @@ add_task(function* test_actionContextMen
     browser_action: {},
     permissions: ["contextMenus"],
   };
 
   async function background() {
     const contexts = ["page_action", "browser_action"];
 
     const parentId = browser.contextMenus.create({contexts, title: "parent"});
-    await browser.contextMenus.create({contexts, parentId, title: "click A"});
-    await browser.contextMenus.create({contexts, parentId, title: "click B"});
+    await browser.contextMenus.create({parentId, title: "click A"});
+    await browser.contextMenus.create({parentId, title: "click B"});
 
     for (let i = 1; i < 9; i++) {
       await browser.contextMenus.create({contexts, title: `click ${i}`});
     }
 
     browser.contextMenus.onClicked.addListener((info, tab) => {
       browser.test.sendMessage("click", {info, tab});
     });
@@ -64,18 +64,21 @@ add_task(function* test_actionContextMen
 });
 
 add_task(function* test_tabContextMenu() {
   const first = ExtensionTestUtils.loadExtension({
     manifest: {
       permissions: ["contextMenus"],
     },
     async background() {
-      await browser.contextMenus.create({title: "alpha", contexts: ["tab"]});
-      await browser.contextMenus.create({title: "beta", contexts: ["tab"]});
+      await browser.contextMenus.create({
+        id: "alpha-beta-parent", title: "alpha-beta parent", contexts: ["tab"],
+      });
+      await browser.contextMenus.create({parentId: "alpha-beta-parent", title: "alpha"});
+      await browser.contextMenus.create({parentId: "alpha-beta-parent", title: "beta"});
 
       browser.contextMenus.onClicked.addListener((info, tab) => {
         browser.test.sendMessage("click", {info, tab});
       });
 
       const [tab] = await browser.tabs.query({active: true});
       browser.test.sendMessage("ready", tab.id);
     },
@@ -98,17 +101,17 @@ add_task(function* test_tabContextMenu()
   const tabId = yield first.awaitMessage("ready");
   yield second.awaitMessage("ready");
 
   const menu = yield openTabContextMenu();
   const [separator, submenu, gamma] = Array.from(menu.children).slice(-3);
   is(separator.tagName, "menuseparator", "Separator before first extension item");
 
   is(submenu.tagName, "menu", "Correct submenu type");
-  is(submenu.label, "Generated extension", "Correct submenu title");
+  is(submenu.label, "alpha-beta parent", "Correct submenu title");
 
   is(gamma.tagName, "menuitem", "Third menu item type is correct");
   is(gamma.label, "gamma", "Third menu item label is correct");
 
   const popup = yield openSubmenu(submenu);
   is(popup, submenu.firstChild, "Correct submenu opened");
   is(popup.children.length, 2, "Correct number of submenu items");
 
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed_tabs.js
@@ -0,0 +1,96 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+function expectedTabInfo(tab, window) {
+  let browser = tab.linkedBrowser;
+  return {
+    url: browser.currentURI.spec,
+    title: browser.contentTitle,
+    favIconUrl: window.gBrowser.getIcon(tab),
+  };
+}
+
+function checkTabInfo(expected, actual) {
+  for (let prop in expected) {
+    is(actual[prop], expected[prop], `Expected value found for ${prop} of tab object.`);
+  }
+}
+
+add_task(async function test_sessions_get_recently_closed_tabs() {
+  async function background() {
+    browser.test.onMessage.addListener(async msg => {
+      if (msg == "check-sessions") {
+        let recentlyClosed = await browser.sessions.getRecentlyClosed();
+        browser.test.sendMessage("recentlyClosed", recentlyClosed);
+      }
+    });
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["sessions", "tabs"],
+    },
+    background,
+  });
+
+  let win = await BrowserTestUtils.openNewBrowserWindow();
+  await BrowserTestUtils.loadURI(win.gBrowser.selectedBrowser, "about:addons");
+  await BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+  let expectedTabs = [];
+  let tab = win.gBrowser.selectedTab;
+  expectedTabs.push(expectedTabInfo(tab, win));
+
+  for (let url of ["about:robots", "about:mozilla"]) {
+    tab = await BrowserTestUtils.openNewForegroundTab(win.gBrowser, url);
+    expectedTabs.push(expectedTabInfo(tab, win));
+  }
+
+  await extension.startup();
+
+  // Test with a closed tab.
+  await BrowserTestUtils.removeTab(tab);
+
+  extension.sendMessage("check-sessions");
+  let recentlyClosed = await extension.awaitMessage("recentlyClosed");
+  let tabInfo = recentlyClosed[0].tab;
+  let expectedTab = expectedTabs.pop();
+  checkTabInfo(expectedTab, tabInfo);
+
+  // Test with a closed window containing tabs.
+  await BrowserTestUtils.closeWindow(win);
+
+  extension.sendMessage("check-sessions");
+  recentlyClosed = await extension.awaitMessage("recentlyClosed");
+  let tabInfos = recentlyClosed[0].window.tabs;
+  is(tabInfos.length, 2, "Expected number of tabs in closed window.");
+  for (let x = 0; x < tabInfos.length; x++) {
+    checkTabInfo(expectedTabs[x], tabInfos[x]);
+  }
+
+  await extension.unload();
+
+  // Test without tabs permission.
+  extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["sessions"],
+    },
+    background,
+  });
+
+  await extension.startup();
+
+  extension.sendMessage("check-sessions");
+  recentlyClosed = await extension.awaitMessage("recentlyClosed");
+  tabInfos = recentlyClosed[0].window.tabs;
+  is(tabInfos.length, 2, "Expected number of tabs in closed window.");
+  for (let tabInfo of tabInfos) {
+    for (let prop in expectedTabs[0]) {
+      is(undefined,
+         tabInfo[prop],
+         `${prop} of tab object is undefined without tabs permission.`);
+    }
+  }
+
+  await extension.unload();
+});
--- a/browser/components/extensions/test/browser/head.js
+++ b/browser/components/extensions/test/browser/head.js
@@ -12,18 +12,18 @@
  *          openExtensionContextMenu closeExtensionContextMenu
  *          openActionContextMenu openSubmenu closeActionContextMenu
  *          openTabContextMenu closeTabContextMenu
  *          imageBuffer getListStyleImage getPanelForNode
  *          awaitExtensionPanel awaitPopupResize
  *          promiseContentDimensions alterContent
  */
 
-const {AppConstants} = Cu.import("resource://gre/modules/AppConstants.jsm");
-const {CustomizableUI} = Cu.import("resource:///modules/CustomizableUI.jsm");
+const {AppConstants} = Cu.import("resource://gre/modules/AppConstants.jsm", {});
+const {CustomizableUI} = Cu.import("resource:///modules/CustomizableUI.jsm", {});
 
 // We run tests under two different configurations, from browser.ini and
 // browser-remote.ini. When running from browser-remote.ini, the tests are
 // copied to the sub-directory "test-oop-extensions", which we detect here, and
 // use to select our configuration.
 if (gTestPath.includes("test-oop-extensions")) {
   SpecialPowers.pushPrefEnv({set: [
     ["dom.ipc.processCount", 1],
--- a/browser/components/migration/tests/unit/test_Edge_db_migration.js
+++ b/browser/components/migration/tests/unit/test_Edge_db_migration.js
@@ -1,13 +1,13 @@
 "use strict";
 
 Cu.import("resource://gre/modules/ctypes.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
-let eseBackStage = Cu.import("resource:///modules/ESEDBReader.jsm");
+let eseBackStage = Cu.import("resource:///modules/ESEDBReader.jsm", {});
 let ESE = eseBackStage.ESE;
 let KERNEL = eseBackStage.KERNEL;
 let gLibs = eseBackStage.gLibs;
 let COLUMN_TYPES = eseBackStage.COLUMN_TYPES;
 let declareESEFunction = eseBackStage.declareESEFunction;
 let loadLibraries = eseBackStage.loadLibraries;
 
 let gESEInstanceCounter = 1;
@@ -463,9 +463,8 @@ add_task(function*() {
       Assert.equal(bookmark.title, importParentFolderName, "Only the extra layer of folders isn't in the input we stuck in the DB.");
       Assert.ok([menuParentGuid, toolbarParentGuid].includes(bookmark.itemGuid), "This item should be one of the containers");
     } else {
       Assert.equal(dbItem.URL || null, bookmark.url && bookmark.url.spec, "URL is correct");
       Assert.equal(dbItem.DateUpdated.valueOf(), (new Date(bookmark.dateAdded / 1000)).valueOf(), "Date added is correct");
     }
   }
 });
-
--- a/browser/components/migration/tests/unit/test_automigration.js
+++ b/browser/components/migration/tests/unit/test_automigration.js
@@ -1,21 +1,23 @@
 "use strict";
 
-let AutoMigrateBackstage = Cu.import("resource:///modules/AutoMigrate.jsm"); /* globals AutoMigrate */
+Cu.import("resource:///modules/AutoMigrate.jsm", this);
 
 let gShimmedMigratorKeyPicker = null;
 let gShimmedMigrator = null;
 
 const kUsecPerMin = 60 * 1000000;
 
 // This is really a proxy on MigrationUtils, but if we specify that directly,
 // we get in trouble because the object itself is frozen, and Proxies can't
 // return a different value to an object when directly proxying a frozen
 // object.
+let AutoMigrateBackstage = Cu.import("resource:///modules/AutoMigrate.jsm", {});
+
 AutoMigrateBackstage.MigrationUtils = new Proxy({}, {
   get(target, name) {
     if (name == "getMigratorKeyForDefaultBrowser" && gShimmedMigratorKeyPicker) {
       return gShimmedMigratorKeyPicker;
     }
     if (name == "getMigrator" && gShimmedMigrator) {
       return function() { return gShimmedMigrator };
     }
@@ -603,9 +605,8 @@ add_task(function* checkUndoVisitsState(
   Assert.equal(yield visitsForURL("http://www.mozilla.org/"), 0,
                "0 mozilla.org visits should have persisted (out of 1).");
   Assert.equal(yield visitsForURL("http://www.example.org/"), 2,
                "2 example.org visits should have persisted (out of 4).");
   Assert.equal(yield visitsForURL("http://www.unrelated.org/"), 1,
                "1 unrelated.org visits should have persisted as it's not involved in the import.");
   yield PlacesTestUtils.clearHistory();
 });
-
--- a/browser/components/newtab/NewTabPrefsProvider.jsm
+++ b/browser/components/newtab/NewTabPrefsProvider.jsm
@@ -93,17 +93,17 @@ PrefsProvider.prototype = {
   init() {
     for (let pref of gPrefsMap.keys()) {
       Services.prefs.addObserver(pref, this, false);
     }
   },
 
   uninit() {
     for (let pref of gPrefsMap.keys()) {
-      Services.prefs.removeObserver(pref, this, false);
+      Services.prefs.removeObserver(pref, this);
     }
   }
 };
 
 /**
  * Singleton that serves as the default new tab pref provider for the grid.
  */
 const gPrefs = new PrefsProvider();
--- a/browser/components/newtab/NewTabSearchProvider.jsm
+++ b/browser/components/newtab/NewTabSearchProvider.jsm
@@ -51,17 +51,17 @@ SearchProvider.prototype = {
   },
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
     Ci.nsISupportsWeakReference
   ]),
 
   uninit() {
     try {
-      Services.obs.removeObserver(this, CURRENT_ENGINE, true);
+      Services.obs.removeObserver(this, CURRENT_ENGINE);
     } catch (e) {
       Cu.reportError(e);
     }
   },
 
   get searchSuggestionUIStrings() {
     return ContentSearch.searchSuggestionUIStrings;
   },
--- a/browser/components/newtab/PreviewProvider.jsm
+++ b/browser/components/newtab/PreviewProvider.jsm
@@ -5,17 +5,17 @@
 
 this.EXPORTED_SYMBOLS = ["PreviewProvider"];
 
 const {utils: Cu} = Components;
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/PageThumbs.jsm");
 Cu.import("resource://gre/modules/FileUtils.jsm");
-const {OS} = Cu.import("resource://gre/modules/osfile.jsm");
+const {OS} = Cu.import("resource://gre/modules/osfile.jsm", {});
 
 XPCOMUtils.defineLazyModuleGetter(this, "BackgroundPageThumbs",
   "resource://gre/modules/BackgroundPageThumbs.jsm");
 XPCOMUtils.defineLazyServiceGetter(this, "MIMEService",
   "@mozilla.org/mime;1", "nsIMIMEService");
 
 let PreviewProvider = {
   /**
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -434,18 +434,17 @@ BrowserGlue.prototype = {
 
     if (AppConstants.NIGHTLY_BUILD) {
       os.addObserver(this, AddonWatcher.TOPIC_SLOW_ADDON_DETECTED, false);
     }
 
     this._flashHangCount = 0;
     this._firstWindowReady = new Promise(resolve => this._firstWindowLoaded = resolve);
 
-    if (AppConstants.isPlatformAndVersionAtMost("win", "5.2") ||
-        AppConstants.platform == "macosx") {
+    if (AppConstants.platform == "macosx") {
       // Handles prompting to inform about incompatibilites when accessibility
       // and e10s are active together.
       E10SAccessibilityCheck.init();
     }
   },
 
   // cleanup (called on application shutdown)
   _dispose: function BG__dispose() {
--- a/browser/components/originattributes/test/browser/browser_favicon_firstParty.js
+++ b/browser/components/originattributes/test/browser/browser_favicon_firstParty.js
@@ -37,17 +37,17 @@ function clearAllPlacesFavicons() {
   let faviconService = Cc["@mozilla.org/browser/favicon-service;1"]
                           .getService(Ci.nsIFaviconService);
 
   return new Promise(resolve => {
     let observer = {
       observe(aSubject, aTopic, aData) {
         if (aTopic === "places-favicons-expired") {
           resolve();
-          Services.obs.removeObserver(observer, "places-favicons-expired", false);
+          Services.obs.removeObserver(observer, "places-favicons-expired");
         }
       }
     };
 
     Services.obs.addObserver(observer, "places-favicons-expired", false);
     faviconService.expireAllFavicons();
   });
 }
@@ -97,17 +97,17 @@ function observeFavicon(aFirstPartyDomai
           let faviconCookie = httpChannel.getRequestHeader("cookie");
 
           is(faviconCookie, aExpectedCookie, "The cookie of the favicon loading is correct.");
         } else {
           ok(false, "Received unexpected topic: ", aTopic);
         }
 
         if (faviconReqXUL && faviconReqPlaces) {
-          Services.obs.removeObserver(observer, "http-on-modify-request", false);
+          Services.obs.removeObserver(observer, "http-on-modify-request");
           resolve();
         }
       }
     };
 
     Services.obs.addObserver(observer, "http-on-modify-request", false);
   });
 }
@@ -127,18 +127,18 @@ function waitOnFaviconResponse(aFaviconU
           }
 
           let result = {
             topic: aTopic,
             firstPartyDomain: loadInfo.originAttributes.firstPartyDomain
           };
 
           resolve(result);
-          Services.obs.removeObserver(observer, "http-on-examine-response", false);
-          Services.obs.removeObserver(observer, "http-on-examine-cached-response", false);
+          Services.obs.removeObserver(observer, "http-on-examine-response");
+          Services.obs.removeObserver(observer, "http-on-examine-cached-response");
         }
       }
     };
 
     Services.obs.addObserver(observer, "http-on-examine-response", false);
     Services.obs.addObserver(observer, "http-on-examine-cached-response", false);
   });
 }
--- a/browser/components/originattributes/test/browser/browser_favicon_userContextId.js
+++ b/browser/components/originattributes/test/browser/browser_favicon_userContextId.js
@@ -37,17 +37,17 @@ function clearAllPlacesFavicons() {
   let faviconService = Cc["@mozilla.org/browser/favicon-service;1"]
                           .getService(Ci.nsIFaviconService);
 
   return new Promise(resolve => {
     let observer = {
       observe(aSubject, aTopic, aData) {
         if (aTopic === "places-favicons-expired") {
           resolve();
-          Services.obs.removeObserver(observer, "places-favicons-expired", false);
+          Services.obs.removeObserver(observer, "places-favicons-expired");
         }
       }
     };
 
     Services.obs.addObserver(observer, "places-favicons-expired", false);
     faviconService.expireAllFavicons();
   });
 }
@@ -191,17 +191,17 @@ function* doTest(aTestPage, aFaviconHost
 
   // Reset the observer for observing requests for the work container.
   observer.reset(USER_CONTEXT_ID_WORK, cookies[1], pageURI, aFaviconURL);
   tabInfo = yield openTabInUserContext(aTestPage, USER_CONTEXT_ID_WORK);
 
   // Waiting for favicon requests are all made.
   yield observer.promise;
 
-  Services.obs.removeObserver(observer, "http-on-modify-request", false);
+  Services.obs.removeObserver(observer, "http-on-modify-request");
 
   yield BrowserTestUtils.removeTab(tabInfo.tab);
 }
 
 add_task(function* setup() {
   // Make sure userContext is enabled.
   yield SpecialPowers.pushPrefEnv({"set": [
       ["privacy.userContext.enabled", true]
--- a/browser/components/preferences/in-content/search.js
+++ b/browser/components/preferences/in-content/search.js
@@ -36,17 +36,17 @@ var gSearchPane = {
     window.addEventListener("command", this, false);
     window.addEventListener("dragstart", this, false);
     window.addEventListener("keypress", this, false);
     window.addEventListener("select", this, false);
     window.addEventListener("blur", this, true);
 
     Services.obs.addObserver(this, "browser-search-engine-modified", false);
     window.addEventListener("unload", () => {
-      Services.obs.removeObserver(this, "browser-search-engine-modified", false);
+      Services.obs.removeObserver(this, "browser-search-engine-modified");
     });
 
     this._initAutocomplete();
 
     let suggestsPref =
       document.getElementById("browser.search.suggest.enabled");
     suggestsPref.addEventListener("change", () => {
       this.updateSuggestsCheckbox();
--- a/browser/components/preferences/in-content/tests/browser_advanced_siteData.js
+++ b/browser/components/preferences/in-content/tests/browser_advanced_siteData.js
@@ -31,28 +31,52 @@ const mockOfflineAppCacheHelper = {
   unregister() {
     OfflineAppCacheHelper.clear = this.originalClear;
   }
 };
 
 const mockSiteDataManager = {
   sites: new Map([
     [
+      "https://account.xyz.com/",
+      {
+        usage: 1024 * 200,
+        host: "account.xyz.com",
+        status: Ci.nsIPermissionManager.ALLOW_ACTION
+      }
+    ],
+    [
       "https://shopping.xyz.com/",
       {
-        usage: 102400,
+        usage: 1024 * 100,
         host: "shopping.xyz.com",
+        status: Ci.nsIPermissionManager.DENY_ACTION
+      }
+    ],
+    [
+      "https://video.bar.com/",
+      {
+        usage: 1024 * 20,
+        host: "video.bar.com",
         status: Ci.nsIPermissionManager.ALLOW_ACTION
       }
     ],
     [
       "https://music.bar.com/",
       {
-        usage: 10240,
+        usage: 1024 * 10,
         host: "music.bar.com",
+        status: Ci.nsIPermissionManager.DENY_ACTION
+      }
+    ],
+    [
+      "https://books.foo.com/",
+      {
+        usage: 1024 * 2,
+        host: "books.foo.com",
         status: Ci.nsIPermissionManager.ALLOW_ACTION
       }
     ],
     [
       "https://news.foo.com/",
       {
         usage: 1024,
         host: "news.foo.com",
@@ -299,8 +323,54 @@ add_task(function* () {
         Assert.lessOrEqual(result, 0, "Should sort sites in the ascending order by usage");
       } else {
         Assert.greaterOrEqual(result, 0, "Should sort sites in the descending order by usage");
       }
     }
   }
 });
 
+add_task(function* () {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.storageManager.enabled", true]]});
+
+  mockSiteDataManager.register();
+  let updatePromise = promiseSitesUpdated();
+  yield openPreferencesViaOpenPreferencesAPI("advanced", "networkTab", { leaveOpen: true });
+  yield updatePromise;
+
+  // Open the siteDataSettings subdialog
+  let doc = gBrowser.selectedBrowser.contentDocument;
+  let settingsBtn = doc.getElementById("siteDataSettings");
+  let dialogOverlay = doc.getElementById("dialogOverlay");
+  let dialogPromise = promiseLoadSubDialog("chrome://browser/content/preferences/siteDataSettings.xul");
+  settingsBtn.doCommand();
+  yield dialogPromise;
+  is(dialogOverlay.style.visibility, "visible", "The dialog should be visible");
+
+  let frameDoc = doc.getElementById("dialogFrame").contentDocument;
+  let searchBox = frameDoc.getElementById("searchBox");
+  let mockOrigins = Array.from(mockSiteDataManager.sites.keys());
+
+  searchBox.value = "xyz";
+  searchBox.doCommand();
+  assertSitesListed(mockOrigins.filter(o => o.includes("xyz")));
+
+  searchBox.value = "bar";
+  searchBox.doCommand();
+  assertSitesListed(mockOrigins.filter(o => o.includes("bar")));
+
+  searchBox.value = "";
+  searchBox.doCommand();
+  assertSitesListed(mockOrigins);
+
+  mockSiteDataManager.unregister();
+  yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+  function assertSitesListed(origins) {
+    let sitesList = frameDoc.getElementById("sitesList");
+    let totalSitesNumber = sitesList.getElementsByTagName("richlistitem").length;
+    is(totalSitesNumber, origins.length, "Should list the right sites number");
+    origins.forEach(origin => {
+      let site = sitesList.querySelector(`richlistitem[data-origin="${origin}"]`);
+      ok(site instanceof XULElement, `Should list the site of ${origin}`);
+    });
+  }
+});
--- a/browser/components/preferences/siteDataSettings.css
+++ b/browser/components/preferences/siteDataSettings.css
@@ -1,12 +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/. */
 
+#searchBoxContainer {
+  -moz-box-align: center;
+}
+
 #sitesList {
   min-height: 20em;
 }
 
 #sitesList > richlistitem {
   -moz-binding: url("chrome://browser/content/preferences/siteListItem.xml#siteListItem");
 }
 
--- a/browser/components/preferences/siteDataSettings.js
+++ b/browser/components/preferences/siteDataSettings.js
@@ -18,34 +18,37 @@ let gSiteDataSettings = {
 
   // Array of meatdata of sites. Each array element is object holding:
   // - uri: uri of site; instance of nsIURI
   // - status: persistent-storage permission status
   // - usage: disk usage which site uses
   _sites: null,
 
   _list: null,
+  _searchBox: null,
 
   init() {
     function setEventListener(id, eventType, callback) {
       document.getElementById(id)
               .addEventListener(eventType, callback.bind(gSiteDataSettings));
     }
 
     this._list = document.getElementById("sitesList");
+    this._searchBox = document.getElementById("searchBox");
     SiteDataManager.getSites().then(sites => {
       this._sites = sites;
       let sortCol = document.getElementById("hostCol");
       this._sortSites(this._sites, sortCol);
       this._buildSitesList(this._sites);
     });
 
     setEventListener("hostCol", "click", this.onClickTreeCol);
     setEventListener("usageCol", "click", this.onClickTreeCol);
     setEventListener("statusCol", "click", this.onClickTreeCol);
+    setEventListener("searchBox", "command", this.onCommandSearch);
   },
 
   /**
    * @param sites {Array}
    * @param col {XULElement} the <treecol> being sorted on
    */
   _sortSites(sites, col) {
     let isCurrentSortCol = col.getAttribute("data-isCurrentSortCol")
@@ -84,32 +87,46 @@ let gSiteDataSettings = {
       c.removeAttribute("sortDirection");
       c.removeAttribute("data-isCurrentSortCol");
     });
     col.setAttribute("data-isCurrentSortCol", true);
     col.setAttribute("sortDirection", sortDirection);
     col.setAttribute("data-last-sortDirection", sortDirection);
   },
 
+  /**
+   * @param sites {Array} array of metadata of sites
+   */
   _buildSitesList(sites) {
     // Clear old entries.
-    while (this._list.childNodes.length > 1) {
-      this._list.removeChild(this._list.lastChild);
+    let oldItems = this._list.querySelectorAll("richlistitem");
+    for (let item of oldItems) {
+      item.remove();
     }
 
     let prefStrBundle = document.getElementById("bundlePreferences");
+    let keyword = this._searchBox.value.toLowerCase().trim();
     for (let data of sites) {
+      let host = data.uri.host;
+      if (keyword && !host.includes(keyword)) {
+        continue;
+      }
+
       let statusStrId = data.status === Ci.nsIPermissionManager.ALLOW_ACTION ? "important" : "default";
       let size = DownloadUtils.convertByteUnits(data.usage);
       let item = document.createElement("richlistitem");
       item.setAttribute("data-origin", data.uri.spec);
-      item.setAttribute("host", data.uri.host);
+      item.setAttribute("host", host);
       item.setAttribute("status", prefStrBundle.getString(statusStrId));
       item.setAttribute("usage", prefStrBundle.getFormattedString("siteUsage", size));
       this._list.appendChild(item);
     }
   },
 
   onClickTreeCol(e) {
     this._sortSites(this._sites, e.target);
     this._buildSitesList(this._sites);
+  },
+
+  onCommandSearch() {
+    this._buildSitesList(this._sites);
   }
 };
--- a/browser/components/preferences/siteDataSettings.xul
+++ b/browser/components/preferences/siteDataSettings.xul
@@ -21,16 +21,22 @@
 
   <stringbundle id="bundlePreferences"
                 src="chrome://browser/locale/preferences/preferences.properties"/>
 
   <vbox flex="1">
     <description>&settings.description;</description>
     <separator class="thin"/>
 
+    <hbox id="searchBoxContainer">
+      <label accesskey="&search.accesskey;" control="searchBox">&search.label;</label>
+      <textbox id="searchBox" type="search" flex="1"/>
+    </hbox>
+    <separator class="thin"/>
+
     <richlistbox id="sitesList" orient="vertical" flex="1">
       <listheader>
         <treecol flex="4" width="50" label="&hostCol.label;" id="hostCol"/>
         <treecol flex="2" width="50" label="&statusCol.label;" id="statusCol"/>
         <treecol flex="1" width="50" label="&usageCol.label;" id="usageCol"/>
       </listheader>
     </richlistbox>
   </vbox>
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_favicon.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_favicon.js
@@ -30,17 +30,17 @@ function clearAllPlacesFavicons() {
   let faviconService = Cc["@mozilla.org/browser/favicon-service;1"]
                           .getService(Ci.nsIFaviconService);
 
   return new Promise(resolve => {
     let observer = {
       observe(aSubject, aTopic, aData) {
         if (aTopic === "places-favicons-expired") {
           resolve();
-          Services.obs.removeObserver(observer, "places-favicons-expired", false);
+          Services.obs.removeObserver(observer, "places-favicons-expired");
         }
       }
     };
 
     Services.obs.addObserver(observer, "places-favicons-expired", false);
     faviconService.expireAllFavicons();
   });
 }
@@ -100,17 +100,17 @@ function observeFavicon(aIsPrivate, aExp
 
           is(faviconCookie, aExpectedCookie, "The cookie of the favicon loading is correct.");
         } else {
           ok(false, "Received unexpected topic: ", aTopic);
         }
 
         if (faviconReqXUL && faviconReqPlaces) {
           resolve();
-          Services.obs.removeObserver(observer, "http-on-modify-request", false);
+          Services.obs.removeObserver(observer, "http-on-modify-request");
         }
       }
     };
 
     Services.obs.addObserver(observer, "http-on-modify-request", false);
   });
 }
 
@@ -129,18 +129,18 @@ function waitOnFaviconResponse(aFaviconU
           }
 
           let result = {
             topic: aTopic,
             privateBrowsingId: loadInfo.originAttributes.privateBrowsingId
           };
 
           resolve(result);
-          Services.obs.removeObserver(observer, "http-on-examine-response", false);
-          Services.obs.removeObserver(observer, "http-on-examine-cached-response", false);
+          Services.obs.removeObserver(observer, "http-on-examine-response");
+          Services.obs.removeObserver(observer, "http-on-examine-cached-response");
         }
       }
     };
 
     Services.obs.addObserver(observer, "http-on-examine-response", false);
     Services.obs.addObserver(observer, "http-on-examine-cached-response", false);
   });
 }
--- a/browser/components/search/test/head.js
+++ b/browser/components/search/test/head.js
@@ -46,25 +46,39 @@ function promiseEvent(aTarget, aEventNam
     }
 
     return true;
   }
 
   return BrowserTestUtils.waitForEvent(aTarget, aEventName, false, cancelEvent);
 }
 
+/**
+ * Adds a new search engine to the search service and confirms it completes.
+ *
+ * @param {String} basename  The file to load that contains the search engine
+ *                           details.
+ * @param {Object} [options] Options for the test:
+ *   - {String} [iconURL]       The icon to use for the search engine.
+ *   - {Boolean} [setAsCurrent] Whether to set the new engine to be the
+ *                              current engine or not.
+ *   - {String} [testPath]      Used to override the current test path if this
+ *                              file is used from a different directory.
+ * @returns {Promise} The promise is resolved once the engine is added, or
+ *                    rejected if the addition failed.
+ */
 function promiseNewEngine(basename, options = {}) {
   return new Promise((resolve, reject) => {
     // Default the setAsCurrent option to true.
     let setAsCurrent =
       options.setAsCurrent == undefined ? true : options.setAsCurrent;
     info("Waiting for engine to be added: " + basename);
     Services.search.init({
       onInitComplete() {
-        let url = getRootDirectory(gTestPath) + basename;
+        let url = getRootDirectory(options.testPath || gTestPath) + basename;
         let current = Services.search.currentEngine;
         Services.search.addEngine(url, null, options.iconURL || "", false, {
           onSuccess(engine) {
             info("Search engine added: " + basename);
             if (setAsCurrent) {
               Services.search.currentEngine = engine;
             }
             registerCleanupFunction(() => {
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -3608,17 +3608,17 @@ var SessionStoreInternal = {
    * @param aTab
    *        the tab to restore
    * @param aLoadArguments
    *        optional load arguments used for loadURI()
    * @param aReloadInFreshProcess
    *        true if we want to reload into a fresh process
    */
   restoreTabContent: function (aTab, aLoadArguments = null, aReloadInFreshProcess = false) {
-    if (aTab.hasAttribute("customizemode")) {
+    if (aTab.hasAttribute("customizemode") && !aLoadArguments) {
       return;
     }
 
     let browser = aTab.linkedBrowser;
     let window = aTab.ownerGlobal;
     let tabbrowser = window.gBrowser;
     let tabData = TabState.clone(aTab);
     let activeIndex = tabData.index - 1;
--- a/browser/components/syncedtabs/SyncedTabsDeckComponent.js
+++ b/browser/components/syncedtabs/SyncedTabsDeckComponent.js
@@ -67,16 +67,17 @@ SyncedTabsDeckComponent.prototype = {
 
   get container() {
     return this._deckView ? this._deckView.container : null;
   },
 
   init() {
     Services.obs.addObserver(this, this._SyncedTabs.TOPIC_TABS_CHANGED, false);
     Services.obs.addObserver(this, FxAccountsCommon.ONLOGIN_NOTIFICATION, false);
+    Services.obs.addObserver(this, "weave:service:login:change", false);
 
     // Go ahead and trigger sync
     this._SyncedTabs.syncTabs()
                     .catch(Cu.reportError);
 
     this._deckView = new this._DeckView(this._window, this.tabListComponent, {
       onAndroidClick: event => this.openAndroidLink(event),
       oniOSClick: event => this.openiOSLink(event),
@@ -89,42 +90,44 @@ SyncedTabsDeckComponent.prototype = {
     this._deckStore.setPanels(Object.keys(this.PANELS).map(k => this.PANELS[k]));
     // Set the initial panel to display
     this.updatePanel();
   },
 
   uninit() {
     Services.obs.removeObserver(this, this._SyncedTabs.TOPIC_TABS_CHANGED);
     Services.obs.removeObserver(this, FxAccountsCommon.ONLOGIN_NOTIFICATION);
+    Services.obs.removeObserver(this, "weave:service:login:change");
     this._deckView.destroy();
   },
 
   observe(subject, topic, data) {
     switch (topic) {
       case this._SyncedTabs.TOPIC_TABS_CHANGED:
         this._syncedTabsListStore.getData();
         this.updatePanel();
         break;
       case FxAccountsCommon.ONLOGIN_NOTIFICATION:
+      case "weave:service:login:change":
         this.updatePanel();
         break;
       default:
         break;
     }
   },
 
   // There's no good way to mock fxAccounts in browser tests where it's already
   // been instantiated, so we have this method for stubbing.
   _accountStatus() {
     return this._fxAccounts.accountStatus();
   },
 
   getPanelStatus() {
     return this._accountStatus().then(exists => {
-      if (!exists) {
+      if (!exists || this._getChromeWindow(this._window).gSyncUI.loginFailed()) {
         return this.PANELS.NOT_AUTHED_INFO;
       }
       if (!this._SyncedTabs.isConfiguredToSyncTabs) {
         return this.PANELS.TABS_DISABLED;
       }
       if (!this._SyncedTabs.hasSyncedThisSession) {
         return this.PANELS.TABS_FETCHING;
       }
--- a/browser/components/syncedtabs/TabListView.js
+++ b/browser/components/syncedtabs/TabListView.js
@@ -282,19 +282,17 @@ TabListView.prototype = {
         this.onOpenSelected(url, event);
       }
     }
 
     // Middle click on a client
     if (itemNode.classList.contains("client")) {
       let where = getChromeWindow(this._window).whereToOpenLink(event);
       if (where != "current") {
-        const tabs = itemNode.querySelector(".item-tabs-list").childNodes;
-        const urls = [...tabs].map(tab => tab.dataset.url);
-        this.props.onOpenTabs(urls, where);
+        this._openAllClientTabs(itemNode, where);
       }
     }
 
     if (event.target.classList.contains("item-twisty-container")
         && event.which != 2) {
       this.props.onToggleBranch(itemNode.dataset.id);
       return;
     }
@@ -350,16 +348,23 @@ TabListView.prototype = {
       let where = event.target.getAttribute("where");
       let params = {
         private: event.target.hasAttribute("private"),
       };
       this.props.onOpenTab(item.dataset.url, where, params);
     }
   },
 
+  onOpenAllInTabs() {
+    let item = this._getSelectedClientNode();
+    if (item) {
+      this._openAllClientTabs(item, "tab");
+    }
+  },
+
   onFilter(event) {
     let query = event.target.value;
     if (query) {
       this.props.onFilter(query);
     } else {
       this.props.onClearFilter();
     }
   },
@@ -378,16 +383,24 @@ TabListView.prototype = {
   _getSelectedTabNode() {
     let item = this.container.querySelector('.item.selected');
     if (this._isTab(item) && item.dataset.url) {
       return item;
     }
     return null;
   },
 
+  _getSelectedClientNode() {
+    let item = this.container.querySelector('.item.selected');
+    if (this._isClient(item)) {
+      return item;
+    }
+    return null;
+  },
+
   // Set up the custom context menu
   _setupContextMenu() {
     Services.els.addSystemEventListener(this._window, "contextmenu", this, false);
     for (let getMenu of [getContextMenu, getTabsFilterContextMenu]) {
       let menu = getMenu(this._window);
       menu.addEventListener("popupshowing", this, true);
       menu.addEventListener("command", this, true);
     }
@@ -456,16 +469,19 @@ TabListView.prototype = {
     let id = event.target.getAttribute("id");
     switch (id) {
       case "syncedTabsOpenSelected":
       case "syncedTabsOpenSelectedInTab":
       case "syncedTabsOpenSelectedInWindow":
       case "syncedTabsOpenSelectedInPrivateWindow":
         this.onOpenSelectedFromContextMenu(event);
         break;
+      case "syncedTabsOpenAllInTabs":
+        this.onOpenAllInTabs();
+        break;
       case "syncedTabsBookmarkSelected":
         this.onBookmarkTab();
         break;
       case "syncedTabsCopySelected":
         this.onCopyTabLocation();
         break;
       case "syncedTabsRefresh":
       case "syncedTabsRefreshFilter":
@@ -501,21 +517,28 @@ TabListView.prototype = {
 
   adjustContextMenu(menu) {
     let item = this.container.querySelector('.item.selected');
     let showTabOptions = this._isTab(item);
 
     let el = menu.firstChild;
 
     while (el) {
-      if (showTabOptions || el.getAttribute("id") === "syncedTabsRefresh") {
-        el.hidden = false;
-      } else {
-        el.hidden = true;
+      let show = false;
+      if (showTabOptions) {
+        if (el.getAttribute("id") != "syncedTabsOpenAllInTabs") {
+          show = true;
+        }
+      } else if (el.getAttribute("id") == "syncedTabsOpenAllInTabs") {
+        const tabs = item.querySelectorAll(".item-tabs-list > .item.tab");
+        show = tabs.length > 0;
+      } else if (el.getAttribute("id") == "syncedTabsRefresh") {
+        show = true;
       }
+      el.hidden = !show;
 
       el = el.nextSibling;
     }
   },
 
   /**
    * Find the parent item element, from a given child element.
    * @param {Element} node - Child element.
@@ -559,10 +582,20 @@ TabListView.prototype = {
   },
 
   _indexOfNode(parent, child) {
     return Array.prototype.indexOf.call(parent.childNodes, child);
   },
 
   _isTab(item) {
     return item && item.classList.contains("tab");
+  },
+
+  _isClient(item) {
+    return item && item.classList.contains("client");
+  },
+
+  _openAllClientTabs(clientNode, where) {
+    const tabs = clientNode.querySelector(".item-tabs-list").childNodes;
+    const urls = [...tabs].map(tab => tab.dataset.url);
+    this.props.onOpenTabs(urls, where);
   }
 };
--- a/browser/components/syncedtabs/test/browser/browser_sidebar_syncedtabslist.js
+++ b/browser/components/syncedtabs/test/browser/browser_sidebar_syncedtabslist.js
@@ -305,39 +305,59 @@ add_task(function* testSyncedTabsSidebar
     ["menuitem#syncedTabsOpenSelected", { hidden: false }],
     ["menuitem#syncedTabsOpenSelectedInTab", { hidden: false }],
     ["menuitem#syncedTabsOpenSelectedInWindow", { hidden: false }],
     ["menuitem#syncedTabsOpenSelectedInPrivateWindow", { hidden: false }],
     ["menuseparator", { hidden: false }],
     ["menuitem#syncedTabsBookmarkSelected", { hidden: false }],
     ["menuitem#syncedTabsCopySelected", { hidden: false }],
     ["menuseparator", { hidden: false }],
+    ["menuitem#syncedTabsOpenAllInTabs", { hidden: true }],
     ["menuitem#syncedTabsRefresh", { hidden: false }],
   ];
   yield* testContextMenu(syncedTabsDeckComponent,
                          "#SyncedTabsSidebarContext",
                          "#tab-7cqCr77ptzX3-0",
                          tabMenuItems);
 
-  info("Right-clicking a client shouldn't show any actions");
+  info("Right-clicking a client should show the Open All in Tabs action");
   let sidebarMenuItems = [
     ["menuitem#syncedTabsOpenSelected", { hidden: true }],
     ["menuitem#syncedTabsOpenSelectedInTab", { hidden: true }],
     ["menuitem#syncedTabsOpenSelectedInWindow", { hidden: true }],
     ["menuitem#syncedTabsOpenSelectedInPrivateWindow", { hidden: true }],
     ["menuseparator", { hidden: true }],
     ["menuitem#syncedTabsBookmarkSelected", { hidden: true }],
     ["menuitem#syncedTabsCopySelected", { hidden: true }],
     ["menuseparator", { hidden: true }],
+    ["menuitem#syncedTabsOpenAllInTabs", { hidden: false }],
+    ["menuitem#syncedTabsRefresh", { hidden: false }],
+  ];
+  yield* testContextMenu(syncedTabsDeckComponent,
+                         "#SyncedTabsSidebarContext",
+                         "#item-7cqCr77ptzX3",
+                         sidebarMenuItems);
+
+  info("Right-clicking a client without any tabs should not show the Open All in Tabs action");
+  let menuItems = [
+    ["menuitem#syncedTabsOpenSelected", { hidden: true }],
+    ["menuitem#syncedTabsOpenSelectedInTab", { hidden: true }],
+    ["menuitem#syncedTabsOpenSelectedInWindow", { hidden: true }],
+    ["menuitem#syncedTabsOpenSelectedInPrivateWindow", { hidden: true }],
+    ["menuseparator", { hidden: true }],
+    ["menuitem#syncedTabsBookmarkSelected", { hidden: true }],
+    ["menuitem#syncedTabsCopySelected", { hidden: true }],
+    ["menuseparator", { hidden: true }],
+    ["menuitem#syncedTabsOpenAllInTabs", { hidden: true }],
     ["menuitem#syncedTabsRefresh", { hidden: false }],
   ];
   yield* testContextMenu(syncedTabsDeckComponent,
                          "#SyncedTabsSidebarContext",
                          "#item-OL3EJCsdb2JD",
-                         sidebarMenuItems);
+                         menuItems);
 });
 
 add_task(testClean);
 
 function checkItem(node, item) {
   Assert.ok(node.classList.contains("item"),
     "Node should have .item class");
   if (item.client) {
--- a/browser/components/syncedtabs/test/xpcshell/test_SyncedTabsDeckComponent.js
+++ b/browser/components/syncedtabs/test/xpcshell/test_SyncedTabsDeckComponent.js
@@ -117,46 +117,68 @@ add_task(function* testObserver() {
   Assert.ok(listStore.getData.called, "gets list data");
   Assert.ok(component.updatePanel.calledTwice, "triggers panel update");
 
   Services.obs.notifyObservers(null, FxAccountsCommon.ONLOGIN_NOTIFICATION, "");
 
   Assert.ok(component.observe.calledWith(null, FxAccountsCommon.ONLOGIN_NOTIFICATION, ""),
     "component is notified of login");
   Assert.equal(component.updatePanel.callCount, 3, "triggers panel update again");
+
+  Services.obs.notifyObservers(null, "weave:service:login:change", "");
+
+  Assert.ok(component.observe.calledWith(null, "weave:service:login:change", ""),
+    "component is notified of login change");
+  Assert.equal(component.updatePanel.callCount, 4, "triggers panel update again");
 });
 
 add_task(function* testPanelStatus() {
   let deckStore = new SyncedTabsDeckStore();
   let listStore = new SyncedTabsListStore();
   let listComponent = {};
   let fxAccounts = {
     accountStatus() {}
   };
   let SyncedTabsMock = {
     getTabClients() {}
   };
+  let loginFailed = false;
+  let chromeWindowMock = {
+    gSyncUI: {
+      loginFailed() {
+        return loginFailed;
+      }
+    }
+  };
+  let getChromeWindowMock = sinon.stub();
+  getChromeWindowMock.returns(chromeWindowMock);
 
   sinon.stub(listStore, "getData");
 
 
   let component = new SyncedTabsDeckComponent({
     fxAccounts,
     deckStore,
     listComponent,
     SyncedTabs: SyncedTabsMock,
+    getChromeWindowMock
   });
 
   let isAuthed = false;
   sinon.stub(fxAccounts, "accountStatus", () => Promise.resolve(isAuthed));
   let result = yield component.getPanelStatus();
   Assert.equal(result, component.PANELS.NOT_AUTHED_INFO);
 
   isAuthed = true;
 
+  loginFailed = true;
+  result = yield component.getPanelStatus();
+  Assert.equal(result, component.PANELS.NOT_AUTHED_INFO);
+  loginFailed = false;
+
   SyncedTabsMock.isConfiguredToSyncTabs = false;
   result = yield component.getPanelStatus();
   Assert.equal(result, component.PANELS.TABS_DISABLED);
 
   SyncedTabsMock.isConfiguredToSyncTabs = true;
 
   SyncedTabsMock.hasSyncedThisSession = false;
   result = yield component.getPanelStatus();
--- a/browser/components/translation/test/unit/test_cld2.js
+++ b/browser/components/translation/test/unit/test_cld2.js
@@ -368,17 +368,18 @@ const kTestPairs = [
   ["fr", "FRENCH", kTeststr_fr_en_Latn, [false, 80, "en", 32]],
 
 // Cross-check the main quadgram table build date
 // Change the expected language each time it is rebuilt
   ["az", "AZERBAIJANI", kTeststr_version]   // 2014.01.31
 ];
 
 Components.utils.import("resource://gre/modules/Timer.jsm");
-let detectorModule = Components.utils.import("resource:///modules/translation/LanguageDetector.jsm");
+let detectorModule = Components.utils.import("resource:///modules/translation/LanguageDetector.jsm", {});
+const LanguageDetector = detectorModule.LanguageDetector;
 
 function check_result(result, langCode, expected) {
   equal(result.language, langCode, "Expected language code");
 
   // Round percentage up to the nearest 5%, since most strings are
   // detected at slightly less than 100%, and we don't want to
   // encode each exact value.
   let percent = result.languages[0].percent;
--- a/browser/components/uitour/test/browser_UITour_detach_tab.js
+++ b/browser/components/uitour/test/browser_UITour_detach_tab.js
@@ -27,17 +27,17 @@ function test() {
 var tests = [
   taskify(function* test_move_tab_to_new_window() {
     const myDocIdentifier = "Hello, I'm a unique expando to identify this document.";
 
     let highlight = document.getElementById("UITourHighlight");
     let windowDestroyedDeferred = Promise.defer();
     let onDOMWindowDestroyed = (aWindow) => {
       if (gContentWindow && aWindow == gContentWindow) {
-        Services.obs.removeObserver(onDOMWindowDestroyed, "dom-window-destroyed", false);
+        Services.obs.removeObserver(onDOMWindowDestroyed, "dom-window-destroyed");
         windowDestroyedDeferred.resolve();
       }
     };
 
     let browserStartupDeferred = Promise.defer();
     Services.obs.addObserver(function onBrowserDelayedStartup(aWindow) {
       Services.obs.removeObserver(onBrowserDelayedStartup, "browser-delayed-startup-finished");
       browserStartupDeferred.resolve(aWindow);
--- a/browser/experiments/test/xpcshell/test_cacherace.js
+++ b/browser/experiments/test/xpcshell/test_cacherace.js
@@ -42,17 +42,17 @@ add_task(function* test_setup() {
   do_register_cleanup(() => gHttpServer.stop(() => {}));
 
   Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true);
   Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0);
   Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true);
   Services.prefs.setCharPref(PREF_MANIFEST_URI, gManifestHandlerURI);
   Services.prefs.setIntPref(PREF_FETCHINTERVAL, 0);
 
-  let ExperimentsScope = Cu.import("resource:///modules/experiments/Experiments.jsm");
+  let ExperimentsScope = Cu.import("resource:///modules/experiments/Experiments.jsm", {});
   let Experiments = ExperimentsScope.Experiments;
 
   gPolicy = new Experiments.Policy();
   patchPolicy(gPolicy, {
     updatechannel: () => "nightly",
     delayCacheWrite: (promise) => {
       return new Promise((resolve, reject) => {
         promise.then(
--- a/browser/experiments/test/xpcshell/test_telemetry.js
+++ b/browser/experiments/test/xpcshell/test_telemetry.js
@@ -1,32 +1,32 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 Cu.import("resource://testing-common/httpd.js");
 Cu.import("resource://gre/modules/TelemetryLog.jsm");
-var bsp = Cu.import("resource:///modules/experiments/Experiments.jsm");
+var {TELEMETRY_LOG, Experiments} = Cu.import("resource:///modules/experiments/Experiments.jsm", {});
 
 
 const MANIFEST_HANDLER         = "manifests/handler";
 
 const SEC_IN_ONE_DAY  = 24 * 60 * 60;
 const MS_IN_ONE_DAY   = SEC_IN_ONE_DAY * 1000;
 
 
 var gHttpServer          = null;
 var gHttpRoot            = null;
 var gDataRoot            = null;
 var gPolicy              = null;
 var gManifestObject      = null;
 var gManifestHandlerURI  = null;
 
-const TLOG = bsp.TELEMETRY_LOG;
+const TLOG = TELEMETRY_LOG;
 
 function checkEvent(event, id, data) {
   do_print("Checking message " + id);
   Assert.equal(event[0], id, "id should match");
   Assert.ok(event[1] > 0, "timestamp should be greater than 0");
 
   if (data === undefined) {
    Assert.equal(event.length, 2, "event array should have 2 entries");
--- a/browser/extensions/pdfjs/content/PdfJs.jsm
+++ b/browser/extensions/pdfjs/content/PdfJs.jsm
@@ -192,21 +192,21 @@ var PdfJs = {
       this._ensureRegistered();
     } else {
       this._ensureUnregistered();
     }
   },
 
   uninit: function uninit() {
     if (this._initialized) {
-      Services.prefs.removeObserver(PREF_DISABLED, this, false);
-      Services.prefs.removeObserver(PREF_DISABLED_PLUGIN_TYPES, this, false);
-      Services.obs.removeObserver(this, TOPIC_PDFJS_HANDLER_CHANGED, false);
-      Services.obs.removeObserver(this, TOPIC_PLUGINS_LIST_UPDATED, false);
-      Services.obs.removeObserver(this, TOPIC_PLUGIN_INFO_UPDATED, false);
+      Services.prefs.removeObserver(PREF_DISABLED, this);
+      Services.prefs.removeObserver(PREF_DISABLED_PLUGIN_TYPES, this);
+      Services.obs.removeObserver(this, TOPIC_PDFJS_HANDLER_CHANGED);
+      Services.obs.removeObserver(this, TOPIC_PLUGINS_LIST_UPDATED);
+      Services.obs.removeObserver(this, TOPIC_PLUGIN_INFO_UPDATED);
       this._initialized = false;
     }
     this._ensureUnregistered();
   },
 
   _migrate: function migrate() {
     const VERSION = 2;
     var currentVersion = getIntPref(PREF_MIGRATION_VERSION, 0);
--- a/browser/extensions/pdfjs/content/PdfjsChromeUtils.jsm
+++ b/browser/extensions/pdfjs/content/PdfjsChromeUtils.jsm
@@ -105,17 +105,17 @@ var PdfjsChromeUtils = {
                                        this);
 
       this._mmg.removeMessageListener('PDFJS:Parent:displayWarning', this);
 
       this._mmg.removeMessageListener('PDFJS:Parent:addEventListener', this);
       this._mmg.removeMessageListener('PDFJS:Parent:removeEventListener', this);
       this._mmg.removeMessageListener('PDFJS:Parent:updateControlState', this);
 
-      Services.obs.removeObserver(this, 'quit-application', false);
+      Services.obs.removeObserver(this, 'quit-application');
 
       this._mmg = null;
       this._ppmm = null;
     }
   },
 
   /*
    * Called by the main module when preference changes are picked up
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -751,29 +751,36 @@ you can use these alternative items. Oth
 <!ENTITY syncedTabs.sidebar.noclients.subtitle "Want to see your tabs from other devices here?">
 <!ENTITY syncedTabs.sidebar.notsignedin.label  "Sign in to view a list of tabs from your other devices.">
 <!ENTITY syncedTabs.sidebar.notabs.label       "No open tabs">
 <!ENTITY syncedTabs.sidebar.openprefs.label    "Open &syncBrand.shortName.label; Preferences">
 <!-- LOCALIZATION NOTE (syncedTabs.sidebar.tabsnotsyncing.label): This is shown
      when Sync is configured but syncing tabs is disabled. -->
 <!ENTITY syncedTabs.sidebar.tabsnotsyncing.label       "Turn on tab syncing to view a list of tabs from your other devices.">
 
+<!-- LOCALIZATION NOTE (syncedTabs.context.open.accesskey,
+                        syncedTabs.context.openAllInTabs.accesskey):
+     These access keys are identical because their associated menu items are
+     mutually exclusive -->
 <!ENTITY syncedTabs.context.open.label                       "Open">
 <!ENTITY syncedTabs.context.open.accesskey                   "O">
 <!ENTITY syncedTabs.context.openInNewTab.label               "Open in a New Tab">
 <!ENTITY syncedTabs.context.openInNewTab.accesskey           "w">
 <!ENTITY syncedTabs.context.openInNewWindow.label            "Open in a New Window">
 <!ENTITY syncedTabs.context.openInNewWindow.accesskey        "N">
 <!ENTITY syncedTabs.context.openInNewPrivateWindow.label     "Open in a New Private Window">
 <!ENTITY syncedTabs.context.openInNewPrivateWindow.accesskey "P">
 <!ENTITY syncedTabs.context.bookmarkSingleTab.label          "Bookmark This Tab…">
 <!ENTITY syncedTabs.context.bookmarkSingleTab.accesskey      "B">
 <!ENTITY syncedTabs.context.copy.label                       "Copy">
 <!ENTITY syncedTabs.context.copy.accesskey                   "C">
 
+<!ENTITY syncedTabs.context.openAllInTabs.label              "Open All in Tabs">
+<!ENTITY syncedTabs.context.openAllInTabs.accesskey          "O">
+
 
 <!ENTITY syncBrand.shortName.label    "Sync">
 
 <!ENTITY syncSignIn.label             "Sign In To &syncBrand.shortName.label;…">
 <!ENTITY syncSignIn.accesskey         "Y">
 <!ENTITY syncSyncNowItem.label        "Sync Now">
 <!ENTITY syncSyncNowItem.accesskey    "S">
 <!ENTITY syncReAuthItem.label         "Reconnect to &syncBrand.shortName.label;…">
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -592,18 +592,16 @@ getUserMedia.sharingMenuAudioCaptureBrow
 # origin for the sharing menu if no readable origin could be deduced from the URL.
 getUserMedia.sharingMenuUnknownHost = Unknown origin
 
 # LOCALIZATION NOTE(emeNotifications.drmContentPlaying.message2): %S is brandShortName.
 emeNotifications.drmContentPlaying.message2 = Some audio or video on this site uses DRM software, which may limit what %S can let you do with it.
 emeNotifications.drmContentPlaying.button.label = Configure…
 emeNotifications.drmContentPlaying.button.accesskey = C
 
-# LOCALIZATION NOTE(emeNotifications.drmContentDisabled.message): NB: inserted via innerHTML, so please don't use <, > or & in this string. %S will be the 'learn more' link
-emeNotifications.drmContentDisabled.message = You must enable DRM to play some audio or video on this page. %S
 emeNotifications.drmContentDisabled.button.label = Enable DRM
 emeNotifications.drmContentDisabled.button.accesskey = E
 # LOCALIZATION NOTE(emeNotifications.drmContentDisabled.learnMoreLabel): NB: inserted via innerHTML, so please don't use <, > or & in this string.
 emeNotifications.drmContentDisabled.learnMoreLabel = Learn More
 
 # LOCALIZATION NOTE(emeNotifications.drmContentCDMInstalling.message): NB: inserted via innerHTML, so please don't use <, > or & in this string. %S is brandShortName
 emeNotifications.drmContentCDMInstalling.message = %S is installing components needed to play the audio or video on this page. Please try again later.
 
@@ -723,20 +721,18 @@ certErrorDetailsCertChain.label = Certif
 pendingCrashReports2.label = You have an unsent crash report;You have #1 unsent crash reports
 pendingCrashReports.viewAll = View
 pendingCrashReports.send = Send
 pendingCrashReports.alwaysSend = Always Send
 
 decoder.noCodecs.button = Learn how
 decoder.noCodecs.accesskey = L
 decoder.noCodecs.message = To play video, you may need to install Microsoft’s Media Feature Pack.
-decoder.noCodecsVista.message = To play video, you may need to install Microsoft’s Platform Update Supplement for Windows Vista.
 decoder.noCodecsLinux.message = To play video, you may need to install the required video codecs.
 decoder.noHWAcceleration.message = To improve video quality, you may need to install Microsoft’s Media Feature Pack.
-decoder.noHWAccelerationVista.message = To improve video quality, you may need to install Microsoft’s Platform Update Supplement for Windows Vista.
 decoder.noPulseAudio.message = To play audio, you may need to install the required PulseAudio software.
 decoder.unsupportedLibavcodec.message = libavcodec may be vulnerable or is not supported, and should be updated to play video.
 
 # LOCALIZATION NOTE (captivePortal.infoMessage2):
 # Shown in a notification bar when we detect a captive portal is blocking network access
 # and requires the user to log in before browsing.
 captivePortal.infoMessage2 = This network may require you to log in to use the internet.
 # LOCALIZATION NOTE (captivePortal.showLoginPage):
--- a/browser/locales/en-US/chrome/browser/preferences/siteDataSettings.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/siteDataSettings.dtd
@@ -2,8 +2,10 @@
    - 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     window.title                  "Settings - Site Data">
 <!ENTITY     settings.description          "The following websites asked to store site data in your disk. You can specify which websites are allowed to store site data. Default site data is temporary and could be deleted automatically.">
 <!ENTITY     hostCol.label                 "Site">
 <!ENTITY     statusCol.label               "Status">
 <!ENTITY     usageCol.label                "Storage">
+<!ENTITY     search.label                  "Search:">
+<!ENTITY     search.accesskey              "S">
--- a/browser/modules/BrowserUsageTelemetry.jsm
+++ b/browser/modules/BrowserUsageTelemetry.jsm
@@ -207,17 +207,17 @@ let URICountListener = {
 };
 
 let urlbarListener = {
   init() {
     Services.obs.addObserver(this, AUTOCOMPLETE_ENTER_TEXT_TOPIC, true);
   },
 
   uninit() {
-    Services.obs.removeObserver(this, AUTOCOMPLETE_ENTER_TEXT_TOPIC, true);
+    Services.obs.removeObserver(this, AUTOCOMPLETE_ENTER_TEXT_TOPIC);
   },
 
   observe(subject, topic, data) {
     switch (topic) {
       case AUTOCOMPLETE_ENTER_TEXT_TOPIC:
         this._handleURLBarTelemetry(subject.QueryInterface(Ci.nsIAutoCompleteInput));
         break;
     }
@@ -295,19 +295,19 @@ let BrowserUsageTelemetry = {
     Services.telemetry.scalarSetMaximum(MAX_TAB_COUNT_SCALAR_NAME, counts.tabCount);
     Services.telemetry.scalarSetMaximum(MAX_WINDOW_COUNT_SCALAR_NAME, counts.winCount);
 
     // Reset the URI counter.
     URICountListener.reset();
   },
 
   uninit() {
-    Services.obs.removeObserver(this, DOMWINDOW_OPENED_TOPIC, false);
-    Services.obs.removeObserver(this, TELEMETRY_SUBSESSIONSPLIT_TOPIC, false);
-    Services.obs.removeObserver(this, WINDOWS_RESTORED_TOPIC, false);
+    Services.obs.removeObserver(this, DOMWINDOW_OPENED_TOPIC);
+    Services.obs.removeObserver(this, TELEMETRY_SUBSESSIONSPLIT_TOPIC);
+    Services.obs.removeObserver(this, WINDOWS_RESTORED_TOPIC);
     urlbarListener.uninit();
   },
 
   observe(subject, topic, data) {
     switch (topic) {
       case WINDOWS_RESTORED_TOPIC:
         this._setupAfterRestore();
         break;
--- a/browser/modules/ContentCrashHandlers.jsm
+++ b/browser/modules/ContentCrashHandlers.jsm
@@ -904,19 +904,19 @@ this.PluginCrashReporter = {
     this.crashReports = new Map();
 
     Services.obs.addObserver(this, "plugin-crashed", false);
     Services.obs.addObserver(this, "gmp-plugin-crash", false);
     Services.obs.addObserver(this, "profile-after-change", false);
   },
 
   uninit() {
-    Services.obs.removeObserver(this, "plugin-crashed", false);
-    Services.obs.removeObserver(this, "gmp-plugin-crash", false);
-    Services.obs.removeObserver(this, "profile-after-change", false);
+    Services.obs.removeObserver(this, "plugin-crashed");
+    Services.obs.removeObserver(this, "gmp-plugin-crash");
+    Services.obs.removeObserver(this, "profile-after-change");
     this.initialized = false;
   },
 
   observe(subject, topic, data) {
     switch (topic) {
       case "plugin-crashed": {
         let propertyBag = subject;
         if (!(propertyBag instanceof Ci.nsIPropertyBag2) ||
--- a/browser/modules/ExtensionsUI.jsm
+++ b/browser/modules/ExtensionsUI.jsm
@@ -16,16 +16,25 @@ const HTML_NS = "http://www.w3.org/1999/
 this.ExtensionsUI = {
   init() {
     Services.obs.addObserver(this, "webextension-permission-prompt", false);
   },
 
   observe(subject, topic, data) {
     if (topic == "webextension-permission-prompt") {
       let {target, info} = subject.wrappedJSObject;
+
+      // Dismiss the progress notification.  Note that this is bad if
+      // there are multiple simultaneous installs happening, see
+      // bug 1329884 for a longer explanation.
+      let progressNotification = target.ownerGlobal.PopupNotifications.getNotification("addon-progress", target);
+      if (progressNotification) {
+        progressNotification.remove();
+      }
+
       this.showPermissionsPrompt(target, info).then(answer => {
         Services.obs.notifyObservers(subject, "webextension-permission-response",
                                      JSON.stringify(answer));
       });
     }
   },
 
   showPermissionsPrompt(target, info) {
--- a/browser/modules/test/browser.ini
+++ b/browser/modules/test/browser.ini
@@ -9,16 +9,18 @@ support-files =
 [browser_CaptivePortalWatcher.js]
 skip-if = os == "win" # Bug 1313894
 [browser_ContentSearch.js]
 support-files =
   contentSearch.js
   contentSearchBadImage.xml
   contentSearchSuggestions.sjs
   contentSearchSuggestions.xml
+  !/browser/components/search/test/head.js
+  !/browser/components/search/test/testEngine.xml
 [browser_NetworkPrioritizer.js]
 [browser_PermissionUI.js]
 [browser_ProcessHangNotifications.js]
 skip-if = !e10s
 [browser_SelfSupportBackend.js]
 support-files =
   ../../components/uitour/test/uitour.html
   ../../components/uitour/UITour-lib.js
--- a/browser/modules/test/browser_ContentSearch.js
+++ b/browser/modules/test/browser_ContentSearch.js
@@ -2,16 +2,34 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const TEST_MSG = "ContentSearchTest";
 const CONTENT_SEARCH_MSG = "ContentSearch";
 const TEST_CONTENT_SCRIPT_BASENAME = "contentSearch.js";
 
 var gMsgMan;
+/* eslint no-undef:"error" */
+/* import-globals-from ../../components/search/test/head.js */
+Services.scriptloader.loadSubScript(
+  "chrome://mochitests/content/browser/browser/components/search/test/head.js",
+  this);
+
+let originalEngine = Services.search.currentEngine;
+
+add_task(function* setup() {
+  yield promiseNewEngine("testEngine.xml", {
+    setAsCurrent: true,
+    testPath: "chrome://mochitests/content/browser/browser/components/search/test/",
+  });
+
+  registerCleanupFunction(() => {
+    Services.search.currentEngine = originalEngine;
+  });
+});
 
 add_task(function* GetState() {
   yield addTab();
   gMsgMan.sendAsyncMessage(TEST_MSG, {
     type: "GetState",
   });
   let msg = yield waitForTestMsg("State");
   checkMsg(msg, {
--- a/browser/modules/test/browser_urlBar_zoom.js
+++ b/browser/modules/test/browser_urlBar_zoom.js
@@ -55,19 +55,19 @@ add_task(function* asyncCleanup() {
     ok(!document.getElementById("zoom-controls"), "Customizable zoom widget removed from toolbar");
   }
 
 });
 
 function promiseObserverNotification(aObserver) {
   let deferred = Promise.defer();
   function notificationCallback(e) {
-    Services.obs.removeObserver(notificationCallback, aObserver, false);
+    Services.obs.removeObserver(notificationCallback, aObserver);
     clearTimeout(timeoutId);
     deferred.resolve();
   }
   let timeoutId = setTimeout(() => {
-    Services.obs.removeObserver(notificationCallback, aObserver, false);
+    Services.obs.removeObserver(notificationCallback, aObserver);
     deferred.reject("Notification '" + aObserver + "' did not happen within 20 seconds.");
   }, kTimeoutInMS);
   Services.obs.addObserver(notificationCallback, aObserver, false);
   return deferred.promise;
 }
--- a/browser/modules/test/contentSearch.js
+++ b/browser/modules/test/contentSearch.js
@@ -13,32 +13,32 @@ content.addEventListener(SERVICE_EVENT_T
   // up with an XrayWrapper to it here, which will screw us up when trying to
   // serialize the object in sendAsyncMessage. Waive Xrays for the benefit of
   // the test machinery.
   sendAsyncMessage(TEST_MSG, Components.utils.waiveXrays(event.detail));
 });
 
 // Forward messages from the test to the in-content service.
 addMessageListener(TEST_MSG, msg => {
-  content.dispatchEvent(
-    new content.CustomEvent(CLIENT_EVENT_TYPE, {
-      detail: msg.data,
-    })
-  );
-
   // If the message is a search, stop the page from loading and then tell the
   // test that it loaded.
   if (msg.data.type == "Search") {
     waitForLoadAndStopIt(msg.data.expectedURL, url => {
       sendAsyncMessage(TEST_MSG, {
         type: "loadStopped",
         url,
       });
     });
   }
+
+  content.dispatchEvent(
+    new content.CustomEvent(CLIENT_EVENT_TYPE, {
+      detail: msg.data,
+    })
+  );
 });
 
 function waitForLoadAndStopIt(expectedURL, callback) {
   let Ci = Components.interfaces;
   let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                             .getInterface(Ci.nsIWebProgress);
   let listener = {
     onStateChange(webProg, req, flags, status) {
--- a/browser/themes/shared/newtab/newTab.inc.css
+++ b/browser/themes/shared/newtab/newTab.inc.css
@@ -112,26 +112,26 @@ body.compact .newtab-cell {
 
 .newtab-cell:empty {
   outline: 2px dashed #c1c1c1;
   outline-offset: -2px;
   -moz-outline-radius: var(--cell-corner-radius);
 }
 
 /* SITES */
-body:not(.compact) .newtab-site {
+.newtab-site {
   border-radius: var(--cell-corner-radius);
   box-shadow: 0 1px 3px #c1c1c1;
   text-decoration: none;
   transition-property: top, left, opacity, box-shadow, background-color;
 }
 
-body:not(.compact) .newtab-cell:not([ignorehover]) .newtab-control:hover ~ .newtab-link,
-body:not(.compact) .newtab-cell:not([ignorehover]) .newtab-link:hover,
-body:not(.compact) .newtab-site[dragged] {
+.newtab-cell:not([ignorehover]) .newtab-control:hover ~ .newtab-link,
+.newtab-cell:not([ignorehover]) .newtab-link:hover,
+.newtab-site[dragged] {
   border: 2px solid white;
   box-shadow: 0 0 6px 1px #add6ff;
   margin: -2px;
 }
 
 .newtab-site[dragged] {
   transition-property: box-shadow, background-color;
   background-color: rgb(242,242,242);
--- a/build/autoconf/frameptr.m4
+++ b/build/autoconf/frameptr.m4
@@ -24,19 +24,21 @@ AC_DEFUN([MOZ_SET_FRAMEPTR_FLAGS], [
     dnl Oy (Frame-Pointer Omission) is only support on x86 compilers
     *-mingw32*)
       MOZ_ENABLE_FRAME_PTR="-Oy-"
       MOZ_DISABLE_FRAME_PTR="-Oy"
     ;;
     esac
   fi
 
-  # if we are debugging, profiling or using sanitizers, we want a frame pointer.
+  # If we are debugging, profiling, using sanitizers, or on win32 we want a
+  # frame pointer.
   if test -z "$MOZ_OPTIMIZE" -o \
           -n "$MOZ_PROFILING" -o \
           -n "$MOZ_DEBUG" -o \
           -n "$MOZ_MSAN" -o \
-          -n "$MOZ_ASAN"; then
+          -n "$MOZ_ASAN" -o \
+          "$OS_ARCH:$CPU_ARCH" = "WINNT:x86"; then
     MOZ_FRAMEPTR_FLAGS="$MOZ_ENABLE_FRAME_PTR"
   else
     MOZ_FRAMEPTR_FLAGS="$MOZ_DISABLE_FRAME_PTR"
   fi
 ])
--- a/build/autoconf/icu.m4
+++ b/build/autoconf/icu.m4
@@ -93,19 +93,22 @@ if test -n "$USE_ICU"; then
 fi
 
 AC_SUBST(MOZ_ICU_VERSION)
 AC_SUBST(ENABLE_INTL_API)
 AC_SUBST(USE_ICU)
 AC_SUBST(ICU_DATA_FILE)
 AC_SUBST(MOZ_ICU_DATA_ARCHIVE)
 
-if test -n "$USE_ICU" -a -z "$MOZ_SYSTEM_ICU"; then
-    if test -z "$YASM" -a -z "$GNU_AS" -a "$COMPILE_ENVIRONMENT"; then
-      AC_MSG_ERROR([Building ICU requires either yasm or a GNU assembler. If you do not have either of those available for this platform you must use --without-intl-api])
-    fi
-    dnl We build ICU as a static library.
-    AC_DEFINE(U_STATIC_IMPLEMENTATION)
+if test -n "$USE_ICU"; then
     dnl Source files that use ICU should have control over which parts of the ICU
     dnl namespace they want to use.
     AC_DEFINE(U_USING_ICU_NAMESPACE,0)
+
+    if test -z "$MOZ_SYSTEM_ICU"; then
+        if test -z "$YASM" -a -z "$GNU_AS" -a "$COMPILE_ENVIRONMENT"; then
+            AC_MSG_ERROR([Building ICU requires either yasm or a GNU assembler. If you do not have either of those available for this platform you must use --without-intl-api])
+        fi
+        dnl We build ICU as a static library.
+        AC_DEFINE(U_STATIC_IMPLEMENTATION)
+    fi
 fi
 ])
--- a/build/clang-plugin/Utils.h
+++ b/build/clang-plugin/Utils.h
@@ -179,16 +179,17 @@ inline bool isIgnoredPathForImplicitCtor
   SourceLocation Loc = Declaration->getLocation();
   const SourceManager &SM = Declaration->getASTContext().getSourceManager();
   SmallString<1024> FileName = SM.getFilename(Loc);
   llvm::sys::fs::make_absolute(FileName);
   llvm::sys::path::reverse_iterator Begin = llvm::sys::path::rbegin(FileName),
                                     End = llvm::sys::path::rend(FileName);
   for (; Begin != End; ++Begin) {
     if (Begin->compare_lower(StringRef("skia")) == 0 ||
+        Begin->compare_lower(StringRef("sfntly")) == 0 ||
         Begin->compare_lower(StringRef("angle")) == 0 ||
         Begin->compare_lower(StringRef("harfbuzz")) == 0 ||
         Begin->compare_lower(StringRef("hunspell")) == 0 ||
         Begin->compare_lower(StringRef("scoped_ptr.h")) == 0 ||
         Begin->compare_lower(StringRef("graphite2")) == 0 ||
         Begin->compare_lower(StringRef("icu")) == 0 ||
         Begin->compare_lower(StringRef("libcubeb")) == 0 ||
         Begin->compare_lower(StringRef("libstagefright")) == 0 ||
@@ -236,16 +237,17 @@ inline bool isIgnoredPathForSprintfLiter
         Begin->compare_lower(StringRef("chromium")) == 0 ||
         Begin->compare_lower(StringRef("crashreporter")) == 0 ||
         Begin->compare_lower(StringRef("google-breakpad")) == 0 ||
         Begin->compare_lower(StringRef("harfbuzz")) == 0 ||
         Begin->compare_lower(StringRef("libstagefright")) == 0 ||
         Begin->compare_lower(StringRef("mtransport")) == 0 ||
         Begin->compare_lower(StringRef("protobuf")) == 0 ||
         Begin->compare_lower(StringRef("skia")) == 0 ||
+        Begin->compare_lower(StringRef("sfntly")) == 0 ||
         // Gtest uses snprintf as GTEST_SNPRINTF_ with sizeof
         Begin->compare_lower(StringRef("testing")) == 0) {
       return true;
     }
     if (Begin->compare_lower(StringRef("webrtc")) == 0) {
       // Ignore trunk/webrtc, but not media/webrtc
       ++Begin;
       return Begin != End && Begin->compare_lower(StringRef("trunk")) == 0;
--- a/build/mozconfig.win-common
+++ b/build/mozconfig.win-common
@@ -9,9 +9,11 @@ if [ "x$IS_NIGHTLY" = "xyes" ]; then
   MOZ_AUTOMATION_SDK=${MOZ_AUTOMATION_SDK-1}
 fi
 
 # Some builds (eg: Mulet) don't want the installer, so only set this if it
 # hasn't already been set.
 MOZ_AUTOMATION_INSTALLER=${MOZ_AUTOMATION_INSTALLER-1}
 
 export SOCORRO_SYMBOL_UPLOAD_TOKEN_FILE=c:/builds/crash-stats-api.token
-export MAKECAB=$topsrcdir/makecab.exe
+
+TOOLTOOL_DIR=${TOOLTOOL_DIR:-$topsrcdir}
+export MAKECAB=$TOOLTOOL_DIR/makecab.exe
--- a/config/system-headers
+++ b/config/system-headers
@@ -1312,24 +1312,26 @@ sys/thr.h
 sys/user.h
 kvm.h
 spawn.h
 err.h
 xlocale.h
 #ifdef MOZ_SYSTEM_ICU
 unicode/locid.h
 unicode/numsys.h
+unicode/plurrule.h
 unicode/timezone.h
 unicode/ucal.h
 unicode/uchar.h
 unicode/uclean.h
 unicode/ucol.h
 unicode/udat.h
 unicode/udatpg.h
 unicode/uenum.h
 unicode/unorm.h
 unicode/unum.h
+unicode/upluralrules.h
 unicode/ustring.h
 unicode/utypes.h
 #endif
 libutil.h
 unwind.h
 fenv.h
--- a/devtools/.eslintrc.js
+++ b/devtools/.eslintrc.js
@@ -278,16 +278,18 @@ module.exports = {
     "no-reserved-keys": "off",
     // Don't restrict usage of specified node modules (not a node environment).
     "no-restricted-modules": "off",
     // Disallow use of assignment in return statement. It is preferable for a
     // single line of code to have only one easily predictable effect.
     "no-return-assign": "error",
     // Allow use of javascript: urls.
     "no-script-url": "off",
+    // Disallow assignments like foo = foo
+    "no-self-assign": "error",
     // Disallow comparisons where both sides are exactly the same.
     "no-self-compare": "error",
     // Disallow use of comma operator.
     "no-sequences": "error",
     // Warn about declaration of variables already declared in the outer scope.
     // This isn't an error because it sometimes is useful to use the same name
     // in a small helper function rather than having to come up with another
     // random name.
@@ -327,16 +329,20 @@ module.exports = {
     "no-unreachable": "error",
     // Disallow global and local variables that aren't used, but allow unused
     // function arguments.
     "no-unused-vars": ["error", {"vars": "all", "args": "none"}],
     // Disallow flow control that escapes from "finally".
     "no-unsafe-finally": "error",
     // Allow using variables before they are defined.
     "no-use-before-define": "off",
+    // Disallow useless Function.prototype.{call/apply}
+    "no-useless-call": "error",
+    // Disallow useless return;
+    "no-useless-return": "error",
     // We use var-only-at-top-level instead of no-var as we allow top level
     // vars.
     "no-var": "off",
     // Allow using TODO/FIXME comments.
     "no-warning-comments": "off",
     // Disallow use of the with statement.
     "no-with": "error",
     // Don't require method and property shorthand syntax for object literals.
--- a/devtools/client/aboutdebugging/test/browser_addons_reload.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_reload.js
@@ -117,17 +117,17 @@ add_task(function* reloadButtonReloadsAd
 
   const reloadButton = getReloadButton(document, ADDON_NAME);
   is(reloadButton.disabled, false, "Reload button should not be disabled");
   is(reloadButton.title, "", "Reload button should not have a tooltip");
   const onInstalled = promiseAddonEvent("onInstalled");
 
   const onBootstrapInstallCalled = new Promise(done => {
     Services.obs.addObserver(function listener() {
-      Services.obs.removeObserver(listener, ADDON_NAME, false);
+      Services.obs.removeObserver(listener, ADDON_NAME);
       info("Add-on was re-installed: " + ADDON_NAME);
       done();
     }, ADDON_NAME, false);
   });
 
   reloadButton.click();
 
   const [reloadedAddon] = yield onInstalled;
--- a/devtools/client/inspector/inspector-search.js
+++ b/devtools/client/inspector/inspector-search.js
@@ -538,12 +538,10 @@ SelectorAutocompleter.prototype = {
           result.suggestions[0][0] === firstPart) {
         result.suggestions = [];
       }
 
       // Wait for the autocomplete-popup to fire its popup-opened event, to make sure
       // the autoSelect item has been selected.
       return this._showPopup(result.suggestions, firstPart, state);
     });
-
-    return;
   }
 };
--- a/devtools/client/inspector/rules/rules.js
+++ b/devtools/client/inspector/rules/rules.js
@@ -1084,21 +1084,21 @@ CssRuleView.prototype = {
       if (seenPseudoElement && !seenNormalElement && !rule.pseudoElement) {
         seenNormalElement = true;
         let div = this.styleDocument.createElementNS(HTML_NS, "div");
         div.className = this._getRuleViewHeaderClassName();
         div.textContent = this.selectedElementLabel;
         this.element.appendChild(div);
       }
 
-      let inheritedSource = rule.inheritedSource;
+      let inheritedSource = rule.inherited;
       if (inheritedSource && inheritedSource !== lastInheritedSource) {
         let div = this.styleDocument.createElementNS(HTML_NS, "div");
         div.className = this._getRuleViewHeaderClassName();
-        div.textContent = inheritedSource;
+        div.textContent = rule.inheritedSource;
         lastInheritedSource = inheritedSource;
         this.element.appendChild(div);
       }
 
       if (!seenPseudoElement && rule.pseudoElement) {
         seenPseudoElement = true;
         container = this.createExpandableContainer(this.pseudoElementLabel,
                                                    true);
@@ -1602,17 +1602,16 @@ RuleViewTool.prototype = {
     location.then(({ source, href, line, column }) => {
       let target = this.inspector.target;
       if (Tools.styleEditor.isTargetSupported(target)) {
         gDevTools.showToolbox(target, "styleeditor").then(function (toolbox) {
           let url = source || href;
           toolbox.getCurrentPanel().selectStyleSheet(url, line, column);
         });
       }
-      return;
     });
   },
 
   onPropertyChanged: function () {
     this.inspector.markDirty();
   },
 
   onViewRefreshed: function () {
--- a/devtools/client/inspector/rules/test/browser.ini
+++ b/devtools/client/inspector/rules/test/browser.ini
@@ -145,16 +145,17 @@ skip-if = (os == "win" && debug) # bug 9
 [browser_rules_grid-highlighter-on-navigate.js]
 [browser_rules_grid-toggle_01.js]
 [browser_rules_grid-toggle_02.js]
 [browser_rules_grid-toggle_03.js]
 [browser_rules_guessIndentation.js]
 [browser_rules_inherited-properties_01.js]
 [browser_rules_inherited-properties_02.js]
 [browser_rules_inherited-properties_03.js]
+[browser_rules_inherited-properties_04.js]
 [browser_rules_inline-source-map.js]
 [browser_rules_invalid.js]
 [browser_rules_invalid-source-map.js]
 [browser_rules_keybindings.js]
 [browser_rules_keyframes-rule_01.js]
 [browser_rules_keyframes-rule_02.js]
 [browser_rules_keyframeLineNumbers.js]
 [browser_rules_lineNumbers.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_inherited-properties_04.js
@@ -0,0 +1,32 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that different inherited properties sections are created for rules
+// inherited from several elements of the same type.
+
+const TEST_URI = `
+  <div style="cursor:pointer">
+    A
+    <div style="cursor:pointer">
+      B<a>Cursor</a>
+    </div>
+  </div>
+`;
+
+add_task(function* () {
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  let {inspector, view} = yield openRuleView();
+  yield selectNode("a", inspector);
+  yield elementStyleInherit(inspector, view);
+});
+
+function* elementStyleInherit(inspector, view) {
+  let gutters = view.element.querySelectorAll(".theme-gutter");
+  is(gutters.length, 2,
+    "Gutters should contains 2 sections");
+  ok(gutters[0].textContent, "Inherited from div");
+  ok(gutters[1].textContent, "Inherited from div");
+}
--- a/devtools/client/inspector/rules/test/doc_author-sheet.html
+++ b/devtools/client/inspector/rules/test/doc_author-sheet.html
@@ -11,17 +11,17 @@
   </style>
 
   <script>
     "use strict";
     var gIOService = SpecialPowers.Cc["@mozilla.org/network/io-service;1"]
                                   .getService(SpecialPowers.Ci.nsIIOService);
 
     var style = "data:text/css,a { background-color: seagreen; }";
-    var uri = gIOService.newURI(style, null, null);
+    var uri = gIOService.newURI(style);
     var windowUtils = SpecialPowers.wrap(window)
         .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
         .getInterface(SpecialPowers.Ci.nsIDOMWindowUtils);
     windowUtils.loadSheet(uri, windowUtils.AUTHOR_SHEET);
   </script>
 
 </head>
 <body>
--- a/devtools/client/inspector/shared/test/doc_author-sheet.html
+++ b/devtools/client/inspector/shared/test/doc_author-sheet.html
@@ -9,17 +9,17 @@
     }
   </style>
   <script>
     "use strict";
     var gIOService = SpecialPowers.Cc["@mozilla.org/network/io-service;1"]
                                   .getService(SpecialPowers.Ci.nsIIOService);
 
     var style = "data:text/css,div { background-color: seagreen; }";
-    var uri = gIOService.newURI(style, null, null);
+    var uri = gIOService.newURI(style);
     var windowUtils = SpecialPowers.wrap(window)
                       .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
                       .getInterface(SpecialPowers.Ci.nsIDOMWindowUtils);
     windowUtils.loadSheet(uri, windowUtils.AUTHOR_SHEET);
   </script>
 </head>
 <body>
   <div id="target"> the ocean </div>
--- a/devtools/client/locales/en-US/netmonitor.properties
+++ b/devtools/client/locales/en-US/netmonitor.properties
@@ -114,16 +114,20 @@ responseHeaders=Response headers
 # LOCALIZATION NOTE (requestCookies): This is the label displayed
 # in the network details params tab identifying the request cookies.
 requestCookies=Request cookies
 
 # LOCALIZATION NOTE (responseCookies): This is the label displayed
 # in the network details params tab identifying the response cookies.
 responseCookies=Response cookies
 
+# LOCALIZATION NOTE (responsePayload): This is the label displayed
+# in the network details response tab identifying the response payload.
+responsePayload=Response payload
+
 # LOCALIZATION NOTE (jsonFilterText): This is the text displayed
 # in the response tab of the network details pane for the JSON filtering input.
 jsonFilterText=Filter properties
 
 # LOCALIZATION NOTE (jsonScopeName): This is the text displayed
 # in the response tab of the network details pane for a JSON scope.
 jsonScopeName=JSON
 
--- a/devtools/client/memory/actions/snapshot.js
+++ b/devtools/client/memory/actions/snapshot.js
@@ -639,17 +639,16 @@ exports.fetchImmediatelyDominated = Task
     removeFromCache();
     dispatch({
       type: actions.FETCH_IMMEDIATELY_DOMINATED_END,
       id,
       path: response.path,
       nodes: response.nodes,
       moreChildrenAvailable: response.moreChildrenAvailable,
     });
-    return;
   }
 });
 
 /**
  * Compute and then fetch the dominator tree of the snapshot with the given
  * `id`.
  *
  * @param {HeapAnalysesClient} heapWorker
@@ -695,27 +694,25 @@ exports.refreshSelectedDominatorTree = f
 
     if (snapshot.dominatorTree &&
         !(snapshot.dominatorTree.state === dominatorTreeState.COMPUTED ||
           snapshot.dominatorTree.state === dominatorTreeState.LOADED ||
           snapshot.dominatorTree.state === dominatorTreeState.INCREMENTAL_FETCHING)) {
       return;
     }
 
+    // We need to check for the snapshot state because if there was an error,
+    // we can't continue and if we are still saving or reading the snapshot,
+    // then takeSnapshotAndCensus will finish the job for us
     if (snapshot.state === states.READ) {
       if (snapshot.dominatorTree) {
         yield dispatch(fetchDominatorTree(heapWorker, snapshot.id));
       } else {
         yield dispatch(computeAndFetchDominatorTree(heapWorker, snapshot.id));
       }
-    } else {
-        // If there was an error, we can't continue. If we are still saving or
-        // reading the snapshot, then takeSnapshotAndCensus will finish the job
-        // for us.
-      return;
     }
   };
 };
 
 /**
  * Select the snapshot with the given id.
  *
  * @param {snapshotId} id
--- a/devtools/client/netmonitor/constants.js
+++ b/devtools/client/netmonitor/constants.js
@@ -3,16 +3,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const general = {
   CONTENT_SIZE_DECIMALS: 2,
   FILTER_SEARCH_DELAY: 200,
   REQUEST_TIME_DECIMALS: 2,
+  // 100 KB in bytes
+  SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE: 102400,
 };
 
 const actionTypes = {
   ADD_REQUEST: "ADD_REQUEST",
   ADD_TIMING_MARKER: "ADD_TIMING_MARKER",
   BATCH_ACTIONS: "BATCH_ACTIONS",
   BATCH_ENABLE: "BATCH_ENABLE",
   CLEAR_REQUESTS: "CLEAR_REQUESTS",
--- a/devtools/client/netmonitor/details-view.js
+++ b/devtools/client/netmonitor/details-view.js
@@ -1,58 +1,39 @@
 /* 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/. */
 
 /* eslint-disable mozilla/reject-some-requires */
-/* globals window, dumpn, $, NetMonitorView, gNetwork */
+/* globals window, dumpn, $, gNetwork */
 
 "use strict";
 
 const promise = require("promise");
 const EventEmitter = require("devtools/shared/event-emitter");
-const Editor = require("devtools/client/sourceeditor/editor");
 const { Heritage } = require("devtools/client/shared/widgets/view-helpers");
 const { Task } = require("devtools/shared/task");
 const { ToolSidebar } = require("devtools/client/framework/sidebar");
 const { VariablesView } = require("resource://devtools/client/shared/widgets/VariablesView.jsm");
-const { VariablesViewController } = require("resource://devtools/client/shared/widgets/VariablesViewController.jsm");
 const { EVENTS } = require("./events");
 const { L10N } = require("./l10n");
 const { Filters } = require("./filter-predicates");
 const {
   decodeUnicodeUrl,
-  formDataURI,
-  getUrlBaseName,
 } = require("./request-utils");
 const { createFactory } = require("devtools/client/shared/vendor/react");
 const ReactDOM = require("devtools/client/shared/vendor/react-dom");
 const Provider = createFactory(require("devtools/client/shared/vendor/react-redux").Provider);
 const ParamsPanel = createFactory(require("./shared/components/params-panel"));
 const PreviewPanel = createFactory(require("./shared/components/preview-panel"));
+const ResponsePanel = createFactory(require("./shared/components/response-panel"));
 const SecurityPanel = createFactory(require("./shared/components/security-panel"));
 const TimingsPanel = createFactory(require("./shared/components/timings-panel"));
 
-// 100 KB in bytes
-const SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE = 102400;
 const HEADERS_SIZE_DECIMALS = 3;
-const CONTENT_MIME_TYPE_MAPPINGS = {
-  "/ecmascript": Editor.modes.js,
-  "/javascript": Editor.modes.js,
-  "/x-javascript": Editor.modes.js,
-  "/html": Editor.modes.html,
-  "/xhtml": Editor.modes.html,
-  "/xml": Editor.modes.html,
-  "/atom": Editor.modes.html,
-  "/soap": Editor.modes.html,
-  "/vnd.mpeg.dash.mpd": Editor.modes.html,
-  "/rdf": Editor.modes.css,
-  "/rss": Editor.modes.css,
-  "/css": Editor.modes.css
-};
 const GENERIC_VARIABLES_VIEW_SETTINGS = {
   lazyEmpty: true,
   // ms
   lazyEmptyDelay: 10,
   searchEnabled: true,
   editableValueTooltip: "",
   editableNameTooltip: "",
   preventDisableOnChange: true,
@@ -101,16 +82,23 @@ DetailsView.prototype = {
 
     this._previewPanelNode = $("#react-preview-tabpanel-hook");
 
     ReactDOM.render(Provider(
       { store },
       PreviewPanel()
     ), this._previewPanelNode);
 
+    this._responsePanelNode = $("#react-response-tabpanel-hook");
+
+    ReactDOM.render(Provider(
+      { store },
+      ResponsePanel()
+    ), this._responsePanelNode);
+
     this._securityPanelNode = $("#react-security-tabpanel-hook");
 
     ReactDOM.render(Provider(
       { store },
       SecurityPanel()
     ), this._securityPanelNode);
 
     this._timingsPanelNode = $("#react-timings-tabpanel-hook");
@@ -131,22 +119,16 @@ DetailsView.prototype = {
         emptyText: L10N.getStr("headersEmptyText"),
         searchPlaceholder: L10N.getStr("headersFilterText")
       }));
     this._cookies = new VariablesView($("#all-cookies"),
       Heritage.extend(GENERIC_VARIABLES_VIEW_SETTINGS, {
         emptyText: L10N.getStr("cookiesEmptyText"),
         searchPlaceholder: L10N.getStr("cookiesFilterText")
       }));
-    this._json = new VariablesView($("#response-content-json"),
-      Heritage.extend(GENERIC_VARIABLES_VIEW_SETTINGS, {
-        onlyEnumVisible: true,
-        searchPlaceholder: L10N.getStr("jsonFilterText")
-      }));
-    VariablesViewController.attach(this._json);
 
     this._requestHeaders = L10N.getStr("requestHeaders");
     this._requestHeadersFromUpload = L10N.getStr("requestHeadersFromUpload");
     this._responseHeaders = L10N.getStr("responseHeaders");
     this._requestCookies = L10N.getStr("requestCookies");
     this._responseCookies = L10N.getStr("responseCookies");
 
     $("tabpanels", this.widget).addEventListener("select", this._onTabSelect);
@@ -154,37 +136,34 @@ DetailsView.prototype = {
 
   /**
    * Destruction function, called when the network monitor is closed.
    */
   destroy: function () {
     dumpn("Destroying the DetailsView");
     ReactDOM.unmountComponentAtNode(this._paramsPanelNode);
     ReactDOM.unmountComponentAtNode(this._previewPanelNode);
+    ReactDOM.unmountComponentAtNode(this._responsePanelNode);
     ReactDOM.unmountComponentAtNode(this._securityPanelNode);
     ReactDOM.unmountComponentAtNode(this._timingsPanelNode);
     this.sidebar.destroy();
     $("tabpanels", this.widget).removeEventListener("select",
       this._onTabSelect);
   },
 
   /**
    * Populates this view with the specified data.
    *
    * @param object data
    *        The data source (this should be the attachment of a request item).
    * @return object
    *        Returns a promise that resolves upon population the view.
    */
   populate: function (data) {
-    $("#response-content-info-header").hidden = true;
-    $("#response-content-json-box").hidden = true;
-    $("#response-content-textarea-box").hidden = true;
     $("#raw-headers").hidden = true;
-    $("#response-content-image-box").hidden = true;
 
     let isHtml = Filters.html(data);
 
     // Show the "Preview" tabpanel only for plain HTML responses.
     this.sidebar.toggleTab(isHtml, "preview-tab");
 
     // Show the "Security" tab only for requests that
     //   1) are https (state != insecure)
@@ -200,17 +179,16 @@ DetailsView.prototype = {
     if (!isHtml && this.widget.selectedPanel === $("#preview-tabpanel") ||
         !hasSecurityInfo && this.widget.selectedPanel ===
           $("#security-tabpanel")) {
       this.widget.selectedIndex = 0;
     }
 
     this._headers.empty();
     this._cookies.empty();
-    this._json.empty();
 
     this._dataSrc = { src: data, populated: [] };
     this._onTabSelect();
     window.emit(EVENTS.NETWORKDETAILSVIEW_POPULATED);
 
     return promise.resolve();
   },
 
@@ -249,20 +227,16 @@ DetailsView.prototype = {
             src.requestHeaders,
             src.requestHeadersFromUploadStream);
           break;
         // "Cookies"
         case 1:
           yield view._setResponseCookies(src.responseCookies);
           yield view._setRequestCookies(src.requestCookies);
           break;
-        // "Response"
-        case 3:
-          yield view._setResponseBody(src.url, src.responseContent);
-          break;
       }
       viewState.updating[tab] = false;
     }).then(() => {
       if (tab == this.widget.selectedIndex) {
         if (viewState.dirty[tab]) {
           // The request information was updated while the task was running.
           viewState.dirty[tab] = false;
           view.populate(viewState.latestData);
@@ -466,134 +440,18 @@ DetailsView.prototype = {
         rawObject[prop] = cookie[prop];
       }
       cookieVar.populate(rawObject);
       cookieVar.twisty = true;
       cookieVar.expanded = true;
     }
   }),
 
-  /**
-   * Sets the network response body shown in this view.
-   *
-   * @param string url
-   *        The request's url.
-   * @param object response
-   *        The message received from the server.
-   * @return object
-   *         A promise that is resolved when the response body is set.
-   */
-  _setResponseBody: Task.async(function* (url, response) {
-    if (!response) {
-      return;
-    }
-    let { mimeType, text, encoding } = response.content;
-    let responseBody = yield gNetwork.getString(text);
-
-    // Handle json, which we tentatively identify by checking the MIME type
-    // for "json" after any word boundary. This works for the standard
-    // "application/json", and also for custom types like "x-bigcorp-json".
-    // Additionally, we also directly parse the response text content to
-    // verify whether it's json or not, to handle responses incorrectly
-    // labeled as text/plain instead.
-    let jsonMimeType, jsonObject, jsonObjectParseError;
-    try {
-      jsonMimeType = /\bjson/.test(mimeType);
-      jsonObject = JSON.parse(responseBody);
-    } catch (e) {
-      jsonObjectParseError = e;
-    }
-    if (jsonMimeType || jsonObject) {
-      // Extract the actual json substring in case this might be a "JSONP".
-      // This regex basically parses a function call and captures the
-      // function name and arguments in two separate groups.
-      let jsonpRegex = /^\s*([\w$]+)\s*\(\s*([^]*)\s*\)\s*;?\s*$/;
-      let [_, callbackPadding, jsonpString] = // eslint-disable-line
-        responseBody.match(jsonpRegex) || [];
-
-      // Make sure this is a valid JSON object first. If so, nicely display
-      // the parsing results in a variables view. Otherwise, simply show
-      // the contents as plain text.
-      if (callbackPadding && jsonpString) {
-        try {
-          jsonObject = JSON.parse(jsonpString);
-        } catch (e) {
-          jsonObjectParseError = e;
-        }
-      }
-
-      // Valid JSON or JSONP.
-      if (jsonObject) {
-        $("#response-content-json-box").hidden = false;
-        let jsonScopeName = callbackPadding
-          ? L10N.getFormatStr("jsonpScopeName", callbackPadding)
-          : L10N.getStr("jsonScopeName");
-
-        let jsonVar = { label: jsonScopeName, rawObject: jsonObject };
-        yield this._json.controller.setSingleVariable(jsonVar).expanded;
-      } else {
-        // Malformed JSON.
-        $("#response-content-textarea-box").hidden = false;
-        let infoHeader = $("#response-content-info-header");
-        infoHeader.setAttribute("value", jsonObjectParseError);
-        infoHeader.setAttribute("tooltiptext", jsonObjectParseError);
-        infoHeader.hidden = false;
-
-        let editor = yield NetMonitorView.editor("#response-content-textarea");
-        editor.setMode(Editor.modes.js);
-        editor.setText(responseBody);
-      }
-    } else if (mimeType.includes("image/")) {
-      // Handle images.
-      $("#response-content-image-box").setAttribute("align", "center");
-      $("#response-content-image-box").setAttribute("pack", "center");
-      $("#response-content-image-box").hidden = false;
-      $("#response-content-image").src = formDataURI(mimeType, encoding, responseBody);
-
-      // Immediately display additional information about the image:
-      // file name, mime type and encoding.
-      $("#response-content-image-name-value").setAttribute("value",
-        getUrlBaseName(url));
-      $("#response-content-image-mime-value").setAttribute("value", mimeType);
-
-      // Wait for the image to load in order to display the width and height.
-      $("#response-content-image").onload = e => {
-        // XUL images are majestic so they don't bother storing their dimensions
-        // in width and height attributes like the rest of the folk. Hack around
-        // this by getting the bounding client rect and subtracting the margins.
-        let { width, height } = e.target.getBoundingClientRect();
-        let dimensions = (width - 2) + " \u00D7 " + (height - 2);
-        $("#response-content-image-dimensions-value").setAttribute("value",
-          dimensions);
-      };
-    } else {
-      $("#response-content-textarea-box").hidden = false;
-      let editor = yield NetMonitorView.editor("#response-content-textarea");
-      editor.setMode(Editor.modes.text);
-      editor.setText(responseBody);
-
-      // Maybe set a more appropriate mode in the Source Editor if possible,
-      // but avoid doing this for very large files.
-      if (responseBody.length < SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE) {
-        let mapping = Object.keys(CONTENT_MIME_TYPE_MAPPINGS).find(key => {
-          return mimeType.includes(key);
-        });
-
-        if (mapping) {
-          editor.setMode(CONTENT_MIME_TYPE_MAPPINGS[mapping]);
-        }
-      }
-    }
-
-    window.emit(EVENTS.RESPONSE_BODY_DISPLAYED);
-  }),
-
   _dataSrc: null,
   _headers: null,
   _cookies: null,
-  _json: null,
   _requestHeaders: "",
   _responseHeaders: "",
   _requestCookies: "",
   _responseCookies: ""
 };
 
 exports.DetailsView = DetailsView;
--- a/devtools/client/netmonitor/events.js
+++ b/devtools/client/netmonitor/events.js
@@ -50,19 +50,16 @@ const EVENTS = {
   // When response content begins, updates and finishes receiving.
   STARTED_RECEIVING_RESPONSE: "NetMonitor:NetworkEventUpdating:ResponseStart",
   UPDATING_RESPONSE_CONTENT: "NetMonitor:NetworkEventUpdating:ResponseContent",
   RECEIVED_RESPONSE_CONTENT: "NetMonitor:NetworkEventUpdated:ResponseContent",
 
   // When the request post params are displayed in the UI.
   REQUEST_POST_PARAMS_DISPLAYED: "NetMonitor:RequestPostParamsAvailable",
 
-  // When the response body is displayed in the UI.
-  RESPONSE_BODY_DISPLAYED: "NetMonitor:ResponseBodyAvailable",
-
   // When the image response thumbnail is displayed in the UI.
   RESPONSE_IMAGE_THUMBNAIL_DISPLAYED:
     "NetMonitor:ResponseImageThumbnailAvailable",
 
   // When a tab is selected in the NetworkDetailsView and subsequently rendered.
   TAB_UPDATED: "NetMonitor:TabUpdated",
 
   // Fired when Sidebar has finished being populated.
--- a/devtools/client/netmonitor/netmonitor-view.js
+++ b/devtools/client/netmonitor/netmonitor-view.js
@@ -3,18 +3,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* eslint-disable mozilla/reject-some-requires */
 /* globals $, gStore, NetMonitorController, dumpn */
 
 "use strict";
 
 const { testing: isTesting } = require("devtools/shared/flags");
-const promise = require("promise");
-const Editor = require("devtools/client/sourceeditor/editor");
 const { Task } = require("devtools/shared/task");
 const { ViewHelpers } = require("devtools/client/shared/widgets/view-helpers");
 const { RequestsMenuView } = require("./requests-menu-view");
 const { CustomRequestView } = require("./custom-request-view");
 const { ToolbarView } = require("./toolbar-view");
 const { SidebarView } = require("./sidebar-view");
 const { DetailsView } = require("./details-view");
 const { StatisticsView } = require("./statistics-view");
@@ -27,22 +25,16 @@ const WDA_DEFAULT_VERIFY_INTERVAL = 50;
 
 // Use longer timeout during testing as the tests need this process to succeed
 // and two seconds is quite short on slow debug builds. The timeout here should
 // be at least equal to the general mochitest timeout of 45 seconds so that this
 // never gets hit during testing.
 // ms
 const WDA_DEFAULT_GIVE_UP_TIMEOUT = isTesting ? 45000 : 2000;
 
-const DEFAULT_EDITOR_CONFIG = {
-  mode: Editor.modes.text,
-  readOnly: true,
-  lineNumbers: true
-};
-
 /**
  * Object defining the network monitor view components.
  */
 var NetMonitorView = {
   /**
    * Initializes the network monitor view.
    */
   initialize: function () {
@@ -97,21 +89,16 @@ var NetMonitorView = {
    */
   _destroyPanes: Task.async(function* () {
     dumpn("Destroying the NetMonitorView panes");
 
     Prefs.networkDetailsWidth = this._detailsPane.getAttribute("width");
     Prefs.networkDetailsHeight = this._detailsPane.getAttribute("height");
 
     this._detailsPane = null;
-
-    for (let p of this._editorPromises.values()) {
-      let editor = yield p;
-      editor.destroy();
-    }
   }),
 
   /**
    * Gets the visibility state of the network details pane.
    * @return boolean
    */
   get detailsPaneHidden() {
     return this._detailsPane.classList.contains("pane-collapsed");
@@ -209,45 +196,18 @@ var NetMonitorView = {
     });
   },
 
   reloadPage: function () {
     NetMonitorController.triggerActivity(
       ACTIVITY_TYPE.RELOAD.WITH_CACHE_DEFAULT);
   },
 
-  /**
-   * Lazily initializes and returns a promise for a Editor instance.
-   *
-   * @param string id
-   *        The id of the editor placeholder node.
-   * @return object
-   *         A promise that is resolved when the editor is available.
-   */
-  editor: function (id) {
-    dumpn("Getting a NetMonitorView editor: " + id);
-
-    if (this._editorPromises.has(id)) {
-      return this._editorPromises.get(id);
-    }
-
-    let deferred = promise.defer();
-    this._editorPromises.set(id, deferred.promise);
-
-    // Initialize the source editor and store the newly created instance
-    // in the ether of a resolved promise's value.
-    let editor = new Editor(DEFAULT_EDITOR_CONFIG);
-    editor.appendTo($(id)).then(() => deferred.resolve(editor));
-
-    return deferred.promise;
-  },
-
   _body: null,
   _detailsPane: null,
-  _editorPromises: new Map()
 };
 
 /**
  * Makes sure certain properties are available on all objects in a data store.
  *
  * @param Store dataStore
  *        A Redux store for which to check the availability of properties.
  * @param array mandatoryFields
--- a/devtools/client/netmonitor/netmonitor.xul
+++ b/devtools/client/netmonitor/netmonitor.xul
@@ -212,52 +212,18 @@
               </tabpanel>
               <tabpanel id="params-tabpanel"
                         class="tabpanel-content">
                 <html:div xmlns="http://www.w3.org/1999/xhtml"
                           id="react-params-tabpanel-hook"/>
               </tabpanel>
               <tabpanel id="response-tabpanel"
                         class="tabpanel-content">
-                <vbox flex="1">
-                  <label id="response-content-info-header"/>
-                  <vbox id="response-content-json-box" flex="1" hidden="true">
-                    <vbox id="response-content-json" flex="1" context="network-response-popup" />
-                  </vbox>
-                  <vbox id="response-content-textarea-box" flex="1" hidden="true">
-                    <vbox id="response-content-textarea" flex="1"/>
-                  </vbox>
-                  <vbox id="response-content-image-box" flex="1" hidden="true">
-                    <image id="response-content-image"/>
-                    <hbox>
-                      <label class="plain tabpanel-summary-label"
-                             data-localization="content=netmonitor.response.name"/>
-                      <label id="response-content-image-name-value"
-                             class="plain tabpanel-summary-value devtools-monospace"
-                             crop="end"
-                             flex="1"/>
-                    </hbox>
-                    <hbox>
-                      <label class="plain tabpanel-summary-label"
-                             data-localization="content=netmonitor.response.dimensions"/>
-                      <label id="response-content-image-dimensions-value"
-                             class="plain tabpanel-summary-value devtools-monospace"
-                             crop="end"
-                             flex="1"/>
-                    </hbox>
-                    <hbox>
-                      <label class="plain tabpanel-summary-label"
-                             data-localization="content=netmonitor.response.mime"/>
-                      <label id="response-content-image-mime-value"
-                             class="plain tabpanel-summary-value devtools-monospace"
-                             crop="end"
-                             flex="1"/>
-                    </hbox>
-                  </vbox>
-                </vbox>
+                <html:div xmlns="http://www.w3.org/1999/xhtml"
+                          id="react-response-tabpanel-hook"/>
               </tabpanel>
               <tabpanel id="timings-tabpanel"
                         class="tabpanel-content">
                 <html:div xmlns="http://www.w3.org/1999/xhtml"
                           id="react-timings-tabpanel-hook"/>
               </tabpanel>
               <tabpanel id="security-tabpanel"
                         class="tabpanel-content">
--- a/devtools/client/netmonitor/request-utils.js
+++ b/devtools/client/netmonitor/request-utils.js
@@ -25,17 +25,17 @@ const { Task } = require("devtools/share
 function getKeyWithEvent(callback, onlySpaceOrReturn) {
   return function (event) {
     let key = event.target.getAttribute("data-key");
     let filterKeyboardEvent = !onlySpaceOrReturn ||
                               event.keyCode === KeyCodes.DOM_VK_SPACE ||
                               event.keyCode === KeyCodes.DOM_VK_RETURN;
 
     if (key && filterKeyboardEvent) {
-      callback.call(null, key);
+      callback(key);
     }
   };
 }
 
 /**
  * Extracts any urlencoded form data sections (e.g. "?foo=bar&baz=42") from a
  * POST request.
  *
--- a/devtools/client/netmonitor/requests-menu-view.js
+++ b/devtools/client/netmonitor/requests-menu-view.js
@@ -258,20 +258,18 @@ RequestsMenuView.prototype = {
         let { text, encoding } = responseContent.content;
         let response = yield gNetwork.getString(text);
         let payload = {};
 
         if (mimeType.includes("image/")) {
           payload.responseContentDataUri = formDataURI(mimeType, encoding, response);
         }
 
-        if (mimeType.includes("text/")) {
-          responseContent.content.text = response;
-          payload.responseContent = responseContent;
-        }
+        responseContent.content.text = response;
+        payload.responseContent = responseContent;
 
         yield this.store.dispatch(Actions.updateRequest(action.id, payload, true));
 
         if (mimeType.includes("image/")) {
           window.emit(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED);
         }
       }
     }
--- a/devtools/client/netmonitor/shared/components/editor.js
+++ b/devtools/client/netmonitor/shared/components/editor.js
@@ -11,32 +11,38 @@ const { div } = DOM;
 
 /**
  * CodeMirror editor as a React component
  */
 const Editor = createClass({
   displayName: "Editor",
 
   propTypes: {
+    // Source editor syntax hightligh mode, which is a mime type defined in CodeMirror
+    mode: PropTypes.string,
+    // Source editor is displayed if set to true
     open: PropTypes.bool,
+    // Source editor content
     text: PropTypes.string,
   },
 
   getDefaultProps() {
     return {
+      mode: null,
       open: true,
       text: "",
     };
   },
 
   componentDidMount() {
-    const { text } = this.props;
+    const { mode, text } = this.props;
 
     this.editor = new SourceEditor({
       lineNumbers: true,
+      mode,
       readOnly: true,
       value: text,
     });
 
     this.deferEditor = this.editor.appendTo(this.refs.editorElement);
   },
 
   componentDidUpdate(prevProps) {
@@ -51,18 +57,18 @@ const Editor = createClass({
         this.editor.setMode(mode);
       });
     }
 
     if (prevProps.text !== text) {
       this.deferEditor.then(() => {
         // FIXME: Workaround for browser_net_accessibility test to
         // make sure editor node exists while setting editor text.
-        // deferEditor workround should be removed in bug 1308442
-        if (this.refs.editor) {
+        // deferEditor workaround should be removed in bug 1308442
+        if (this.refs.editorElement) {
           this.editor.setText(text);
         }
       });
     }
   },
 
   componentWillUnmount() {
     this.deferEditor.then(() => {
--- a/devtools/client/netmonitor/shared/components/moz.build
+++ b/devtools/client/netmonitor/shared/components/moz.build
@@ -2,11 +2,12 @@
 # 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/.
 
 DevToolsModules(
     'editor.js',
     'params-panel.js',
     'preview-panel.js',
     'properties-view.js',
+    'response-panel.js',
     'security-panel.js',
     'timings-panel.js',
 )
--- a/devtools/client/netmonitor/shared/components/properties-view.js
+++ b/devtools/client/netmonitor/shared/components/properties-view.js
@@ -190,8 +190,9 @@ const PropertiesView = createClass({
         ),
       )
     );
   }
 
 });
 
 module.exports = PropertiesView;
+
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/shared/components/response-panel.js
@@ -0,0 +1,204 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {
+  createClass,
+  createFactory,
+  DOM,
+  PropTypes,
+} = require("devtools/client/shared/vendor/react");
+const { connect } = require("devtools/client/shared/vendor/react-redux");
+const { L10N } = require("../../l10n");
+const { getSelectedRequest } = require("../../selectors/index");
+const { formDataURI, getUrlBaseName } = require("../../request-utils");
+
+// Components
+const PropertiesView = createFactory(require("./properties-view"));
+
+const { div, img } = DOM;
+const JSON_SCOPE_NAME = L10N.getStr("jsonScopeName");
+const JSON_FILTER_TEXT = L10N.getStr("jsonFilterText");
+const RESPONSE_IMG_NAME = L10N.getStr("netmonitor.response.name");
+const RESPONSE_IMG_DIMENSIONS = L10N.getStr("netmonitor.response.dimensions");
+const RESPONSE_IMG_MIMETYPE = L10N.getStr("netmonitor.response.mime");
+const RESPONSE_PAYLOAD = L10N.getStr("responsePayload");
+
+/*
+ * Response panel component
+ * Displays the GET parameters and POST data of a request
+ */
+const ResponsePanel = createClass({
+  displayName: "ResponsePanel",
+
+  propTypes: {
+    encoding: PropTypes.string,
+    mimeType: PropTypes.string,
+    response: PropTypes.string,
+    url: PropTypes.string,
+  },
+
+  getInitialState() {
+    return {
+      imageDimensions: {
+        width: 0,
+        height: 0,
+      },
+    };
+  },
+
+  updateImageDimemsions({ target }) {
+    this.setState({
+      imageDimensions: {
+        width: target.naturalWidth,
+        height: target.naturalHeight,
+      },
+    });
+  },
+
+  // Handle json, which we tentatively identify by checking the MIME type
+  // for "json" after any word boundary. This works for the standard
+  // "application/json", and also for custom types like "x-bigcorp-json".
+  // Additionally, we also directly parse the response text content to
+  // verify whether it's json or not, to handle responses incorrectly
+  // labeled as text/plain instead.
+  isJSON(mimeType, response) {
+    let json, error;
+    try {
+      json = JSON.parse(response);
+    } catch (err) {
+      error = err;
+    }
+
+    if (/\bjson/.test(mimeType) || json) {
+      // Extract the actual json substring in case this might be a "JSONP".
+      // This regex basically parses a function call and captures the
+      // function name and arguments in two separate groups.
+      let jsonpRegex = /^\s*([\w$]+)\s*\(\s*([^]*)\s*\)\s*;?\s*$/;
+      let [, jsonpCallback, jsonp] = response.match(jsonpRegex) || [];
+      let result = {};
+
+      // Make sure this is a valid JSON object first. If so, nicely display
+      // the parsing results in a tree view.
+      if (jsonpCallback && jsonp) {
+        error = null;
+        try {
+          json = JSON.parse(jsonp);
+        } catch (err) {
+          error = err;
+        }
+      }
+
+      // Valid JSON
+      if (json) {
+        result.json = json;
+      }
+      // Valid JSONP
+      if (jsonpCallback) {
+        result.jsonpCallback = jsonpCallback;
+      }
+      // Malformed JSON
+      if (error) {
+        result.error = "" + error;
+      }
+
+      return result;
+    }
+
+    return null;
+  },
+
+  render() {
+    const { encoding, mimeType, response, url } = this.props;
+
+    if (!mimeType || !url || typeof response !== "string") {
+      return null;
+    }
+
+    if (mimeType.includes("image/")) {
+      let { width, height } = this.state.imageDimensions;
+
+      return (
+        div({ className: "response-image-box devtools-monospace" },
+          img({
+            className: "response-image",
+            src: formDataURI(mimeType, encoding, response),
+            onLoad: this.updateImageDimemsions,
+          }),
+          div({ className: "response-summary" },
+            div({ className: "tabpanel-summary-label" }, RESPONSE_IMG_NAME),
+            div({ className: "tabpanel-summary-value" }, getUrlBaseName(url)),
+          ),
+          div({ className: "response-summary" },
+            div({ className: "tabpanel-summary-label" }, RESPONSE_IMG_DIMENSIONS),
+            div({ className: "tabpanel-summary-value" }, `${width} × ${height}`),
+          ),
+          div({ className: "response-summary" },
+            div({ className: "tabpanel-summary-label" }, RESPONSE_IMG_MIMETYPE),
+            div({ className: "tabpanel-summary-value" }, mimeType),
+          ),
+        )
+      );
+    }
+
+    // Display Properties View
+    let { json, jsonpCallback, error } = this.isJSON(mimeType, response) || {};
+    let object = {};
+    let sectionName;
+
+    if (json) {
+      if (jsonpCallback) {
+        sectionName = L10N.getFormatStr("jsonpScopeName", jsonpCallback);
+      } else {
+        sectionName = JSON_SCOPE_NAME;
+      }
+      object[sectionName] = json;
+    } else {
+      sectionName = RESPONSE_PAYLOAD;
+
+      object[sectionName] = {
+        EDITOR_CONFIG: {
+          text: response,
+          mode: mimeType.replace(/;.+/, ""),
+        },
+      };
+    }
+
+    return (
+      div({},
+        error && div({ className: "response-error-header", title: error },
+          error
+        ),
+        PropertiesView({
+          object,
+          filterPlaceHolder: JSON_FILTER_TEXT,
+          sectionNames: [sectionName],
+        }),
+      )
+    );
+  }
+});
+
+module.exports = connect(
+  (state) => {
+    const selectedRequest = getSelectedRequest(state);
+
+    if (selectedRequest) {
+      const { responseContent, url } = selectedRequest;
+      if (responseContent) {
+        const { encoding, mimeType, text } = responseContent.content;
+
+        return {
+          encoding,
+          mimeType,
+          response: text,
+          url,
+        };
+      }
+    }
+
+    return {};
+  },
+)(ResponsePanel);
--- a/devtools/client/netmonitor/test/browser_net_accessibility-02.js
+++ b/devtools/client/netmonitor/test/browser_net_accessibility-02.js
@@ -117,16 +117,13 @@ add_task(function* () {
   check(0, true);
   EventUtils.sendKey("DOWN", window);
   check(1, true);
   EventUtils.sendKey("END", window);
   check(19, true);
   EventUtils.sendKey("DOWN", window);
   check(19, true);
 
-  EventUtils.sendMouseEvent({ type: "mousedown" }, $("#details-pane-toggle"));
-  check(-1, false);
-
   EventUtils.sendMouseEvent({ type: "mousedown" }, $(".request-list-item"));
   check(0, true);
 
   yield teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/browser_net_brotli.js
+++ b/devtools/client/netmonitor/test/browser_net_brotli.js
@@ -11,17 +11,17 @@ const BROTLI_REQUESTS = 1;
  */
 
 add_task(function* () {
   let { L10N } = require("devtools/client/netmonitor/l10n");
 
   let { tab, monitor } = yield initNetMonitor(BROTLI_URL);
   info("Starting test... ");
 
-  let { document, Editor, NetMonitorView } = monitor.panelWin;
+  let { document, NetMonitorView } = monitor.panelWin;
   let { RequestsMenu } = NetMonitorView;
 
   RequestsMenu.lazyUpdate = false;
 
   let wait = waitForNetworkEvents(monitor, BROTLI_REQUESTS);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
@@ -33,59 +33,33 @@ add_task(function* () {
       statusText: "Connected",
       type: "plain",
       fullMimeType: "text/plain",
       transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 10),
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 64),
       time: true
     });
 
-  let onEvent = waitForResponseBodyDisplayed();
+  wait = waitForDOM(document, "#response-tabpanel .editor-mount iframe");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll("#details-pane tab")[3]);
-  yield onEvent;
-  yield testResponseTab("br");
+  let [editorFrame] = yield wait;
+  yield once(editorFrame, "DOMContentLoaded");
+  yield waitForDOM(editorFrame.contentDocument, ".CodeMirror-code");
+  yield testResponse("br");
 
   yield teardown(monitor);
 
-  function* testResponseTab(type) {
-    let tabEl = document.querySelectorAll("#details-pane tab")[3];
-    let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
-
-    is(tabEl.getAttribute("selected"), "true",
-      "The response tab in the network details pane should be selected.");
-
-    function checkVisibility(box) {
-      is(tabpanel.querySelector("#response-content-info-header")
-        .hasAttribute("hidden"), true,
-        "The response info header doesn't have the intended visibility.");
-      is(tabpanel.querySelector("#response-content-json-box")
-        .hasAttribute("hidden"), box != "json",
-        "The response content json box doesn't have the intended visibility.");
-      is(tabpanel.querySelector("#response-content-textarea-box")
-        .hasAttribute("hidden"), box != "textarea",
-        "The response content textarea box doesn't have the intended visibility.");
-      is(tabpanel.querySelector("#response-content-image-box")
-        .hasAttribute("hidden"), box != "image",
-        "The response content image box doesn't have the intended visibility.");
-    }
-
+  function* testResponse(type) {
     switch (type) {
       case "br": {
-        checkVisibility("textarea");
+        let text = editorFrame.contentDocument
+          .querySelector(".CodeMirror-line").textContent;
 
-        let expected = "X".repeat(64);
-        let editor = yield NetMonitorView.editor("#response-content-textarea");
-        is(editor.getText(), expected,
+        is(text, "X".repeat(64),
           "The text shown in the source editor is incorrect for the brotli request.");
-        is(editor.getMode(), Editor.modes.text,
-          "The mode active in the source editor is incorrect for the brotli request.");
         break;
       }
     }
   }
-
-  function waitForResponseBodyDisplayed() {
-    return monitor.panelWin.once(monitor.panelWin.EVENTS.RESPONSE_BODY_DISPLAYED);
-  }
 });
--- a/devtools/client/netmonitor/test/browser_net_content-type.js
+++ b/devtools/client/netmonitor/test/browser_net_content-type.js
@@ -8,17 +8,17 @@
  */
 
 add_task(function* () {
   let { L10N } = require("devtools/client/netmonitor/l10n");
 
   let { tab, monitor } = yield initNetMonitor(CONTENT_TYPE_WITHOUT_CACHE_URL);
   info("Starting test... ");
 
-  let { document, Editor, NetMonitorView } = monitor.panelWin;
+  let { document, NetMonitorView } = monitor.panelWin;
   let { RequestsMenu } = NetMonitorView;
 
   RequestsMenu.lazyUpdate = false;
 
   let wait = waitForNetworkEvents(monitor, CONTENT_TYPE_WITHOUT_CACHE_REQUESTS);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
@@ -85,171 +85,180 @@ add_task(function* () {
       statusText: "OK",
       type: "plain",
       fullMimeType: "text/plain",
       transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 73),
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 10.73),
       time: true
     });
 
-  let onEvent = waitForResponseBodyDisplayed();
+  wait = waitForDOM(document, "#response-tabpanel");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll("#details-pane tab")[3]);
-  yield onEvent;
+  yield wait;
+
+  RequestsMenu.selectedIndex = -1;
+
+  yield selectIndexAndWaitForEditor(0);
   yield testResponseTab("xml");
 
-  yield selectIndexAndWaitForTabUpdated(1);
+  yield selectIndexAndWaitForEditor(1);
   yield testResponseTab("css");
 
-  yield selectIndexAndWaitForTabUpdated(2);
+  yield selectIndexAndWaitForEditor(2);
   yield testResponseTab("js");
 
-  yield selectIndexAndWaitForTabUpdated(3);
+  yield selectIndexAndWaitForJSONView(3);
   yield testResponseTab("json");
 
-  yield selectIndexAndWaitForTabUpdated(4);
+  yield selectIndexAndWaitForEditor(4);
   yield testResponseTab("html");
 
-  yield selectIndexAndWaitForTabUpdated(5);
+  yield selectIndexAndWaitForImageView(5);
   yield testResponseTab("png");
 
-  yield selectIndexAndWaitForTabUpdated(6);
+  yield selectIndexAndWaitForEditor(6);
   yield testResponseTab("gzip");
 
   yield teardown(monitor);
 
   function* testResponseTab(type) {
-    let tabEl = document.querySelectorAll("#details-pane tab")[3];
     let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
 
-    is(tabEl.getAttribute("selected"), "true",
-      "The response tab in the network details pane should be selected.");
-
     function checkVisibility(box) {
-      is(tabpanel.querySelector("#response-content-info-header")
-        .hasAttribute("hidden"), true,
-        "The response info header doesn't have the intended visibility.");
-      is(tabpanel.querySelector("#response-content-json-box")
-        .hasAttribute("hidden"), box != "json",
-        "The response content json box doesn't have the intended visibility.");
-      is(tabpanel.querySelector("#response-content-textarea-box")
-        .hasAttribute("hidden"), box != "textarea",
-        "The response content textarea box doesn't have the intended visibility.");
-      is(tabpanel.querySelector("#response-content-image-box")
-        .hasAttribute("hidden"), box != "image",
-        "The response content image box doesn't have the intended visibility.");
+      is(tabpanel.querySelector(".response-error-header") === null,
+        true,
+        "The response error header doesn't display");
+      let jsonView = tabpanel.querySelector(".tree-section .treeLabel") || {};
+      is(jsonView.textContent !== L10N.getStr("jsonScopeName"),
+        box != "json",
+        "The response json view doesn't display");
+      is(tabpanel.querySelector(".editor-mount") === null,
+        box != "textarea",
+        "The response editor doesn't display");
+      is(tabpanel.querySelector(".response-image-box") === null,
+        box != "image",
+        "The response image view doesn't display");
     }
 
     switch (type) {
       case "xml": {
         checkVisibility("textarea");
 
-        let editor = yield NetMonitorView.editor("#response-content-textarea");
-        is(editor.getText(), "<label value='greeting'>Hello XML!</label>",
+        let editor = tabpanel.querySelector(".editor-mount iframe");
+        let text = editor.contentDocument .querySelector(".CodeMirror-line").textContent;
+
+        is(text, "<label value='greeting'>Hello XML!</label>",
           "The text shown in the source editor is incorrect for the xml request.");
-        is(editor.getMode(), Editor.modes.html,
-          "The mode active in the source editor is incorrect for the xml request.");
         break;
       }
       case "css": {
         checkVisibility("textarea");
 
-        let editor = yield NetMonitorView.editor("#response-content-textarea");
-        is(editor.getText(), "body:pre { content: 'Hello CSS!' }",
-          "The text shown in the source editor is incorrect for the xml request.");
-        is(editor.getMode(), Editor.modes.css,
-          "The mode active in the source editor is incorrect for the xml request.");
+        let editor = tabpanel.querySelector(".editor-mount iframe");
+        let text = editor.contentDocument.querySelector(".CodeMirror-line").textContent;
+
+        is(text, "body:pre { content: 'Hello CSS!' }",
+          "The text shown in the source editor is incorrect for the css request.");
         break;
       }
       case "js": {
         checkVisibility("textarea");
 
-        let editor = yield NetMonitorView.editor("#response-content-textarea");
-        is(editor.getText(), "function() { return 'Hello JS!'; }",
-          "The text shown in the source editor is incorrect for the xml request.");
-        is(editor.getMode(), Editor.modes.js,
-          "The mode active in the source editor is incorrect for the xml request.");
+        let editor = tabpanel.querySelector(".editor-mount iframe");
+        let text = editor.contentDocument.querySelector(".CodeMirror-line").textContent;
+
+        is(text, "function() { return 'Hello JS!'; }",
+          "The text shown in the source editor is incorrect for the js request.");
         break;
       }
       case "json": {
         checkVisibility("json");
 
-        is(tabpanel.querySelectorAll(".variables-view-scope").length, 1,
-          "There should be 1 json scope displayed in this tabpanel.");
-        is(tabpanel.querySelectorAll(".variables-view-property").length, 2,
-          "There should be 2 json properties displayed in this tabpanel.");
-        is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
+        is(tabpanel.querySelectorAll(".tree-section").length, 1,
+          "There should be 1 tree sections displayed in this tabpanel.");
+        is(tabpanel.querySelectorAll(".empty-notice").length, 0,
           "The empty notice should not be displayed in this tabpanel.");
 
-        let jsonScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
-
-        is(jsonScope.querySelector(".name").getAttribute("value"),
+        is(tabpanel.querySelector(".tree-section .treeLabel").textContent,
           L10N.getStr("jsonScopeName"),
-          "The json scope doesn't have the correct title.");
+          "The json view section doesn't have the correct title.");
 
-        is(jsonScope.querySelectorAll(".variables-view-property .name")[0]
-          .getAttribute("value"),
-          "greeting", "The first json property name was incorrect.");
-        is(jsonScope.querySelectorAll(".variables-view-property .value")[0]
-          .getAttribute("value"),
-          "\"Hello JSON!\"", "The first json property value was incorrect.");
+        let labels = tabpanel
+          .querySelectorAll("tr:not(.tree-section) .treeLabelCell .treeLabel");
+        let values = tabpanel
+          .querySelectorAll("tr:not(.tree-section) .treeValueCell .objectBox");
 
-        is(jsonScope.querySelectorAll(".variables-view-property .name")[1]
-          .getAttribute("value"),
-          "__proto__", "The second json property name was incorrect.");
-        is(jsonScope.querySelectorAll(".variables-view-property .value")[1]
-          .getAttribute("value"),
-          "Object", "The second json property value was incorrect.");
+        is(labels[0].textContent, "greeting",
+          "The first json property name was incorrect.");
+        is(values[0].textContent,
+          "\"Hello JSON!\"", "The first json property value was incorrect.");
         break;
       }
       case "html": {
         checkVisibility("textarea");
 
-        let editor = yield NetMonitorView.editor("#response-content-textarea");
-        is(editor.getText(), "<blink>Not Found</blink>",
-          "The text shown in the source editor is incorrect for the xml request.");
-        is(editor.getMode(), Editor.modes.html,
-          "The mode active in the source editor is incorrect for the xml request.");
+        let editor = document.querySelector(".editor-mount iframe");
+        let text = editor.contentDocument.querySelector(".CodeMirror-line").textContent;
+
+        is(text, "<blink>Not Found</blink>",
+          "The text shown in the source editor is incorrect for the html request.");
         break;
       }
       case "png": {
         checkVisibility("image");
 
-        let imageNode = tabpanel.querySelector("#response-content-image");
-        yield once(imageNode, "load");
+        let [name, dimensions, mime] = tabpanel
+          .querySelectorAll(".response-image-box .tabpanel-summary-value");
 
-        is(tabpanel.querySelector("#response-content-image-name-value")
-          .getAttribute("value"), "test-image.png",
+        is(name.textContent, "test-image.png",
           "The image name info isn't correct.");
-        is(tabpanel.querySelector("#response-content-image-mime-value")
-          .getAttribute("value"), "image/png",
+        is(mime.textContent, "image/png",
           "The image mime info isn't correct.");
-        is(tabpanel.querySelector("#response-content-image-dimensions-value")
-          .getAttribute("value"), "16" + " \u00D7 " + "16",
+        is(dimensions.textContent, "16" + " \u00D7 " + "16",
           "The image dimensions info isn't correct.");
         break;
       }
       case "gzip": {
         checkVisibility("textarea");
 
-        let expected = new Array(1000).join("Hello gzip!");
-        let editor = yield NetMonitorView.editor("#response-content-textarea");
-        is(editor.getText(), expected,
+        let editor = tabpanel.querySelector(".editor-mount iframe");
+        let text = editor.contentDocument.querySelector(".CodeMirror-line").textContent;
+
+        is(text, new Array(1000).join("Hello gzip!"),
           "The text shown in the source editor is incorrect for the gzip request.");
-        is(editor.getMode(), Editor.modes.text,
-          "The mode active in the source editor is incorrect for the gzip request.");
         break;
       }
     }
   }
 
-  function selectIndexAndWaitForTabUpdated(index) {
-    let onTabUpdated = monitor.panelWin.once(monitor.panelWin.EVENTS.TAB_UPDATED);
-    RequestsMenu.selectedIndex = index;
-    return onTabUpdated;
+  function* selectIndexAndWaitForEditor(index) {
+    let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
+    let editor = tabpanel.querySelector(".editor-mount iframe");
+    if (!editor) {
+      let waitDOM = waitForDOM(tabpanel, ".editor-mount iframe");
+      RequestsMenu.selectedIndex = index;
+      [editor] = yield waitDOM;
+      yield once(editor, "DOMContentLoaded");
+    } else {
+      RequestsMenu.selectedIndex = index;
+    }
+
+    yield waitForDOM(editor.contentDocument, ".CodeMirror-code");
   }
 
-  function waitForResponseBodyDisplayed() {
-    return monitor.panelWin.once(monitor.panelWin.EVENTS.RESPONSE_BODY_DISPLAYED);
+  function* selectIndexAndWaitForJSONView(index) {
+    let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
+    let waitDOM = waitForDOM(tabpanel, ".treeTable");
+    RequestsMenu.selectedIndex = index;
+    yield waitDOM;
+  }
+
+  function* selectIndexAndWaitForImageView(index) {
+    let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
+    let waitDOM = waitForDOM(tabpanel, ".response-image");
+    RequestsMenu.selectedIndex = index;
+    let [imageNode] = yield waitDOM;
+    yield once(imageNode, "load");
   }
 });
--- a/devtools/client/netmonitor/test/browser_net_cyrillic-01.js
+++ b/devtools/client/netmonitor/test/browser_net_cyrillic-01.js
@@ -6,40 +6,41 @@
 /**
  * Tests if cyrillic text is rendered correctly in the source editor.
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CYRILLIC_URL);
   info("Starting test... ");
 
-  let { document, EVENTS, Editor, NetMonitorView } = monitor.panelWin;
+  let { document, NetMonitorView } = monitor.panelWin;
   let { RequestsMenu } = NetMonitorView;
 
   RequestsMenu.lazyUpdate = false;
 
   let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(0),
     "GET", CONTENT_TYPE_SJS + "?fmt=txt", {
       status: 200,
       statusText: "DA DA DA"
     });
 
+  wait = waitForDOM(document, "#response-tabpanel .editor-mount iframe");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll("#details-pane tab")[3]);
+  let [editor] = yield wait;
+  yield once(editor, "DOMContentLoaded");
+  yield waitForDOM(editor.contentDocument, ".CodeMirror-code");
+  let text = editor.contentDocument
+          .querySelector(".CodeMirror-line").textContent;
 
-  yield monitor.panelWin.once(EVENTS.RESPONSE_BODY_DISPLAYED);
-  let editor = yield NetMonitorView.editor("#response-content-textarea");
-  // u044F = я
-  is(editor.getText().indexOf("\u044F"), 26,
+  ok(text.includes("\u0411\u0440\u0430\u0442\u0430\u043d"),
     "The text shown in the source editor is correct.");
-  is(editor.getMode(), Editor.modes.text,
-    "The mode active in the source editor is correct.");
 
   yield teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/browser_net_cyrillic-02.js
+++ b/devtools/client/netmonitor/test/browser_net_cyrillic-02.js
@@ -7,38 +7,39 @@
  * Tests if cyrillic text is rendered correctly in the source editor
  * when loaded directly from an HTML page.
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CYRILLIC_URL);
   info("Starting test... ");
 
-  let { document, EVENTS, Editor, NetMonitorView } = monitor.panelWin;
+  let { document, NetMonitorView } = monitor.panelWin;
   let { RequestsMenu } = NetMonitorView;
 
   RequestsMenu.lazyUpdate = false;
 
   let wait = waitForNetworkEvents(monitor, 1);
   tab.linkedBrowser.reload();
   yield wait;
 
   verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(0),
     "GET", CYRILLIC_URL, {
       status: 200,
       statusText: "OK"
     });
 
+  wait = waitForDOM(document, "#response-tabpanel .editor-mount iframe");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll("#details-pane tab")[3]);
+  let [editor] = yield wait;
+  yield once(editor, "DOMContentLoaded");
+  yield waitForDOM(editor.contentDocument, ".CodeMirror-code");
+  let text = editor.contentDocument
+          .querySelector(".CodeMirror-code").textContent;
 
-  yield monitor.panelWin.once(EVENTS.RESPONSE_BODY_DISPLAYED);
-  let editor = yield NetMonitorView.editor("#response-content-textarea");
-  // u044F = я
-  is(editor.getText().indexOf("\u044F"), 486,
+  ok(text.includes("\u0411\u0440\u0430\u0442\u0430\u043d"),
     "The text shown in the source editor is correct.");
-  is(editor.getMode(), Editor.modes.html,
-    "The mode active in the source editor is correct.");
 
   return teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/browser_net_json-long.js
+++ b/devtools/client/netmonitor/test/browser_net_json-long.js
@@ -12,17 +12,17 @@ add_task(function* () {
 
   let { tab, monitor } = yield initNetMonitor(JSON_LONG_URL);
   info("Starting test... ");
 
   // This is receiving over 80 KB of json and will populate over 6000 items
   // in a variables view instance. Debug builds are slow.
   requestLongerTimeout(4);
 
-  let { document, EVENTS, NetMonitorView } = monitor.panelWin;
+  let { document, NetMonitorView } = monitor.panelWin;
   let { RequestsMenu } = NetMonitorView;
 
   RequestsMenu.lazyUpdate = false;
 
   let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
@@ -34,65 +34,59 @@ add_task(function* () {
       statusText: "OK",
       type: "json",
       fullMimeType: "text/json; charset=utf-8",
       size: L10N.getFormatStr("networkMenu.sizeKB",
         L10N.numberWithDecimals(85975 / 1024, 2)),
       time: true
     });
 
-  let onEvent = monitor.panelWin.once(EVENTS.RESPONSE_BODY_DISPLAYED);
+  wait = waitForDOM(document, "#response-tabpanel");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll("#details-pane tab")[3]);
-  yield onEvent;
+  yield wait;
 
   testResponseTab();
 
   yield teardown(monitor);
 
   function testResponseTab() {
-    let tabEl = document.querySelectorAll("#details-pane tab")[3];
     let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
 
-    is(tabEl.getAttribute("selected"), "true",
-      "The response tab in the network details pane should be selected.");
+    is(tabpanel.querySelector(".response-error-header") === null, true,
+      "The response error header doesn't have the intended visibility.");
+    let jsonView = tabpanel.querySelector(".tree-section .treeLabel") || {};
+    is(jsonView.textContent === L10N.getStr("jsonScopeName"), true,
+      "The response json view has the intended visibility.");
+    is(tabpanel.querySelector(".editor-mount iframe") === null, true,
+      "The response editor doesn't have the intended visibility.");
+    is(tabpanel.querySelector(".response-image-box") === null, true,
+      "The response image box doesn't have the intended visibility.");
 
-    is(tabpanel.querySelector("#response-content-info-header")
-      .hasAttribute("hidden"), true,
-      "The response info header doesn't have the intended visibility.");
-    is(tabpanel.querySelector("#response-content-json-box")
-      .hasAttribute("hidden"), false,
-      "The response content json box doesn't have the intended visibility.");
-    is(tabpanel.querySelector("#response-content-textarea-box")
-      .hasAttribute("hidden"), true,
-      "The response content textarea box doesn't have the intended visibility.");
-    is(tabpanel.querySelector("#response-content-image-box")
-      .hasAttribute("hidden"), true,
-      "The response content image box doesn't have the intended visibility.");
-
-    is(tabpanel.querySelectorAll(".variables-view-scope").length, 1,
-      "There should be 1 json scope displayed in this tabpanel.");
-    is(tabpanel.querySelectorAll(".variables-view-property").length, 6143,
-      "There should be 6143 json properties displayed in this tabpanel.");
-    is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
+    is(tabpanel.querySelectorAll(".tree-section").length, 1,
+      "There should be 1 tree sections displayed in this tabpanel.");
+    is(tabpanel.querySelectorAll(".treeRow:not(.tree-section)").length, 4094,
+      "There should be 4094 json properties displayed in this tabpanel.");
+    is(tabpanel.querySelectorAll(".empty-notice").length, 0,
       "The empty notice should not be displayed in this tabpanel.");
 
-    let jsonScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
-    let names = ".variables-view-property > .title > .name";
-    let values = ".variables-view-property > .title > .value";
+    is(tabpanel.querySelector(".tree-section .treeLabel").textContent,
+      L10N.getStr("jsonScopeName"),
+      "The json view section doesn't have the correct title.");
 
-    is(jsonScope.querySelector(".name").getAttribute("value"),
-      L10N.getStr("jsonScopeName"),
-      "The json scope doesn't have the correct title.");
+    let labels = tabpanel
+      .querySelectorAll("tr:not(.tree-section) .treeLabelCell .treeLabel");
+    let values = tabpanel
+      .querySelectorAll("tr:not(.tree-section) .treeValueCell .objectBox");
 
-    is(jsonScope.querySelectorAll(names)[0].getAttribute("value"),
-      "0", "The first json property name was incorrect.");
-    is(jsonScope.querySelectorAll(values)[0].getAttribute("value"),
-      "Object", "The first json property value was incorrect.");
+    is(labels[0].textContent, "0",
+      "The first json property name was incorrect.");
+    is(values[0].textContent, "Object",
+      "The first json property value was incorrect.");
 
-    is(jsonScope.querySelectorAll(names)[1].getAttribute("value"),
-      "greeting", "The second json property name was incorrect.");
-    is(jsonScope.querySelectorAll(values)[1].getAttribute("value"),
-      "\"Hello long string JSON!\"", "The second json property value was incorrect.");
+    is(labels[1].textContent, "greeting",
+      "The second json property name was incorrect.");
+    is(values[1].textContent, "\"Hello long string JSON!\"",
+      "The second json property value was incorrect.");
   }
 });
--- a/devtools/client/netmonitor/test/browser_net_json-malformed.js
+++ b/devtools/client/netmonitor/test/browser_net_json-malformed.js
@@ -3,20 +3,21 @@
 
 "use strict";
 
 /**
  * Tests if malformed JSON responses are handled correctly.
  */
 
 add_task(function* () {
+  let { L10N } = require("devtools/client/netmonitor/l10n");
   let { tab, monitor } = yield initNetMonitor(JSON_MALFORMED_URL);
   info("Starting test... ");
 
-  let { document, EVENTS, Editor, NetMonitorView } = monitor.panelWin;
+  let { document, NetMonitorView } = monitor.panelWin;
   let { RequestsMenu } = NetMonitorView;
 
   RequestsMenu.lazyUpdate = false;
 
   let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
@@ -25,53 +26,45 @@ add_task(function* () {
   verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(0),
     "GET", CONTENT_TYPE_SJS + "?fmt=json-malformed", {
       status: 200,
       statusText: "OK",
       type: "json",
       fullMimeType: "text/json; charset=utf-8"
     });
 
-  let onEvent = monitor.panelWin.once(EVENTS.RESPONSE_BODY_DISPLAYED);
+  wait = waitForDOM(document, "#response-tabpanel .editor-mount iframe");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll("#details-pane tab")[3]);
-  yield onEvent;
-
-  let tabEl = document.querySelectorAll("#details-pane tab")[3];
-  let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
+  let [editor] = yield wait;
+  yield once(editor, "DOMContentLoaded");
+  yield waitForDOM(editor.contentDocument, ".CodeMirror-code");
 
-  is(tabEl.getAttribute("selected"), "true",
-    "The response tab in the network details pane should be selected.");
-
-  is(tabpanel.querySelector("#response-content-info-header")
-    .hasAttribute("hidden"), false,
-    "The response info header doesn't have the intended visibility.");
-  is(tabpanel.querySelector("#response-content-info-header")
-    .getAttribute("value"),
+  let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
+  is(tabpanel.querySelector(".response-error-header") === null, false,
+    "The response error header doesn't have the intended visibility.");
+  is(tabpanel.querySelector(".response-error-header").textContent,
+    "SyntaxError: JSON.parse: unexpected non-whitespace character after JSON data" +
+      " at line 1 column 40 of the JSON data",
+    "The response error header doesn't have the intended text content.");
+  is(tabpanel.querySelector(".response-error-header").getAttribute("title"),
     "SyntaxError: JSON.parse: unexpected non-whitespace character after JSON data" +
       " at line 1 column 40 of the JSON data",
-    "The response info header doesn't have the intended value attribute.");
-  is(tabpanel.querySelector("#response-content-info-header")
-    .getAttribute("tooltiptext"),
-    "SyntaxError: JSON.parse: unexpected non-whitespace character after JSON data" +
-      " at line 1 column 40 of the JSON data",
-    "The response info header doesn't have the intended tooltiptext attribute.");
+    "The response error header doesn't have the intended tooltiptext attribute.");
+  let jsonView = tabpanel.querySelector(".tree-section .treeLabel") || {};
+  is(jsonView.textContent === L10N.getStr("jsonScopeName"), false,
+    "The response json view doesn't have the intended visibility.");
+  is(tabpanel.querySelector(".editor-mount iframe") === null, false,
+    "The response editor doesn't have the intended visibility.");
+  is(tabpanel.querySelector(".response-image-box") === null, true,
+    "The response image box doesn't have the intended visibility.");
 
-  is(tabpanel.querySelector("#response-content-json-box")
-    .hasAttribute("hidden"), true,
-    "The response content json box doesn't have the intended visibility.");
-  is(tabpanel.querySelector("#response-content-textarea-box")
-    .hasAttribute("hidden"), false,
-    "The response content textarea box doesn't have the intended visibility.");
-  is(tabpanel.querySelector("#response-content-image-box")
-    .hasAttribute("hidden"), true,
-    "The response content image box doesn't have the intended visibility.");
+  // Strip CodeMirror line number through slice(1)
+  let text = editor.contentDocument
+    .querySelector(".CodeMirror-line").textContent;
 
-  let editor = yield NetMonitorView.editor("#response-content-textarea");
-  is(editor.getText(), "{ \"greeting\": \"Hello malformed JSON!\" },",
+  is(text, "{ \"greeting\": \"Hello malformed JSON!\" },",
     "The text shown in the source editor is incorrect.");
-  is(editor.getMode(), Editor.modes.js,
-    "The mode active in the source editor is incorrect.");
 
   yield teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/browser_net_json_custom_mime.js
+++ b/devtools/client/netmonitor/test/browser_net_json_custom_mime.js
@@ -8,17 +8,17 @@
  */
 
 add_task(function* () {
   let { L10N } = require("devtools/client/netmonitor/l10n");
 
   let { tab, monitor } = yield initNetMonitor(JSON_CUSTOM_MIME_URL);
   info("Starting test... ");
 
-  let { document, EVENTS, NetMonitorView } = monitor.panelWin;
+  let { document, NetMonitorView } = monitor.panelWin;
   let { RequestsMenu } = NetMonitorView;
 
   RequestsMenu.lazyUpdate = false;
 
   let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
@@ -29,62 +29,50 @@ add_task(function* () {
       status: 200,
       statusText: "OK",
       type: "x-bigcorp-json",
       fullMimeType: "text/x-bigcorp-json; charset=utf-8",
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 41),
       time: true
     });
 
-  let onEvent = monitor.panelWin.once(EVENTS.RESPONSE_BODY_DISPLAYED);
+  wait = waitForDOM(document, "#response-tabpanel");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll("#details-pane tab")[3]);
-  yield onEvent;
+  yield wait;
 
   testResponseTab();
 
   yield teardown(monitor);
 
   function testResponseTab() {
-    let tabEl = document.querySelectorAll("#details-pane tab")[3];
     let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
 
-    is(tabEl.getAttribute("selected"), "true",
-      "The response tab in the network details pane should be selected.");
+    is(tabpanel.querySelector(".response-error-header") === null, true,
+      "The response error header doesn't have the intended visibility.");
+    let jsonView = tabpanel.querySelector(".tree-section .treeLabel") || {};
+    is(jsonView.textContent === L10N.getStr("jsonScopeName"), true,
+      "The response json view has the intended visibility.");
+    is(tabpanel.querySelector(".editor-mount iframe") === null, true,
+      "The response editor doesn't have the intended visibility.");
+    is(tabpanel.querySelector(".response-image-box") === null, true,
+      "The response image box doesn't have the intended visibility.");
 
-    is(tabpanel.querySelector("#response-content-info-header")
-      .hasAttribute("hidden"), true,
-      "The response info header doesn't have the intended visibility.");
-    is(tabpanel.querySelector("#response-content-json-box")
-      .hasAttribute("hidden"), false,
-      "The response content json box doesn't have the intended visibility.");
-    is(tabpanel.querySelector("#response-content-textarea-box")
-      .hasAttribute("hidden"), true,
-      "The response content textarea box doesn't have the intended visibility.");
-    is(tabpanel.querySelector("#response-content-image-box")
-      .hasAttribute("hidden"), true,
-      "The response content image box doesn't have the intended visibility.");
-
-    is(tabpanel.querySelectorAll(".variables-view-scope").length, 1,
-      "There should be 1 json scope displayed in this tabpanel.");
-    is(tabpanel.querySelectorAll(".variables-view-property").length, 2,
-      "There should be 2 json properties displayed in this tabpanel.");
-    is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
+    is(tabpanel.querySelectorAll(".tree-section").length, 1,
+      "There should be 1 tree sections displayed in this tabpanel.");
+    is(tabpanel.querySelectorAll(".treeRow:not(.tree-section)").length, 1,
+      "There should be 1 json properties displayed in this tabpanel.");
+    is(tabpanel.querySelectorAll(".empty-notice").length, 0,
       "The empty notice should not be displayed in this tabpanel.");
 
-    let jsonScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
-    is(jsonScope.querySelectorAll(".variables-view-property .name")[0]
-      .getAttribute("value"),
-      "greeting", "The first json property name was incorrect.");
-    is(jsonScope.querySelectorAll(".variables-view-property .value")[0]
-      .getAttribute("value"),
-      "\"Hello oddly-named JSON!\"", "The first json property value was incorrect.");
+    let labels = tabpanel
+      .querySelectorAll("tr:not(.tree-section) .treeLabelCell .treeLabel");
+    let values = tabpanel
+      .querySelectorAll("tr:not(.tree-section) .treeValueCell .objectBox");
 
-    is(jsonScope.querySelectorAll(".variables-view-property .name")[1]
-      .getAttribute("value"),
-      "__proto__", "The second json property name was incorrect.");
-    is(jsonScope.querySelectorAll(".variables-view-property .value")[1]
-      .getAttribute("value"),
-      "Object", "The second json property value was incorrect.");
+    is(labels[0].textContent, "greeting",
+      "The first json property name was incorrect.");
+    is(values[0].textContent, "\"Hello oddly-named JSON!\"",
+      "The first json property value was incorrect.");
   }
 });
--- a/devtools/client/netmonitor/test/browser_net_json_text_mime.js
+++ b/devtools/client/netmonitor/test/browser_net_json_text_mime.js
@@ -8,17 +8,17 @@
  */
 
 add_task(function* () {
   let { L10N } = require("devtools/client/netmonitor/l10n");
 
   let { tab, monitor } = yield initNetMonitor(JSON_TEXT_MIME_URL);
   info("Starting test... ");
 
-  let { document, EVENTS, NetMonitorView } = monitor.panelWin;
+  let { document, NetMonitorView } = monitor.panelWin;
   let { RequestsMenu } = NetMonitorView;
 
   RequestsMenu.lazyUpdate = false;
 
   let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
@@ -29,62 +29,50 @@ add_task(function* () {
       status: 200,
       statusText: "OK",
       type: "plain",
       fullMimeType: "text/plain; charset=utf-8",
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 41),
       time: true
     });
 
-  let onEvent = monitor.panelWin.once(EVENTS.RESPONSE_BODY_DISPLAYED);
+  wait = waitForDOM(document, "#response-tabpanel");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll("#details-pane tab")[3]);
-  yield onEvent;
+  yield wait;
 
   testResponseTab();
 
   yield teardown(monitor);
 
   function testResponseTab() {
-    let tabEl = document.querySelectorAll("#details-pane tab")[3];
     let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
 
-    is(tabEl.getAttribute("selected"), "true",
-      "The response tab in the network details pane should be selected.");
+    is(tabpanel.querySelector(".response-error-header") === null, true,
+      "The response error header doesn't have the intended visibility.");
+    let jsonView = tabpanel.querySelector(".tree-section .treeLabel") || {};
+    is(jsonView.textContent === L10N.getStr("jsonScopeName"), true,
+      "The response json view has the intended visibility.");
+    is(tabpanel.querySelector(".editor-mount iframe") === null, true,
+      "The response editor doesn't have the intended visibility.");
+    is(tabpanel.querySelector(".response-image-box") === null, true,
+      "The response image box doesn't have the intended visibility.");
 
-    is(tabpanel.querySelector("#response-content-info-header")
-      .hasAttribute("hidden"), true,
-      "The response info header doesn't have the intended visibility.");
-    is(tabpanel.querySelector("#response-content-json-box")
-      .hasAttribute("hidden"), false,
-      "The response content json box doesn't have the intended visibility.");
-    is(tabpanel.querySelector("#response-content-textarea-box")
-      .hasAttribute("hidden"), true,
-      "The response content textarea box doesn't have the intended visibility.");
-    is(tabpanel.querySelector("#response-content-image-box")
-      .hasAttribute("hidden"), true,
-      "The response content image box doesn't have the intended visibility.");
-
-    is(tabpanel.querySelectorAll(".variables-view-scope").length, 1,
-      "There should be 1 json scope displayed in this tabpanel.");
-    is(tabpanel.querySelectorAll(".variables-view-property").length, 2,
-      "There should be 2 json properties displayed in this tabpanel.");
-    is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
+    is(tabpanel.querySelectorAll(".tree-section").length, 1,
+      "There should be 1 tree sections displayed in this tabpanel.");
+    is(tabpanel.querySelectorAll(".treeRow:not(.tree-section)").length, 1,
+      "There should be 1 json properties displayed in this tabpanel.");
+    is(tabpanel.querySelectorAll(".empty-notice").length, 0,
       "The empty notice should not be displayed in this tabpanel.");
 
-    let jsonScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
-    is(jsonScope.querySelectorAll(".variables-view-property .name")[0]
-      .getAttribute("value"),
-      "greeting", "The first json property name was incorrect.");
-    is(jsonScope.querySelectorAll(".variables-view-property .value")[0]
-      .getAttribute("value"),
-      "\"Hello third-party JSON!\"", "The first json property value was incorrect.");
+    let labels = tabpanel
+      .querySelectorAll("tr:not(.tree-section) .treeLabelCell .treeLabel");
+    let values = tabpanel
+      .querySelectorAll("tr:not(.tree-section) .treeValueCell .objectBox");
 
-    is(jsonScope.querySelectorAll(".variables-view-property .name")[1]
-      .getAttribute("value"),
-      "__proto__", "The second json property name was incorrect.");
-    is(jsonScope.querySelectorAll(".variables-view-property .value")[1]
-      .getAttribute("value"),
-      "Object", "The second json property value was incorrect.");
+    is(labels[0].textContent, "greeting",
+      "The first json property name was incorrect.");
+    is(values[0].textContent, "\"Hello third-party JSON!\"",
+      "The first json property value was incorrect.");
   }
 });
--- a/devtools/client/netmonitor/test/browser_net_jsonp.js
+++ b/devtools/client/netmonitor/test/browser_net_jsonp.js
@@ -8,21 +8,20 @@
  */
 
 add_task(function* () {
   let { L10N } = require("devtools/client/netmonitor/l10n");
 
   let { tab, monitor } = yield initNetMonitor(JSONP_URL);
   info("Starting test... ");
 
-  let { document, EVENTS, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu, NetworkDetails } = NetMonitorView;
+  let { document, NetMonitorView } = monitor.panelWin;
+  let { RequestsMenu } = NetMonitorView;
 
   RequestsMenu.lazyUpdate = false;
-  NetworkDetails._json.lazyEmpty = false;
 
   let wait = waitForNetworkEvents(monitor, 2);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(0),
@@ -39,73 +38,56 @@ add_task(function* () {
       status: 200,
       statusText: "OK",
       type: "json",
       fullMimeType: "text/json; charset=utf-8",
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 54),
       time: true
     });
 
-  let onEvent = monitor.panelWin.once(EVENTS.RESPONSE_BODY_DISPLAYED);
+  wait = waitForDOM(document, "#response-tabpanel");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll("#details-pane tab")[3]);
-  yield onEvent;
+  yield wait;
 
   testResponseTab("$_0123Fun", "\"Hello JSONP!\"");
 
-  onEvent = monitor.panelWin.once(EVENTS.RESPONSE_BODY_DISPLAYED);
+  wait = waitForDOM(document, "#response-tabpanel .tree-section");
   RequestsMenu.selectedIndex = 1;
-  yield onEvent;
+  yield wait;
 
   testResponseTab("$_4567Sad", "\"Hello weird JSONP!\"");
 
   yield teardown(monitor);
 
   function testResponseTab(func, greeting) {
-    let tabEl = document.querySelectorAll("#details-pane tab")[3];
     let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
 
-    is(tabEl.getAttribute("selected"), "true",
-      "The response tab in the network details pane should be selected.");
+    is(tabpanel.querySelector(".response-error-header") === null, true,
+      "The response error header doesn't have the intended visibility.");
+    is(tabpanel.querySelector(".tree-section .treeLabel").textContent,
+      L10N.getFormatStr("jsonpScopeName", func),
+      "The response json view has the intened visibility and correct title.");
+    is(tabpanel.querySelector(".editor-mount iframe") === null, true,
+      "The response editor doesn't have the intended visibility.");
+    is(tabpanel.querySelector(".responseImageBox") === null, true,
+      "The response image box doesn't have the intended visibility.");
 
-    is(tabpanel.querySelector("#response-content-info-header")
-      .hasAttribute("hidden"), true,
-      "The response info header doesn't have the intended visibility.");
-    is(tabpanel.querySelector("#response-content-json-box")
-      .hasAttribute("hidden"), false,
-      "The response content json box doesn't have the intended visibility.");
-    is(tabpanel.querySelector("#response-content-textarea-box")
-      .hasAttribute("hidden"), true,
-      "The response content textarea box doesn't have the intended visibility.");
-    is(tabpanel.querySelector("#response-content-image-box")
-      .hasAttribute("hidden"), true,
-      "The response content image box doesn't have the intended visibility.");
-
-    is(tabpanel.querySelectorAll(".variables-view-scope").length, 1,
-      "There should be 1 json scope displayed in this tabpanel.");
-    is(tabpanel.querySelectorAll(".variables-view-property").length, 2,
-      "There should be 2 json properties displayed in this tabpanel.");
-    is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
+    is(tabpanel.querySelectorAll(".tree-section").length, 1,
+      "There should be 1 tree sections displayed in this tabpanel.");
+    is(tabpanel.querySelectorAll(".treeRow:not(.tree-section)").length, 1,
+      "There should be 1 json properties displayed in this tabpanel.");
+    is(tabpanel.querySelectorAll(".empty-notice").length, 0,
       "The empty notice should not be displayed in this tabpanel.");
 
-    let jsonScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
-
-    is(jsonScope.querySelector(".name").getAttribute("value"),
-      L10N.getFormatStr("jsonpScopeName", func),
-      "The json scope doesn't have the correct title.");
+    let labels = tabpanel
+      .querySelectorAll("tr:not(.tree-section) .treeLabelCell .treeLabel");
+    let values = tabpanel
+      .querySelectorAll("tr:not(.tree-section) .treeValueCell .objectBox");
 
-    is(jsonScope.querySelectorAll(".variables-view-property .name")[0]
-      .getAttribute("value"),
-      "greeting", "The first json property name was incorrect.");
-    is(jsonScope.querySelectorAll(".variables-view-property .value")[0]
-      .getAttribute("value"),
-      greeting, "The first json property value was incorrect.");
-
-    is(jsonScope.querySelectorAll(".variables-view-property .name")[1]
-      .getAttribute("value"),
-      "__proto__", "The second json property name was incorrect.");
-    is(jsonScope.querySelectorAll(".variables-view-property .value")[1]
-      .getAttribute("value"),
-      "Object", "The second json property value was incorrect.");
+    is(labels[0].textContent, "greeting",
+      "The first json property name was incorrect.");
+    is(values[0].textContent, greeting,
+      "The first json property value was incorrect.");
   }
 });
--- a/devtools/client/netmonitor/test/browser_net_large-response.js
+++ b/devtools/client/netmonitor/test/browser_net_large-response.js
@@ -12,44 +12,45 @@ const HTML_LONG_URL = CONTENT_TYPE_SJS +
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
   info("Starting test... ");
 
   // This test could potentially be slow because over 100 KB of stuff
   // is going to be requested and displayed in the source editor.
   requestLongerTimeout(2);
 
-  let { document, EVENTS, Editor, NetMonitorView } = monitor.panelWin;
+  let { document, NetMonitorView } = monitor.panelWin;
   let { RequestsMenu } = NetMonitorView;
 
   RequestsMenu.lazyUpdate = false;
 
   let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, HTML_LONG_URL, function* (url) {
     content.wrappedJSObject.performRequests(1, url);
   });
   yield wait;
 
   verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(0),
     "GET", CONTENT_TYPE_SJS + "?fmt=html-long", {
       status: 200,
       statusText: "OK"
     });
 
-  let onEvent = monitor.panelWin.once(EVENTS.RESPONSE_BODY_DISPLAYED);
+  let waitDOM = waitForDOM(document, "#response-tabpanel .editor-mount iframe");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll("#details-pane tab")[3]);
-  yield onEvent;
+  let [editor] = yield waitDOM;
+  yield once(editor, "DOMContentLoaded");
+  yield waitForDOM(editor.contentDocument, ".CodeMirror-code");
 
-  let editor = yield NetMonitorView.editor("#response-content-textarea");
-  ok(editor.getText().match(/^<p>/),
-    "The text shown in the source editor is incorrect.");
-  is(editor.getMode(), Editor.modes.text,
-    "The mode active in the source editor is incorrect.");
+  let text = editor.contentDocument
+        .querySelector(".CodeMirror-line").textContent;
+
+  ok(text.match(/^<p>/), "The text shown in the source editor is incorrect.");
 
   yield teardown(monitor);
 
   // This test uses a lot of memory, so force a GC to help fragmentation.
   info("Forcing GC after netmonitor test.");
   Cu.forceGC();
 });
--- a/devtools/client/netmonitor/test/browser_net_security-details.js
+++ b/devtools/client/netmonitor/test/browser_net_security-details.js
@@ -4,63 +4,58 @@
 "use strict";
 
 /**
  * Test that Security details tab contains the expected data.
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
-  let { $, $all, EVENTS, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu, NetworkDetails } = NetMonitorView;
+  let { document, NetMonitorView } = monitor.panelWin;
+  let { RequestsMenu } = NetMonitorView;
+
   RequestsMenu.lazyUpdate = false;
 
   info("Performing a secure request.");
   const REQUESTS_URL = "https://example.com" + CORS_SJS_PATH;
   let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, REQUESTS_URL, function* (url) {
     content.wrappedJSObject.performRequests(1, url);
   });
   yield wait;
 
-  info("Selecting the request.");
-  RequestsMenu.selectedIndex = 0;
-
-  info("Waiting for details pane to be updated.");
-  yield monitor.panelWin.once(EVENTS.TAB_UPDATED);
-
-  info("Selecting security tab.");
-  NetworkDetails.widget.selectedIndex = 5;
+  wait = waitForDOM(document, "#security-tabpanel");
+  EventUtils.sendMouseEvent({ type: "mousedown" },
+    document.getElementById("details-pane-toggle"));
+  EventUtils.sendMouseEvent({ type: "mousedown" },
+    document.querySelectorAll("#details-pane tab")[5]);
+  yield wait;
 
-  info("Waiting for security tab to be updated.");
-  yield monitor.panelWin.once(EVENTS.TAB_UPDATED);
-
-  let errorbox = $("#security-error");
-  let infobox = $("#security-information");
+  is(document.querySelector("#security-error"), null, "Error box is hidden.");
+  ok(document.querySelector("#security-information"), "Information box visible.");
 
-  is(errorbox, null, "Error box is hidden.");
-  ok(infobox, "Information box visible.");
+  let tabpanel = document.querySelector("#security-tabpanel");
+  let textboxes = tabpanel.querySelectorAll(".textbox-input");
 
-  let textboxes = $all(".textbox-input");
   // Connection
-
   // The protocol will be TLS but the exact version depends on which protocol
   // the test server example.com supports.
   let protocol = textboxes[0].value;
   ok(protocol.startsWith("TLS"), "The protocol " + protocol + " seems valid.");
 
   // The cipher suite used by the test server example.com might change at any
   // moment but all of them should start with "TLS_".
   // http://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml
   let suite = textboxes[1].value;
   ok(suite.startsWith("TLS_"), "The suite " + suite + " seems valid.");
 
   // Host
-  let hostLabel = $all(".treeLabel.objectLabel")[1];
-  is(hostLabel.textContent, "Host example.com:", "Label has the expected value.");
+  is(tabpanel.querySelectorAll(".treeLabel.objectLabel")[1].textContent,
+    "Host example.com:",
+    "Label has the expected value.");
   is(textboxes[2].value, "Disabled", "Label has the expected value.");
   is(textboxes[3].value, "Disabled", "Label has the expected value.");
 
   // Cert
   is(textboxes[4].value, "example.com", "Label has the expected value.");
   is(textboxes[5].value, "<Not Available>", "Label has the expected value.");
   is(textboxes[6].value, "<Not Available>", "Label has the expected value.");
 
--- a/devtools/client/netmonitor/test/browser_net_streaming-response.js
+++ b/devtools/client/netmonitor/test/browser_net_streaming-response.js
@@ -8,22 +8,22 @@
  * displayed as XML or plain text
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
 
   info("Starting test... ");
   let { panelWin } = monitor;
-  let { document, Editor, NetMonitorView } = panelWin;
+  let { document, NetMonitorView } = panelWin;
   let { RequestsMenu } = NetMonitorView;
 
   const REQUESTS = [
-    [ "hls-m3u8", /^#EXTM3U/, Editor.modes.text ],
-    [ "mpeg-dash", /^<\?xml/, Editor.modes.html ]
+    [ "hls-m3u8", /^#EXTM3U/ ],
+    [ "mpeg-dash", /^<\?xml/ ]
   ];
 
   RequestsMenu.lazyUpdate = false;
 
   let wait = waitForNetworkEvents(monitor, REQUESTS.length);
   for (let [fmt] of REQUESTS) {
     let url = CONTENT_TYPE_SJS + "?fmt=" + fmt;
     yield ContentTask.spawn(tab.linkedBrowser, { url }, function* (args) {
@@ -35,35 +35,50 @@ add_task(function* () {
   REQUESTS.forEach(([ fmt ], i) => {
     verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(i),
       "GET", CONTENT_TYPE_SJS + "?fmt=" + fmt, {
         status: 200,
         statusText: "OK"
       });
   });
 
+  wait = waitForDOM(document, "#response-tabpanel");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll("#details-pane tab")[3]);
-
-  yield panelWin.once(panelWin.EVENTS.RESPONSE_BODY_DISPLAYED);
-  let editor = yield NetMonitorView.editor("#response-content-textarea");
-
-  // the hls-m3u8 part
-  testEditorContent(editor, REQUESTS[0]);
-
-  wait = panelWin.once(panelWin.EVENTS.RESPONSE_BODY_DISPLAYED);
-  RequestsMenu.selectedIndex = 1;
   yield wait;
 
+  RequestsMenu.selectedIndex = -1;
+
+  yield selectIndexAndWaitForEditor(0);
+  // the hls-m3u8 part
+  testEditorContent(REQUESTS[0]);
+
+  yield selectIndexAndWaitForEditor(1);
   // the mpeg-dash part
-  testEditorContent(editor, REQUESTS[1]);
+  testEditorContent(REQUESTS[1]);
 
   return teardown(monitor);
 
-  function testEditorContent(e, [ fmt, textRe, mode ]) {
-    ok(e.getText().match(textRe),
+  function* selectIndexAndWaitForEditor(index) {
+    let editor = document.querySelector("#response-tabpanel .editor-mount iframe");
+    if (!editor) {
+      let waitDOM = waitForDOM(document, "#response-tabpanel .editor-mount iframe");
+      RequestsMenu.selectedIndex = index;
+      [editor] = yield waitDOM;
+      yield once(editor, "DOMContentLoaded");
+    } else {
+      RequestsMenu.selectedIndex = index;
+    }
+
+    yield waitForDOM(editor.contentDocument, ".CodeMirror-code");
+  }
+
+  function testEditorContent([ fmt, textRe ]) {
+    let editor = document.querySelector("#response-tabpanel .editor-mount iframe");
+    let text = editor.contentDocument
+          .querySelector(".CodeMirror-line").textContent;
+
+    ok(text.match(textRe),
       "The text shown in the source editor for " + fmt + " is correct.");
-    is(e.getMode(), mode,
-      "The mode active in the source editor for " + fmt + " is correct.");
   }
 });
--- a/devtools/client/responsive.html/test/browser/head.js
+++ b/devtools/client/responsive.html/test/browser/head.js
@@ -183,21 +183,31 @@ function getElRect(selector, win) {
   return el.getBoundingClientRect();
 }
 
 /**
  * Drag an element identified by 'selector' by [x,y] amount. Returns
  * the rect of the dragged element as it was before drag.
  */
 function dragElementBy(selector, x, y, win) {
+  let React = win.require("devtools/client/shared/vendor/react");
+  let { Simulate } = React.addons.TestUtils;
   let rect = getElRect(selector, win);
-  let startPoint = [ rect.left + rect.width / 2, rect.top + rect.height / 2 ];
-  let endPoint = [ startPoint[0] + x, startPoint[1] + y ];
+  let startPoint = {
+    clientX: rect.left + Math.floor(rect.width / 2),
+    clientY: rect.top + Math.floor(rect.height / 2),
+  };
+  let endPoint = [ startPoint.clientX + x, startPoint.clientY + y ];
 
-  EventUtils.synthesizeMouseAtPoint(...startPoint, { type: "mousedown" }, win);
+  let elem = win.document.querySelector(selector);
+
+  // mousedown is a React listener, need to use its testing tools to avoid races
+  Simulate.mouseDown(elem, startPoint);
+
+  // mousemove and mouseup are regular DOM listeners
   EventUtils.synthesizeMouseAtPoint(...endPoint, { type: "mousemove" }, win);
   EventUtils.synthesizeMouseAtPoint(...endPoint, { type: "mouseup" }, win);
 
   return rect;
 }
 
 function* testViewportResize(ui, selector, moveBy,
                              expectedViewportSize, expectedHandleMove) {
--- a/devtools/client/scratchpad/scratchpad.js
+++ b/devtools/client/scratchpad/scratchpad.js
@@ -2461,18 +2461,17 @@ var CloseObserver = {
   uninit: function CO_uninit()
   {
     // Will throw exception if removeObserver is called twice.
     if (this._uninited) {
       return;
     }
 
     this._uninited = true;
-    Services.obs.removeObserver(this, "browser-lastwindow-close-requested",
-                                false);
+    Services.obs.removeObserver(this, "browser-lastwindow-close-requested");
   },
 };
 
 XPCOMUtils.defineLazyGetter(Scratchpad, "strings", function () {
   return Services.strings.createBundle(SCRATCHPAD_L10N);
 });
 
 addEventListener("load", Scratchpad.onLoad.bind(Scratchpad), false);
--- a/devtools/client/scratchpad/test/browser_scratchpad_browser_last_window_closing.js
+++ b/devtools/client/scratchpad/test/browser_scratchpad_browser_last_window_closing.js
@@ -69,11 +69,11 @@ var CloseObserver = {
     aSubject.QueryInterface(Ci.nsISupportsPRBool);
     let message = this.expectedValue ? "close" : "stay open";
     ok(this.expectedValue === aSubject.data, "Expected browser to " + message);
     aSubject.data = true;
   },
 
   uninit: function ()
   {
-    Services.obs.removeObserver(this, "browser-lastwindow-close-requested", false);
+    Services.obs.removeObserver(this, "browser-lastwindow-close-requested");
   },
 };
--- a/devtools/client/shared/components/tree.js
+++ b/devtools/client/shared/components/tree.js
@@ -440,38 +440,38 @@ module.exports = createClass({
       return;
     }
 
     this._preventArrowKeyScrolling(e);
 
     switch (e.key) {
       case "ArrowUp":
         this._focusPrevNode();
-        return;
+        break;
 
       case "ArrowDown":
         this._focusNextNode();
-        return;
+        break;
 
       case "ArrowLeft":
         if (this.props.isExpanded(this.props.focused)
             && this.props.getChildren(this.props.focused).length) {
           this._onCollapse(this.props.focused);
         } else {
           this._focusParentNode();
         }
-        return;
+        break;
 
       case "ArrowRight":
         if (!this.props.isExpanded(this.props.focused)) {
           this._onExpand(this.props.focused);
         } else {
           this._focusNextNode();
         }
-        return;
+        break;
     }
   },
 
   /**
    * Sets the previous node relative to the currently focused item, to focused.
    */
   _focusPrevNode: oncePerAnimationFrame(function () {
     // Start a depth first search and keep going until we reach the currently
--- a/devtools/client/shared/widgets/view-helpers.js
+++ b/devtools/client/shared/widgets/view-helpers.js
@@ -1519,35 +1519,35 @@ const WidgetMethods = exports.WidgetMeth
   _onWidgetKeyPress: function (name, event) {
     // Prevent scrolling when pressing navigation keys.
     ViewHelpers.preventScrolling(event);
 
     switch (event.keyCode) {
       case KeyCodes.DOM_VK_UP:
       case KeyCodes.DOM_VK_LEFT:
         this.focusPrevItem();
-        return;
+        break;
       case KeyCodes.DOM_VK_DOWN:
       case KeyCodes.DOM_VK_RIGHT:
         this.focusNextItem();
-        return;
+        break;
       case KeyCodes.DOM_VK_PAGE_UP:
         this.focusItemAtDelta(-(this.pageSize ||
                                (this.itemCount / PAGE_SIZE_ITEM_COUNT_RATIO)));
-        return;
+        break;
       case KeyCodes.DOM_VK_PAGE_DOWN:
         this.focusItemAtDelta(+(this.pageSize ||
                                (this.itemCount / PAGE_SIZE_ITEM_COUNT_RATIO)));
-        return;
+        break;
       case KeyCodes.DOM_VK_HOME:
         this.focusFirstVisibleItem();
-        return;
+        break;
       case KeyCodes.DOM_VK_END:
         this.focusLastVisibleItem();
-        return;
+        break;
     }
   },
 
   /**
    * The mousePress event listener for this container.
    * @param string name
    * @param MouseEvent event
    */
--- a/devtools/client/styleeditor/StyleEditorUtil.jsm
+++ b/devtools/client/styleeditor/StyleEditorUtil.jsm
@@ -225,10 +225,9 @@ function showFilePicker(path, toSave, pa
   if (toSave && suggestedFilename) {
     fp.defaultString = suggestedFilename;
   }
 
   fp.init(parentWindow, getString(key + ".title"), mode);
   fp.appendFilter(getString(key + ".filter"), "*.css");
   fp.appendFilters(fp.filterAll);
   fp.open(fpCallback);
-  return;
 }
--- a/devtools/client/themes/netmonitor.css
+++ b/devtools/client/themes/netmonitor.css
@@ -712,32 +712,40 @@
 }
 
 .raw-response-textarea {
   height: 50vh;
 }
 
 /* Response tabpanel */
 
-#response-content-info-header {
+.response-error-header {
   margin: 0;
   padding: 3px 8px;
   background-color: var(--theme-highlight-red);
   color: var(--theme-selection-color);
 }
 
-#response-content-image-box {
-  padding-top: 10px;
-  padding-bottom: 10px;
+.response-image-box {
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  /* Minus 24px * 2 for toolbox height + tabpanel height + padding top + padding bottom  */
+  height: calc(100vh - 68px);
+  overflow-y: auto;
+  padding: 10px;
 }
 
-#response-content-image {
+.response-image {
   background: #fff;
   border: 1px dashed GrayText;
   margin-bottom: 10px;
+  max-width: 100%;
+  max-height: 100%;
 }
 
 /* Preview tabpanel */
 
 #preview-tabpanel {
   background: #fff;
 }
 
@@ -1181,42 +1189,48 @@
   white-space: nowrap;
 }
 
 .empty-notice {
   color: var(--theme-body-color-alt);
   padding: 3px 8px;
 }
 
+.response-summary {
+  display: flex;
+}
+
 .editor-container,
 .editor-mount,
 .editor-mount iframe {
   border: none;
   width: 100%;
   height: 100%;
 }
 
 /*
  * FIXME: normal html block element cannot fill outer XUL element
  * This workaround should be removed after netmonitor is migrated to react
  */
 #react-params-tabpanel-hook,
 #react-preview-tabpanel-hook,
+#react-response-tabpanel-hook,
 #react-security-tabpanel-hook,
 #react-timings-tabpanel-hook,
 #network-statistics-charts,
 #primed-cache-chart,
 #empty-cache-chart {
   display: -moz-box;
   -moz-box-flex: 1;
 }
 
 /* For vbox */
 #react-params-tabpanel-hook,
 #react-preview-tabpanel-hook,
+#react-response-tabpanel-hook,
 #react-security-tabpanel-hook,
 #react-timings-tabpanel-hook,
 #primed-cache-chart,
 #empty-cache-chart {
   -moz-box-orient: vertical;
 }
 
 #primed-cache-chart,
--- a/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
+++ b/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
@@ -44,31 +44,25 @@ NewConsoleOutputWrapper.prototype = {
         emitNewMessage: (node, messageId) => {
           this.jsterm.hud.emit("new-messages", new Set([{
             node,
             messageId,
           }]));
         },
         hudProxyClient: this.jsterm.hud.proxy.client,
         onViewSourceInDebugger: frame => {
-          this.toolbox.viewSourceInDebugger.call(
-            this.toolbox,
-            frame.url,
-            frame.line
-          ).then(() =>
+          this.toolbox.viewSourceInDebugger(frame.url, frame.line).then(() =>
             this.jsterm.hud.emit("source-in-debugger-opened")
           );
         },
-        onViewSourceInScratchpad: frame => this.toolbox.viewSourceInScratchpad.call(
-          this.toolbox,
+        onViewSourceInScratchpad: frame => this.toolbox.viewSourceInScratchpad(
           frame.url,
           frame.line
         ),
-        onViewSourceInStyleEditor: frame => this.toolbox.viewSourceInStyleEditor.call(
-          this.toolbox,
+        onViewSourceInStyleEditor: frame => this.toolbox.viewSourceInStyleEditor(
           frame.url,
           frame.line
         ),
         openContextMenu: (e, message) => {
           let { screenX, screenY, target } = e;
 
           let messageEl = target.closest(".message");
           let clipboardText = messageEl ? messageEl.textContent : null;
@@ -87,17 +81,17 @@ NewConsoleOutputWrapper.prototype = {
           return menu;
         },
         openNetworkPanel: (requestId) => {
           return this.toolbox.selectTool("netmonitor").then(panel => {
             return panel.panelWin.NetMonitorController.inspectRequest(requestId);
           });
         },
         sourceMapService: this.toolbox ? this.toolbox._sourceMapService : null,
-        openLink: url => this.jsterm.hud.owner.openLink.call(this.jsterm.hud.owner, url),
+        openLink: url => this.jsterm.hud.owner.openLink(url),
         createElement: nodename => {
           return this.document.createElementNS("http://www.w3.org/1999/xhtml", nodename);
         },
         highlightDomElement: (grip, options = {}) => {
           return this.toolbox && this.toolbox.highlighterUtils
             ? this.toolbox.highlighterUtils.highlightDomValueGrip(grip, options)
             : null;
         },
--- a/devtools/client/webide/content/prefs.js
+++ b/devtools/client/webide/content/prefs.js
@@ -29,17 +29,17 @@ window.addEventListener("load", function
 }, true);
 
 window.addEventListener("unload", function onUnload() {
   window.removeEventListener("unload", onUnload);
   let inputs = document.querySelectorAll("[data-pref]");
   for (let i of inputs) {
     let pref = i.dataset.pref;
     i.removeEventListener("change", SaveForm, false);
-    Services.prefs.removeObserver(pref, FillForm, false);
+    Services.prefs.removeObserver(pref, FillForm);
   }
 }, true);
 
 function CloseUI() {
   window.parent.UI.openProject();
 }
 
 function ShowAddons() {
--- a/devtools/server/actors/highlighters/utils/markup.js
+++ b/devtools/server/actors/highlighters/utils/markup.js
@@ -281,18 +281,26 @@ CanvasFrameAnonymousContentHelper.protot
     if (isXUL(this.highlighterEnv.window)) {
       return;
     }
 
     // For now highlighters.css is injected in content as a ua sheet because
     // <style scoped> doesn't work inside anonymous content (see bug 1086532).
     // If it did, highlighters.css would be injected as an anonymous content
     // node using CanvasFrameAnonymousContentHelper instead.
-    installHelperSheet(this.highlighterEnv.window,
-      "@import url('" + STYLESHEET_URI + "');");
+    if (!installedHelperSheets.has(doc)) {
+      installedHelperSheets.set(doc, true);
+      let source = "@import url('" + STYLESHEET_URI + "');";
+      let url = "data:text/css;charset=utf-8," + encodeURIComponent(source);
+      let winUtils = this.highlighterEnv.window
+                         .QueryInterface(Ci.nsIInterfaceRequestor)
+                         .getInterface(Ci.nsIDOMWindowUtils);
+      winUtils.loadSheetUsingURIString(url, winUtils.AGENT_SHEET);
+    }
+
     let node = this.nodeBuilder();
 
     // It was stated that hidden documents don't accept
     // `insertAnonymousContent` calls yet. That doesn't seems the case anymore,
     // at least on desktop. Therefore, removing the code that was dealing with
     // that scenario, fixes when we're adding anonymous content in a tab that
     // is not the active one (see bug 1260043 and bug 1260044)
     this._content = doc.insertAnonymousContent(node);
--- a/devtools/server/actors/storage.js
+++ b/devtools/server/actors/storage.js
@@ -164,17 +164,17 @@ StorageActors.defaults = function (typeN
       this.onWindowDestroyed = this.onWindowDestroyed.bind(this);
       events.on(this.storageActor, "window-ready", this.onWindowReady);
       events.on(this.storageActor, "window-destroyed", this.onWindowDestroyed);
     },
 
     destroy() {
       if (observationTopics) {
         observationTopics.forEach((observationTopic) => {
-          Services.obs.removeObserver(this, observationTopic, false);
+          Services.obs.removeObserver(this, observationTopic);
         });
       }
       events.off(this.storageActor, "window-ready", this.onWindowReady);
       events.off(this.storageActor, "window-destroyed", this.onWindowDestroyed);
 
       this.hostVsStores.clear();
 
       protocol.Actor.prototype.destroy.call(this);
@@ -889,17 +889,17 @@ var cookieHelpers = {
   },
 
   addCookieObservers() {
     Services.obs.addObserver(cookieHelpers, "cookie-changed", false);
     return null;
   },
 
   removeCookieObservers() {
-    Services.obs.removeObserver(cookieHelpers, "cookie-changed", false);
+    Services.obs.removeObserver(cookieHelpers, "cookie-changed");
     return null;
   },
 
   observe(subject, topic, data) {
     if (!subject) {
       return;
     }
 
@@ -2456,18 +2456,18 @@ let StorageActor = protocol.ActorClassWi
     this.destroyed = false;
     this.boundUpdate = {};
   },
 
   destroy() {
     clearTimeout(this.batchTimer);
     this.batchTimer = null;
     // Remove observers
-    Services.obs.removeObserver(this, "content-document-global-created", false);
-    Services.obs.removeObserver(this, "inner-window-destroyed", false);
+    Services.obs.removeObserver(this, "content-document-global-created");
+    Services.obs.removeObserver(this, "inner-window-destroyed");
     this.destroyed = true;
     if (this.parentActor.browser) {
       this.parentActor.browser.removeEventListener("pageshow", this.onPageChange, true);
       this.parentActor.browser.removeEventListener("pagehide", this.onPageChange, true);
     }
     // Destroy the registered store types
     for (let actor of this.childActorPool.values()) {
       actor.destroy();
--- a/devtools/server/actors/tab.js
+++ b/devtools/server/actors/tab.js
@@ -1493,17 +1493,17 @@ function DebuggerProgressListener(tabAct
 DebuggerProgressListener.prototype = {
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsIWebProgressListener,
     Ci.nsISupportsWeakReference,
     Ci.nsISupports,
   ]),
 
   destroy() {
-    Services.obs.removeObserver(this, "inner-window-destroyed", false);
+    Services.obs.removeObserver(this, "inner-window-destroyed");
     this._knownWindowIDs.clear();
     this._knownWindowIDs = null;
   },
 
   watch(docShell) {
     // Add the docshell to the watched set. We're actually adding the window,
     // because docShell objects are not wrappercached and would be rejected
     // by the WeakSet.
--- a/devtools/server/actors/webextension-inspected-window.js
+++ b/devtools/server/actors/webextension-inspected-window.js
@@ -161,17 +161,17 @@ CustomizedReload.prototype = {
   stop(error) {
     if (this.stopped) {
       return;
     }
 
     this.docShell.removeProgressListener(this);
 
     if (this.injectedScript) {
-      Services.obs.removeObserver(this, "document-element-inserted", false);
+      Services.obs.removeObserver(this, "document-element-inserted");
     }
 
     // Reset the customized user agent.
     if (this.userAgent && this.docShell.customUserAgent == this.userAgent) {
       this.docShell.customUserAgent = null;
     }
 
     if (error) {
--- a/devtools/server/actors/worker.js
+++ b/devtools/server/actors/worker.js
@@ -297,17 +297,17 @@ protocol.ActorClassWithSpec(serviceWorke
       // - In non-e10s: registrations always have at least one worker, if the worker is
       // active, the registration is active.
       active: isE10s ? true : !!activeWorker
     };
   },
 
   destroy() {
     protocol.Actor.prototype.destroy.call(this);
-    Services.obs.removeObserver(this, PushService.subscriptionModifiedTopic, false);
+    Services.obs.removeObserver(this, PushService.subscriptionModifiedTopic);
     this._registration.removeListener(this);
     this._registration = null;
     if (this._pushSubscriptionActor) {
       this._pushSubscriptionActor.destroy();
     }
     this._pushSubscriptionActor = null;
 
     this._installingWorker.destroy();
--- a/devtools/server/tests/unit/test_register_actor.js
+++ b/devtools/server/tests/unit/test_register_actor.js
@@ -87,17 +87,17 @@ function test_lazy_api() {
   }
   function onRequest(aResponse) {
     do_check_eq(aResponse, "world");
 
     // Finally, the actor is loaded on the first request being made to it
     do_check_true(isActorLoaded);
     do_check_true(isActorInstanciated);
 
-    Services.obs.removeObserver(onActorEvent, "actor", false);
+    Services.obs.removeObserver(onActorEvent, "actor");
     client.close().then(() => run_next_test());
   }
 }
 
 function cleanup() {
   DebuggerServer.destroy();
 
   // Check that all actors are unregistered on server destruction
--- a/devtools/shared/apps/app-actor-front.js
+++ b/devtools/shared/apps/app-actor-front.js
@@ -714,18 +714,16 @@ AppActorFront.prototype = {
           // Fake a appClose event if we didn't got one before uninstall
           if (app.running) {
             app.running = false;
             this._notifyListeners("appClose", app);
           }
           this._apps.delete(manifestURL);
           this._notifyListeners("appUninstall", app);
           break;
-        default:
-          return;
       }
     });
   },
 
   _notifyListeners: function (type, app) {
     this._listeners.forEach(f => {
       f(type, app);
     });
--- a/devtools/shared/content-observer.js
+++ b/devtools/shared/content-observer.js
@@ -33,19 +33,19 @@ ContentObserver.prototype = {
       this._onInnerWindowDestroyed, "inner-window-destroyed", false);
   },
 
   /**
    * Stops listening for the required observer messages.
    */
   stopListening: function () {
     Services.obs.removeObserver(
-      this._onContentGlobalCreated, "content-document-global-created", false);
+      this._onContentGlobalCreated, "content-document-global-created");
     Services.obs.removeObserver(
-      this._onInnerWindowDestroyed, "inner-window-destroyed", false);
+      this._onInnerWindowDestroyed, "inner-window-destroyed");
   },
 
   /**
    * Fired immediately after a web content document window has been set up.
    */
   _onContentGlobalCreated: function (subject, topic, data) {
     if (subject == this._contentWindow) {
       events.emit(this, "global-created", subject);
--- a/devtools/shared/event-emitter.js
+++ b/devtools/shared/event-emitter.js
@@ -127,17 +127,17 @@
      *        that this is needed) then use listener
      */
     once(event, listener) {
       let deferred = defer();
 
       let handler = (_, first, ...rest) => {
         this.off(event, handler);
         if (listener) {
-          listener.apply(null, [event, first, ...rest]);
+          listener(event, first, ...rest);
         }
         deferred.resolve(first);
       };
 
       handler._originalListener = listener;
       this.on(event, handler);
 
       return deferred.promise;
--- a/devtools/shared/inspector/css-logic.js
+++ b/devtools/shared/inspector/css-logic.js
@@ -279,17 +279,21 @@ function prettifyCSS(text, ruleCount) {
         result = result + indent + text.substring(startIndex, endIndex);
         if (isCloseBrace) {
           result += prettifyCSS.LINE_SEPARATOR;
         }
       }
     }
 
     if (isCloseBrace) {
-      indent = TAB_CHARS.repeat(--indentLevel);
+      // Even if the stylesheet contains extra closing braces, the indent level should
+      // remain > 0.
+      indentLevel = Math.max(0, indentLevel - 1);
+
+      indent = TAB_CHARS.repeat(indentLevel);
       result = result + indent + "}";
     }
 
     if (!token) {
       break;
     }
 
     if (token.tokenType === "symbol" && token.text === "{") {
--- a/devtools/shared/security/auth.js
+++ b/devtools/shared/security/auth.js
@@ -375,17 +375,17 @@ OOBCert.Client.prototype = {
             // Server previously persisted Client as allowed
             // Step C.5
             // Debugging begins
             transport.hooks = null;
             deferred.resolve(transport);
             break;
           default:
             transport.close(new Error("Invalid auth result: " + authResult));
-            return;
+            break;
         }
       }.bind(this)),
       onClosed(reason) {
         closeDialog();
         // Transport died before auth completed
         transport.hooks = null;
         deferred.reject(reason);
       }
--- a/devtools/shared/task.js
+++ b/devtools/shared/task.js
@@ -153,17 +153,17 @@ var Task = {
    *          returned promise.
    *        - If you specify anything else, you get a promise that is already
    *          resolved with the specified value.
    *
    * @return A promise object where you can register completion callbacks to be
    *         called when the task terminates.
    */
   spawn: function (task) {
-    return createAsyncFunction(task).call(undefined);
+    return createAsyncFunction(task)();
   },
 
   /**
    * Create and return an 'async function' that starts a new task.
    *
    * This is similar to 'spawn' except that it doesn't immediately start
    * the task, it binds the task to the async function's 'this' object and
    * arguments, and it requires the task to be a function.
--- a/devtools/shared/tests/unit/test_prettifyCSS.js
+++ b/devtools/shared/tests/unit/test_prettifyCSS.js
@@ -44,16 +44,29 @@ const TESTS = [
   { name: "leading whitespace",
     input: "\n    div{color: red;}",
     expected: [
       "div {",
       "\tcolor: red;",
       "}"
     ]
   },
+
+  { name: "CSS with extra closing brace",
+    input: "body{margin:0}} div{color:red}",
+    expected: [
+      "body {",
+      "\tmargin:0",
+      "}",
+      "}",
+      "div {",
+      "\tcolor:red",
+      "}",
+    ]
+  },
 ];
 
 function run_test() {
   // Note that prettifyCSS.LINE_SEPARATOR is computed lazily, so we
   // ensure it is set.
   prettifyCSS("");
 
   for (let test of TESTS) {
--- a/devtools/shared/webconsole/network-monitor.js
+++ b/devtools/shared/webconsole/network-monitor.js
@@ -1484,17 +1484,17 @@ NetworkMonitor.prototype = {
   destroy: function () {
     if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT) {
       gActivityDistributor.removeObserver(this);
       Services.obs.removeObserver(this._httpResponseExaminer,
                                   "http-on-examine-response");
       Services.obs.removeObserver(this._httpResponseExaminer,
                                   "http-on-examine-cached-response");
       Services.obs.removeObserver(this._httpModifyExaminer,
-                                  "http-on-modify-request", false);
+                                  "http-on-modify-request");
     }
 
     Services.obs.removeObserver(this._serviceWorkerRequest,
                                 "service-worker-synthesized-response");
 
     this.interceptedChannels.clear();
     this.openRequests = {};
     this.openResponses = {};
--- a/devtools/shared/webconsole/server-logger-monitor.js
+++ b/devtools/shared/webconsole/server-logger-monitor.js
@@ -117,17 +117,17 @@ var ServerLoggerMonitor = {
     let size = this.targets.size;
     trace.log("ServerLoggerMonitor.onDetachChild; size: ", size, target);
 
     // If this is the last child process attached, unregister
     // the global HTTP observer.
     if (!size) {
       trace.log("ServerLoggerMonitor.onDetachChild; Remove HTTP Observer");
       Services.obs.removeObserver(this.onExamineResponse,
-        "http-on-examine-response", false);
+        "http-on-examine-response");
     }
   },
 
   // HTTP Observer
 
   onExamineResponse: makeInfallible(function (subject, topic) {
     let httpChannel = subject.QueryInterface(Ci.nsIHttpChannel);
 
--- a/devtools/shared/webconsole/server-logger.js
+++ b/devtools/shared/webconsole/server-logger.js
@@ -96,17 +96,17 @@ var ServerLoggingListener = Class({
    */
   detach: makeInfallible(function () {
     trace.log("ServerLoggingListener.detach; ", this.owner.actorID);
 
     if (DebuggerServer.isInChildProcess) {
       this.detachParentProcess();
     } else {
       Services.obs.removeObserver(this.onExamineResponse,
-        "http-on-examine-response", false);
+        "http-on-examine-response");
     }
   }),
 
   // Parent Child Relationship
 
   attachParentProcess: function () {
     trace.log("ServerLoggingListener.attachParentProcess;");
 
--- a/dom/animation/KeyframeEffectReadOnly.cpp
+++ b/dom/animation/KeyframeEffectReadOnly.cpp
@@ -343,16 +343,44 @@ KeyframeEffectReadOnly::CompositeValue(
     default:
       MOZ_ASSERT_UNREACHABLE("Unknown compisite operation type");
       break;
   }
   return StyleAnimationValue();
 }
 
 StyleAnimationValue
+KeyframeEffectReadOnly::GetUnderlyingStyle(
+  nsCSSPropertyID aProperty,
+  const RefPtr<AnimValuesStyleRule>& aAnimationRule)
+{
+  StyleAnimationValue result;
+
+  if (aAnimationRule->HasValue(aProperty)) {
+    // If we have already composed style for the property, we use the style
+    // as the underlying style.
+    DebugOnly<bool> success = aAnimationRule->GetValue(aProperty, result);
+    MOZ_ASSERT(success, "AnimValuesStyleRule::GetValue should not fail");
+  } else {
+    // If we are composing with composite operation that is not 'replace'
+    // and we have not composed style for the property yet, we have to get
+    // the base style for the property.
+    RefPtr<nsStyleContext> styleContext = GetTargetStyleContext();
+    result = EffectCompositor::GetBaseStyle(aProperty,
+                                            styleContext,
+                                            *mTarget->mElement,
+                                            mTarget->mPseudoType);
+    MOZ_ASSERT(!result.IsNull(), "The base style should be set");
+    SetNeedsBaseStyle(aProperty);
+  }
+
+  return result;
+}
+
+StyleAnimationValue
 KeyframeEffectReadOnly::CompositeValue(
   nsCSSPropertyID aProperty,
   const RefPtr<AnimValuesStyleRule>& aAnimationRule,
   const StyleAnimationValue& aValueToComposite,
   CompositeOperation aCompositeOperation)
 {
   MOZ_ASSERT(mTarget, "CompositeValue should be called with target element");
 
@@ -369,33 +397,17 @@ KeyframeEffectReadOnly::CompositeValue(
   if (mDocument->IsStyledByServo()) {
     return result;
   }
 
   MOZ_ASSERT(!aValueToComposite.IsNull() ||
              aCompositeOperation == CompositeOperation::Add,
              "InputValue should be null only if additive composite");
 
-  if (aAnimationRule->HasValue(aProperty)) {
-    // If we have already composed style for the property, we use the style
-    // as the underlying style.
-    DebugOnly<bool> success = aAnimationRule->GetValue(aProperty, result);
-    MOZ_ASSERT(success, "AnimValuesStyleRule::GetValue should not fail");
-  } else {
-    // If we are composing with composite operation that is not 'replace'
-    // and we have not composed style for the property yet, we have to get
-    // the base style for the property.
-    RefPtr<nsStyleContext> styleContext = GetTargetStyleContext();
-    result = EffectCompositor::GetBaseStyle(aProperty,
-                                            styleContext,
-                                            *mTarget->mElement,
-                                            mTarget->mPseudoType);
-    MOZ_ASSERT(!result.IsNull(), "The base style should be set");
-    SetNeedsBaseStyle(aProperty);
-  }
+  result = GetUnderlyingStyle(aProperty, aAnimationRule);
 
   return CompositeValue(aProperty,
                         aValueToComposite,
                         result,
                         aCompositeOperation);
 }
 
 void
@@ -528,24 +540,27 @@ KeyframeEffectReadOnly::ComposeStyle(
     // Iteration composition for accumulate
     if (mEffectOptions.mIterationComposite ==
           IterationCompositeOperation::Accumulate &&
         computedTiming.mCurrentIteration > 0) {
       const AnimationPropertySegment& lastSegment =
         prop.mSegments.LastElement();
       // FIXME: Bug 1293492: Add a utility function to calculate both of
       // below StyleAnimationValues.
+      StyleAnimationValue lastValue = lastSegment.mToValue.IsNull()
+        ? GetUnderlyingStyle(prop.mProperty, aStyleRule)
+        : lastSegment.mToValue;
       fromValue =
         StyleAnimationValue::Accumulate(prop.mProperty,
-                                        lastSegment.mToValue,
+                                        lastValue,
                                         Move(fromValue),
                                         computedTiming.mCurrentIteration);
       toValue =
         StyleAnimationValue::Accumulate(prop.mProperty,
-                                        lastSegment.mToValue,
+                                        lastValue,
                                         Move(toValue),
                                         computedTiming.mCurrentIteration);
     }
 
     // Special handling for zero-length segments
     if (segment->mToKey == segment->mFromKey) {
       if (computedTiming.mProgress.Value() < 0) {
         aStyleRule->AddValue(prop.mProperty, Move(fromValue));
--- a/dom/animation/KeyframeEffectReadOnly.h
+++ b/dom/animation/KeyframeEffectReadOnly.h
@@ -15,18 +15,18 @@
 #include "nsTArray.h"
 #include "nsWrapperCache.h"
 #include "mozilla/AnimationPerformanceWarning.h"
 #include "mozilla/AnimationTarget.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/ComputedTimingFunction.h"
 #include "mozilla/EffectCompositor.h"
 #include "mozilla/KeyframeEffectParams.h"
-#include "mozilla/ServoBindingTypes.h" // RawServoDeclarationBlock and
-                                       // associated RefPtrTraits
+// RawServoDeclarationBlock and associated RefPtrTraits
+#include "mozilla/ServoBindingTypes.h"
 #include "mozilla/StyleAnimationValue.h"
 #include "mozilla/dom/AnimationEffectReadOnly.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/Element.h"
 
 struct JSContext;
 class JSObject;
 class nsIContent;
@@ -118,16 +118,19 @@ struct Keyframe
 };
 
 struct AnimationPropertySegment
 {
   float mFromKey, mToKey;
   // NOTE: In the case that no keyframe for 0 or 1 offset is specified
   // the unit of mFromValue or mToValue is eUnit_Null.
   StyleAnimationValue mFromValue, mToValue;
+  // FIXME add a deep == impl for RawServoAnimationValue
+  RefPtr<RawServoAnimationValue> mServoFromValue, mServoToValue;
+
   Maybe<ComputedTimingFunction> mTimingFunction;
   dom::CompositeOperation mFromComposite = dom::CompositeOperation::Replace;
   dom::CompositeOperation mToComposite = dom::CompositeOperation::Replace;
 
   bool operator==(const AnimationPropertySegment& aOther) const
   {
     return mFromKey == aOther.mFromKey &&
            mToKey == aOther.mToKey &&
@@ -418,16 +421,21 @@ protected:
   // |aAnimationRule|, uses the base value for the property recorded on the
   // target element's EffectSet.
   StyleAnimationValue CompositeValue(
     nsCSSPropertyID aProperty,
     const RefPtr<AnimValuesStyleRule>& aAnimationRule,
     const StyleAnimationValue& aValueToComposite,
     CompositeOperation aCompositeOperation);
 
+  // Returns underlying style animation value for |aProperty|.
+  StyleAnimationValue GetUnderlyingStyle(
+    nsCSSPropertyID aProperty,
+    const RefPtr<AnimValuesStyleRule>& aAnimationRule);
+
   // Set a bit in mNeedsBaseStyleSet if |aProperty| can be run on the
   // compositor.
   void SetNeedsBaseStyle(nsCSSPropertyID aProperty);
 
   // Ensure the base styles is available for any properties that can be run on
   // the compositor and which are not includes in |aPropertiesToSkip|.
   void EnsureBaseStylesForCompositor(
     const nsCSSPropertyIDSet& aPropertiesToSkip);
--- a/dom/animation/KeyframeUtils.cpp
+++ b/dom/animation/KeyframeUtils.cpp
@@ -5,28 +5,30 @@
 
 #include "mozilla/KeyframeUtils.h"
 
 #include "mozilla/AnimationUtils.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/Move.h"
 #include "mozilla/RangedArray.h"
 #include "mozilla/ServoBindings.h"
+#include "mozilla/ServoBindingTypes.h"
 #include "mozilla/StyleAnimationValue.h"
 #include "mozilla/TimingParams.h"
 #include "mozilla/dom/BaseKeyframeTypesBinding.h" // For FastBaseKeyframe etc.
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/KeyframeEffectBinding.h"
 #include "mozilla/dom/KeyframeEffectReadOnly.h" // For PropertyValuesPair etc.
 #include "jsapi.h" // For ForOfIterator etc.
 #include "nsClassHashtable.h"
 #include "nsCSSParser.h"
 #include "nsCSSPropertyIDSet.h"
 #include "nsCSSProps.h"
 #include "nsCSSPseudoElements.h" // For CSSPseudoElementType
+#include "nsStyleContext.h"
 #include "nsTArray.h"
 #include <algorithm> // For std::stable_sort
 
 namespace mozilla {
 
 // ------------------------------------------------------------------
 //
 // Internal data types
@@ -265,16 +267,18 @@ struct AdditionalProperty
  *
  * KeyframeValueEntry is used in GetAnimationPropertiesFromKeyframes
  * to gather data for each individual segment.
  */
 struct KeyframeValueEntry
 {
   nsCSSPropertyID mProperty;
   StyleAnimationValue mValue;
+  RefPtr<RawServoAnimationValue> mServoValue;
+
   float mOffset;
   Maybe<ComputedTimingFunction> mTimingFunction;
   dom::CompositeOperation mComposite;
 
   struct PropertyOffsetComparator
   {
     static bool Equals(const KeyframeValueEntry& aLhs,
                        const KeyframeValueEntry& aRhs)
@@ -595,16 +599,26 @@ KeyframeUtils::GetComputedKeyframeValues
   MOZ_ASSERT(aStyleContext);
   MOZ_ASSERT(aElement);
 
   StyleBackendType styleBackend = aElement->OwnerDoc()->GetStyleBackendType();
 
   const size_t len = aKeyframes.Length();
   nsTArray<ComputedKeyframeValues> result(len);
 
+  const ServoComputedValues* currentStyle = nullptr;
+  const ServoComputedValues* parentStyle = nullptr;
+
+  if (styleBackend == StyleBackendType::Servo) {
+    currentStyle = aStyleContext->StyleSource().AsServoComputedValues();
+    if (aStyleContext->GetParent()) {
+      parentStyle = aStyleContext->GetParent()->StyleSource().AsServoComputedValues();
+    }
+  }
+
   for (const Keyframe& frame : aKeyframes) {
     nsCSSPropertyIDSet propertiesOnThisKeyframe;
     ComputedKeyframeValues* computedValues = result.AppendElement();
     for (const PropertyValuePair& pair :
            PropertyPriorityIterator(frame.mPropertyValues)) {
       MOZ_ASSERT(!pair.mServoDeclarationBlock ||
                  styleBackend == StyleBackendType::Servo,
                  "Animation values were parsed using Servo backend but target"
@@ -619,16 +633,21 @@ KeyframeUtils::GetComputedKeyframeValues
       nsTArray<PropertyStyleAnimationValuePair> values;
 
       if (styleBackend == StyleBackendType::Servo) {
         if (!StyleAnimationValue::ComputeValues(pair.mProperty,
               CSSEnabledState::eForAllContent, aStyleContext,
               *pair.mServoDeclarationBlock, values)) {
           continue;
         }
+        Servo_AnimationValues_Populate(&values,
+                                       pair.mServoDeclarationBlock,
+                                       currentStyle,
+                                       parentStyle,
+                                       aStyleContext->PresContext());
       } else {
         // For shorthands, we store the string as a token stream so we need to
         // extract that first.
         if (nsCSSProps::IsShorthand(pair.mProperty)) {
           nsCSSValueTokenStream* tokenStream = pair.mValue.GetTokenStreamValue();
           if (!StyleAnimationValue::ComputeValues(pair.mProperty,
                 CSSEnabledState::eForAllContent, aElement, aStyleContext,
                 tokenStream->mTokenStream, /* aUseSVGMode */ false, values) ||
@@ -648,18 +667,18 @@ KeyframeUtils::GetComputedKeyframeValues
       }
 
       for (auto& value : values) {
         // If we already got a value for this property on the keyframe,
         // skip this one.
         if (propertiesOnThisKeyframe.HasProperty(value.mProperty)) {
           continue;
         }
-        computedValues->AppendElement(value);
         propertiesOnThisKeyframe.AddProperty(value.mProperty);
+        computedValues->AppendElement(Move(value));
       }
     }
   }
 
   MOZ_ASSERT(result.Length() == aKeyframes.Length(), "Array length mismatch");
   return result;
 }
 
@@ -680,16 +699,17 @@ KeyframeUtils::GetAnimationPropertiesFro
     const Keyframe& frame = aKeyframes[i];
     for (auto& value : aComputedValues[i]) {
       MOZ_ASSERT(frame.mComputedOffset != Keyframe::kComputedOffsetNotSet,
                  "Invalid computed offset");
       KeyframeValueEntry* entry = entries.AppendElement();
       entry->mOffset = frame.mComputedOffset;
       entry->mProperty = value.mProperty;
       entry->mValue = value.mValue;
+      entry->mServoValue = value.mServoValue;
       entry->mTimingFunction = frame.mTimingFunction;
       entry->mComposite =
         frame.mComposite ? frame.mComposite.value() : aEffectComposite;
     }
   }
 
   nsTArray<AnimationProperty> result;
   BuildSegmentsFromValueEntries(entries, result);
@@ -1366,23 +1386,25 @@ BuildSegmentsFromValueEntries(nsTArray<K
       lastProperty = aEntries[i].mProperty;
     }
 
     MOZ_ASSERT(animationProperty, "animationProperty should be valid pointer.");
 
     // Now generate the segment.
     AnimationPropertySegment* segment =
       animationProperty->mSegments.AppendElement();
-    segment->mFromKey   = aEntries[i].mOffset;
-    segment->mToKey     = aEntries[j].mOffset;
-    segment->mFromValue = aEntries[i].mValue;
-    segment->mToValue   = aEntries[j].mValue;
+    segment->mFromKey        = aEntries[i].mOffset;
+    segment->mToKey          = aEntries[j].mOffset;
+    segment->mFromValue      = aEntries[i].mValue;
+    segment->mToValue        = aEntries[j].mValue;
+    segment->mServoFromValue = aEntries[i].mServoValue;
+    segment->mServoToValue   = aEntries[j].mServoValue;
     segment->mTimingFunction = aEntries[i].mTimingFunction;
-    segment->mFromComposite = aEntries[i].mComposite;
-    segment->mToComposite = aEntries[j].mComposite;
+    segment->mFromComposite  = aEntries[i].mComposite;
+    segment->mToComposite    = aEntries[j].mComposite;
 
     i = j;
   }
 }
 
 /**
  * Converts a JS object representing a property-indexed keyframe into
  * an array of Keyframe objects.
--- a/dom/animation/test/chrome.ini
+++ b/dom/animation/test/chrome.ini
@@ -1,14 +1,15 @@
 [DEFAULT]
 support-files =
   testcommon.js
   ../../imptests/testharness.js
   ../../imptests/testharnessreport.js
   !/dom/animation/test/chrome/file_animate_xrays.html
+  chrome/file_animation_performance_warning.html
 
 [chrome/test_animate_xrays.html]
 # file_animate_xrays.html needs to go in mochitest.ini since it is served
 # over HTTP
 [chrome/test_animation_observers.html]
 [chrome/test_animation_performance_warning.html]
 [chrome/test_animation_properties.html]
 [chrome/test_generated_content_getAnimations.html]
copy from dom/animation/test/chrome/test_animation_performance_warning.html
copy to dom/animation/test/chrome/file_animation_performance_warning.html
--- a/dom/animation/test/chrome/test_animation_performance_warning.html
+++ b/dom/animation/test/chrome/file_animation_performance_warning.html
@@ -1,15 +1,13 @@
 <!doctype html>
 <head>
 <meta charset=utf-8>
 <title>Bug 1196114 - Test metadata related to which animation properties
        are running on the compositor</title>
-<script type="application/javascript" src="../testharness.js"></script>
-<script type="application/javascript" src="../testharnessreport.js"></script>
 <script type="application/javascript" src="../testcommon.js"></script>
 <style>
 .compositable {
   /* Element needs geometry to be eligible for layerization */
   width: 100px;
   height: 100px;
   background-color: white;
 }
@@ -26,24 +24,26 @@
 <script>
 'use strict';
 
 // This is used for obtaining localized strings.
 var gStringBundle;
 
 W3CTest.runner.requestLongerTimeout(2);
 
-SpecialPowers.pushPrefEnv({ "set": [
-                            ["general.useragent.locale", "en-US"],
-                            // Need to set devPixelsPerPx explicitly to gain
-                            // consistent pixel values in warning messages
-                            // regardless of platform DPIs.
-                            ["layout.css.devPixelsPerPx", 1],
-                          ] },
-                          start);
+/**
+ * Promise wrapper for requestIdleCallback.
+ * NOTE: DON'T move this function into testcommon.js until requestIdleCallback
+ * is enabled by default on all channels.
+ */
+function waitForIdleCallback() {
+  return new Promise(function(resolve, reject) {
+    window.requestIdleCallback(resolve);
+  });
+}
 
 function compare_property_state(a, b) {
   if (a.property > b.property) {
     return -1;
   } else if (a.property < b.property) {
     return 1;
   }
   if (a.runningOnCompositor != b.runningOnCompositor) {
@@ -1251,13 +1251,17 @@ function start() {
       assert_animation_property_state_equals(
         cssAnimation.effect.getProperties(),
         [ { property: 'opacity', runningOnCompositor: true } ]);
       assert_animation_property_state_equals(
         scriptAnimation.effect.getProperties(),
         [ { property: 'opacity', runningOnCompositor: true } ]);
     });
   }, 'overridden animation');
+
+  done();
 }
 
+start();
+
 </script>
 
 </body>
--- a/dom/animation/test/chrome/test_animation_performance_warning.html
+++ b/dom/animation/test/chrome/test_animation_performance_warning.html
@@ -1,1263 +1,21 @@
 <!doctype html>
-<head>
 <meta charset=utf-8>
-<title>Bug 1196114 - Test metadata related to which animation properties
-       are running on the compositor</title>
 <script type="application/javascript" src="../testharness.js"></script>
 <script type="application/javascript" src="../testharnessreport.js"></script>
-<script type="application/javascript" src="../testcommon.js"></script>
-<style>
-.compositable {
-  /* Element needs geometry to be eligible for layerization */
-  width: 100px;
-  height: 100px;
-  background-color: white;
-}
-@keyframes fade {
-  from { opacity: 1 }
-  to   { opacity: 0 }
-}
-</style>
-</head>
-<body>
-<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1196114"
-  target="_blank">Mozilla Bug 1196114</a>
 <div id="log"></div>
 <script>
 'use strict';
-
-// This is used for obtaining localized strings.
-var gStringBundle;
-
-W3CTest.runner.requestLongerTimeout(2);
-
-SpecialPowers.pushPrefEnv({ "set": [
-                            ["general.useragent.locale", "en-US"],
-                            // Need to set devPixelsPerPx explicitly to gain
-                            // consistent pixel values in warning messages
-                            // regardless of platform DPIs.
-                            ["layout.css.devPixelsPerPx", 1],
-                          ] },
-                          start);
-
-function compare_property_state(a, b) {
-  if (a.property > b.property) {
-    return -1;
-  } else if (a.property < b.property) {
-    return 1;
-  }
-  if (a.runningOnCompositor != b.runningOnCompositor) {
-    return a.runningOnCompositor ? 1 : -1;
-  }
-  return a.warning > b.warning ? -1 : 1;
-}
-
-function assert_animation_property_state_equals(actual, expected) {
-  assert_equals(actual.length, expected.length, 'Number of properties');
-
-  var sortedActual = actual.sort(compare_property_state);
-  var sortedExpected = expected.sort(compare_property_state);
-
-  for (var i = 0; i < sortedActual.length; i++) {
-    assert_equals(sortedActual[i].property,
-                  sortedExpected[i].property,
-                  'CSS property name should match');
-    assert_equals(sortedActual[i].runningOnCompositor,
-                  sortedExpected[i].runningOnCompositor,
-                  'runningOnCompositor property should match');
-    if (sortedExpected[i].warning instanceof RegExp) {
-      assert_regexp_match(sortedActual[i].warning,
-                          sortedExpected[i].warning,
-                          'warning message should match');
-    } else if (sortedExpected[i].warning) {
-      assert_equals(sortedActual[i].warning,
-                    gStringBundle.GetStringFromName(sortedExpected[i].warning),
-                    'warning message should match');
-    }
-  }
-}
-
-// Check that the animation is running on compositor and
-// warning property is not set for the CSS property regardless
-// expected values.
-function assert_all_properties_running_on_compositor(actual, expected) {
-  assert_equals(actual.length, expected.length);
-
-  var sortedActual = actual.sort(compare_property_state);
-  var sortedExpected = expected.sort(compare_property_state);
-
-  for (var i = 0; i < sortedActual.length; i++) {
-    assert_equals(sortedActual[i].property,
-                  sortedExpected[i].property,
-                  'CSS property name should match');
-    assert_true(sortedActual[i].runningOnCompositor,
-                'runningOnCompositor property should be true on ' +
-                sortedActual[i].property);
-    assert_not_exists(sortedActual[i], 'warning',
-                      'warning property should not be set');
-  }
-}
-
-function testBasicOperation() {
-  [
-    {
-      desc: 'animations on compositor',
-      frames: {
-        opacity: [0, 1]
-      },
-      expected: [
-        {
-          property: 'opacity',
-          runningOnCompositor: true
-        }
-      ]
-    },
-    {
-      desc: 'animations on main thread',
-      frames: {
-        backgroundColor: ['white', 'red']
-      },
-      expected: [
-        {
-          property: 'background-color',
-          runningOnCompositor: false
-        }
-      ]
-    },
-    {
-      desc: 'animations on both threads',
-      frames: {
-        backgroundColor: ['white', 'red'],
-        transform: ['translate(0px)', 'translate(100px)']
-      },
-      expected: [
-        {
-          property: 'background-color',
-          runningOnCompositor: false
-        },
-        {
-          property: 'transform',
-          runningOnCompositor: true
-        }
-      ]
-    },
-    {
-      desc: 'two animation properties on compositor thread',
-      frames: {
-        opacity: [0, 1],
-        transform: ['translate(0px)', 'translate(100px)']
-      },
-      expected: [
-        {
-          property: 'opacity',
-          runningOnCompositor: true
-        },
-        {
-          property: 'transform',
-          runningOnCompositor: true
-        }
-      ]
-    },
-    {
-      desc: 'opacity on compositor with animation of geometric properties',
-      frames: {
-        width: ['100px', '200px'],
-        opacity: [0, 1]
-      },
-      expected: [
-        {
-          property: 'width',
-          runningOnCompositor: false
-        },
-        {
-          property: 'opacity',
-          runningOnCompositor: true
-        }
-      ]
-    },
-  ].forEach(subtest => {
-    promise_test(function(t) {
-      var animation = addDivAndAnimate(t, { class: 'compositable' },
-                                       subtest.frames, 100 * MS_PER_SEC);
-      return animation.ready.then(function() {
-        assert_animation_property_state_equals(
-          animation.effect.getProperties(),
-          subtest.expected);
-      });
-    }, subtest.desc);
-  });
-}
-
-// Test adding/removing a 'width' property on the same animation object.
-function testKeyframesWithGeometricProperties() {
-  [
-    {
-      desc: 'transform',
-      frames: {
-        transform: ['translate(0px)', 'translate(100px)']
-      },
-      expected: {
-        withoutGeometric: [
-          {
-            property: 'transform',
-            runningOnCompositor: true
-          }
-        ],
-        withGeometric: [
-          {
-            property: 'width',
-            runningOnCompositor: false
-          },
-          {
-            property: 'transform',
-            runningOnCompositor: false,
-            warning: 'CompositorAnimationWarningTransformWithGeometricProperties'
-          }
-        ]
-      }
-    },
-    {
-      desc: 'opacity and transform',
-      frames: {
-        opacity: [0, 1],
-        transform: ['translate(0px)', 'translate(100px)']
-      },
-      expected: {
-        withoutGeometric: [
-          {
-            property: 'opacity',
-            runningOnCompositor: true
-          },
-          {
-            property: 'transform',
-            runningOnCompositor: true
-          }
-        ],
-        withGeometric: [
-          {
-            property: 'width',
-            runningOnCompositor: false
-          },
-          {
-            property: 'opacity',
-            runningOnCompositor: true
-          },
-          {
-            property: 'transform',
-            runningOnCompositor: false,
-            warning: 'CompositorAnimationWarningTransformWithGeometricProperties'
-          }
-        ]
-      }
-    },
-  ].forEach(subtest => {
-    promise_test(function(t) {
-      var animation = addDivAndAnimate(t, { class: 'compositable' },
-                                       subtest.frames, 100 * MS_PER_SEC);
-      return animation.ready.then(function() {
-        // First, a transform animation is running on compositor.
-        assert_animation_property_state_equals(
-          animation.effect.getProperties(),
-          subtest.expected.withoutGeometric);
-      }).then(function() {
-        // Add a 'width' property.
-        var keyframes = animation.effect.getKeyframes();
-
-        keyframes[0].width = '100px';
-        keyframes[1].width = '200px';
-
-        animation.effect.setKeyframes(keyframes);
-        return waitForFrame();
-      }).then(function() {
-        // Now the transform animation is not running on compositor because of
-        // the 'width' property.
-        assert_animation_property_state_equals(
-          animation.effect.getProperties(),
-          subtest.expected.withGeometric);
-      }).then(function() {
-        // Remove the 'width' property.
-        var keyframes = animation.effect.getKeyframes();
-
-        delete keyframes[0].width;
-        delete keyframes[1].width;
-
-        animation.effect.setKeyframes(keyframes);
-        return waitForFrame();
-      }).then(function() {
-        // Finally, the transform animation is running on compositor.
-        assert_animation_property_state_equals(
-          animation.effect.getProperties(),
-          subtest.expected.withoutGeometric);
-      });
-    }, 'An animation has: ' + subtest.desc);
-  });
-}
-
-// Test that the expected set of geometric properties all block transform
-// animations.
-function testSetOfGeometricProperties() {
-  const geometricProperties = [
-    'width', 'height',
-    'top', 'right', 'bottom', 'left',
-    'margin-top', 'margin-right', 'margin-bottom', 'margin-left',
-    'padding-top', 'padding-right', 'padding-bottom', 'padding-left'
-  ];
-
-  geometricProperties.forEach(property => {
-    promise_test(function(t) {
-      const keyframes = {
-        [propertyToIDL(property)]: [ '100px', '200px' ],
-        transform: [ 'translate(0px)', 'translate(100px)' ]
-      };
-      var animation = addDivAndAnimate(t, { class: 'compositable' },
-                                       keyframes, 100 * MS_PER_SEC);
-
-      return animation.ready.then(function() {
-        assert_animation_property_state_equals(
-          animation.effect.getProperties(),
-          [
-            {
-              property,
-              runningOnCompositor: false
-            },
-            {
-              property: 'transform',
-              runningOnCompositor: false,
-              warning: 'CompositorAnimationWarningTransformWithSyncGeometricAnimations'
-            }
-          ]);
-      }, 'Transform animation should be run on the main thread');
-    }, `${property} is treated as a geometric property`);
-  });
-}
-
-// Performance warning tests that set and clear a style property.
-function testStyleChanges() {
-  [
-    {
-      desc: 'preserve-3d transform',
-      frames: {
-        transform: ['translate(0px)', 'translate(100px)']
-      },
-      style: 'transform-style: preserve-3d',
-      expected: [
-        {
-          property: 'transform',
-          runningOnCompositor: false,
-          warning: 'CompositorAnimationWarningTransformPreserve3D'
-        }
-      ]
-    },
-    {
-      desc: 'transform with backface-visibility:hidden',
-      frames: {
-        transform: ['translate(0px)', 'translate(100px)']
-      },
-      style: 'backface-visibility: hidden;',
-      expected: [
-        {
-          property: 'transform',
-          runningOnCompositor: false,
-          warning: 'CompositorAnimationWarningTransformBackfaceVisibilityHidden'
-        }
-      ]
-    },
-    {
-      desc: 'opacity and transform with preserve-3d',
-      frames: {
-        opacity: [0, 1],
-        transform: ['translate(0px)', 'translate(100px)']
-      },
-      style: 'transform-style: preserve-3d',
-      expected: [
-        {
-          property: 'opacity',
-          runningOnCompositor: true
-        },
-        {
-          property: 'transform',
-          runningOnCompositor: false,
-          warning: 'CompositorAnimationWarningTransformPreserve3D'
-        }
-      ]
-    },
-    {
-      desc: 'opacity and transform with backface-visibility:hidden',
-      frames: {
-        opacity: [0, 1],
-        transform: ['translate(0px)', 'translate(100px)']
-      },
-      style: 'backface-visibility: hidden;',
-      expected: [
-        {
-          property: 'opacity',
-          runningOnCompositor: true
-        },
-        {
-          property: 'transform',
-          runningOnCompositor: false,
-          warning: 'CompositorAnimationWarningTransformBackfaceVisibilityHidden'
-        }
-      ]
-    },
-  ].forEach(subtest => {
-    promise_test(function(t) {
-      var animation = addDivAndAnimate(t, { class: 'compositable' },
-                                       subtest.frames, 100 * MS_PER_SEC);
-      return animation.ready.then(function() {
-        assert_all_properties_running_on_compositor(
-          animation.effect.getProperties(),
-          subtest.expected);
-        animation.effect.target.style = subtest.style;
-        return waitForFrame();
-      }).then(function() {
-        assert_animation_property_state_equals(
-          animation.effect.getProperties(),
-          subtest.expected);
-        animation.effect.target.style = '';
-        return waitForFrame();
-      }).then(function() {
-        assert_all_properties_running_on_compositor(
-          animation.effect.getProperties(),
-          subtest.expected);
-      });
-    }, subtest.desc);
-  });
-}
-
-// Performance warning tests that set and clear the id property
-function testIdChanges() {
-  [
-    {
-      desc: 'moz-element referencing a transform',
-      frames: {
-        transform: ['translate(0px)', 'translate(100px)']
-      },
-      id: 'transformed',
-      createelement: 'width:100px; height:100px; background: -moz-element(#transformed)',
-      expected: [
-        {
-          property: 'transform',
-          runningOnCompositor: false,
-          warning: 'CompositorAnimationWarningHasRenderingObserver'
-        }
-      ]
-    },
-  ].forEach(subtest => {
-    promise_test(function(t) {
-      if (subtest.createelement) {
-        addDiv(t, { style: subtest.createelement });
-      }
-
-      var animation = addDivAndAnimate(t, { class: 'compositable' },
-                                       subtest.frames, 100 * MS_PER_SEC);
-      return animation.ready.then(function() {
-        assert_all_properties_running_on_compositor(
-          animation.effect.getProperties(),
-          subtest.expected);
-        animation.effect.target.id = subtest.id;
-        return waitForFrame();
-      }).then(function() {
-        assert_animation_property_state_equals(
-          animation.effect.getProperties(),
-          subtest.expected);
-        animation.effect.target.id = '';
-        return waitForFrame();
-      }).then(function() {
-        assert_all_properties_running_on_compositor(
-          animation.effect.getProperties(),
-          subtest.expected);
-      });
-    }, subtest.desc);
-  });
-}
-
-function testMultipleAnimations() {
-  [
-    {
-      desc: 'opacity and transform with preserve-3d',
-      style: 'transform-style: preserve-3d',
-      animations: [
-        {
-          frames: {
-            transform: ['translate(0px)', 'translate(100px)']
-          },
-          expected: [
-            {
-              property: 'transform',
-              runningOnCompositor: false,
-              warning: 'CompositorAnimationWarningTransformPreserve3D'
-            }
-          ]
-        },
-        {
-          frames: {
-            opacity: [0, 1]
-          },
-          expected: [
-            {
-              property: 'opacity',
-              runningOnCompositor: true,
-            }
-          ]
-        }
-      ],
-    },
-    {
-      desc: 'opacity and transform with backface-visibility:hidden',
-      style: 'backface-visibility: hidden;',
-      animations: [
-        {
-          frames: {
-            transform: ['translate(0px)', 'translate(100px)']
-          },
-          expected: [
-            {
-              property: 'transform',
-              runningOnCompositor: false,
-              warning: 'CompositorAnimationWarningTransformBackfaceVisibilityHidden'
-            }
-          ]
-        },
-        {
-          frames: {
-            opacity: [0, 1]
-          },
-          expected: [
-            {
-              property: 'opacity',
-              runningOnCompositor: true,
-            }
-          ]
-        }
-      ],
-    },
-  ].forEach(subtest => {
-    promise_test(function(t) {
-      var div = addDiv(t, { class: 'compositable' });
-      var animations = subtest.animations.map(function(anim) {
-        var animation = div.animate(anim.frames, 100 * MS_PER_SEC);
-
-        // Bind expected values to animation object.
-        animation.expected = anim.expected;
-        return animation;
-      });
-      return waitForAllAnimations(animations).then(function() {
-        animations.forEach(anim => {
-          assert_all_properties_running_on_compositor(
-            anim.effect.getProperties(),
-            anim.expected);
-        });
-        div.style = subtest.style;
-        return waitForFrame();
-      }).then(function() {
-        animations.forEach(anim => {
-          assert_animation_property_state_equals(
-            anim.effect.getProperties(),
-            anim.expected);
-        });
-        div.style = '';
-        return waitForFrame();
-      }).then(function() {
-        animations.forEach(anim => {
-          assert_all_properties_running_on_compositor(
-            anim.effect.getProperties(),
-            anim.expected);
-        });
-      });
-    }, 'Multiple animations: ' + subtest.desc);
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.requestIdleCallback.enabled", true],
+            ["general.useragent.locale", "en-US"],
+            // Need to set devPixelsPerPx explicitly to gain
+            // consistent pixel values in warning messages
+            // regardless of platform DPIs.
+            ["layout.css.devPixelsPerPx", 1]]},
+  function() {
+    // Open a new window after enabling requestIdleCallback so that we can
+    // use the function in the new window.
+    window.open("file_animation_performance_warning.html");
   });
-}
-
-// Test adding/removing a 'width' keyframe on the same animation object, where
-// multiple animation objects belong to the same element.
-// The 'width' property is added to animations[1].
-function testMultipleAnimationsWithGeometricKeyframes() {
-  [
-    {
-      desc: 'transform and opacity with geometric keyframes',
-      animations: [
-        {
-          frames: {
-            transform: ['translate(0px)', 'translate(100px)']
-          },
-          expected: {
-            withoutGeometric: [
-              {
-                property: 'transform',
-                runningOnCompositor: true
-              }
-            ],
-            withGeometric: [
-              {
-                property: 'transform',
-                runningOnCompositor: false,
-                warning: 'CompositorAnimationWarningTransformWithGeometricProperties'
-              }
-            ]
-          }
-        },
-        {
-          frames: {
-            opacity: [0, 1]
-          },
-          expected: {
-            withoutGeometric: [
-              {
-                property: 'opacity',
-                runningOnCompositor: true,
-              }
-            ],
-            withGeometric: [
-              {
-                property: 'width',
-                runningOnCompositor: false,
-              },
-              {
-                property: 'opacity',
-                runningOnCompositor: true,
-              }
-            ]
-          }
-        }
-      ],
-    },
-    {
-      desc: 'opacity and transform with geometric keyframes',
-      animations: [
-        {
-          frames: {
-            opacity: [0, 1]
-          },
-          expected: {
-            withoutGeometric: [
-              {
-                property: 'opacity',
-                runningOnCompositor: true,
-              }
-            ],
-            withGeometric: [
-              {
-                property: 'opacity',
-                runningOnCompositor: true,
-              }
-            ]
-          }
-        },
-        {
-          frames: {
-            transform: ['translate(0px)', 'translate(100px)']
-          },
-          expected: {
-            withoutGeometric: [
-              {
-                property: 'transform',
-                runningOnCompositor: true
-              }
-            ],
-            withGeometric: [
-              {
-                property: 'width',
-                runningOnCompositor: false,
-              },
-              {
-                property: 'transform',
-                runningOnCompositor: false,
-                warning: 'CompositorAnimationWarningTransformWithGeometricProperties'
-              }
-            ]
-          }
-        }
-      ]
-    },
-  ].forEach(subtest => {
-    promise_test(function(t) {
-      var div = addDiv(t, { class: 'compositable' });
-      var animations = subtest.animations.map(function(anim) {
-        var animation = div.animate(anim.frames, 100 * MS_PER_SEC);
-
-        // Bind expected values to animation object.
-        animation.expected = anim.expected;
-        return animation;
-      });
-      return waitForAllAnimations(animations).then(function() {
-        // First, all animations are running on compositor.
-        animations.forEach(anim => {
-          assert_animation_property_state_equals(
-            anim.effect.getProperties(),
-            anim.expected.withoutGeometric);
-        });
-      }).then(function() {
-        // Add a 'width' property to animations[1].
-        var keyframes = animations[1].effect.getKeyframes();
-
-        keyframes[0].width = '100px';
-        keyframes[1].width = '200px';
-
-        animations[1].effect.setKeyframes(keyframes);
-        return waitForFrame();
-      }).then(function() {
-        // Now the transform animation is not running on compositor because of
-        // the 'width' property.
-        animations.forEach(anim => {
-          assert_animation_property_state_equals(
-            anim.effect.getProperties(),
-            anim.expected.withGeometric);
-        });
-      }).then(function() {
-        // Remove the 'width' property from animations[1].
-        var keyframes = animations[1].effect.getKeyframes();
-
-        delete keyframes[0].width;
-        delete keyframes[1].width;
-
-        animations[1].effect.setKeyframes(keyframes);
-        return waitForFrame();
-      }).then(function() {
-        // Finally, all animations are running on compositor.
-        animations.forEach(anim => {
-          assert_animation_property_state_equals(
-            anim.effect.getProperties(),
-            anim.expected.withoutGeometric);
-        });
-      });
-    }, 'Multiple animations with geometric property: ' + subtest.desc);
-  });
-}
-
-// Tests adding/removing 'width' animation on the same element which has async
-// animations.
-function testMultipleAnimationsWithGeometricAnimations() {
-  [
-    {
-      desc: 'transform',
-      animations: [
-        {
-          frames: {
-            transform: ['translate(0px)', 'translate(100px)']
-          },
-          expected: [
-            {
-              property: 'transform',
-              runningOnCompositor: false,
-              warning: 'CompositorAnimationWarningTransformWithGeometricProperties'
-            }
-          ]
-        },
-      ]
-    },
-    {
-      desc: 'opacity',
-      animations: [
-        {
-          frames: {
-            opacity: [0, 1]
-          },
-          expected: [
-            {
-              property: 'opacity',
-              runningOnCompositor: true
-            }
-          ]
-        },
-      ]
-    },
-    {
-      desc: 'opacity and transform',
-      animations: [
-        {
-          frames: {
-            transform: ['translate(0px)', 'translate(100px)']
-          },
-          expected: [
-            {
-              property: 'transform',
-              runningOnCompositor: false,
-              warning: 'CompositorAnimationWarningTransformWithGeometricProperties'
-            }
-          ]
-        },
-        {
-          frames: {
-            opacity: [0, 1]
-          },
-          expected: [
-            {
-              property: 'opacity',
-              runningOnCompositor: true,
-            }
-          ]
-        }
-      ],
-    },
-  ].forEach(subtest => {
-    promise_test(function(t) {
-      var div = addDiv(t, { class: 'compositable' });
-      var animations = subtest.animations.map(function(anim) {
-        var animation = div.animate(anim.frames, 100 * MS_PER_SEC);
-
-        // Bind expected values to animation object.
-        animation.expected = anim.expected;
-        return animation;
-      });
-
-      var widthAnimation;
-
-      return waitForAllAnimations(animations).then(function() {
-        animations.forEach(anim => {
-          assert_all_properties_running_on_compositor(
-            anim.effect.getProperties(),
-            anim.expected);
-        });
-      }).then(function() {
-        // Append 'width' animation on the same element.
-        widthAnimation = div.animate({ width: ['100px', '200px'] },
-                                     100 * MS_PER_SEC);
-        return waitForFrame();
-      }).then(function() {
-        // Now transform animations are not running on compositor because of
-        // the 'width' animation.
-        animations.forEach(anim => {
-          assert_animation_property_state_equals(
-            anim.effect.getProperties(),
-            anim.expected);
-        });
-        // Remove the 'width' animation.
-        widthAnimation.cancel();
-        return waitForFrame();
-      }).then(function() {
-        // Now all animations are running on compositor.
-        animations.forEach(anim => {
-          assert_all_properties_running_on_compositor(
-            anim.effect.getProperties(),
-            anim.expected);
-        });
-      });
-    }, 'Multiple async animations and geometric animation: ' + subtest.desc);
-  });
-}
-
-function testSmallElements() {
-  [
-    {
-      desc: 'opacity on too small element',
-      frames: {
-        opacity: [0, 1]
-      },
-      style: { style: 'width: 8px; height: 8px; background-color: red;' +
-                      // We need to set transform here to try creating an
-                      // individual frame for this opacity element.
-                      // Without this, this small element is created on the same
-                      // nsIFrame of mochitest iframe, i.e. the document which are
-                      // running this test, as a result the layer corresponding
-                      // to the frame is sent to compositor.
-                      'transform: translateX(100px);' },
-      expected: [
-        {
-          property: 'opacity',
-          runningOnCompositor: false,
-          warning: /Animation cannot be run on the compositor because frame size \(8, 8\) is smaller than \(16, 16\)/
-        }
-      ]
-    },
-    {
-      desc: 'transform on too small element',
-      frames: {
-        transform: ['translate(0px)', 'translate(100px)']
-      },
-      style: { style: 'width: 8px; height: 8px; background-color: red;' },
-      expected: [
-        {
-          property: 'transform',
-          runningOnCompositor: false,
-          warning: /Animation cannot be run on the compositor because frame size \(8, 8\) is smaller than \(16, 16\)/
-        }
-      ]
-    },
-  ].forEach(subtest => {
-    promise_test(function(t) {
-    var div = addDiv(t, subtest.style);
-    var animation = div.animate(subtest.frames, 100 * MS_PER_SEC);
-      return animation.ready.then(function() {
-        assert_animation_property_state_equals(
-          animation.effect.getProperties(),
-          subtest.expected);
-      });
-    }, subtest.desc);
-  });
-}
-
-function testSynchronizedAnimations() {
-  promise_test(function(t) {
-    const elemA = addDiv(t, { class: 'compositable' });
-    const elemB = addDiv(t, { class: 'compositable' });
-
-    const animA = elemA.animate({ transform: [ 'translate(0px)',
-                                               'translate(100px)' ] },
-                                100 * MS_PER_SEC);
-    const animB = elemB.animate({ marginLeft: [ '0px', '100px' ] },
-                                100 * MS_PER_SEC);
-
-    return Promise.all([animA.ready, animB.ready])
-      .then(() => {
-        assert_animation_property_state_equals(
-          animA.effect.getProperties(),
-          [ { property: 'transform',
-              runningOnCompositor: false,
-              warning: 'CompositorAnimationWarningTransformWithSyncGeometricAnimations'
-          } ]);
-      });
-  }, 'Animations created within the same tick are synchronized'
-     + ' (compositor animation created first)');
-
-  promise_test(function(t) {
-    const elemA = addDiv(t, { class: 'compositable' });
-    const elemB = addDiv(t, { class: 'compositable' });
-
-    const animA = elemA.animate({ marginLeft: [ '0px', '100px' ] },
-                                100 * MS_PER_SEC);
-    const animB = elemB.animate({ transform: [ 'translate(0px)',
-                                               'translate(100px)' ] },
-                                100 * MS_PER_SEC);
-
-    return Promise.all([animA.ready, animB.ready])
-      .then(() => {
-        assert_animation_property_state_equals(
-          animB.effect.getProperties(),
-          [ { property: 'transform',
-              runningOnCompositor: false,
-              warning: 'CompositorAnimationWarningTransformWithSyncGeometricAnimations'
-          } ]);
-      });
-  }, 'Animations created within the same tick are synchronized'
-     + ' (compositor animation created second)');
-
-  promise_test(function(t) {
-    const attrs = { class: 'compositable',
-                    style: 'transition: all 100s' };
-    const elemA = addDiv(t, attrs);
-    const elemB = addDiv(t, attrs);
-    elemA.style.transform = 'translate(0px)';
-    elemB.style.marginLeft = '0px';
-    getComputedStyle(elemA).transform;
-    getComputedStyle(elemB).marginLeft;
-
-    // Generally the sequence of steps is as follows:
-    //
-    //   Tick -> requestAnimationFrame -> Style -> Paint -> Events (-> Tick...)
-    //
-    // In this test we want to set up two transitions during the "Events"
-    // stage but only flush style for one such that the second one is actually
-    // generated during the "Style" stage of the *next* tick.
-    //
-    // Web content often generates transitions in this way (that is, it doesn't
-    // pay regard to when style is flushed and nor should it). However, we
-    // still want transitions generated in this way to be synchronized.
-    let timeForFirstFrame;
-    return waitForIdleCallback()
-      .then(() => {
-        timeForFirstFrame = document.timeline.currentTime;
-        elemA.style.transform = 'translate(100px)';
-        // Flush style to trigger first transition
-        getComputedStyle(elemA).transform;
-        elemB.style.marginLeft = '100px';
-        // DON'T flush style here (this includes calling getAnimations!)
-        return waitForFrame();
-      }).then(() => {
-        assert_not_equals(timeForFirstFrame, document.timeline.currentTime,
-                          'Should be on the other side of a tick');
-        // Wait another tick so we can let the transition be started
-        // by regular style resolution.
-        return waitForFrame();
-      }).then(() => {
-        const transitionA = elemA.getAnimations()[0];
-        assert_animation_property_state_equals(
-          transitionA.effect.getProperties(),
-          [ { property: 'transform',
-              runningOnCompositor: false,
-              warning: 'CompositorAnimationWarningTransformWithSyncGeometricAnimations'
-          } ]);
-      });
-  }, 'Transitions created before and after a tick are synchronized');
-
-  promise_test(function(t) {
-    const elemA = addDiv(t, { class: 'compositable' });
-    const elemB = addDiv(t, { class: 'compositable' });
-
-    const animA = elemA.animate({ transform: [ 'translate(0px)',
-                                               'translate(100px)' ],
-                                  opacity: [ 0, 1 ] },
-                                100 * MS_PER_SEC);
-    const animB = elemB.animate({ marginLeft: [ '0px', '100px' ] },
-                                100 * MS_PER_SEC);
-
-    return Promise.all([animA.ready, animB.ready])
-      .then(() => {
-        assert_animation_property_state_equals(
-          animA.effect.getProperties(),
-          [ { property: 'transform',
-              runningOnCompositor: false,
-              warning: 'CompositorAnimationWarningTransformWithSyncGeometricAnimations'
-            },
-            { property: 'opacity',
-              runningOnCompositor: true
-            } ]);
-      });
-  }, 'Opacity animations on the same element continue running on the'
-     + ' compositor when transform animations are synchronized with geometric'
-     + ' animations');
-
-  promise_test(function(t) {
-    const elemA = addDiv(t, { class: 'compositable' });
-    const elemB = addDiv(t, { class: 'compositable' });
-
-    const animA = elemA.animate({ marginLeft: [ '0px', '100px' ] },
-                                100 * MS_PER_SEC);
-    let animB;
-
-    return waitForFrame()
-      .then(() => {
-        animB = elemB.animate({ transform: [ 'translate(0px)',
-                                             'translate(100px)' ] },
-                                100 * MS_PER_SEC);
-        return animB.ready;
-      }).then(() => {
-        assert_animation_property_state_equals(
-          animB.effect.getProperties(),
-          [ { property: 'transform',
-              runningOnCompositor: true } ]);
-      });
-  }, 'Transform animations are NOT synchronized with geometric animations'
-     + ' started in the previous frame');
-
-  promise_test(function(t) {
-    const elemA = addDiv(t, { class: 'compositable' });
-    const elemB = addDiv(t, { class: 'compositable' });
-
-    const animA = elemA.animate({ transform: [ 'translate(0px)',
-                                               'translate(100px)' ] },
-                                100 * MS_PER_SEC);
-    let animB;
-
-    return waitForFrame()
-      .then(() => {
-        animB = elemB.animate({ marginLeft: [ '0px', '100px' ] },
-                              100 * MS_PER_SEC);
-        return animB.ready;
-      }).then(() => {
-        assert_animation_property_state_equals(
-          animA.effect.getProperties(),
-          [ { property: 'transform',
-              runningOnCompositor: true } ]);
-      });
-  }, 'Transform animations are NOT synchronized with geometric animations'
-     + ' started in the next frame');
-
-  promise_test(function(t) {
-    const elemA = addDiv(t, { class: 'compositable' });
-    const elemB = addDiv(t, { class: 'compositable' });
-
-    const animA = elemA.animate({ transform: [ 'translate(0px)',
-                                               'translate(100px)' ] },
-                                100 * MS_PER_SEC);
-    const animB = elemB.animate({ marginLeft: [ '0px', '100px' ] },
-                                100 * MS_PER_SEC);
-    animB.pause();
-
-    return Promise.all([animA.ready, animB.ready])
-      .then(() => {
-        assert_animation_property_state_equals(
-          animA.effect.getProperties(),
-          [ { property: 'transform', runningOnCompositor: true } ]);
-      });
-  }, 'Paused animations are not synchronized');
-
-  promise_test(function(t) {
-    const elemA = addDiv(t, { class: 'compositable' });
-    const elemB = addDiv(t, { class: 'compositable' });
-
-    const animA = elemA.animate({ transform: [ 'translate(0px)',
-                                               'translate(100px)' ] },
-                                100 * MS_PER_SEC);
-    const animB = elemB.animate({ marginLeft: [ '0px', '100px' ] },
-                                100 * MS_PER_SEC);
-
-    // Seek one of the animations so that their start times will differ
-    animA.currentTime = 5000;
-
-    return Promise.all([animA.ready, animB.ready])
-      .then(() => {
-        assert_not_equals(animA.startTime, animB.startTime,
-                          'Animations should have different start times');
-        assert_animation_property_state_equals(
-          animA.effect.getProperties(),
-          [ { property: 'transform',
-              runningOnCompositor: false,
-              warning: 'CompositorAnimationWarningTransformWithSyncGeometricAnimations'
-          } ]);
-      });
-  }, 'Animations are synchronized based on when they are started'
-     + ' and NOT their start time');
-
-  promise_test(function(t) {
-    const elemA = addDiv(t, { class: 'compositable' });
-    const elemB = addDiv(t, { class: 'compositable' });
-
-    const animA = elemA.animate({ transform: [ 'translate(0px)',
-                                               'translate(100px)' ] },
-                                100 * MS_PER_SEC);
-    const animB = elemB.animate({ marginLeft: [ '0px', '100px' ] },
-                                100 * MS_PER_SEC);
-
-    return Promise.all([animA.ready, animB.ready])
-      .then(() => {
-        assert_animation_property_state_equals(
-          animA.effect.getProperties(),
-          [ { property: 'transform',
-              runningOnCompositor: false } ]);
-        // Restart animation
-        animA.pause();
-        animA.play();
-        return animA.ready;
-      }).then(() => {
-        assert_animation_property_state_equals(
-          animA.effect.getProperties(),
-          [ { property: 'transform',
-              runningOnCompositor: true } ]);
-      });
-  }, 'An initially synchronized animation may be unsynchronized if restarted');
-
-  promise_test(function(t) {
-    const elemA = addDiv(t, { class: 'compositable' });
-    const elemB = addDiv(t, { class: 'compositable' });
-
-    const animA = elemA.animate({ transform: [ 'translate(0px)',
-                                               'translate(100px)' ] },
-                                100 * MS_PER_SEC);
-    const animB = elemB.animate({ marginLeft: [ '0px', '100px' ] },
-                                100 * MS_PER_SEC);
-
-    // Clear target effect
-    animB.effect.target = null;
-
-    return Promise.all([animA.ready, animB.ready])
-      .then(() => {
-        assert_animation_property_state_equals(
-          animA.effect.getProperties(),
-          [ { property: 'transform',
-              runningOnCompositor: true } ]);
-      });
-  }, 'A geometric animation with no target element is not synchronized');
-}
-
-function start() {
-  var bundleService = SpecialPowers.Cc['@mozilla.org/intl/stringbundle;1']
-    .getService(SpecialPowers.Ci.nsIStringBundleService);
-  gStringBundle = bundleService
-    .createBundle("chrome://global/locale/layout_errors.properties");
-
-  testBasicOperation();
-  testKeyframesWithGeometricProperties();
-  testSetOfGeometricProperties();
-  testStyleChanges();
-  testIdChanges();
-  testMultipleAnimations();
-  testMultipleAnimationsWithGeometricKeyframes();
-  testMultipleAnimationsWithGeometricAnimations();
-  testSmallElements();
-  testSynchronizedAnimations();
-
-  promise_test(function(t) {
-    var animation = addDivAndAnimate(t,
-                                     { class: 'compositable' },
-                                     { transform: [ 'translate(0px)',
-                                                    'translate(100px)'] },
-                                     100 * MS_PER_SEC);
-    return animation.ready.then(function() {
-      assert_animation_property_state_equals(
-        animation.effect.getProperties(),
-        [ { property: 'transform', runningOnCompositor: true } ]);
-      animation.effect.target.style = 'width: 10000px; height: 10000px';
-      return waitForFrame();
-    }).then(function() {
-      // viewport depends on test environment.
-      var expectedWarning = new RegExp(
-        "Animation cannot be run on the compositor because the frame size " +
-        "\\(10000, 10000\\) is too large relative to the viewport " + 
-        "\\(larger than \\(\\d+, \\d+\\)\\) or larger than the " +
-        "maximum allowed value \\(\\d+, \\d+\\)");
-      assert_animation_property_state_equals(
-        animation.effect.getProperties(),
-        [ {
-          property: 'transform',
-          runningOnCompositor: false,
-          warning: expectedWarning
-        } ]);
-      animation.effect.target.style = 'width: 100px; height: 100px';
-      return waitForFrame();
-    }).then(function() {
-      // FIXME: Bug 1253164: the animation should get back on compositor.
-      assert_animation_property_state_equals(
-        animation.effect.getProperties(),
-        [ { property: 'transform', runningOnCompositor: false } ]);
-    });
-  }, 'transform on too big element');
-
-  promise_test(function(t) {
-    var svg  = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
-    svg.setAttribute('width', '100');
-    svg.setAttribute('height', '100');
-    var rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
-    rect.setAttribute('width', '100');
-    rect.setAttribute('height', '100');
-    rect.setAttribute('fill', 'red');
-    svg.appendChild(rect);
-    document.body.appendChild(svg);
-    t.add_cleanup(function() {
-      svg.remove();
-    });
-
-    var animation = svg.animate(
-      { transform: ['translate(0px)', 'translate(100px)'] }, 100 * MS_PER_SEC);
-    return animation.ready.then(function() {
-      assert_animation_property_state_equals(
-        animation.effect.getProperties(),
-        [ { property: 'transform', runningOnCompositor: true } ]);
-      svg.setAttribute('transform', 'translate(10, 20)');
-      return waitForFrame();
-    }).then(function() {
-      assert_animation_property_state_equals(
-        animation.effect.getProperties(),
-        [ {
-          property: 'transform',
-          runningOnCompositor: false,
-          warning: 'CompositorAnimationWarningTransformSVG'
-        } ]);
-      svg.removeAttribute('transform');
-      return waitForFrame();
-    }).then(function() {
-      assert_animation_property_state_equals(
-        animation.effect.getProperties(),
-        [ { property: 'transform', runningOnCompositor: true } ]);
-    });
-  }, 'transform of nsIFrame with SVG transform');
-
-  promise_test(function(t) {
-    var div = addDiv(t, { class: 'compositable',
-                          style: 'animation: fade 100s' });
-    var cssAnimation = div.getAnimations()[0];
-    var scriptAnimation = div.animate({ opacity: [ 1, 0 ] }, 100 * MS_PER_SEC);
-    return scriptAnimation.ready.then(function() {
-      assert_animation_property_state_equals(
-        cssAnimation.effect.getProperties(),
-        [ { property: 'opacity', runningOnCompositor: true } ]);
-      assert_animation_property_state_equals(
-        scriptAnimation.effect.getProperties(),
-        [ { property: 'opacity', runningOnCompositor: true } ]);
-    });
-  }, 'overridden animation');
-}
-
 </script>
-
-</body>
new file mode 100644
--- /dev/null
+++ b/dom/animation/test/crashtests/1325193-1.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<meta charset="UTF-8">
+<script>
+function boom(){
+  o_0.animate([{"perspective": "262ex"}, {}], {duration: 1070, iterationStart: 68, iterationComposite: "accumulate"});
+  o_0.animate([{"color": "white", "flex": "inherit"}, {"color": "red", "flex": "66% "}], 3439);
+  o_0.animate([{"font": "icon"}], 1849);
+  setTimeout(function() {
+    document.documentElement.classList.remove("reftest-wait");
+  }, 500);
+}
+document.addEventListener("DOMContentLoaded", boom);
+</script>
+</head>
+<body><details id=o_0><q></q></details></body>
+</html>
--- a/dom/animation/test/crashtests/crashtests.list
+++ b/dom/animation/test/crashtests/crashtests.list
@@ -11,8 +11,9 @@ asserts-if(stylo,4-10) pref(dom.animatio
 asserts-if(stylo,5) pref(dom.animations-api.core.enabled,true) load 1278485-1.html # bug 1324691
 asserts-if(stylo,31) pref(dom.animations-api.core.enabled,true) load 1277272-1.html # bug 1324694
 asserts-if(stylo,2) pref(dom.animations-api.core.enabled,true) load 1290535-1.html # bug 1324690
 pref(dom.animations-api.core.enabled,true) load 1304886-1.html
 pref(dom.animations-api.core.enabled,true) load 1322382-1.html
 skip-if(stylo) pref(dom.animations-api.core.enabled,true) load 1322291-1.html # bug 1323733
 asserts-if(stylo,0-5) pref(dom.animations-api.core.enabled,true) load 1323114-1.html # bug 1324690
 asserts-if(stylo,0-5) pref(dom.animations-api.core.enabled,true) load 1323114-2.html # bug 1324690
+skip-if(stylo) pref(dom.animations-api.core.enabled,true) load 1325193-1.html # bug 1311257
--- a/dom/animation/test/style/file_missing-keyframe-on-compositor.html
+++ b/dom/animation/test/style/file_missing-keyframe-on-compositor.html
@@ -485,11 +485,31 @@ promise_test(t => {
       SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
     assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 150, 0)',
       'Transform value for animation with positive delay should be composed ' +
       'onto the base style');
   });
 }, 'Transform value for animation with no keyframe at offset 0 and with ' +
    'positive delay');
 
+promise_test(t => {
+  useTestRefreshMode(t);
+
+  var div = addDiv(t, { style: 'transform: translateX(100px)' });
+
+  div.animate([{ offset: 0, transform: 'translateX(200px)'}],
+              { duration: 100 * MS_PER_SEC,
+                iterationStart: 1,
+                iterationComposite: 'accumulate' });
+
+  return waitForPaintsFlushed().then(() => {
+    var transform =
+      SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
+    assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 300, 0)',
+      'Transform value for animation with no keyframe at offset 1 and its ' +
+      'iterationComposite is accumulate');
+  });
+}, 'Transform value for animation with no keyframe at offset 1 and its ' +
+   'iterationComposite is accumulate');
+
 done();
 </script>
 </body>
--- a/dom/animation/test/style/file_missing-keyframe.html
+++ b/dom/animation/test/style/file_missing-keyframe.html
@@ -80,15 +80,29 @@ test(t => {
   div.animate([{ offset: 0, marginLeft: '200px' }], 100 * MS_PER_SEC);
 
   div.getAnimations().forEach(animation => {
     animation.currentTime = 50 * MS_PER_SEC;
   });
                                                  // (200px + 200px) * 0.5
   assert_equals(getComputedStyle(div).marginLeft, '200px',
                 'The margin-left value at 50% should be additive value of ' +
-                'of the transition and animation');
+                'the transition and animation');
 }, 'margin-left value at 50% for an animation with no keyframe at offset 1 ' +
    'is that of transition');
 
+test(t => {
+  var div = addDiv(t, { style: 'margin-left: 100px' });
+
+  var animation = div.animate([{ offset: 0, marginLeft: '200px' }],
+                              { duration: 100 * MS_PER_SEC,
+                                iterationStart: 1,
+                                iterationComposite: 'accumulate' });
+
+  assert_equals(getComputedStyle(div).marginLeft, '300px',
+                'The margin-left value should be additive value of the ' +
+                'accumulation of the initial value onto the base value ');
+}, 'margin-left value for an animation with no keyframe at offset 1 and its ' +
+   'iterationComposite is accumulate');
+
 done();
 </script>
 </body>
--- a/dom/animation/test/testcommon.js
+++ b/dom/animation/test/testcommon.js
@@ -158,25 +158,16 @@ function propertyToIDL(property) {
  */
 function waitForFrame() {
   return new Promise(function(resolve, reject) {
     window.requestAnimationFrame(resolve);
   });
 }
 
 /**
- * Promise wrapper for requestIdleCallback.
- */
-function waitForIdleCallback() {
-  return new Promise(function(resolve, reject) {
-    window.requestIdleCallback(resolve);
-  });
-}
-
-/**
  * Returns a Promise that is resolved after the given number of consecutive
  * animation frames have occured (using requestAnimationFrame callbacks).
  *
  * @param frameCount  The number of animation frames.
  * @param onFrame  An optional function to be processed in each animation frame.
  */
 function waitForAnimationFrames(frameCount, onFrame) {
   return new Promise(function(resolve, reject) {
@@ -215,25 +206,29 @@ function flushComputedStyle(elem) {
   var cs = window.getComputedStyle(elem);
   cs.marginLeft;
 }
 
 if (opener) {
   for (var funcName of ["async_test", "assert_not_equals", "assert_equals",
                         "assert_approx_equals", "assert_less_than",
                         "assert_less_than_equal", "assert_greater_than",
+                        "assert_greater_than_equal",
+                        "assert_not_exists",
                         "assert_between_inclusive",
                         "assert_true", "assert_false",
                         "assert_class_string", "assert_throws",
                         "assert_unreached", "assert_regexp_match",
                         "promise_test", "test"]) {
     window[funcName] = opener[funcName].bind(opener);
   }
 
   window.EventWatcher = opener.EventWatcher;
+  // Used for requestLongerTimeout.
+  window.W3CTest = opener.W3CTest;
 
   function done() {
     opener.add_completion_callback(function() {
       self.close();
     });
     opener.done();
   }
 }
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -1996,18 +1996,27 @@ Element::IsLabelable() const
 
 bool
 Element::IsInteractiveHTMLContent(bool aIgnoreTabindex) const
 {
   return false;
 }
 
 DeclarationBlock*
-Element::GetInlineStyleDeclaration()
+Element::GetInlineStyleDeclaration() const
 {
+  if (!MayHaveStyle()) {
+    return nullptr;
+  }
+  const nsAttrValue* attrVal = mAttrsAndChildren.GetAttr(nsGkAtoms::style);
+
+  if (attrVal && attrVal->Type() == nsAttrValue::eCSSDeclaration) {
+    return attrVal->GetCSSDeclarationValue();
+  }
+
   return nullptr;
 }
 
 nsresult
 Element::SetInlineStyleDeclaration(DeclarationBlock* aDeclaration,
                                    const nsAString* aSerialized,
                                    bool aNotify)
 {
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -262,17 +262,17 @@ public:
   /**
    * Clear all style state locks on this element.
    */
   void ClearStyleStateLocks();
 
   /**
    * Get the inline style declaration, if any, for this element.
    */
-  virtual DeclarationBlock* GetInlineStyleDeclaration();
+  DeclarationBlock* GetInlineStyleDeclaration() const;
 
   /**
    * Set the inline style declaration for this element. This will send
    * an appropriate AttributeChanged notification if aNotify is true.
    */
   virtual nsresult SetInlineStyleDeclaration(DeclarationBlock* aDeclaration,
                                              const nsAString* aSerialized,
                                              bool aNotify);
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -6743,18 +6743,16 @@ nsContentUtils::IsPatternMatching(nsAStr
                                   nsIDocument* aDocument)
 {
   NS_ASSERTION(aDocument, "aDocument should be a valid pointer (not null)");
 
   AutoJSAPI jsapi;
   jsapi.Init();
   JSContext* cx = jsapi.cx();
 
-  MOZ_RELEASE_ASSERT(js::AllowGCBarriers(cx), "IsPatternMatching can enter the JS engine during painting. See bug 1310335.");
-
   // We can use the junk scope here, because we're just using it for
   // regexp evaluation, not actual script execution.
   JSAutoCompartment ac(cx, xpc::UnprivilegedJunkScope());
 
   // The pattern has to match the entire value.
   aPattern.Insert(NS_LITERAL_STRING("^(?:"), 0);
   aPattern.AppendLiteral(")$");
 
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -4021,17 +4021,20 @@ NS_IMETHODIMP
 nsDOMWindowUtils::ForceUseCounterFlush(nsIDOMNode *aNode)
 {
   NS_ENSURE_ARG_POINTER(aNode);
 
   if (nsCOMPtr<nsIDocument> doc = do_QueryInterface(aNode)) {
     mozilla::css::ImageLoader* loader = doc->StyleImageLoader();
     loader->FlushUseCounters();
 
-    static_cast<nsDocument*>(doc.get())->ReportUseCounters();
+    // Flush the document and any external documents that it depends on.
+    const auto reportKind
+      = nsDocument::UseCounterReportKind::eIncludeExternalResources;
+    static_cast<nsDocument*>(doc.get())->ReportUseCounters(reportKind);
     return NS_OK;
   }
 
   if (nsCOMPtr<nsIContent> content = do_QueryInterface(aNode)) {
     if (HTMLImageElement* img = HTMLImageElement::FromContent(content)) {
       img->FlushUseCounters();
       return NS_OK;
     }
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -12299,26 +12299,39 @@ MightBeAboutOrChromeScheme(nsIURI* aURI)
   MOZ_ASSERT(aURI);
   bool isAbout = true;
   bool isChrome = true;
   aURI->SchemeIs("about", &isAbout);
   aURI->SchemeIs("chrome", &isChrome);
   return isAbout || isChrome;
 }
 
-void
-nsDocument::ReportUseCounters()
+static bool
+ReportExternalResourceUseCounters(nsIDocument* aDocument, void* aData)
+{
+  const auto reportKind
+    = nsDocument::UseCounterReportKind::eIncludeExternalResources;
+  static_cast<nsDocument*>(aDocument)->ReportUseCounters(reportKind);
+  return true;
+}
+
+void
+nsDocument::ReportUseCounters(UseCounterReportKind aKind)
 {
   static const bool sDebugUseCounters = false;
   if (mReportedUseCounters) {
     return;
   }
 
   mReportedUseCounters = true;
 
+  if (aKind == UseCounterReportKind::eIncludeExternalResources) {
+    EnumerateExternalResources(ReportExternalResourceUseCounters, nullptr);
+  }
+
   if (Telemetry::HistogramUseCounterCount > 0 &&
       (IsContentDocument() || IsResourceDoc())) {
     nsCOMPtr<nsIURI> uri;
     NodePrincipal()->GetURI(getter_AddRefs(uri));
     if (!uri || MightBeAboutOrChromeScheme(uri)) {
       return;
     }
 
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -769,17 +769,29 @@ public:
   virtual void SetValueMissingState(const nsAString& aName, bool aValue) override;
 
   // for radio group
   nsRadioGroupStruct* GetRadioGroup(const nsAString& aName) const;
   nsRadioGroupStruct* GetOrCreateRadioGroup(const nsAString& aName);
 
   virtual nsViewportInfo GetViewportInfo(const mozilla::ScreenIntSize& aDisplaySize) override;
 
-  void ReportUseCounters();
+  enum class UseCounterReportKind {
+    // Flush the document's use counters only; the use counters for any
+    // external resource documents will be flushed when the external
+    // resource documents themselves are destroyed.
+    eDefault,
+
+    // Flush use counters for the document and for its external resource
+    // documents. (Should only be necessary for tests, where we need
+    // flushing to happen synchronously and deterministically.)
+    eIncludeExternalResources,
+  };
+
+  void ReportUseCounters(UseCounterReportKind aKind = UseCounterReportKind::eDefault);
 
   virtual void AddIntersectionObserver(
     mozilla::dom::DOMIntersectionObserver* aObserver) override;
   virtual void RemoveIntersectionObserver(
     mozilla::dom::DOMIntersectionObserver* aObserver) override;
   virtual void UpdateIntersectionObservations() override;
   virtual void ScheduleIntersectionObserverNotification() override;
   virtual void NotifyIntersectionObservers() override;
--- a/dom/base/nsImageLoadingContent.cpp
+++ b/dom/base/nsImageLoadingContent.cpp
@@ -761,23 +761,18 @@ nsImageLoadingContent::LoadImage(const n
   }
 
   // Fire loadstart event
   FireEvent(NS_LITERAL_STRING("loadstart"));
 
   // Parse the URI string to get image URI
   nsCOMPtr<nsIURI> imageURI;
   nsresult rv = StringToURI(aNewURI, doc, getter_AddRefs(imageURI));
-  if (NS_FAILED(rv)) {
-    // Cancel image requests and then fire error and loadend events per spec
-    CancelImageRequests(aNotify);
-    FireEvent(NS_LITERAL_STRING("error"));
-    FireEvent(NS_LITERAL_STRING("loadend"));
-    return NS_OK;
-  }
+  NS_ENSURE_SUCCESS(rv, rv);
+  // XXXbiesi fire onerror if that failed?
 
   NS_TryToSetImmutable(imageURI);
 
   return LoadImage(imageURI, aForce, aNotify, aImageLoadType, false, doc);
 }
 
 nsresult
 nsImageLoadingContent::LoadImage(nsIURI* aNewURI,
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -985,18 +985,17 @@ nsJSContext::AddSupportsPrimitiveTojsval
 
       *aArgv = v;
 
       break;
     }
     case nsISupportsPrimitive::TYPE_ID :
     case nsISupportsPrimitive::TYPE_PRUINT64 :
     case nsISupportsPrimitive::TYPE_PRINT64 :
-    case nsISupportsPrimitive::TYPE_PRTIME :
-    case nsISupportsPrimitive::TYPE_VOID : {
+    case nsISupportsPrimitive::TYPE_PRTIME : {
       NS_WARNING("Unsupported primitive type used");
       aArgv->setNull();
       break;
     }
     default : {
       NS_WARNING("Unknown primitive type used");
       aArgv->setNull();
       break;
--- a/dom/base/nsStyledElement.cpp
+++ b/dom/base/nsStyledElement.cpp
@@ -88,31 +88,16 @@ nsStyledElement::SetInlineStyleDeclarati
     static_cast<uint8_t>(nsIDOMMutationEvent::MODIFICATION) :
     static_cast<uint8_t>(nsIDOMMutationEvent::ADDITION);
 
   return SetAttrAndNotify(kNameSpaceID_None, nsGkAtoms::style, nullptr,
                           oldValue, attrValue, modType, hasListeners,
                           aNotify, kDontCallAfterSetAttr);
 }
 
-DeclarationBlock*
-nsStyledElement::GetInlineStyleDeclaration()
-{
-  if (!MayHaveStyle()) {
-    return nullptr;
-  }
-  const nsAttrValue* attrVal = mAttrsAndChildren.GetAttr(nsGkAtoms::style);
-
-  if (attrVal && attrVal->Type() == nsAttrValue::eCSSDeclaration) {
-    return attrVal->GetCSSDeclarationValue();
-  }
-
-  return nullptr;
-}
-
 // ---------------------------------------------------------------
 // Others and helpers
 
 nsICSSDeclaration*
 nsStyledElement::Style()
 {
   Element::nsDOMSlots *slots = DOMSlots();
 
--- a/dom/base/nsStyledElement.h
+++ b/dom/base/nsStyledElement.h
@@ -39,17 +39,16 @@ protected:
 
 public:
   // We don't want to implement AddRef/Release because that would add an extra
   // function call for those on pretty much all elements.  But we do need QI, so
   // we can QI to nsStyledElement.
   NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
 
   // Element interface methods
-  virtual mozilla::DeclarationBlock* GetInlineStyleDeclaration() override;
   virtual nsresult SetInlineStyleDeclaration(mozilla::DeclarationBlock* aDeclaration,
                                              const nsAString* aSerialized,
                                              bool aNotify) override;
 
   nsICSSDeclaration* Style();
 
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_STYLED_ELEMENT_IID)
 
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -729,16 +729,18 @@ support-files = file_receiveMessage.html
 [test_range_bounds.html]
 skip-if = toolkit == 'android'
 [test_reentrant_flush.html]
 skip-if = toolkit == 'android'
 [test_referrer_redirect.html]
 [test_root_iframe.html]
 [test_screen_orientation.html]
 [test_script_loader_crossorigin_data_url.html]
+[test_selection_with_anon_trees.html]
+skip-if = toolkit == 'android'
 [test_setInterval_uncatchable_exception.html]
 skip-if = debug == false
 [test_settimeout_extra_arguments.html]
 [test_settimeout_inner.html]
 [test_setTimeoutWith0.html]
 [test_setting_opener.html]
 [test_simplecontentpolicy.html]
 skip-if = e10s # Bug 1156489.
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_selection_with_anon_trees.html
@@ -0,0 +1,445 @@
+<!DOCTYPE>
+<html>
+<head>
+<title>selection over trees of anonymous nodes</title>
+  <script src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+<style type="text/css">
+@font-face {
+  font-family: Ahem;
+  src: url("Ahem.ttf");
+}
+* { font-family: Ahem; font-size: 20px; }
+</style>
+
+</head>
+<body>
+
+<div id="test1">
+aaaaa aaaaa aaaaaaa aaaaaaaa aaaaaaaa
+<table>
+  <tbody>
+    <tr>
+      <td>
+        <input value="1111">
+      </td>
+    </tr>
+  <tbody>
+</table>
+bbbbbbbb
+<br>
+<br>
+</div>
+
+<div id="test2">
+ccccc ccccccc cccccccccccc ccccccccc
+<div>
+  <input value="2222">
+</div>
+dddddd
+</div>
+
+<div id="test3">
+aaaaa
+<br>
+<input type="button"><br>
+<br>
+<input type="button"><br>
+BBBBB BB<br>
+<br>
+</div>
+
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+const kIsWin = navigator.platform.indexOf("Win") == 0;
+
+function test()
+{
+  function clear(w)
+  {
+    var sel = (w ? w : window).getSelection();
+    sel.removeAllRanges();
+  }
+  function doneTest(e)
+  {
+    // We hide the elements we're done with so that later tests
+    // are inside the rather narrow iframe mochitest gives us.
+    // It matters for synthesizeMouse event tests.
+    e.style.display = 'none';
+    e.offsetHeight;
+  }
+  function show(e) { e.style.display = ''; }
+
+  function dragSelectWithKey(e, clickCount, shiftKey, pointsArray)
+  {
+    const kMove = { type:"mousemove", shiftKey:shiftKey };
+    let pt = pointsArray[0];
+    let x = pt[0];
+    let y = pt[1];
+    synthesizeMouse(e, x, y, { type:"mousedown", clickCount:clickCount, shiftKey:shiftKey });
+    pointsArray.forEach(function(pt) {
+      let x = pt[0];
+      let y = pt[1];
+      synthesizeMouse(e, x, y, kMove);
+    });
+    pt = pointsArray[pointsArray.length - 1];
+    x = pt[0];
+    y = pt[1];
+    synthesizeMouse(e, x, y, kMove);
+    synthesizeMouse(e, x, y, { type:"mouseup", shiftKey:shiftKey });
+  }
+
+  function shiftDragSelect(e, clickCount, pointsArray)
+  {
+    dragSelectWithKey(e, clickCount, true, pointsArray);
+  }
+  function dragSelect(e, clickCount, pointsArray)
+  {
+    dragSelectWithKey(e, clickCount, false, pointsArray);
+  }
+
+  function shiftClick(e, x, y)
+  {
+    synthesizeMouse(e, x, y, { type: "mousedown", shiftKey: true });
+    synthesizeMouse(e, x, y, { type: "mouseup", shiftKey: true });
+  }
+
+  function click(e, x, y)
+  {
+    synthesizeMouse(e, x, y, { type: "mousedown" });
+    synthesizeMouse(e, x, y, { type: "mouseup" });
+  }
+
+  function repeatShiftKey(k, n) {
+    for (let i = 0; i < n; ++i) {
+      synthesizeKey(k, { shiftKey:true });
+    }
+  }
+
+  function init(arr, e)
+  {
+    clear();
+    var sel = window.getSelection();
+    for (i = 0; i < arr.length; ++i) {
+      var data = arr[i];
+      var r = new Range()
+      r.setStart(node(e, data[0]), data[1]);
+      r.setEnd(node(e, data[2]), data[3]);
+      sel.addRange(r);
+    }
+  }
+
+  function NL(s) { return s.replace(/(\r\n|\n\r|\r)/g, '\n'); }
+
+  function checkText(text, e)
+  {
+    var sel = window.getSelection();
+    is(NL(sel.toString()), text, e.id + ": selected text")
+  }
+
+  function checkRangeText(text, index)
+  {
+    var r = window.getSelection().getRangeAt(index);
+    is(NL(r.toString()), text, e.id + ": range["+index+"].toString()")
+  }
+
+  function node(e, arg)
+  {
+    if (typeof arg == "number")
+      return arg == -1 ? e : e.childNodes[arg];
+    return arg;
+  }
+
+  function checkRangeCount(n, e)
+  {
+    var sel = window.getSelection();
+    is(sel.rangeCount, n, e.id + ": Selection range count");
+  }
+
+  function checkRange(r, expected, e) {
+    is(r.startContainer, node(e, expected[0]), e.id + ": range["+i+"].startContainer");
+    is(r.startOffset, expected[1], e.id + ": range["+i+"].startOffset");
+    is(r.endContainer, node(e, expected[2]), e.id + ": range["+i+"].endContainer");
+    is(r.endOffset, expected[3], e.id + ": range["+i+"].endOffset");
+  }
+
+  function checkRanges(arr, e)
+  {
+    let sel = window.getSelection();
+    checkRangeCount(arr.length, e);
+    for (i = 0; i < arr.length; ++i) {
+      let expected = arr[i];
+      let r = sel.getRangeAt(i);
+      checkRange(r, expected, e);
+    }
+  }
+
+  // ======================================================
+  // ================== dragSelect tests ==================
+  // ======================================================
+
+  // Downward word-select with SHIFT
+  clear();
+  var e = document.getElementById('test1');
+  shiftDragSelect(e, 2, [[20,5], [30,5], [250,5], [250,20], [250,50], [60,110], [10,110]]);
+  checkText('aaaaa aaaaa aaaaaaa aaaaaaaa aaaaaaaa\nbbbbbbbb ', e);
+  checkRanges([[0,1,-1,3]], e);
+
+  // Downward word-select without SHIFT
+  clear();
+  dragSelect(e, 2, [[20,5], [30,5], [250,5], [250,20], [250,50], [60,110], [10,110]]);
+  checkText('aaaaa aaaaa aaaaaaa aaaaaaaa aaaaaaaa\nbbbbbbbb ', e);
+  checkRanges([[0,1,-1,3]], e);
+
+  // Downward char-select with SHIFT
+  clear();
+  click(e, 1, 1);
+  shiftDragSelect(e, 1, [[30,5], [250,5], [250,20], [250,50], [60,110], [20,110]]);
+  checkText('aaaaa aaaaa aaaaaaa aaaaaaaa aaaaaaaa\nb', e);
+  checkRanges([[0,1,2,2]], e);
+
+  // Downward char-select without SHIFT
+  clear();
+  dragSelect(e, 1, [[40,5], [60,70], [30,110]]);
+  checkText('aaa aaaaa aaaaaaa aaaaaaaa aaaaaaaa\nb', e);
+  checkRanges([[0,3,2,2]], e);
+
+  // Upward word-select with SHIFT
+  clear();
+  shiftDragSelect(e, 2, [[20,110], [60,110], [250,50], [250,20], [250,5], [30,5], [30,5]]);
+  checkText('aaaaa aaaaa aaaaaaa aaaaaaaa aaaaaaaa\nbbbbbbbb ', e);
+  checkRanges([[0,1,-1,3]], e);
+
+  // Upward word-select without SHIFT
+  clear();
+  dragSelect(e, 2, [[20,110], [60,110], [250,50], [250,20], [250,5], [30,5], [30,5]]);
+  checkText('aaaaa aaaaa aaaaaaa aaaaaaaa aaaaaaaa\nbbbbbbbb ', e);
+  checkRanges([[0,1,-1,3]], e);
+
+  // Upward char-select with SHIFT
+  clear();
+  click(e, 100, 110);
+  shiftDragSelect(e, 1, [[20,110], [250,50], [250,20], [250,5], [30,5]]);
+  checkText('aaaa aaaaa aaaaaaa aaaaaaaa aaaaaaaa\nbbbbb', e);
+  checkRanges([[0,2,2,6]], e);
+
+  // Upward char-select without SHIFT
+  clear();
+  dragSelect(e, 1, [[30,110], [60,110], [40,5]]);
+  checkText('aaa aaaaa aaaaaaa aaaaaaaa aaaaaaaa\nb', e);
+  checkRanges([[0,3,2,2]], e);
+
+  doneTest(e);
+
+  // Downward word-select with SHIFT
+  var e = document.getElementById('test2');
+  shiftDragSelect(e, 2, [[20,5], [30,5], [250,5], [250,20], [250,50], [60,100], [10,100]]);
+  checkText('ccccc ccccccc cccccccccccc ccccccccc\ndddddd', e);
+  checkRanges([[0,1,2,7]], e);
+
+  // Downward word-select without SHIFT
+  clear();
+  dragSelect(e, 2, [[20,5], [30,5], [250,5], [250,20], [250,50], [60,100], [10,100]]);
+  checkText('ccccc ccccccc cccccccccccc ccccccccc\ndddddd', e);
+  checkRanges([[0,1,2,7]], e);
+
+  // Downward char-select with SHIFT
+  clear();
+  click(e, 1, 1);
+  shiftDragSelect(e, 1, [[30,5], [30,5], [250,5], [250,20], [250,50], [60,100], [20,100]]);
+  checkText('ccccc ccccccc cccccccccccc ccccccccc\nd', e);
+  checkRanges([[0,1,2,2]], e);
+
+  // Downward char-select without SHIFT
+  clear();
+  dragSelect(e, 1, [[40,5], [250,5], [250,20], [250,50], [60,100], [20,100]]);
+  checkText('ccc ccccccc cccccccccccc ccccccccc\nd', e);
+  checkRanges([[0,3,2,2]], e);
+
+  // Upward word-select with SHIFT
+  var e = document.getElementById('test2');
+  shiftDragSelect(e, 2, [[10,100], [60,100], [250,50], [250,20], [250,5], [30,5], [20,5]]);
+  checkText('ccccc ccccccc cccccccccccc ccccccccc\ndddddd', e);
+  checkRanges([[0,1,2,7]], e);
+
+  // Upward word-select without SHIFT
+  clear();
+  shiftDragSelect(e, 2, [[10,100], [60,100], [250,50], [250,20], [250,5], [30,5], [20,5]]);
+  checkText('ccccc ccccccc cccccccccccc ccccccccc\ndddddd', e);
+  checkRanges([[0,1,2,7]], e);
+
+  // Upward char-select with SHIFT
+  clear();
+  click(e, 100, 100);
+  shiftDragSelect(e, 1, [[20,100], [250,50], [250,20], [250,5], [60,5]]);
+  checkText('cc ccccccc cccccccccccc ccccccccc\nddddd', e);
+  checkRanges([[0,4,2,6]], e);
+
+  // Upward char-select without SHIFT
+  clear();
+  dragSelect(e, 1, [[20,100], [60,100], [60,5]]);
+  checkText('cc ccccccc cccccccccccc ccccccccc\nd', e);
+  checkRanges([[0,4,2,2]], e);
+
+  doneTest(e);
+
+  // On Windows word-selection also selects the space after the word.
+  kSpaceAfterWord = kIsWin ? ' ' : '';
+
+  // Downward word-select with SHIFT
+  e = document.getElementById('test3');
+  clear();
+  shiftDragSelect(e, 2, [[50,5], [60,125], [10,125]]);
+  checkText('aaaaa\n\n\n\nBBBBB' + kSpaceAfterWord, e);
+  checkRanges([[0,1,-1,3], [4,0,-1,8], [9,0,10,6 + kSpaceAfterWord.length]], e);
+
+  // Downward word-select without SHIFT
+  e = document.getElementById('test3');
+  clear();
+  dragSelect(e, 2, [[40,5], [30,5], [50,5], [60,125], [10,125]]);
+  checkText('aaaaa\n\n\n\nBBBBB' + kSpaceAfterWord, e);
+  checkRanges([[0,1,-1,3], [4,0,-1,8], [9,0,10,6 + kSpaceAfterWord.length]], e);
+
+  // Downward char-select without SHIFT
+  e = document.getElementById('test3');
+  clear();
+  dragSelect(e, 1, [[60,5], [60,125], [20,125]]);
+  checkText('aa\n\n\n\nB', e);
+  checkRanges([[0,4,-1,3], [4,0,-1,8], [9,0,10,2]], e);
+
+  // Upward word-select with SHIFT
+  e = document.getElementById('test3');
+  clear();
+  shiftDragSelect(e, 2, [[10,125], [60,125], [50,5]]);
+  checkText('aaaaa\n\n\n\nBBBBB' + kSpaceAfterWord, e);
+  checkRanges([[0,1,-1,3], [4,0,-1,8], [9,0,10,6 + kSpaceAfterWord.length]], e);
+
+  // Upward word-select without SHIFT
+  e = document.getElementById('test3');
+  clear();
+  dragSelect(e, 2, [[40,125], [60,125], [50,5], [30,5], [20,5]]);
+  checkText('aaaaa\n\n\n\nBBBBB' + kSpaceAfterWord, e);
+  checkRanges([[0,1,-1,3], [4,0,-1,8], [9,0,10,6 + kSpaceAfterWord.length]], e);
+
+  // Upward char-select without SHIFT
+  e = document.getElementById('test3');
+  clear();
+  dragSelect(e, 1, [[60,125], [60,125], [50,5], [30,5], [40,5]]);
+  checkText('aaa\n\n\n\nBBB', e);
+  checkRanges([[0,3,-1,3], [4,0,-1,8], [9,0,10,4]], e);
+
+  doneTest(e);
+
+
+  // ======================================================
+  // ================== shift+click tests =================
+  // ======================================================
+
+  e = document.getElementById('test1');
+  show(e);
+
+  clear();
+  init([[0,0,0,1]], e);
+  shiftClick(e, 100, 100);
+  checkText(' aaaaa aaaaa aaaaaaa aaaaaaaa aaaaaaaa\nbbbbb', e);
+  checkRanges([[0,0,2,6]], e);
+  
+  clear();
+  init([[2,6,2,6]], e);
+  shiftClick(e, 60, 5);
+  checkText('aa aaaaa aaaaaaa aaaaaaaa aaaaaaaa\nbbbbb', e);
+  checkRanges([[0,4,2,6]], e);
+  
+  doneTest(e);
+  e = document.getElementById('test2');
+  show(e);
+
+  clear();
+  init([[0,4,0,4]], e);
+  shiftClick(e, 100, 100);
+  checkText('cc ccccccc cccccccccccc ccccccccc\nddddd', e);
+  checkRanges([[0,4,2,6]], e);
+
+  clear();
+  init([[2,6,2,6]], e);
+  shiftClick(e, 1, 5);
+  checkText('ccccc ccccccc cccccccccccc ccccccccc\nddddd', e);
+  checkRanges([[0,1,2,6]], e);
+
+  doneTest(e);
+  e = document.getElementById('test3');
+  show(e);
+
+  clear();
+  init([[0,4,0,4]], e);
+  shiftClick(e, 70, 125);
+  checkText('aa\n\n\n\nBBB', e);
+  checkRanges([[0,4,-1,3], [4,0,-1,8], [9,0,10,4]], e);
+
+  // ======================================================
+  // ================== Kbd command tests =================
+  // ======================================================
+
+  doneTest(e);
+  e = document.getElementById('test1');
+  show(e);
+
+  clear();
+  init([[0,0,0,0]], e);
+  repeatShiftKey("VK_RIGHT", 45);
+  checkText(' aaaaa aaaaa aaaaaaa aaaaaaaa aaaaaaaa\nbbb', e);
+  checkRanges([[0,0,2,4]], e);
+
+  clear();
+  init([[2,5,2,5]], e);
+  repeatShiftKey("VK_LEFT", 45);
+  checkText('aaaaa aaaaa aaaaaaa aaaaaaaa aaaaaaaa\nbbbb', e);
+  checkRanges([[0,1,2,5]], e);
+
+  doneTest(e);
+  e = document.getElementById('test2');
+  show(e);
+
+  clear();
+  init([[0,0,0,0]], e);
+  repeatShiftKey("VK_RIGHT", 45);
+  checkText(' ccccc ccccccc cccccccccccc ccccccccc\ndddd', e);
+  checkRanges([[0,0,2,5]], e);
+
+  clear();
+  init([[2,6,2,6]], e);
+  repeatShiftKey("VK_LEFT", 45);
+  checkText('ccccc ccccccc cccccccccccc ccccccccc\nddddd', e);
+  checkRanges([[0,1,2,6]], e);
+
+  doneTest(e);
+  e = document.getElementById('test3');
+  show(e);
+
+  clear();
+  init([[0,0,0,0]], e);
+  repeatShiftKey("VK_RIGHT", 15);
+  checkText(' aaaaa\n\n\n\nBBB', e);
+  checkRanges([[0,0,-1,3], [4,0,-1,8], [9,0,10,4]], e);
+
+  clear();
+  init([[10,4,10,4]], e);
+  repeatShiftKey("VK_LEFT", 10);
+  checkText('aaaa\n\n\n\nBBB', e);
+  checkRanges([[0,2,-1,3], [4,0,-1,8], [9,0,10,4]], e);
+
+  clear();
+  SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(test);
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/cache/CacheStorage.cpp
+++ b/dom/cache/CacheStorage.cpp
@@ -496,54 +496,49 @@ CacheStorage::WrapObject(JSContext* aCon
 {
   return mozilla::dom::CacheStorageBinding::Wrap(aContext, this, aGivenProto);
 }
 
 void
 CacheStorage::ActorCreated(PBackgroundChild* aActor)
 {
   NS_ASSERT_OWNINGTHREAD(CacheStorage);
-  MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(mStatus));
-  MOZ_DIAGNOSTIC_ASSERT(!mActor);
   MOZ_DIAGNOSTIC_ASSERT(aActor);
 
   if (NS_WARN_IF(mWorkerHolder && mWorkerHolder->Notified())) {
     ActorFailed();
     return;
   }
 
   // WorkerHolder ownership is passed to the CacheStorageChild actor and any
   // actors it may create.  The WorkerHolder will keep the worker thread alive
   // until the actors can gracefully shutdown.
-  mActor = new CacheStorageChild(this, mWorkerHolder);
-  mWorkerHolder = nullptr;
+  CacheStorageChild* newActor = new CacheStorageChild(this, mWorkerHolder);
+  PCacheStorageChild* constructedActor =
+    aActor->SendPCacheStorageConstructor(newActor, mNamespace, *mPrincipalInfo);
 
-  // Pass the actor construction message to the parent.  Note, if this fails
-  // we can get DestroyInternal() and ActorFailed() called synchronously.  This
-  // will null out mActor and set an error mStatus.
-  PCacheStorageChild* constructedActor =
-    aActor->SendPCacheStorageConstructor(mActor, mNamespace, *mPrincipalInfo);
-
-  if (NS_WARN_IF(NS_FAILED(mStatus))) {
-    MOZ_DIAGNOSTIC_ASSERT(!mActor);
+  if (NS_WARN_IF(!constructedActor)) {
+    ActorFailed();
     return;
   }
 
-  MOZ_DIAGNOSTIC_ASSERT(mActor);
-  MOZ_DIAGNOSTIC_ASSERT(constructedActor == mActor);
+  mWorkerHolder = nullptr;
+
+  MOZ_DIAGNOSTIC_ASSERT(constructedActor == newActor);
+  mActor = newActor;
 
   MaybeRunPendingRequests();
   MOZ_DIAGNOSTIC_ASSERT(mPendingRequests.IsEmpty());
 }
 
 void
 CacheStorage::ActorFailed()
 {
   NS_ASSERT_OWNINGTHREAD(CacheStorage);
-  MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(mStatus));
+  MOZ_DIAGNOSTIC_ASSERT(!NS_FAILED(mStatus));
 
   mStatus = NS_ERROR_UNEXPECTED;
   mWorkerHolder = nullptr;
 
   for (uint32_t i = 0; i < mPendingRequests.Length(); ++i) {
     nsAutoPtr<Entry> entry(mPendingRequests[i].forget());
     entry->mPromise->MaybeReject(NS_ERROR_UNEXPECTED);
   }
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -1338,17 +1338,17 @@ CanvasRenderingContext2D::AllowOpenGLCan
   // as well, so it wouldn't help much.
 
   return (mCompositorBackend == LayersBackend::LAYERS_OPENGL) &&
     gfxPlatform::GetPlatform()->AllowOpenGLCanvas();
 }
 
 bool CanvasRenderingContext2D::SwitchRenderingMode(RenderingMode aRenderingMode)
 {
-  if (!IsTargetValid() || mRenderingMode == aRenderingMode) {
+  if (!(IsTargetValid() || mBufferProvider) || mRenderingMode == aRenderingMode) {
     return false;
   }
 
   MOZ_ASSERT(mBufferProvider);
 
 #ifdef USE_SKIA_GPU
   // Do not attempt to switch into GL mode if the platform doesn't allow it.
   if ((aRenderingMode == RenderingMode::OpenGLBackendMode) &&
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -1074,16 +1074,17 @@ WebGLContext::SetDimensions(int32_t sign
     mOptions.antialias = gl->Caps().antialias;
 
     //////
     // Initial setup.
 
     MakeContextCurrent();
 
     gl->fViewport(0, 0, mWidth, mHeight);
+    mViewportX = mViewportY = 0;
     mViewportWidth = mWidth;
     mViewportHeight = mHeight;
 
     gl->fScissor(0, 0, mWidth, mHeight);
     gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, 0);
 
     //////
     // Check everything
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -1442,16 +1442,18 @@ protected:
 
     // What is supported:
     uint32_t mGLMaxColorAttachments;
     uint32_t mGLMaxDrawBuffers;
     // What we're allowing:
     uint32_t mImplMaxColorAttachments;
     uint32_t mImplMaxDrawBuffers;
 
+    uint32_t mImplMaxViewportDims[2];
+
 public:
     GLenum LastColorAttachmentEnum() const {
         return LOCAL_GL_COLOR_ATTACHMENT0 + mImplMaxColorAttachments - 1;
     }
 
     const decltype(mOptions)& Options() const { return mOptions; }
 
 protected:
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -2135,16 +2135,19 @@ void
 WebGLContext::Viewport(GLint x, GLint y, GLsizei width, GLsizei height)
 {
     if (IsContextLost())
         return;
 
     if (width < 0 || height < 0)
         return ErrorInvalidValue("viewport: negative size");
 
+    width = std::min(width, (GLsizei)mImplMaxViewportDims[0]);
+    height = std::min(height, (GLsizei)mImplMaxViewportDims[1]);
+
     MakeContextCurrent();
     gl->fViewport(x, y, width, height);
 
     mViewportX = x;
     mViewportY = y;
     mViewportWidth = width;
     mViewportHeight = height;
 }
--- a/dom/canvas/WebGLContextValidate.cpp
+++ b/dom/canvas/WebGLContextValidate.cpp
@@ -564,16 +564,18 @@ WebGLContext::InitAndValidateGL(FailureR
     }
 
     mBound2DTextures.SetLength(mGLMaxTextureUnits);
     mBoundCubeMapTextures.SetLength(mGLMaxTextureUnits);
     mBound3DTextures.SetLength(mGLMaxTextureUnits);
     mBound2DArrayTextures.SetLength(mGLMaxTextureUnits);
     mBoundSamplers.SetLength(mGLMaxTextureUnits);
 
+    gl->fGetIntegerv(LOCAL_GL_MAX_VIEWPORT_DIMS, (GLint*)mImplMaxViewportDims);
+
     ////////////////
 
     if (MinCapabilityMode()) {
         mImplMaxTextureSize = MINVALUE_GL_MAX_TEXTURE_SIZE;
         mImplMaxCubeMapTextureSize = MINVALUE_GL_MAX_CUBE_MAP_TEXTURE_SIZE;
         mImplMaxRenderbufferSize = MINVALUE_GL_MAX_RENDERBUFFER_SIZE;
 
         mImplMax3DTextureSize = MINVALUE_GL_MAX_3D_TEXTURE_SIZE;
--- a/dom/canvas/WebGLFramebuffer.cpp
+++ b/dom/canvas/WebGLFramebuffer.cpp
@@ -282,16 +282,24 @@ WebGLFBAttachPoint::IsComplete(WebGLCont
         AttachmentName(&attachName);
 
         *out_info = nsPrintfCString("%s has an effective format of %s, which is not"
                                     " renderable",
                                     attachName.BeginReading(), formatUsage->format->name);
         return false;
     }
 
+    if (webgl->IsWebGL2() && Texture() &&
+        Texture()->IsCubeMap() && !Texture()->IsCubeComplete())
+    {
+        AttachmentName(out_info);
+        out_info->AppendLiteral(" is not cube complete");
+        return false;
+    }
+
     const auto format = formatUsage->format;
 
     bool hasRequiredBits;
 
     switch (mAttachmentPoint) {
     case LOCAL_GL_DEPTH_ATTACHMENT:
         hasRequiredBits = format->d;
         break;
--- a/dom/canvas/test/webgl-conf/generated-mochitest.ini
+++ b/dom/canvas/test/webgl-conf/generated-mochitest.ini
@@ -5811,18 +5811,17 @@ skip-if = (os == 'android' || os == 'lin
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1'))
 [generated/test_2_conformance__textures__misc__copy-tex-image-2d-formats.html]
 skip-if = (os == 'mac') || (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1'))
 [generated/test_2_conformance__textures__misc__copy-tex-image-and-sub-image-2d.html]
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1'))
 [generated/test_2_conformance__textures__misc__copy-tex-sub-image-2d-partial-texture.html]
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1'))
 [generated/test_2_conformance__textures__misc__cube-incomplete-fbo.html]
-skip-if = (os == 'win') || (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1'))
-fail-if = (os == 'mac')
+skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1'))
 [generated/test_2_conformance__textures__misc__default-texture.html]
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1'))
 [generated/test_2_conformance__textures__misc__gl-pixelstorei.html]
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1'))
 [generated/test_2_conformance__textures__misc__gl-teximage.html]
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1'))
 [generated/test_2_conformance__textures__misc__mipmap-fbo.html]
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1'))
--- a/dom/canvas/test/webgl-conf/mochitest-errata.ini
+++ b/dom/canvas/test/webgl-conf/mochitest-errata.ini
@@ -142,19 +142,16 @@ skip-if = (os == 'android' && debug)
 [generated/test_conformance__extensions__oes-vertex-array-object.html]
 # 10.6 crash:
 # PROCESS-CRASH | dom/canvas/test/webgl-conf/generated/test_conformance__extensions__oes-vertex-array-object.html | application crashed [@ gleRunVertexSubmitImmediate + 0xf24]
 skip-if = (os == 'mac' && os_version == '10.6')
 [generated/test_conformance__textures__misc__texture-size.html]
 # application crashed [@ mozilla::gl::GLContext::AfterGLCall]
 skip-if = (os == 'android') || (os == 'win')
 
-[generated/test_2_conformance__textures__misc__cube-incomplete-fbo.html]
-fail-if = (os == 'mac')
-skip-if = (os == 'win')
 [generated/test_2_conformance2__rendering__draw-buffers.html]
 fail-if = (os == 'mac') || (os == 'win')
 [generated/test_2_conformance__textures__misc__tex-image-with-format-and-type.html]
 fail-if = (os == 'mac')
 [generated/test_2_conformance__attribs__gl-vertexattribpointer.html]
 fail-if = (os == 'mac') || (os == 'win')
 [generated/test_2_conformance2__vertex_arrays__vertex-array-object.html]
 fail-if = (os == 'mac') || (os == 'win')
--- a/dom/events/test/test_all_synthetic_events.html
+++ b/dom/events/test/test_all_synthetic_events.html
@@ -287,17 +287,17 @@ const kEventConstructors = {
                                                          var e = document.createEvent("notifypaintevent");
                                                          e.initEvent(aName, aProps.bubbles, aProps.cancelable);
                                                          return e;
                                                        },
                                              },
   OfflineAudioCompletionEvent:               { create: "AudioContext" in self
                                                         ? function (aName, aProps) {
                                                             var ac = new AudioContext();
-                                                            var ab = new AudioBuffer(ac, { length: 42 });
+                                                            var ab = new AudioBuffer({ length: 42, sampleRate: ac.sampleRate });
                                                             aProps.renderedBuffer = ab;
                                                             return new OfflineAudioCompletionEvent(aName, aProps);
                                                           }
                                                         : null,
                                              },
   PageTransitionEvent:                       { create: function (aName, aProps) {
                                                          return new PageTransitionEvent(aName, aProps);
                                                        },
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -1245,16 +1245,23 @@ ContentChild::AllocPGMPServiceChild(mozi
 mozilla::ipc::IPCResult
 ContentChild::RecvGMPsChanged(nsTArray<GMPCapabilityData>&& capabilities)
 {
   GeckoMediaPluginServiceChild::UpdateGMPCapabilities(Move(capabilities));
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
+ContentChild::RecvInitProcessHangMonitor(Endpoint<PProcessHangMonitorChild>&& aHangMonitor)
+{
+  CreateHangMonitorChild(Move(aHangMonitor));
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
 ContentChild::RecvInitRendering(Endpoint<PCompositorBridgeChild>&& aCompositor,
                                 Endpoint<PImageBridgeChild>&& aImageBridge,
                                 Endpoint<PVRManagerChild>&& aVRBridge,
                                 Endpoint<PVideoDecoderManagerChild>&& aVideoManager)
 {
   if (!CompositorBridgeChild::InitForContent(Move(aCompositor))) {
     return IPC_FAIL_NO_REASON(this);
   }
@@ -1307,23 +1314,16 @@ ContentChild::RecvReinitRendering(Endpoi
 
 PBackgroundChild*
 ContentChild::AllocPBackgroundChild(Transport* aTransport,
                                     ProcessId aOtherProcess)
 {
   return BackgroundChild::Alloc(aTransport, aOtherProcess);
 }
 
-PProcessHangMonitorChild*
-ContentChild::AllocPProcessHangMonitorChild(Transport* aTransport,
-                                            ProcessId aOtherProcess)
-{
-  return CreateHangMonitorChild(aTransport, aOtherProcess);
-}
-
 #if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
 
 #include <stdlib.h>
 
 static bool
 GetAppPaths(nsCString &aAppPath, nsCString &aAppBinaryPath, nsCString &aAppDir)
 {
   nsAutoCString appPath;
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -161,33 +161,32 @@ public:
   PGMPServiceChild*
   AllocPGMPServiceChild(mozilla::ipc::Transport* transport,
                         base::ProcessId otherProcess) override;
 
   mozilla::ipc::IPCResult
   RecvGMPsChanged(nsTArray<GMPCapabilityData>&& capabilities) override;
 
   mozilla::ipc::IPCResult
+  RecvInitProcessHangMonitor(Endpoint<PProcessHangMonitorChild>&& aHangMonitor) override;
+
+  mozilla::ipc::IPCResult
   RecvInitRendering(
     Endpoint<PCompositorBridgeChild>&& aCompositor,
     Endpoint<PImageBridgeChild>&& aImageBridge,
     Endpoint<PVRManagerChild>&& aVRBridge,
     Endpoint<PVideoDecoderManagerChild>&& aVideoManager) override;
 
   mozilla::ipc::IPCResult
   RecvReinitRendering(
     Endpoint<PCompositorBridgeChild>&& aCompositor,
     Endpoint<PImageBridgeChild>&& aImageBridge,
     Endpoint<PVRManagerChild>&& aVRBridge,
     Endpoint<PVideoDecoderManagerChild>&& aVideoManager) override;
 
-  PProcessHangMonitorChild*
-  AllocPProcessHangMonitorChild(Transport* aTransport,
-                                ProcessId aOtherProcess) override;
-
   virtual mozilla::ipc::IPCResult RecvSetProcessSandbox(const MaybeFileDesc& aBroker) override;
 
   PBackgroundChild*
   AllocPBackgroundChild(Transport* aTransport, ProcessId aOtherProcess)
                         override;
 
   virtual PBrowserChild* AllocPBrowserChild(const TabId& aTabId,
                                             const IPCTabContext& aContext,
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -1772,17 +1772,17 @@ ContentParent::LaunchSubprocess(ProcessP
      base::GetProcId(mSubprocess->GetChildProcessHandle()));
 
   InitInternal(aInitialPriority,
                true, /* Setup off-main thread compositing */
                true  /* Send registered chrome */);