Merge m-c to autoland. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Tue, 23 Aug 2016 10:07:52 -0400
changeset 341659 f3456e162ec32efd653f2c606480f90fa735348b
parent 341658 e9b6832f50a052fd32069af01a958729a441eb3b (current diff)
parent 341612 052656fc513c05da969590ac5934abd67271a897 (diff)
child 341660 bd7645928990649c84609d3f531e803c2d41f269
child 341726 79de44e4ad089c3bab069457b1a21e60730f5a49
push id5
push userfmarier@mozilla.com
push dateFri, 26 Aug 2016 00:45:46 +0000
reviewersmerge
milestone51.0a1
Merge m-c to autoland. a=merge
dom/events/test/pointerevents/pointerevent_button_attribute_mouse-manual.html
dom/events/test/pointerevents/test_pointerevent_button_attribute_mouse-manual.html
js/src/jslock.h
mobile/android/base/strings.xml.in
mobile/android/chrome/content/browser.js
netwerk/test/unit_ipc/child_tracable_listener.js
netwerk/test/unit_ipc/test_traceable_channel_decoded_data.js
netwerk/test/unit_ipc/test_traceable_channel_modify_response.js
--- a/accessible/ipc/moz.build
+++ b/accessible/ipc/moz.build
@@ -27,18 +27,16 @@ else:
     else:
         LOCAL_INCLUDES += [
             '/accessible/other',
         ]
 
 if CONFIG['GNU_CXX']:
     CXXFLAGS += ['-Wno-error=shadow']
 
-# with --disable-accessibility we need to compile PDocAccessible.ipdl, but not
-# the C++.
 if CONFIG['ACCESSIBILITY']:
     EXPORTS.mozilla.a11y += [
         'DocAccessibleChildBase.h',
         'DocAccessibleParent.h',
         'ProxyAccessibleBase.h',
     ]
 
     UNIFIED_SOURCES += [
--- a/accessible/ipc/other/moz.build
+++ b/accessible/ipc/other/moz.build
@@ -1,18 +1,18 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+# With --disable-accessibility, we need to compile PDocAccessible.ipdl, but
+# not the C++.
 IPDL_SOURCES += ['PDocAccessible.ipdl']
 
-# with --disable-accessibility we need to compile PDocAccessible.ipdl, but not
-# the C++.
 if CONFIG['ACCESSIBILITY']:
     EXPORTS.mozilla.a11y += [
         'DocAccessibleChild.h',
         'ProxyAccessible.h',
     ]
 
     SOURCES += [
         'DocAccessibleChild.cpp',
--- a/accessible/ipc/win/moz.build
+++ b/accessible/ipc/win/moz.build
@@ -1,23 +1,23 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DIRS += ['typelib']
 
+# With --disable-accessibility, we need to compile PDocAccessible.ipdl (which
+# also depends on COMPtrTypes.h), but not the C++.
 IPDL_SOURCES += ['PDocAccessible.ipdl']
+EXPORTS.mozilla.a11y += ['COMPtrTypes.h']
 
-# with --disable-accessibility we need to compile PDocAccessible.ipdl, but not
-# the C++.
 if CONFIG['ACCESSIBILITY']:
     EXPORTS.mozilla.a11y += [
-        'COMPtrTypes.h',
         'DocAccessibleChild.h',
         'PlatformChild.h',
         'ProxyAccessible.h'
     ]
 
     SOURCES += [
         'COMPtrTypes.cpp',
         'DocAccessibleChild.cpp',
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1474,16 +1474,19 @@ pref("toolkit.pageThumbs.minHeight", 190
 // Enable speech synthesis
 pref("media.webspeech.synth.enabled", true);
 
 pref("browser.esedbreader.loglevel", "Error");
 
 pref("browser.laterrun.enabled", false);
 
 pref("browser.migrate.automigrate.enabled", false);
+// 4 here means the suggestion notification will be automatically
+// hidden the 4th day, so it will actually be shown on 3 different days.
+pref("browser.migrate.automigrate.daysToOfferUndo", 4);
 
 // Enable browser frames for use on desktop.  Only exposed to chrome callers.
 pref("dom.mozBrowserFramesEnabled", true);
 
 pref("extensions.pocket.enabled", true);
 
 pref("signon.schemeUpgrades", true);
 
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -16,16 +16,17 @@
   </stringbundleset>
 
   <commandset id="mainCommandSet">
     <command id="cmd_newNavigator" oncommand="OpenBrowserWindow()" reserved="true"/>
     <command id="cmd_handleBackspace" oncommand="BrowserHandleBackspace();" />
     <command id="cmd_handleShiftBackspace" oncommand="BrowserHandleShiftBackspace();" />
 
     <command id="cmd_newNavigatorTab" oncommand="BrowserOpenTab(event);" reserved="true"/>
+    <command id="cmd_newNavigatorTabNoEvent" oncommand="BrowserOpenTab();" reserved="true"/>
     <command id="Browser:OpenFile"  oncommand="BrowserOpenFileWindow();"/>
     <command id="Browser:SavePage" oncommand="saveBrowser(gBrowser.selectedBrowser);"/>
 
     <command id="Browser:SendLink"
              oncommand="MailIntegration.sendLinkForBrowser(gBrowser.selectedBrowser);"/>
 
     <command id="cmd_pageSetup" oncommand="PrintUtils.showPageSetup();"/>
     <command id="cmd_print" oncommand="PrintUtils.printWindow(window.gBrowser.selectedBrowser.outerWindowID, window.gBrowser.selectedBrowser);"/>
@@ -191,17 +192,17 @@
     </broadcaster>
   </broadcasterset>
 
   <keyset id="mainKeyset">
     <key id="key_newNavigator"
          key="&newNavigatorCmd.key;"
          command="cmd_newNavigator"
          modifiers="accel"/>
-    <key id="key_newNavigatorTab" key="&tabCmd.commandkey;" modifiers="accel" command="cmd_newNavigatorTab"/>
+    <key id="key_newNavigatorTab" key="&tabCmd.commandkey;" modifiers="accel" command="cmd_newNavigatorTabNoEvent"/>
     <key id="focusURLBar" key="&openCmd.commandkey;" command="Browser:OpenLocation"
          modifiers="accel"/>
 #ifndef XP_MACOSX
     <key id="focusURLBar2" key="&urlbar.accesskey;" command="Browser:OpenLocation"
          modifiers="alt"/>
 #endif
 
 #
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -974,17 +974,16 @@ var gBrowserInit = {
         document.documentElement.setAttribute("sizemode", "maximized");
       }
     }
 
     if (!window.toolbar.visible) {
       // adjust browser UI for popups
       gURLBar.setAttribute("readonly", "true");
       gURLBar.setAttribute("enablehistory", "false");
-      goSetCommandEnabled("cmd_newNavigatorTab", false);
     }
 
     // Misc. inits.
     TabletModeUpdater.init();
     CombinedStopReload.init();
     gPrivateBrowsingUI.init();
 
     if (window.matchMedia("(-moz-os-version: windows-win8)").matches &&
@@ -1896,26 +1895,18 @@ function BrowserOpenTab(event) {
 
   if (event) {
     where = whereToOpenLink(event, false, true);
 
     switch (where) {
       case "tab":
       case "tabshifted":
         // When accel-click or middle-click are used, open the new tab as
-        // related to the current tab. We need to exclude key events here,
-        // where the accel key is required for the shortcut.
-        // 'event' and its sourceEvent are command events, the latter of which
-        // doesn't have its own sourceEvent. These events don't indicate how
-        // they were invoked, except that the sourceEvent for keyboard
-        // shortcuts have <key> targets, and those for clicking a toolbar
-        // button or activating a menu item have that button or menuitem as
-        // their target.
-        relatedToCurrent = !event.sourceEvent ||
-                           event.sourceEvent.target.localName != "key";
+        // related to the current tab.
+        relatedToCurrent = true;
         break;
       case "current":
         where = "tab";
         break;
     }
   }
 
   openUILinkIn(BROWSER_NEW_TAB_URL, where, { relatedToCurrent });
@@ -3304,17 +3295,16 @@ var PrintPreviewListener = {
     this._tabBeforePrintPreview = null;
     gInPrintPreviewMode = false;
     this._toggleAffectedChrome();
     if (this._simplifyPageTab) {
       gBrowser.removeTab(this._simplifyPageTab);
       this._simplifyPageTab = null;
     }
     gBrowser.removeTab(this._printPreviewTab);
-    gBrowser.deactivatePrintPreviewBrowsers();
     this._printPreviewTab = null;
   },
   _toggleAffectedChrome: function () {
     gNavToolbox.collapsed = gInPrintPreviewMode;
 
     if (gInPrintPreviewMode)
       this._hideChrome();
     else
@@ -3360,21 +3350,17 @@ var PrintPreviewListener = {
     if (this._chromeState.globalNotificationsOpen)
       document.getElementById("global-notificationbox").notificationsHidden = false;
 
     if (this._chromeState.syncNotificationsOpen)
       document.getElementById("sync-notifications").notificationsHidden = false;
 
     if (this._chromeState.sidebarOpen)
       SidebarUI.show(this._sidebarCommand);
-  },
-
-  activateBrowser(browser) {
-    gBrowser.activateBrowserForPrintPreview(browser);
-  },
+  }
 }
 
 function getMarkupDocumentViewer()
 {
   return gBrowser.markupDocumentViewer;
 }
 
 // This function is obsolete. Newer code should use <tooltip page="true"/> instead.
--- a/browser/base/content/tab-content.js
+++ b/browser/base/content/tab-content.js
@@ -154,16 +154,17 @@ var AboutHomeListener = {
     docElt.setAttribute("snippetsVersion", aData.snippetsVersion);
   },
 
   onPageLoad: function() {
     addMessageListener("AboutHome:Update", this);
     addEventListener("click", this, true);
     addEventListener("pagehide", this, true);
 
+    sendAsyncMessage("AboutHome:MaybeShowAutoMigrationUndoNotification");
     sendAsyncMessage("AboutHome:RequestUpdate");
   },
 
   onClick: function(aEvent) {
     if (!aEvent.isTrusted || // Don't trust synthetic events
         aEvent.button == 2 || aEvent.target.localName != "button") {
       return;
     }
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1627,17 +1627,18 @@
             }
 
             aBrowser.droppedLinkHandler = droppedLinkHandler;
 
             // Switching a browser's remoteness will create a new frameLoader.
             // As frameLoaders start out with an active docShell we have to
             // deactivate it if this is not the selected tab's browser or the
             // browser window is minimized.
-            aBrowser.docShellIsActive = this.shouldActivateDocShell(aBrowser);
+            aBrowser.docShellIsActive = (aBrowser == this.selectedBrowser &&
+                                         window.windowState != window.STATE_MINIMIZED);
 
             // Create a new tab progress listener for the new browser we just injected,
             // since tab progress listeners have logic for handling the initial about:blank
             // load
             listener = this.mTabProgressListener(tab, aBrowser, false, false);
             this._tabListeners.set(tab, listener);
             filter.addProgressListener(listener, Ci.nsIWebProgress.NOTIFY_ALL);
 
@@ -2728,21 +2729,18 @@
 
             // Unmap old outerWindowIDs.
             this._outerWindowIDBrowserMap.delete(ourBrowser.outerWindowID);
             let remoteBrowser = aOtherBrowser.ownerDocument.defaultView.gBrowser;
             if (remoteBrowser) {
               remoteBrowser._outerWindowIDBrowserMap.delete(aOtherBrowser.outerWindowID);
             }
 
-            // If switcher is active, it will intercept swap events and
-            // react as needed.
-            if (!this._switcher) {
-              aOtherBrowser.docShellIsActive = this.shouldActivateDocShell(ourBrowser);
-            }
+            aOtherBrowser.docShellIsActive = (ourBrowser == this.selectedBrowser &&
+                                              window.windowState != window.STATE_MINIMIZED);
 
             // Swap the docshells
             ourBrowser.swapDocShells(aOtherBrowser);
 
             if (ourBrowser.isRemoteBrowser) {
               // Switch outerWindowIDs for remote browsers.
               let ourOuterWindowID = ourBrowser._outerWindowID;
               ourBrowser._outerWindowID = aOtherBrowser._outerWindowID;
@@ -3238,66 +3236,16 @@
         <body>
           <![CDATA[
             return SessionStore.duplicateTab(window, aTab, 0, aRestoreTabImmediately);
           ]]>
         </body>
       </method>
 
       <!--
-        List of browsers whose docshells must be active in order for print preview
-        to work.
-      -->
-      <field name="_printPreviewBrowsers">
-        new Set()
-      </field>
-
-      <method name="activateBrowserForPrintPreview">
-        <parameter name="aBrowser"/>
-        <body>
-          <![CDATA[
-            this._printPreviewBrowsers.add(aBrowser);
-            if (this._switcher) {
-              this._switcher.activateBrowserForPrintPreview(aBrowser);
-            }
-            aBrowser.docShellIsActive = true;
-          ]]>
-        </body>
-      </method>
-
-      <method name="deactivatePrintPreviewBrowsers">
-        <body>
-          <![CDATA[
-            let browsers = this._printPreviewBrowsers;
-            this._printPreviewBrowsers = new Set();
-            for (let browser of browsers) {
-              browser.docShellIsActive = this.shouldActivateDocShell(browser);
-            }
-          ]]>
-        </body>
-      </method>
-
-      <!--
-        Returns true if a given browser's docshell should be active.
-      -->
-      <method name="shouldActivateDocShell">
-        <parameter name="aBrowser"/>
-        <body>
-          <![CDATA[
-            if (this._switcher) {
-              return this._switcher.shouldActivateDocShell(aBrowser);
-            }
-            return (aBrowser == this.selectedBrowser &&
-                    window.windowState != window.STATE_MINIMIZED) ||
-                   this._printPreviewBrowsers.has(aBrowser);
-          ]]>
-        </body>
-      </method>
-
-      <!--
         The tab switcher is responsible for asynchronously switching
         tabs in e10s. It waits until the new tab is ready (i.e., the
         layer tree is available) before switching to it. Then it
         unloads the layer tree for the old tab.
 
         The tab switcher is a state machine. For each tab, it
         maintains state about whether the layer tree for the tab is
         available, being loaded, being unloaded, or unavailable. It
@@ -3318,22 +3266,45 @@
 
         3. We discard layer trees on a delay. This way, if the user is
         switching among the same tabs frequently, we don't continually
         load the same tabs.
 
         It's important that we always show either the spinner or a tab
         whose layers are available. Otherwise the compositor will draw
         an entirely black frame, which is very jarring. To ensure this
-        never happens when switching away from a tab, we assume the
-        old tab might still be drawn until a MozAfterPaint event
-        occurs. Because layout and compositing happen asynchronously,
-        we don't have any other way of knowing when the switch
-        actually takes place. Therefore, we don't unload the old tab
-        until the next MozAfterPaint event.
+        never happens, we do the following:
+
+        1. When switching away from a tab, we assume the old tab might
+        still be drawn until a MozAfterPaint event occurs. Because
+        layout and compositing happen asynchronously, we don't have
+        any other way of knowing when the switch actually takes
+        place. Therefore, we don't unload the old tab until the next
+        MozAfterPaint event.
+
+        2. Suppose that the user switches from tab A to B and then
+        back to A. Suppose that we ask for tab A's layers to be
+        unloaded via message M1 after the first switch and then
+        request them again via message M2 once the second switch
+        happens. Both loading and unloading of layers happens
+        asynchronously, and this can cause problems. It's possible
+        that the content process publishes one last layer tree before
+        M1 is received. The parent process doesn't know that this
+        layer tree was published before M1 and not after M2, so it
+        will display the tab. However, once M1 arrives, the content
+        process will destroy the layer tree for A and now we will
+        display black for it.
+
+        To counter this problem, we keep tab A in a separate
+        "unloading" state until the layer tree is actually dropped in
+        the compositor thread. While the tab is in the "unloading"
+        state, we're not allowed to request layers for it. Once the
+        layers are dropped in the compositor, an event will fire and
+        we will transition the tab to the "unloaded" state. Then we
+        are free to request the tab's layers again.
       -->
       <field name="_switcher">null</field>
       <method name="_getSwitcher">
         <body><![CDATA[
           if (this._switcher) {
             return this._switcher;
           }
 
@@ -3370,16 +3341,21 @@
             unloadTimer: null, // UNLOAD_DELAY nsITimer instance.
 
             // Map from tabs to STATE_* (below).
             tabState: new Map(),
 
             // True if we're in the midst of switching tabs.
             switchInProgress: false,
 
+            // Keep an exact list of content processes (tabParent) in which
+            // we're actively suppressing the display port. This gives a robust
+            // way to make sure we don't forget to un-suppress.
+            activeSuppressDisplayport: new Set(),
+
             // Set of tabs that might be visible right now. We maintain
             // this set because we can't be sure when a tab is actually
             // drawn. A tab is added to this set when we ask to make it
             // visible. All tabs but the most recently shown tab are
             // removed from the set upon MozAfterPaint.
             maybeVisibleTabs: new Set([this.selectedTab]),
 
             STATE_UNLOADED: 0,
@@ -3411,93 +3387,79 @@
             getTabState: function(tab) {
               let state = this.tabState.get(tab);
               if (state === undefined) {
                 return this.STATE_UNLOADED;
               }
               return state;
             },
 
-            setTabStateNoAction(tab, state) {
+            setTabState: function(tab, state) {
               if (state == this.STATE_UNLOADED) {
                 this.tabState.delete(tab);
               } else {
                 this.tabState.set(tab, state);
               }
-            },
-
-            setTabState: function(tab, state) {
-              this.setTabStateNoAction(tab, state);
 
               let browser = tab.linkedBrowser;
-              let {tabParent} = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
+              let fl = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
               if (state == this.STATE_LOADING) {
-                this.assert(!this.minimized);
+                // Ask for a MozLayerTreeReady event.
+                fl.requestNotifyLayerTreeReady();
                 browser.docShellIsActive = true;
-                if (!tabParent) {
-                  this.onLayersReady(browser);
-		}
               } else if (state == this.STATE_UNLOADING) {
+                // Ask for MozLayerTreeCleared event.
+                fl.requestNotifyLayerTreeCleared();
                 browser.docShellIsActive = false;
-                if (!tabParent) {
-                  this.onLayersCleared(browser);
-                }
               }
             },
 
-            get minimized() {
-              return window.windowState == window.STATE_MINIMIZED;
-            },
-
             init: function() {
               this.log("START");
 
               window.addEventListener("MozAfterPaint", this);
               window.addEventListener("MozLayerTreeReady", this);
               window.addEventListener("MozLayerTreeCleared", this);
               window.addEventListener("TabRemotenessChange", this);
-              window.addEventListener("sizemodechange", this);
-              window.addEventListener("SwapDocShells", this, true);
-              window.addEventListener("EndSwapDocShells", this, true);
-              if (!this.minimized) {
-                this.setTabState(this.requestedTab, this.STATE_LOADED);
-              }
+              this.setTabState(this.requestedTab, this.STATE_LOADED);
             },
 
             destroy: function() {
               if (this.unloadTimer) {
                 this.clearTimer(this.unloadTimer);
                 this.unloadTimer = null;
               }
               if (this.loadTimer) {
                 this.clearTimer(this.loadTimer);
                 this.loadTimer = null;
               }
 
               window.removeEventListener("MozAfterPaint", this);
               window.removeEventListener("MozLayerTreeReady", this);
               window.removeEventListener("MozLayerTreeCleared", this);
               window.removeEventListener("TabRemotenessChange", this);
-              window.removeEventListener("sizemodechange", this);
-              window.removeEventListener("SwapDocShells", this, true);
-              window.removeEventListener("EndSwapDocShells", this, true);
 
               this.tabbrowser._switcher = null;
+
+              this.activeSuppressDisplayport.forEach(function(tabParent) {
+                tabParent.suppressDisplayport(false);
+              });
+              this.activeSuppressDisplayport.clear();
             },
 
             finish: function() {
               this.log("FINISH");
 
               this.assert(this.tabbrowser._switcher);
               this.assert(this.tabbrowser._switcher === this);
               this.assert(!this.spinnerTab);
               this.assert(!this.loadTimer);
               this.assert(!this.loadingTab);
               this.assert(this.lastVisibleTab === this.requestedTab);
-              this.assert(this.minimized || this.getTabState(this.requestedTab) == this.STATE_LOADED);
+              this.assert(this.getTabState(this.requestedTab) == this.STATE_LOADED);
 
               this.destroy();
 
               let toBrowser = this.requestedTab.linkedBrowser;
               toBrowser.setAttribute("type", "content-primary");
 
               this.tabbrowser._adjustFocusAfterTabSwitch(this.requestedTab);
 
@@ -3527,17 +3489,17 @@
                 // available, show it.
                 showTab = this.lastVisibleTab;
               } else {
                 // Show the requested tab. If it's not available, we'll show the spinner.
                 showTab = this.requestedTab;
               }
 
               // Show or hide the spinner as needed.
-              let needSpinner = this.getTabState(showTab) != this.STATE_LOADED && !this.minimized;
+              let needSpinner = this.getTabState(showTab) != this.STATE_LOADED;
               if (!needSpinner && this.spinnerTab) {
                 this.spinnerHidden();
                 this.tabbrowser.removeAttribute("pendingpaint");
                 this.spinnerTab.linkedBrowser.removeAttribute("pendingpaint");
                 this.spinnerTab = null;
               } else if (needSpinner && this.spinnerTab !== showTab) {
                 if (this.spinnerTab) {
                   this.spinnerTab.linkedBrowser.removeAttribute("pendingpaint");
@@ -3582,25 +3544,24 @@
                 dump("Assertion failure\n" + Error().stack);
                 throw new Error("Assertion failure");
               }
             },
 
             // We've decided to try to load requestedTab.
             loadRequestedTab: function() {
               this.assert(!this.loadTimer);
-              this.assert(!this.minimized);
 
               // loadingTab can be non-null here if we timed out loading the current tab.
               // In that case we just overwrite it with a different tab; it's had its chance.
               this.loadingTab = this.requestedTab;
               this.log("Loading tab " + this.tinfo(this.loadingTab));
 
+              this.setTabState(this.requestedTab, this.STATE_LOADING);
               this.loadTimer = this.setTimer(() => this.onLoadTimeout(), this.TAB_SWITCH_TIMEOUT);
-              this.setTabState(this.requestedTab, this.STATE_LOADING);
             },
 
             // This function runs before every event. It fixes up the state
             // to account for closed tabs.
             preActions: function() {
               this.assert(this.tabbrowser._switcher);
               this.assert(this.tabbrowser._switcher === this);
 
@@ -3635,31 +3596,23 @@
                           this.getTabState(this.loadingTab) == this.STATE_LOADING);
 
               // We guarantee that loadingTab is non-null iff loadTimer is non-null. So
               // the timer is set only when we're loading something.
               this.assert(!this.loadTimer || this.loadingTab);
               this.assert(!this.loadingTab || this.loadTimer);
 
               // If we're not loading anything, try loading the requested tab.
-              let requestedState = this.getTabState(this.requestedTab);
-              if (!this.loadTimer && !this.minimized &&
-                  (requestedState == this.STATE_UNLOADED ||
-                   requestedState == this.STATE_UNLOADING)) {
+              if (!this.loadTimer && this.getTabState(this.requestedTab) == this.STATE_UNLOADED) {
                 this.loadRequestedTab();
               }
 
               // See how many tabs still have work to do.
               let numPending = 0;
               for (let [tab, state] of this.tabState) {
-                // Skip print preview browsers since they shouldn't affect tab switching.
-                if (this.tabbrowser._printPreviewBrowsers.has(tab.linkedBrowser)) {
-                  continue;
-                }
-
                 if (state == this.STATE_LOADED && tab !== this.requestedTab) {
                   numPending++;
                 }
                 if (state == this.STATE_LOADING || state == this.STATE_UNLOADING) {
                   numPending++;
                 }
               }
 
@@ -3684,20 +3637,16 @@
               this.logState("onUnloadTimeout");
               this.unloadTimer = null;
               this.preActions();
 
               let numPending = 0;
 
               // Unload any tabs that can be unloaded.
               for (let [tab, state] of this.tabState) {
-                if (this.tabbrowser._printPreviewBrowsers.has(tab.linkedBrowser)) {
-                  continue;
-                }
-
                 if (state == this.STATE_LOADED &&
                     !this.maybeVisibleTabs.has(tab) &&
                     tab !== this.lastVisibleTab &&
                     tab !== this.loadingTab &&
                     tab !== this.requestedTab)
                 {
                   this.setTabState(tab, this.STATE_UNLOADING);
                 }
@@ -3721,25 +3670,19 @@
               this.preActions();
               this.loadTimer = null;
               this.loadingTab = null;
               this.postActions();
             },
 
             // Fires when the layers become available for a tab.
             onLayersReady: function(browser) {
+              this.logState("onLayersReady");
+
               let tab = this.tabbrowser.getTabForBrowser(browser);
-              if (!tab) {
-                // Devtools sometimes makes its own remote browsers.
-                return;
-              }
-              this.logState(`onLayersReady(${tab._tPos})`);
-
-              this.assert(this.getTabState(tab) == this.STATE_LOADING ||
-                          this.getTabState(tab) == this.STATE_LOADED);
               this.setTabState(tab, this.STATE_LOADED);
 
               this.maybeFinishTabSwitch();
 
               if (this.loadingTab === tab) {
                 this.clearTimer(this.loadTimer);
                 this.loadTimer = null;
                 this.loadingTab = null;
@@ -3751,30 +3694,29 @@
             // around.
             onPaint: function() {
               this.maybeVisibleTabs.clear();
               this.maybeFinishTabSwitch();
             },
 
             // Called when we're done clearing the layers for a tab.
             onLayersCleared: function(browser) {
+              this.logState("onLayersCleared");
+
               let tab = this.tabbrowser.getTabForBrowser(browser);
               if (tab) {
-                this.logState(`onLayersCleared(${tab._tPos})`);
-                this.assert(this.getTabState(tab) == this.STATE_UNLOADING ||
-                            this.getTabState(tab) == this.STATE_UNLOADED);
                 this.setTabState(tab, this.STATE_UNLOADED);
               }
             },
 
             // Called when a tab switches from remote to non-remote. In this case
             // a MozLayerTreeReady notification that we requested may never fire,
             // so we need to simulate it.
             onRemotenessChange: function(tab) {
-              this.logState(`onRemotenessChange(${tab._tPos}, ${tab.linkedBrowser.isRemoteBrowser})`);
+              this.logState("onRemotenessChange");
               if (!tab.linkedBrowser.isRemoteBrowser) {
                 if (this.getTabState(tab) == this.STATE_LOADING) {
                   this.onLayersReady(tab.linkedBrowser);
                 } else if (this.getTabState(tab) == this.STATE_UNLOADING) {
                   this.onLayersCleared(tab.linkedBrowser);
                 }
               }
             },
@@ -3787,108 +3729,44 @@
                 // going to be removed during this tick of the event loop.
                 // This will cause us to show a tab spinner instead.
                 this.preActions();
                 this.lastVisibleTab = null;
                 this.postActions();
               }
             },
 
-            onSizeModeChange() {
-              if (this.minimized) {
-                for (let [tab, state] of this.tabState) {
-                  // Skip print preview browsers since they shouldn't affect tab switching.
-                  if (this.tabbrowser._printPreviewBrowsers.has(tab.linkedBrowser)) {
-                    continue;
-                  }
-
-                  if (state == this.STATE_LOADING || state == this.STATE_LOADED) {
-                    this.setTabState(tab, this.STATE_UNLOADING);
-                  }
-                  if (this.loadTimer) {
-                    this.clearTimer(this.loadTimer);
-                    this.loadTimer = null;
-                  }
-                  this.loadingTab = null;
-                }
-              } else {
-                // Do nothing. We'll automatically start loading the requested tab in
-                // postActions.
-              }
-            },
-
-            onSwapDocShells(ourBrowser, otherBrowser) {
-              // This event fires before the swap. ourBrowser is from
-              // our window. We save the state of otherBrowser since ourBrowser
-              // needs to take on that state at the end of the swap.
-
-              let otherTabbrowser = otherBrowser.ownerDocument.defaultView.gBrowser;
-              let otherState;
-              if (otherTabbrowser && otherTabbrowser._switcher) {
-                let otherTab = otherTabbrowser.getTabForBrowser(otherBrowser);
-                otherState = otherTabbrowser._switcher.getTabState(otherTab);
-              } else {
-                otherState = (otherBrowser.docShellIsActive
-                              ? this.STATE_LOADED
-                              : this.STATE_UNLOADED);
-              }
-
-              if (!this.swapMap) {
-                this.swapMap = new WeakMap();
-              }
-              this.swapMap.set(otherBrowser, otherState);
-            },
-
-            onEndSwapDocShells(ourBrowser, otherBrowser) {
-              // The swap has happened. We reset the loadingTab in
-              // case it has been swapped. We also set ourBrowser's state
-              // to whatever otherBrowser's state was before the swap.
-
-              if (this.loadTimer) {
-                // Clearing the load timer means that we will
-                // immediately display a spinner if ourBrowser isn't
-                // ready yet. Typically it will already be ready
-                // though. If it's not, we're probably in a new window,
-                // in which case we have no other tabs to display anyway.
-                this.clearTimer(this.loadTimer);
-                this.loadTimer = null;
-              }
-              this.loadingTab = null;
-
-              let otherState = this.swapMap.get(otherBrowser);
-              this.swapMap.delete(otherBrowser);
-
-              let ourTab = this.tabbrowser.getTabForBrowser(ourBrowser);
-              if (ourTab) {
-                this.setTabStateNoAction(ourTab, otherState);
-              }
-            },
-
-            shouldActivateDocShell(browser) {
-              let tab = this.tabbrowser.getTabForBrowser(browser);
-              let state = this.getTabState(tab);
-              return state == this.STATE_LOADING || state == this.STATE_LOADED;
-            },
-
-            activateBrowserForPrintPreview(browser) {
-	      let tab = this.tabbrowser.getTabForBrowser(browser);
-	      this.setTabState(tab, this.STATE_LOADING);
-	    },
-
             // Called when the user asks to switch to a given tab.
             requestTab: function(tab) {
               if (tab === this.requestedTab) {
                 return;
               }
 
+              // Instrumentation to figure out bug 1166351 - if the binding
+              // on the browser we're switching to has gone away, try to find out
+              // why. We should remove this and the checkBrowserBindingAlive
+              // method once bug 1166351 has been closed.
+              if (this.tabbrowser.AppConstants.E10S_TESTING_ONLY &&
+                  !this.checkBrowserBindingAlive(tab)) {
+                Cu.reportError("Please report the above errors in bug 1166351.");
+                return;
+              }
+
               this.logState("requestTab " + this.tinfo(tab));
               this.startTabSwitch();
 
               this.requestedTab = tab;
 
+              let browser = this.requestedTab.linkedBrowser;
+              let fl = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
+              if (fl && fl.tabParent && !this.activeSuppressDisplayport.has(fl.tabParent)) {
+                fl.tabParent.suppressDisplayport(true);
+                this.activeSuppressDisplayport.add(fl.tabParent);
+              }
+
               this.preActions();
 
               if (this.unloadTimer) {
                 this.clearTimer(this.unloadTimer);
               }
               this.unloadTimer = this.setTimer(() => this.onUnloadTimeout(), this.UNLOAD_DELAY);
 
               this.postActions();
@@ -3910,22 +3788,16 @@
               if (event.type == "MozLayerTreeReady") {
                 this.onLayersReady(event.originalTarget);
               } if (event.type == "MozAfterPaint") {
                 this.onPaint();
               } else if (event.type == "MozLayerTreeCleared") {
                 this.onLayersCleared(event.originalTarget);
               } else if (event.type == "TabRemotenessChange") {
                 this.onRemotenessChange(event.target);
-              } else if (event.type == "sizemodechange") {
-                this.onSizeModeChange();
-              } else if (event.type == "SwapDocShells") {
-                this.onSwapDocShells(event.originalTarget, event.detail);
-              } else if (event.type == "EndSwapDocShells") {
-                this.onEndSwapDocShells(event.originalTarget, event.detail);
               }
 
               this.postActions();
               this._processing = false;
             },
 
             /*
              * Telemetry and Profiler related helpers for recording tab switch
@@ -3999,16 +3871,52 @@
                 result = Services.prefs.getBoolPref("browser.tabs.remote.logSwitchTiming");
               } catch (ex) {
               }
               this._shouldLog = result;
               this._logInit = true;
               return this._shouldLog;
             },
 
+            // Instrumentation for bug 1166351
+            checkBrowserBindingAlive: function(tab) {
+              let err = Cu.reportError;
+
+              if (!tab.linkedBrowser) {
+                err("Attempting to switch to tab that has no linkedBrowser.");
+                return false;
+              }
+
+              let b = tab.linkedBrowser;
+
+              if (!b._alive) {
+                // The browser binding has been removed. Dump a bunch of
+                // diagnostic information to the browser console.
+                let utils = Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
+                let results = utils.getBindingURLs(b);
+                let urls = [];
+
+                for (let i = 0; i < results.length; ++i) {
+                  urls.push(results.queryElementAt(i, Ci.nsIURI).spec);
+                }
+                err("The browser has the following bindings:");
+                err(urls);
+                err("MozBinding is currently: " +
+                    window.getComputedStyle(b).MozBinding);
+                if (!b.parentNode) {
+                  err("Browser was removed from the DOM.");
+                } else {
+                  err("Parent is: " + b.parentNode.outerHTML);
+                }
+                return false;
+              }
+
+              return true;
+            },
+
             tinfo: function(tab) {
               if (tab) {
                 return tab._tPos + "(" + tab.linkedBrowser.currentURI.spec + ")";
               }
               return "null";
             },
 
             log: function(s) {
@@ -4367,17 +4275,17 @@
               this._handleKeyDownEvent(aEvent);
               break;
             case "keypress":
               this._handleKeyPressEventMac(aEvent);
               break;
             case "sizemodechange":
               if (aEvent.target == window) {
                 this.mCurrentBrowser.setDocShellIsActiveAndForeground(
-                  this.shouldActivateDocShell(this.mCurrentBrowser));
+                  window.windowState != window.STATE_MINIMIZED);
               }
               break;
           }
         ]]></body>
       </method>
 
       <method name="receiveMessage">
         <parameter name="aMessage"/>
--- a/browser/base/content/test/general/browser_popupUI.js
+++ b/browser/base/content/test/general/browser_popupUI.js
@@ -39,16 +39,18 @@ function testPopupUI(win) {
      "'open location' command is not disabled in the popup");
 
   let historyButton = doc.getAnonymousElementByAttribute(win.gURLBar, "anonid",
                                                          "historydropmarker");
   is(historyButton.clientWidth, 0, "history dropdown button is hidden in the popup");
 
   EventUtils.synthesizeKey("t", { accelKey: true }, win);
   is(win.gBrowser.browsers.length, 1, "Accel+T doesn't open a new tab in the popup");
+  is(gBrowser.browsers.length, 2, "Accel+T opened a new tab in the parent window");
+  gBrowser.removeCurrentTab();
 
   EventUtils.synthesizeKey("w", { accelKey: true }, win);
   ok(win.closed, "Accel+W closes the popup");
 
   if (!win.closed)
     win.close();
   gBrowser.addTab();
   gBrowser.removeCurrentTab();
--- a/browser/components/migration/AutoMigrate.jsm
+++ b/browser/components/migration/AutoMigrate.jsm
@@ -9,22 +9,27 @@ this.EXPORTED_SYMBOLS = ["AutoMigrate"];
 const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
 
 const kAutoMigrateEnabledPref = "browser.migrate.automigrate.enabled";
 
 const kAutoMigrateStartedPref = "browser.migrate.automigrate.started";
 const kAutoMigrateFinishedPref = "browser.migrate.automigrate.finished";
 const kAutoMigrateBrowserPref = "browser.migrate.automigrate.browser";
 
+const kAutoMigrateLastUndoPromptDateMsPref = "browser.migrate.automigrate.lastUndoPromptDateMs";
+const kAutoMigrateDaysToOfferUndoPref = "browser.migrate.automigrate.daysToOfferUndo";
+
 const kPasswordManagerTopic = "passwordmgr-storage-changed";
 const kPasswordManagerTopicTypes = new Set([
   "addLogin",
   "modifyLogin",
 ]);
 
+const kNotificationId = "abouthome-automigration-undo";
+
 Cu.import("resource:///modules/MigrationUtils.jsm");
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/PlacesUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 const AutoMigrate = {
@@ -244,16 +249,110 @@ const AutoMigrate = {
       Services.obs.removeObserver(this, kPasswordManagerTopic);
     } catch (ex) {}
     try {
       PlacesUtils.removeLazyBookmarkObserver(this);
     } catch (ex) {}
     Services.prefs.clearUserPref(kAutoMigrateStartedPref);
     Services.prefs.clearUserPref(kAutoMigrateFinishedPref);
     Services.prefs.clearUserPref(kAutoMigrateBrowserPref);
+
+    let browserWindows = Services.wm.getEnumerator("navigator:browser");
+    while (browserWindows.hasMoreElements()) {
+      let win = browserWindows.getNext();
+      if (!win.closed) {
+        for (let browser of win.gBrowser.browsers) {
+          let nb = win.gBrowser.getNotificationBox(browser);
+          let notification = nb.getNotificationWithValue(kNotificationId);
+          if (notification) {
+            nb.removeNotification(notification);
+          }
+        }
+      }
+    }
+  },
+
+  getBrowserUsedForMigration() {
+    let browserId = Services.prefs.getCharPref(kAutoMigrateBrowserPref);
+    if (browserId) {
+      return MigrationUtils.getBrowserName(browserId);
+    }
+    return null;
+  },
+
+  maybeShowUndoNotification(target) {
+    this.canUndo().then(canUndo => {
+      // The tab might have navigated since we requested the undo state:
+      if (!canUndo || target.currentURI.spec != "about:home") {
+        return;
+      }
+      let win = target.ownerGlobal;
+      let notificationBox = win.gBrowser.getNotificationBox(target);
+      if (!notificationBox || notificationBox.getNotificationWithValue("abouthome-automigration-undo")) {
+        return;
+      }
+
+      // At this stage we're committed to show the prompt - unless we shouldn't,
+      // in which case we remove the undo prefs (which will cause canUndo() to
+      // return false from now on.):
+      if (!this.shouldStillShowUndoPrompt()) {
+        this.removeUndoOption();
+        return;
+      }
+
+      let browserName = this.getBrowserUsedForMigration();
+      let message;
+      if (browserName) {
+        message = MigrationUtils.getLocalizedString("automigration.undo.message",
+                                                    [browserName]);
+      } else {
+        message = MigrationUtils.getLocalizedString("automigration.undo.unknownBrowserMessage");
+      }
+
+      let buttons = [
+        {
+          label: MigrationUtils.getLocalizedString("automigration.undo.keep.label"),
+          accessKey: MigrationUtils.getLocalizedString("automigration.undo.keep.accesskey"),
+          callback: () => {
+            this.removeUndoOption();
+          },
+        },
+        {
+          label: MigrationUtils.getLocalizedString("automigration.undo.dontkeep.label"),
+          accessKey: MigrationUtils.getLocalizedString("automigration.undo.dontkeep.accesskey"),
+          callback: () => {
+            this.undo();
+          },
+        },
+      ];
+      notificationBox.appendNotification(
+        message, kNotificationId, null, notificationBox.PRIORITY_INFO_HIGH, buttons
+      );
+    });
+  },
+
+  shouldStillShowUndoPrompt() {
+    let today = new Date();
+    // Round down to midnight:
+    today = new Date(today.getFullYear(), today.getMonth(), today.getDate());
+    // We store the unix timestamp corresponding to midnight on the last day
+    // on which we prompted. Fetch that and compare it to today's date.
+    // (NB: stored as a string because int prefs are too small for unix
+    // timestamps.)
+    let previousPromptDateMsStr = Preferences.get(kAutoMigrateLastUndoPromptDateMsPref, "0");
+    let previousPromptDate = new Date(parseInt(previousPromptDateMsStr, 10));
+    if (previousPromptDate < today) {
+      let remainingDays = Preferences.get(kAutoMigrateDaysToOfferUndoPref, 4) - 1;
+      Preferences.set(kAutoMigrateDaysToOfferUndoPref, remainingDays);
+      Preferences.set(kAutoMigrateLastUndoPromptDateMsPref, today.valueOf().toString());
+      if (remainingDays <= 0) {
+        return false;
+      }
+    }
+    return true;
   },
 
   QueryInterface: XPCOMUtils.generateQI(
     [Ci.nsIObserver, Ci.nsINavBookmarkObserver, Ci.nsISupportsWeakReference]
   ),
 };
 
 AutoMigrate.init();
--- a/browser/components/migration/MigrationUtils.jsm
+++ b/browser/components/migration/MigrationUtils.jsm
@@ -5,29 +5,31 @@
 "use strict";
 
 this.EXPORTED_SYMBOLS = ["MigrationUtils", "MigratorPrototype"];
 
 const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
 const TOPIC_WILL_IMPORT_BOOKMARKS = "initial-migration-will-import-default-bookmarks";
 const TOPIC_DID_IMPORT_BOOKMARKS = "initial-migration-did-import-default-bookmarks";
 
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/AppConstants.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
-Cu.import("resource://gre/modules/AppConstants.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "AutoMigrate",
+                                  "resource:///modules/AutoMigrate.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "BookmarkHTMLUtils",
+                                  "resource://gre/modules/BookmarkHTMLUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "BookmarkHTMLUtils",
-                                  "resource://gre/modules/BookmarkHTMLUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
                                   "resource://gre/modules/PromiseUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "AutoMigrate",
-                                  "resource:///modules/AutoMigrate.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
+                                  "resource://gre/modules/TelemetryStopwatch.jsm");
 
 var gMigrators = null;
 var gProfileStartup = null;
 var gMigrationBundle = null;
 
 XPCOMUtils.defineLazyGetter(this, "gAvailableMigratorKeys", function() {
   if (AppConstants.platform == "win") {
     return [
@@ -192,16 +194,20 @@ this.MigratorPrototype = {
     let resources = this._getMaybeCachedResources(aProfile);
     if (!resources) {
       return [];
     }
     let types = resources.map(r => r.type);
     return types.reduce((a, b) => a |= b, 0);
   },
 
+  getKey: function MP_getKey() {
+    return this.contractID.match(/\=([^\=]+)$/)[1];
+  },
+
   /**
    * DO NOT OVERRIDE - After deCOMing migration, the UI will just call
    * migrate for each resource.
    *
    * @see nsIBrowserProfileMigrator
    */
   migrate: function MP_migrate(aItems, aStartup, aProfile) {
     let resources = this._getMaybeCachedResources(aProfile);
@@ -213,16 +219,41 @@ this.MigratorPrototype = {
 
     // Used to periodically give back control to the main-thread loop.
     let unblockMainThread = function () {
       return new Promise(resolve => {
         Services.tm.mainThread.dispatch(resolve, Ci.nsIThread.DISPATCH_NORMAL);
       });
     };
 
+    let getHistogramForResourceType = resourceType => {
+      if (resourceType == MigrationUtils.resourceTypes.HISTORY) {
+        return "FX_MIGRATION_HISTORY_IMPORT_MS";
+      }
+      if (resourceType == MigrationUtils.resourceTypes.BOOKMARKS) {
+        return "FX_MIGRATION_BOOKMARKS_IMPORT_MS";
+      }
+      if (resourceType == MigrationUtils.resourceTypes.PASSWORDS) {
+        return "FX_MIGRATION_LOGINS_IMPORT_MS";
+      }
+      return null;
+    };
+    let maybeStartTelemetryStopwatch = (resourceType, resource) => {
+      let histogram = getHistogramForResourceType(resourceType);
+      if (histogram) {
+        TelemetryStopwatch.startKeyed(histogram, this.getKey(), resource);
+      }
+    };
+    let maybeStopTelemetryStopwatch = (resourceType, resource) => {
+      let histogram = getHistogramForResourceType(resourceType);
+      if (histogram) {
+        TelemetryStopwatch.finishKeyed(histogram, this.getKey(), resource);
+      }
+    };
+
     // Called either directly or through the bookmarks import callback.
     let doMigrate = Task.async(function*() {
       let resourcesGroupedByItems = new Map();
       resources.forEach(function(resource) {
         if (!resourcesGroupedByItems.has(resource.type)) {
           resourcesGroupedByItems.set(resource.type, new Set());
         }
         resourcesGroupedByItems.get(resource.type).add(resource)
@@ -241,18 +272,20 @@ this.MigratorPrototype = {
         let migrationType = key, itemResources = value;
 
         notify("Migration:ItemBeforeMigrate", migrationType);
 
         let itemSuccess = false;
         for (let res of itemResources) {
           // Workaround bug 449811.
           let resource = res;
+          maybeStartTelemetryStopwatch(migrationType, resource);
           let completeDeferred = PromiseUtils.defer();
           let resourceDone = function(aSuccess) {
+            maybeStopTelemetryStopwatch(migrationType, resource);
             itemResources.delete(resource);
             itemSuccess |= aSuccess;
             if (itemResources.size == 0) {
               notify(itemSuccess ?
                      "Migration:ItemAfterMigrate" : "Migration:ItemError",
                      migrationType);
               resourcesGroupedByItems.delete(migrationType);
               if (resourcesGroupedByItems.size == 0) {
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -136,16 +136,23 @@
 @BINPATH@/@DLL_PREFIX@mozavcodec@DLL_SUFFIX@
 #endif
 @RESPATH@/browser/blocklist.xml
 #ifdef XP_UNIX
 #ifndef XP_MACOSX
 @RESPATH@/run-mozilla.sh
 #endif
 #endif
+#ifdef XP_WIN
+#ifdef _AMD64_
+@BINPATH@/@DLL_PREFIX@qipcap64@DLL_SUFFIX@
+#else
+@BINPATH@/@DLL_PREFIX@qipcap@DLL_SUFFIX@
+#endif
+#endif
 
 ; [Components]
 #ifdef MOZ_ARTIFACT_BUILDS
 @RESPATH@/components/prebuilt-interfaces.manifest
 @RESPATH@/components/interfaces.xpt
 @RESPATH@/browser/components/prebuilt-interfaces.manifest
 @RESPATH@/browser/components/interfaces.xpt
 #endif
--- a/browser/locales/en-US/chrome/browser/migration/migration.properties
+++ b/browser/locales/en-US/chrome/browser/migration/migration.properties
@@ -65,8 +65,16 @@ 32_360se=Bookmarks
 64_ie=Other Data
 64_edge=Other Data
 64_safari=Other Data
 64_chrome=Other Data
 64_firefox_other=Other Data
 64_360se=Other Data
 
 128_firefox=Windows and Tabs
+
+# Automigration undo notification.
+automigration.undo.message               = We automatically imported your data from %S. Would you like to keep it?
+automigration.undo.unknownBrowserMessage = We automatically imported your data from another browser. Would you like to keep it?
+automigration.undo.keep.label            = Keep
+automigration.undo.keep.accesskey        = K
+automigration.undo.dontkeep.label        = Don’t Keep
+automigration.undo.dontkeep.accesskey    = D
--- a/browser/modules/AboutHome.jsm
+++ b/browser/modules/AboutHome.jsm
@@ -10,20 +10,22 @@ var Cu = Components.utils;
 
 this.EXPORTED_SYMBOLS = [ "AboutHomeUtils", "AboutHome" ];
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
   "resource://gre/modules/AppConstants.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AutoMigrate",
+  "resource:///modules/AutoMigrate.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
+  "resource://gre/modules/FxAccounts.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
   "resource://gre/modules/PrivateBrowsingUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
-  "resource://gre/modules/FxAccounts.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
   "resource://gre/modules/Promise.jsm");
 
 // Url to fetch snippets, in the urlFormatter service format.
 const SNIPPETS_URL_PREF = "browser.aboutHomeSnippets.updateUrl";
 
 // Should be bumped up if the snippets content format changes.
 const STARTPAGE_VERSION = 4;
@@ -94,16 +96,17 @@ var AboutHome = {
     "AboutHome:RestorePreviousSession",
     "AboutHome:Downloads",
     "AboutHome:Bookmarks",
     "AboutHome:History",
     "AboutHome:Addons",
     "AboutHome:Sync",
     "AboutHome:Settings",
     "AboutHome:RequestUpdate",
+    "AboutHome:MaybeShowAutoMigrationUndoNotification",
   ],
 
   init: function() {
     let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
 
     for (let msg of this.MESSAGES) {
       mm.addMessageListener(msg, this);
     }
@@ -143,16 +146,20 @@ var AboutHome = {
 
       case "AboutHome:Settings":
         window.openPreferences();
         break;
 
       case "AboutHome:RequestUpdate":
         this.sendAboutHomeData(aMessage.target);
         break;
+
+      case "AboutHome:MaybeShowAutoMigrationUndoNotification":
+        AutoMigrate.maybeShowUndoNotification(aMessage.target);
+        break;
     }
   },
 
   // Send all the chrome-privileged data needed by about:home. This
   // gets re-sent when the search engine changes.
   sendAboutHomeData: function(target) {
     let wrapper = {};
     Components.utils.import("resource:///modules/sessionstore/SessionStore.jsm",
@@ -177,10 +184,11 @@ var AboutHome = {
         target.messageManager.sendAsyncMessage("AboutHome:Update", data);
       } else {
         let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
         mm.broadcastAsyncMessage("AboutHome:Update", data);
       }
     }).then(null, function onError(x) {
       Cu.reportError("Error in AboutHome.sendAboutHomeData: " + x);
     });
-  }
+  },
+
 };
--- a/browser/themes/shared/controlcenter/panel.inc.css
+++ b/browser/themes/shared/controlcenter/panel.inc.css
@@ -392,17 +392,17 @@ description#identity-popup-content-verif
 
 .identity-popup-permission-label {
   margin-inline-start: 1em;
 }
 
 .identity-popup-permission-state-label {
   margin-inline-end: 5px;
   text-align: end;
-  opacity: 0.6;
+  color: graytext;
 }
 
 .identity-popup-permission-remove-button {
   -moz-appearance: none;
   margin: 0;
   border-width: 0;
   border-radius: 50%;
   min-width: 0;
@@ -416,26 +416,27 @@ description#identity-popup-content-verif
 }
 
 .identity-popup-permission-remove-button > .button-box > .button-icon {
   margin: 0;
   width: 16px;
   height: 16px;
   list-style-image: url(chrome://browser/skin/panel-icons.svg#cancel);
   filter: url(chrome://browser/skin/filters.svg#fill);
-  fill: #999;
+  fill: graytext;
 }
 
 .identity-popup-permission-remove-button > .button-box > .button-text {
   display: none;
 }
 
+/* swap foreground / background colors on hover */
 .identity-popup-permission-remove-button:hover {
-  background-color: #999;
+  background-color: graytext;
 }
 
 .identity-popup-permission-remove-button:hover > .button-box > .button-icon {
-  fill: #fff;
+  fill: -moz-field;
 }
 
 .identity-popup-permission-remove-button:hover:active {
-  background-color: #808080;
+  background-color: -moz-fieldtext;
 }
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -45,16 +45,22 @@
   --urlbar-dropmarker-hover-2x-region: rect(0, 44px, 28px, 22px);
   --urlbar-dropmarker-active-2x-region: rect(0, 66px, 28px, 44px);
 
   --panel-separator-color: ThreeDLightShadow;
 
   --urlbar-separator-color: ThreeDLightShadow;
 }
 
+@media (-moz-windows-default-theme) {
+  :root {
+    --panel-separator-color: hsla(210,4%,10%,.14);
+  }
+}
+
 #nav-bar[brighttext] {
   --toolbarbutton-hover-background: rgba(255,255,255,.25);
   --toolbarbutton-hover-bordercolor: rgba(255,255,255,.5);
 
   --toolbarbutton-active-background: rgba(255,255,255,.4);
   --toolbarbutton-active-bordercolor: rgba(255,255,255,.7);
   --toolbarbutton-active-boxshadow: 0 0 0 1px rgba(255,255,255,.4) inset;
 
--- a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-08.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-08.js
@@ -35,17 +35,17 @@ var test = Task.async(function* () {
   }
   ok(obj, "Should have found the 'obj' variable");
 
   info("Expanding variable 'obj'");
   let expanded = once(variables, "fetched");
   obj.expand();
   yield expanded;
 
-  let values = [" ", "\r", "\n", "\t", "\f", "\uFEFF", "\xA0"];
+  let values = ["", " ", "\r", "\n", "\t", "\f", "\uFEFF", "\xA0"];
   let count = values.length;
 
   for (let [property, value] of obj) {
     let index = values.indexOf(property);
     if (index >= 0) {
       --count;
       is(value._nameString, property,
          "The _nameString is different than the property name");
--- a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-data.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-data.js
@@ -84,17 +84,17 @@ function performTest() {
   closeDebuggerAndFinish(gPanel);
 }
 
 function testHierarchy() {
   is(gVariablesView._currHierarchy.size, 13,
     "There should be 1 scope, 1 var, 1 proto, 8 props, 1 getter and 1 setter.");
 
   gScope = gVariablesView._currHierarchy.get("");
-  gVariable = gVariablesView._currHierarchy.get("[\"\"]");
+  gVariable = gVariablesView._currHierarchy.get("[]");
 
   is(gVariablesView._store.length, 1,
     "There should be only one scope in the view.");
   is(gScope._store.size, 1,
     "There should be only one variable in the scope.");
   is(gVariable._store.size, 9,
     "There should be 1 __proto__ and 8 properties in the variable.");
 }
--- a/devtools/client/debugger/test/mochitest/doc_whitespace-property-names.html
+++ b/devtools/client/debugger/test/mochitest/doc_whitespace-property-names.html
@@ -5,25 +5,25 @@
 <html>
   <head>
     <meta charset="utf-8"/>
     <title>Debugger + Whitespace property name test page</title>
   </head>
 
   <body>
     <script>
-    window.obj = {
-      " ": 0,
-      "\r": 1,
-      "\n": 2,
-      "\t": 3,
-      "\f": 4,
-      "\uFEFF": 5,
-      "\xA0": 6
-    };
     window.doPause = function () {
-      var obj = window.obj;
+      var obj = {
+        "": 0,
+        " ": 1,
+        "\r": 2,
+        "\n": 3,
+        "\t": 4,
+        "\f": 5,
+        "\uFEFF": 6,
+        "\xA0": 7
+      };
       debugger;
     };
     </script>
   </body>
 
 </html>
--- a/devtools/client/inspector/inspector-search.js
+++ b/devtools/client/inspector/inspector-search.js
@@ -149,16 +149,17 @@ InspectorSearch.prototype = {
       console.error(e);
     }
   },
 
   _onClearSearch: function () {
     this.searchBox.classList.remove("devtools-style-searchbox-no-match");
     this.searchBox.value = "";
     this.searchClearButton.hidden = true;
+    this.emit("search-cleared");
   }
 };
 
 /**
  * Converts any input box on a page to a CSS selector search and suggestion box.
  *
  * Emits 'processing-done' event when it is done processing the current
  * keypress, search request or selection from the list, whether that led to a
--- a/devtools/client/inspector/markup/test/actor_events_form.js
+++ b/devtools/client/inspector/markup/test/actor_events_form.js
@@ -3,17 +3,17 @@
 
 "use strict";
 
 // This test actor is used for testing the addition of custom form data
 // on NodeActor. Custom form property is set when 'form' event is sent
 // by NodeActor actor (see 'onNodeActorForm' method).
 
 const Events = require("sdk/event/core");
-const {ActorClass, Actor, FrontClass, Front, generateActorSpec} =
+const {ActorClassWithSpec, Actor, FrontClassWithSpec, Front, generateActorSpec} =
   require("devtools/shared/protocol");
 
 const {NodeActor} = require("devtools/server/actors/inspector");
 
 var eventsSpec = generateActorSpec({
   typeName: "eventsFormActor",
 
   methods: {
@@ -23,17 +23,17 @@ var eventsSpec = generateActorSpec({
     },
     detach: {
       request: {},
       response: {}
     }
   }
 });
 
-var EventsFormActor = ActorClass(eventsSpec, {
+var EventsFormActor = ActorClassWithSpec(eventsSpec, {
   initialize: function () {
     Actor.prototype.initialize.apply(this, arguments);
   },
 
   attach: function () {
     Events.on(NodeActor, "form", this.onNodeActorForm);
   },
 
@@ -45,17 +45,17 @@ var EventsFormActor = ActorClass(eventsS
     let nodeActor = event.target;
     if (nodeActor.rawNode.id == "container") {
       let form = event.data;
       form.setFormProperty("test-property", "test-value");
     }
   }
 });
 
-var EventsFormFront = FrontClass(eventsSpec, {
+var EventsFormFront = FrontClassWithSpec(eventsSpec, {
   initialize: function (client, form) {
     Front.prototype.initialize.apply(this, arguments);
 
     this.actorID = form[EventsFormActor.prototype.typeName];
     this.manage(this);
   }
 });
 
--- a/devtools/client/inspector/markup/test/head.js
+++ b/devtools/client/inspector/markup/test/head.js
@@ -465,18 +465,18 @@ function createTestHTTPServer() {
 /**
  * Registers new backend tab actor.
  *
  * @param {DebuggerClient} client RDP client object (toolbox.target.client)
  * @param {Object} options Configuration object with the following options:
  *
  * - moduleUrl {String}: URL of the module that contains actor implementation.
  * - prefix {String}: prefix of the actor.
- * - actorClass {ActorClass}: Constructor object for the actor.
- * - frontClass {FrontClass}: Constructor object for the front part
+ * - actorClass {ActorClassWithSpec}: Constructor object for the actor.
+ * - frontClass {FrontClassWithSpec}: Constructor object for the front part
  * of the registered actor.
  *
  * @returns {Promise} A promise that is resolved when the actor is registered.
  * The resolved value has two properties:
  *
  * - registrar {ActorActor}: A handle to the registered actor that allows
  * unregistration.
  * - form {Object}: The JSON actor form provided by the server.
--- a/devtools/client/inspector/test/browser.ini
+++ b/devtools/client/inspector/test/browser.ini
@@ -141,16 +141,17 @@ skip-if = os == "mac" # Full keyboard na
 [browser_inspector_search-03.js]
 [browser_inspector_search-04.js]
 [browser_inspector_search-05.js]
 [browser_inspector_search-06.js]
 [browser_inspector_search-07.js]
 [browser_inspector_search-08.js]
 [browser_inspector_search-filter_context-menu.js]
 [browser_inspector_search_keyboard_trap.js]
+[browser_inspector_search-label.js]
 [browser_inspector_search-reserved.js]
 [browser_inspector_search-selection.js]
 [browser_inspector_search-sidebar.js]
 [browser_inspector_select-docshell.js]
 [browser_inspector_select-last-selected.js]
 [browser_inspector_search-navigation.js]
 [browser_inspector_sidebarstate.js]
 [browser_inspector_switch-to-inspector-on-pick.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/test/browser_inspector_search-label.js
@@ -0,0 +1,33 @@
+/* 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";
+
+// Check that search label updated correctcly based on the search result.
+
+const TEST_URL = URL_ROOT + "doc_inspector_search.html";
+
+add_task(function* () {
+  let { inspector } = yield openInspectorForURL(TEST_URL);
+  let { panelWin, searchResultsLabel } = inspector;
+
+  info("Searching for test node #d1");
+  // Expect the label shows 1 result
+  yield focusSearchBoxUsingShortcut(panelWin);
+  synthesizeKeys("#d1", panelWin);
+  EventUtils.synthesizeKey("VK_RETURN", {}, panelWin);
+
+  yield inspector.search.once("search-result");
+  is(searchResultsLabel.textContent, "1 of 1");
+
+  info("Click the clear button");
+  // Expect the label is cleared after clicking the clear button.
+
+  inspector.searchClearButton.click();
+  is(searchResultsLabel.textContent, "");
+
+  // Catch-all event for remaining server requests when searching for the new
+  // node.
+  yield inspector.once("inspector-updated");
+});
--- a/devtools/client/responsive.html/app.js
+++ b/devtools/client/responsive.html/app.js
@@ -38,17 +38,21 @@ let App = createClass({
     viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired,
   },
 
   onBrowserMounted() {
     window.postMessage({ type: "browser-mounted" }, "*");
   },
 
   onChangeViewportDevice(id, device) {
-    this.props.dispatch(changeDevice(id, device));
+    window.postMessage({
+      type: "update-user-agent",
+      userAgent: device.userAgent
+    }, "*");
+    this.props.dispatch(changeDevice(id, device.name));
   },
 
   onContentResize({ width, height }) {
     window.postMessage({
       type: "content-resize",
       width,
       height,
     }, "*");
--- a/devtools/client/responsive.html/components/device-selector.js
+++ b/devtools/client/responsive.html/components/device-selector.js
@@ -31,27 +31,25 @@ module.exports = createClass({
       onResizeViewport,
       onUpdateDeviceModalOpen,
     } = this.props;
 
     if (target.value === OPEN_DEVICE_MODAL_VALUE) {
       onUpdateDeviceModalOpen(true);
       return;
     }
-
     for (let type of devices.types) {
       for (let device of devices[type]) {
         if (device.name === target.value) {
           onResizeViewport(device.width, device.height);
-          break;
+          onChangeViewportDevice(device);
+          return;
         }
       }
     }
-
-    onChangeViewportDevice(target.value);
   },
 
   render() {
     let {
       devices,
       selectedDevice,
     } = this.props;
 
--- a/devtools/client/responsive.html/components/resizable-viewport.js
+++ b/devtools/client/responsive.html/components/resizable-viewport.js
@@ -102,17 +102,17 @@ module.exports = createClass({
       height = VIEWPORT_MIN_HEIGHT;
     } else {
       lastClientY = clientY;
     }
 
     // Update the viewport store with the new width and height.
     this.props.onResizeViewport(width, height);
     // Change the device selector back to an unselected device
-    this.props.onChangeViewportDevice("");
+    this.props.onChangeViewportDevice({ name: "" });
 
     this.setState({
       lastClientX,
       lastClientY
     });
   },
 
   render() {
--- a/devtools/client/responsive.html/components/viewport-dimension.js
+++ b/devtools/client/responsive.html/components/viewport-dimension.js
@@ -109,17 +109,17 @@ module.exports = createClass({
         height,
         isInvalid: false,
       });
 
       return;
     }
 
     // Change the device selector back to an unselected device
-    this.props.onChangeViewportDevice("");
+    this.props.onChangeViewportDevice({ name: "" });
     this.props.onResizeViewport(parseInt(this.state.width, 10),
                                 parseInt(this.state.height, 10));
   },
 
   render() {
     let editableClass = "viewport-dimension-editable";
     let inputClass = "viewport-dimension-input";
 
--- a/devtools/client/responsive.html/manager.js
+++ b/devtools/client/responsive.html/manager.js
@@ -393,32 +393,44 @@ ResponsiveUI.prototype = {
         break;
       case "exit":
         ResponsiveUIManager.closeIfNeeded(browserWindow, tab);
         break;
       case "update-touch-simulation":
         let { enabled } = event.data;
         this.updateTouchSimulation(enabled);
         break;
+      case "update-user-agent":
+        let { userAgent } = event.data;
+        this.updateUserAgent(userAgent);
+        break;
     }
   },
 
   updateTouchSimulation: Task.async(function* (enabled) {
     if (enabled) {
       let reloadNeeded = yield this.emulationFront.setTouchEventsOverride(
         Ci.nsIDocShell.TOUCHEVENTS_OVERRIDE_ENABLED
       );
       if (reloadNeeded) {
         this.getViewportBrowser().reload();
       }
     } else {
       this.emulationFront.clearTouchEventsOverride();
     }
   }),
 
+  updateUserAgent: function (userAgent) {
+    if (userAgent) {
+      this.emulationFront.setUserAgentOverride(userAgent);
+    } else {
+      this.emulationFront.clearUserAgentOverride();
+    }
+  },
+
   /**
    * Helper for tests. Assumes a single viewport for now.
    */
   getViewportSize() {
     return this.toolWindow.getViewportSize();
   },
 
   /**
--- a/devtools/client/responsive.html/test/browser/browser.ini
+++ b/devtools/client/responsive.html/test/browser/browser.ini
@@ -10,16 +10,17 @@ support-files =
   head.js
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/framework/test/shared-head.js
   !/devtools/client/framework/test/shared-redux-head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/test-actor.js
   !/devtools/client/shared/test/test-actor-registry.js
 
+[browser_device_change.js]
 [browser_device_modal_error.js]
 [browser_device_modal_exit.js]
 [browser_device_modal_submit.js]
 [browser_device_width.js]
 [browser_exit_button.js]
 [browser_frame_script_active.js]
 [browser_menu_item_01.js]
 [browser_menu_item_02.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/test/browser/browser_device_change.js
@@ -0,0 +1,57 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests changing viewport device
+const TEST_URL = "data:text/html;charset=utf-8,Device list test";
+const DEFAULT_UA = Cc["@mozilla.org/network/protocol;1?name=http"]
+  .getService(Ci.nsIHttpProtocolHandler)
+  .userAgent;
+const NOKIA_UA = "Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; " +
+  "Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 520)";
+
+addRDMTask(TEST_URL, function* ({ ui, manager }) {
+  // Test defaults
+  testViewportDimensions(ui, 320, 480);
+  yield testUserAgent(ui, DEFAULT_UA);
+  testViewportSelectLabel(ui, "no device selected");
+
+  // Test device with custom UA
+  switchDevice(ui, "Nokia Lumia 520");
+  yield waitForViewportResizeTo(ui, 320, 533);
+  yield testUserAgent(ui, NOKIA_UA);
+
+  // Test resetting device when resizing viewport
+  yield testViewportResize(ui, ".viewport-vertical-resize-handle",
+    [-10, -10], [320, 523], [0, -10], ui);
+  yield testUserAgent(ui, DEFAULT_UA);
+  testViewportSelectLabel(ui, "no device selected");
+
+  // Test device where UA field is blank
+  switchDevice(ui, "Laptop (1366 x 768)");
+  yield waitForViewportResizeTo(ui, 1366, 768);
+  yield testUserAgent(ui, DEFAULT_UA);
+});
+
+function testViewportDimensions(ui, w, h) {
+  let viewport = ui.toolWindow.document.querySelector(".viewport-content");
+
+  is(ui.toolWindow.getComputedStyle(viewport).getPropertyValue("width"),
+     `${w}px`, `Viewport should have width of ${w}px`);
+  is(ui.toolWindow.getComputedStyle(viewport).getPropertyValue("height"),
+     `${h}px`, `Viewport should have height of ${h}px`);
+}
+
+function testViewportSelectLabel(ui, label) {
+  let select = ui.toolWindow.document.querySelector(".viewport-device-selector");
+  is(select.selectedOptions[0].textContent, label,
+     `Select label should be changed to ${label}`);
+}
+
+function* testUserAgent(ui, value) {
+  let ua = yield ContentTask.spawn(ui.getViewportBrowser(), {}, function* () {
+    return content.navigator.userAgent;
+  });
+  is(ua, value, `UA should be set to ${value}`);
+}
--- a/devtools/client/responsive.html/test/browser/browser_mouse_resize.js
+++ b/devtools/client/responsive.html/test/browser/browser_mouse_resize.js
@@ -1,51 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
 http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 const TEST_URL = "data:text/html;charset=utf-8,";
 
-function getElRect(selector, win) {
-  let el = win.document.querySelector(selector);
-  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 rect = getElRect(selector, win);
-  let startPoint = [ rect.left + rect.width / 2, rect.top + rect.height / 2 ];
-  let endPoint = [ startPoint[0] + x, startPoint[1] + y ];
-
-  EventUtils.synthesizeMouseAtPoint(...startPoint, { type: "mousedown" }, win);
-  EventUtils.synthesizeMouseAtPoint(...endPoint, { type: "mousemove" }, win);
-  EventUtils.synthesizeMouseAtPoint(...endPoint, { type: "mouseup" }, win);
-
-  return rect;
-}
-
-function* testViewportResize(ui, selector, moveBy,
-                             expectedViewportSize, expectedHandleMove) {
-  let win = ui.toolWindow;
-
-  let resized = waitForViewportResizeTo(ui, ...expectedViewportSize);
-  let startRect = dragElementBy(selector, ...moveBy, win);
-  yield resized;
-
-  let endRect = getElRect(selector, win);
-  is(endRect.left - startRect.left, expectedHandleMove[0],
-    `The x move of ${selector} is as expected`);
-  is(endRect.top - startRect.top, expectedHandleMove[1],
-    `The y move of ${selector} is as expected`);
-}
-
 addRDMTask(TEST_URL, function* ({ ui, manager }) {
   ok(ui, "An instance of the RDM should be attached to the tab.");
   yield setViewportSize(ui, manager, 300, 300);
 
   // Do horizontal + vertical resize
   yield testViewportResize(ui, ".viewport-resize-handle",
     [10, 10], [320, 310], [10, 10]);
 
--- a/devtools/client/responsive.html/test/browser/head.js
+++ b/devtools/client/responsive.html/test/browser/head.js
@@ -142,16 +142,52 @@ var setViewportSize = Task.async(functio
        `set to: ${width} x ${height}`);
   if (size.width != width || size.height != height) {
     let resized = waitForViewportResizeTo(ui, width, height);
     ui.setViewportSize(width, height);
     yield resized;
   }
 });
 
+function getElRect(selector, win) {
+  let el = win.document.querySelector(selector);
+  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 rect = getElRect(selector, win);
+  let startPoint = [ rect.left + rect.width / 2, rect.top + rect.height / 2 ];
+  let endPoint = [ startPoint[0] + x, startPoint[1] + y ];
+
+  EventUtils.synthesizeMouseAtPoint(...startPoint, { type: "mousedown" }, win);
+  EventUtils.synthesizeMouseAtPoint(...endPoint, { type: "mousemove" }, win);
+  EventUtils.synthesizeMouseAtPoint(...endPoint, { type: "mouseup" }, win);
+
+  return rect;
+}
+
+function* testViewportResize(ui, selector, moveBy,
+                             expectedViewportSize, expectedHandleMove) {
+  let win = ui.toolWindow;
+
+  let resized = waitForViewportResizeTo(ui, ...expectedViewportSize);
+  let startRect = dragElementBy(selector, ...moveBy, win);
+  yield resized;
+
+  let endRect = getElRect(selector, win);
+  is(endRect.left - startRect.left, expectedHandleMove[0],
+    `The x move of ${selector} is as expected`);
+  is(endRect.top - startRect.top, expectedHandleMove[1],
+    `The y move of ${selector} is as expected`);
+}
+
 function openDeviceModal(ui) {
   let { document } = ui.toolWindow;
   let select = document.querySelector(".viewport-device-selector");
   let modal = document.querySelector("#device-modal-wrapper");
   let editDeviceOption = [...select.options].filter(o => {
     return o.value === OPEN_DEVICE_MODAL_VALUE;
   })[0];
 
@@ -164,16 +200,30 @@ function openDeviceModal(ui) {
     ui.toolWindow);
   EventUtils.synthesizeMouseAtCenter(editDeviceOption, {type: "mouseup"},
     ui.toolWindow);
 
   ok(modal.classList.contains("opened") && !modal.classList.contains("closed"),
     "The device modal is displayed.");
 }
 
+function switchDevice(ui, device) {
+  let { document } = ui.toolWindow;
+  let select = document.querySelector(".viewport-device-selector");
+  select.scrollIntoView();
+  let deviceOption = [...select.options].filter(o => {
+    return o.value === device;
+  })[0];
+  EventUtils.synthesizeMouseAtCenter(select, {type: "mousedown"},
+    ui.toolWindow);
+  EventUtils.synthesizeMouseAtCenter(deviceOption, {type: "mouseup"},
+    ui.toolWindow);
+  is(select.selectedOptions[0], deviceOption, "Device should be selected");
+}
+
 function getSessionHistory(browser) {
   return ContentTask.spawn(browser, {}, function* () {
     /* eslint-disable no-undef */
     let { interfaces: Ci } = Components;
     let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
     let sessionHistory = webNav.sessionHistory;
     let result = {
       index: sessionHistory.index,
--- a/devtools/client/responsivedesign/responsivedesign.jsm
+++ b/devtools/client/responsivedesign/responsivedesign.jsm
@@ -14,16 +14,17 @@ var {TouchEventSimulator} = require("dev
 var {Task} = require("devtools/shared/task");
 var promise = require("promise");
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
 var flags = require("devtools/shared/flags");
 var Services = require("Services");
 var EventEmitter = require("devtools/shared/event-emitter");
 var {ViewHelpers} = require("devtools/client/shared/widgets/view-helpers");
 var { LocalizationHelper } = require("devtools/client/shared/l10n");
+var { EmulationFront } = require("devtools/shared/fronts/emulation");
 
 loader.lazyImporter(this, "SystemAppProxy",
                     "resource://gre/modules/SystemAppProxy.jsm");
 loader.lazyRequireGetter(this, "DebuggerClient",
                          "devtools/shared/client/main", true);
 loader.lazyRequireGetter(this, "DebuggerServer",
                          "devtools/server/main", true);
 
@@ -269,21 +270,18 @@ ResponsiveUI.prototype = {
   connectToServer: Task.async(function* () {
     if (!DebuggerServer.initialized) {
       DebuggerServer.init();
       DebuggerServer.addBrowserActors();
     }
     this.client = new DebuggerClient(DebuggerServer.connectPipe());
     yield this.client.connect();
     let {tab} = yield this.client.getTab();
-    let [response, tabClient] = yield this.client.attachTab(tab.actor);
-    this.tabClient = tabClient;
-    if (!tabClient) {
-      console.error(new Error("Responsive Mode: failed to attach tab"));
-    }
+    yield this.client.attachTab(tab.actor);
+    this.emulationFront = EmulationFront(this.client, tab);
   }),
 
   loadPresets: function () {
     // Try to load presets from prefs
     let presets = defaultPresets;
     if (Services.prefs.prefHasUserValue("devtools.responsiveUI.presets")) {
       try {
         presets = JSON.parse(Services.prefs.getCharPref("devtools.responsiveUI.presets"));
@@ -387,17 +385,17 @@ ResponsiveUI.prototype = {
 
     ActiveTabs.delete(this.tab);
     if (this.touchEventSimulator) {
       this.touchEventSimulator.stop();
     }
 
     yield new Promise((resolve, reject) => {
       this.client.close(resolve);
-      this.client = this.tabClient = null;
+      this.client = this.emulationFront = null;
     });
 
     this._telemetry.toolClosed("responsive");
 
     if (this.tab.linkedBrowser.messageManager) {
       let stopped = this.waitForMessage("ResponsiveMode:Stop:Done");
       this.tab.linkedBrowser.messageManager.sendAsyncMessage("ResponsiveMode:Stop");
       yield stopped;
@@ -969,28 +967,29 @@ ResponsiveUI.prototype = {
     return navigatedDeferred.promise;
   },
 
   /**
    * Change the user agent string
    */
   changeUA: Task.async(function* () {
     let value = this.userAgentInput.value;
+    let changed;
     if (value) {
+      changed = yield this.emulationFront.setUserAgentOverride(value);
       this.userAgentInput.setAttribute("attention", "true");
     } else {
+      changed = yield this.emulationFront.clearUserAgentOverride();
       this.userAgentInput.removeAttribute("attention");
     }
-
-    // Changing the UA triggers an automatic reload.  Ensure we wait for this to
-    // complete before emitting the changed event, so that tests wait for the
-    // reload.
-    let reloaded = this.waitForReload();
-    yield this.tabClient.reconfigure({customUserAgent: value});
-    yield reloaded;
+    if (changed) {
+      let reloaded = this.waitForReload();
+      this.tab.linkedBrowser.reload();
+      yield reloaded;
+    }
     ResponsiveUIManager.emit("userAgentChanged", { tab: this.tab });
   }),
 
   /**
    * Get the current width and height.
    */
   getSize() {
     let width = Number(this.stack.style.minWidth.replace("px", ""));
--- a/devtools/client/shared/components/reps/event.js
+++ b/devtools/client/shared/components/reps/event.js
@@ -20,28 +20,52 @@ define(function (require, exports, modul
   let Event = React.createClass({
     displayName: "event",
 
     propTypes: {
       object: React.PropTypes.object.isRequired
     },
 
     render: function () {
-      // Use `Object.assign` to keep `this.props` without changes becuase:
+      // Use `Object.assign` to keep `this.props` without changes because:
       // 1. JSON.stringify/JSON.parse is slow.
       // 2. Immutable.js is planned for the future.
       let props = Object.assign({}, this.props);
       props.object = Object.assign({}, this.props.object);
       props.object.preview = Object.assign({}, this.props.object.preview);
       props.object.preview.ownProperties = props.object.preview.properties;
       delete props.object.preview.properties;
       props.object.ownPropertyLength =
         Object.keys(props.object.preview.ownProperties).length;
+
+      switch (props.object.class) {
+        case "MouseEvent":
+          props.isInterestingProp = (type, value, name) => {
+            return (name == "clientX" ||
+                    name == "clientY" ||
+                    name == "layerX" ||
+                    name == "layerY");
+          };
+          break;
+        case "KeyboardEvent":
+          props.isInterestingProp = (type, value, name) => {
+            return (name == "key" ||
+                    name == "charCode" ||
+                    name == "keyCode");
+          };
+          break;
+        case "MessageEvent":
+          props.isInterestingProp = (type, value, name) => {
+            return (name == "isTrusted" ||
+                    name == "data");
+          };
+          break;
+      }
       return rep(props);
-    },
+    }
   });
 
   // Registration
 
   function supportsObject(grip, type) {
     if (!isGrip(grip)) {
       return false;
     }
--- a/devtools/client/shared/components/reps/grip.js
+++ b/devtools/client/shared/components/reps/grip.js
@@ -22,16 +22,17 @@ define(function (require, exports, modul
    * for this rep component.
    */
   const GripRep = React.createClass({
     displayName: "Grip",
 
     propTypes: {
       object: React.PropTypes.object.isRequired,
       mode: React.PropTypes.string,
+      isInterestingProp: React.PropTypes.func
     },
 
     getTitle: function (object) {
       if (this.props.objectLink) {
         return this.props.objectLink({
           object: object
         }, object.class);
       }
@@ -45,31 +46,31 @@ define(function (require, exports, modul
       } catch (err) {
         console.error(err);
       }
       return [];
     },
 
     propIterator: function (object, max) {
       // Property filter. Show only interesting properties to the user.
-      let isInterestingProp = (type, value) => {
+      let isInterestingProp = this.props.isInterestingProp || ((type, value) => {
         return (
           type == "boolean" ||
           type == "number" ||
           (type == "string" && value.length != 0)
         );
-      };
+      });
 
       let ownProperties = object.preview ? object.preview.ownProperties : [];
       let indexes = this.getPropIndexes(ownProperties, max, isInterestingProp);
       if (indexes.length < max && indexes.length < object.ownPropertyLength) {
         // There are not enough props yet. Then add uninteresting props to display them.
         indexes = indexes.concat(
-          this.getPropIndexes(ownProperties, max - indexes.length, (t, value) => {
-            return !isInterestingProp(t, value);
+          this.getPropIndexes(ownProperties, max - indexes.length, (t, value, name) => {
+            return !isInterestingProp(t, value, name);
           })
         );
       }
 
       let props = this.getProps(ownProperties, indexes);
       if (props.length < object.ownPropertyLength) {
         // There are some undisplayed props. Then display "more...".
         let objectLink = this.props.objectLink || span;
@@ -147,17 +148,17 @@ define(function (require, exports, modul
           let prop = ownProperties[name];
           let value = prop.value !== undefined ? prop.value : prop;
 
           // Type is specified in grip's "class" field and for primitive
           // values use typeof.
           let type = (value.class || typeof value);
           type = type.toLowerCase();
 
-          if (filter(type, value)) {
+          if (filter(type, value, name)) {
             indexes.push(i);
           }
           i++;
         }
       } catch (err) {
         console.error(err);
       }
 
--- a/devtools/client/shared/components/test/mochitest/test_reps_event.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_event.html
@@ -38,17 +38,17 @@ window.onload = Task.async(function* () 
     is(renderedComponent.textContent,
        "Event { isTrusted: true, eventPhase: 2, bubbles: false, 7 more… }",
        "Event rep has expected text content for an event");
   }
 
   function testMouseEvent() {
     const renderedComponent = renderComponent(Event.rep, { object: getGripStub("testMouseEvent") });
     is(renderedComponent.textContent,
-       "MouseEvent { buttons: 0, clientX: 62, clientY: 18, 2 more… }",
+       "MouseEvent { clientX: 62, clientY: 18, layerX: 0, 2 more… }",
        "Event rep has expected text content for a mouse event");
   }
 
   function testKeyboardEvent() {
     const renderedComponent = renderComponent(Event.rep, { object: getGripStub("testKeyboardEvent") });
     is(renderedComponent.textContent,
        "KeyboardEvent { key: \"Control\", charCode: 0, keyCode: 17 }",
        "Event rep has expected text content for a keyboard event");
--- a/devtools/client/shared/test/test-actor.js
+++ b/devtools/client/shared/test/test-actor.js
@@ -259,17 +259,17 @@ var testSpec = protocol.generateActorSpe
       },
       response: {
         value: RetVal("json")
       }
     }
   }
 });
 
-var TestActor = exports.TestActor = protocol.ActorClass(testSpec, {
+var TestActor = exports.TestActor = protocol.ActorClassWithSpec(testSpec, {
   initialize: function (conn, tabActor, options) {
     this.conn = conn;
     this.tabActor = tabActor;
   },
 
   get content() {
     return this.tabActor.window;
   },
@@ -723,17 +723,17 @@ var TestActor = exports.TestActor = prot
         textContent: node.textContent
       };
     }
 
     return info;
   }
 });
 
-var TestActorFront = exports.TestActorFront = protocol.FrontClass(testSpec, {
+var TestActorFront = exports.TestActorFront = protocol.FrontClassWithSpec(testSpec, {
   initialize: function (client, { testActor }, toolbox) {
     protocol.Front.prototype.initialize.call(this, client, { actor: testActor });
     this.manage(this);
     this.toolbox = toolbox;
   },
 
   /**
    * Zoom the current page to a given level.
--- a/devtools/client/shared/widgets/VariablesView.jsm
+++ b/devtools/client/shared/widgets/VariablesView.jsm
@@ -112,17 +112,17 @@ VariablesView.prototype = {
    *
    * @param object aObject
    *        The raw object to display. You can only provide this object
    *        if you want the variables view to work in sync mode.
    */
   set rawObject(aObject) {
     this.empty();
     this.addScope()
-        .addItem("", { enumerable: true })
+        .addItem(undefined, { enumerable: true })
         .populate(aObject, { sorted: true });
   },
 
   /**
    * Adds a scope to contain any inspected variables.
    *
    * This new scope will be considered the parent of any other scope
    * added afterwards.
@@ -556,17 +556,17 @@ VariablesView.prototype = {
    * just unhidden.
    *
    * @param string aToken
    *        The variable or property to search for.
    */
   _doSearch: function (aToken) {
     if (this.controller && this.controller.supportsSearch()) {
       // Retrieve the main Scope in which we add attributes
-      let scope = this._store[0]._store.get("");
+      let scope = this._store[0]._store.get(undefined);
       if (!aToken) {
         // Prune the view from old previous content
         // so that we delete the intermediate search results
         // we created in previous searches
         for (let property of scope._store.values()) {
           property.remove();
         }
       }
@@ -1108,17 +1108,17 @@ VariablesView.simpleValueEvalMacro = fun
  * @param string aCurrentString
  *        The trimmed user inputted string.
  * @param string aPrefix [optional]
  *        Prefix for the symbolic name.
  * @return string
  *         The string to be evaluated.
  */
 VariablesView.overrideValueEvalMacro = function (aItem, aCurrentString, aPrefix = "") {
-  let property = "\"" + aItem._nameString + "\"";
+  let property = escapeString(aItem._nameString);
   let parent = aPrefix + aItem.ownerView.symbolicName || "this";
 
   return "Object.defineProperty(" + parent + "," + property + "," +
     "{ value: " + aCurrentString +
     ", enumerable: " + parent + ".propertyIsEnumerable(" + property + ")" +
     ", configurable: true" +
     ", writable: true" +
     "})";
@@ -1135,17 +1135,17 @@ VariablesView.overrideValueEvalMacro = f
  *        Prefix for the symbolic name.
  * @return string
  *         The string to be evaluated.
  */
 VariablesView.getterOrSetterEvalMacro = function (aItem, aCurrentString, aPrefix = "") {
   let type = aItem._nameString;
   let propertyObject = aItem.ownerView;
   let parentObject = propertyObject.ownerView;
-  let property = "\"" + propertyObject._nameString + "\"";
+  let property = escapeString(propertyObject._nameString);
   let parent = aPrefix + parentObject.symbolicName || "this";
 
   switch (aCurrentString) {
     case "":
     case "null":
     case "undefined":
       let mirrorType = type == "get" ? "set" : "get";
       let mirrorLookup = type == "get" ? "__lookupSetter__" : "__lookupGetter__";
@@ -1320,27 +1320,27 @@ Scope.prototype = {
    *        * boolean internalItem  true if the item is internally generated.
    *                           This is used for special variables
    *                           like <return> or <exception> and distinguishes
    *                           them from ordinary properties that happen
    *                           to have the same name
    * @return Variable
    *         The newly created Variable instance, null if it already exists.
    */
-  addItem: function (aName = "", aDescriptor = {}, aOptions = {}) {
+  addItem: function (aName, aDescriptor = {}, aOptions = {}) {
     let {relaxed} = aOptions;
     if (this._store.has(aName) && !relaxed) {
       return this._store.get(aName);
     }
 
     let child = this._createChild(aName, aDescriptor, aOptions);
     this._store.set(aName, child);
     this._variablesView._itemsByElement.set(child._target, child);
     this._variablesView._currHierarchy.set(child.absoluteName, child);
-    child.header = !!aName;
+    child.header = aName !== undefined;
 
     return child;
   },
 
   /**
    * Adds items for this variable.
    *
    * @param object aItems
@@ -1806,17 +1806,17 @@ Scope.prototype = {
    *
    * @param string aName
    *        The scope's name.
    * @param string aTargetClassName
    *        A custom class name for this scope's target element.
    * @param string aTitleClassName [optional]
    *        A custom class name for this scope's title element.
    */
-  _displayScope: function (aName, aTargetClassName, aTitleClassName = "") {
+  _displayScope: function (aName = "", aTargetClassName, aTitleClassName = "") {
     let document = this.document;
 
     let element = this._target = document.createElement("vbox");
     element.id = this._idString;
     element.className = aTargetClassName;
 
     let arrow = this._arrow = document.createElement("hbox");
     arrow.className = "arrow theme-twisty";
@@ -2355,29 +2355,29 @@ Variable.prototype = Heritage.extend(Sco
 
   /**
    * Gets this variable's path to the topmost scope in the form of a string
    * meant for use via eval() or a similar approach.
    * For example, a symbolic name may look like "arguments['0']['foo']['bar']".
    * @return string
    */
   get symbolicName() {
-    return this._nameString;
+    return this._nameString || "";
   },
 
   /**
    * Gets full path to this variable, including name of the scope.
    * @return string
    */
   get absoluteName() {
     if (this._absoluteName) {
       return this._absoluteName;
     }
 
-    this._absoluteName = this.ownerView._nameString + "[\"" + this._nameString + "\"]";
+    this._absoluteName = this.ownerView._nameString + "[" + escapeString(this._nameString) + "]";
     return this._absoluteName;
   },
 
   /**
    * Gets this variable's symbolic path to the topmost scope.
    * @return array
    * @see Variable._buildSymbolicPath
    */
@@ -2444,17 +2444,17 @@ Variable.prototype = Heritage.extend(Sco
    *             - "nasu"
    *             - { type: "undefined" }
    *             - { type: "null" }
    *             - { type: "object", class: "Object" }
    */
   setGrip: function (aGrip) {
     // Don't allow displaying grip information if there's no name available
     // or the grip is malformed.
-    if (!this._nameString || aGrip === undefined || aGrip === null) {
+    if (this._nameString === undefined || aGrip === undefined || aGrip === null) {
       return;
     }
     // Getters and setters should display grip information in sub-properties.
     if (this.getter || this.setter) {
       return;
     }
 
     let prevGrip = this._valueGrip;
@@ -3091,30 +3091,30 @@ Property.prototype = Heritage.extend(Var
    * @see Variable.symbolicName
    * @return string
    */
   get symbolicName() {
     if (this._symbolicName) {
       return this._symbolicName;
     }
 
-    this._symbolicName = this.ownerView.symbolicName + "[\"" + this._nameString + "\"]";
+    this._symbolicName = this.ownerView.symbolicName + "[" + escapeString(this._nameString) + "]";
     return this._symbolicName;
   },
 
   /**
    * @see Variable.absoluteName
    * @return string
    */
   get absoluteName() {
     if (this._absoluteName) {
       return this._absoluteName;
     }
 
-    this._absoluteName = this.ownerView.absoluteName + "[\"" + this._nameString + "\"]";
+    this._absoluteName = this.ownerView.absoluteName + "[" + escapeString(this._nameString) + "]";
     return this._absoluteName;
   }
 });
 
 /**
  * A generator-iterator over the VariablesView, Scopes, Variables and Properties.
  */
 VariablesView.prototype[Symbol.iterator] =
@@ -3905,16 +3905,27 @@ VariablesView.getClass = function (aGrip
 var generateId = (function () {
   let count = 0;
   return function (aName = "") {
     return aName.toLowerCase().trim().replace(/\s+/g, "-") + (++count);
   };
 })();
 
 /**
+ * Serialize a string to JSON. The result can be inserted in a string evaluated by `eval`.
+ *
+ * @param string aString
+ *       The string to be escaped. If undefined, the function returns the empty string.
+ * @return string
+ */
+function escapeString(aString) {
+  return JSON.stringify(aString) || "";
+}
+
+/**
  * Escape some HTML special characters. We do not need full HTML serialization
  * here, we just want to make strings safe to display in HTML attributes, for
  * the stringifiers.
  *
  * @param string aString
  * @return string
  */
 function escapeHTML(aString) {
--- a/devtools/client/shared/widgets/VariablesViewController.jsm
+++ b/devtools/client/shared/widgets/VariablesViewController.jsm
@@ -338,17 +338,17 @@ VariablesViewController.prototype = {
         aTarget.addItem("<handler>", { value: aGrip.proxyHandler }, { internalItem: true }),
         aGrip.proxyHandler);
 
       // Refuse to play the proxy's stupid game and return immediately
       let deferred = defer();
       deferred.resolve();
       return deferred.promise;
     }
-    
+
     if (aGrip.class === "Promise" && aGrip.promiseState) {
       const { state, value, reason } = aGrip.promiseState;
       aTarget.addItem("<state>", { value: state }, { internalItem: true });
       if (state === "fulfilled") {
         this.addExpander(
           aTarget.addItem("<value>", { value }, { internalItem: true }),
           value);
       } else if (state === "rejected") {
@@ -757,17 +757,17 @@ VariablesViewController.prototype = {
   setSingleVariable: function (options, configuration = {}) {
     this._setEvaluationMacros(configuration);
     this.view.empty();
 
     let scope = this.view.addScope(options.label);
     scope.expanded = true; // Expand the scope by default.
     scope.locked = true; // Prevent collapsing the scope.
 
-    let variable = scope.addItem("", { enumerable: true });
+    let variable = scope.addItem(undefined, { enumerable: true });
     let populated;
 
     if (options.objectActor) {
       // Save objectActor for properties filtering
       this.objectActor = options.objectActor;
       if (VariablesView.isPrimitive({ value: this.objectActor })) {
         populated = promise.resolve();
       } else {
--- a/devtools/client/webconsole/test/browser_jsterm_inspect.js
+++ b/devtools/client/webconsole/test/browser_jsterm_inspect.js
@@ -20,17 +20,17 @@ add_task(function* () {
 
   let updated = jsterm.once("variablesview-updated");
   jsterm.execute("inspect(window)");
   let view = yield updated;
   ok(view, "variables view object");
 
   // The single variable view contains a scope with the variable name
   // and unnamed subitem that contains the properties
-  let variable = view.getScopeAtIndex(0).get("");
+  let variable = view.getScopeAtIndex(0).get(undefined);
   ok(variable, "variable object");
 
   yield findVariableViewProperties(variable, [
     { name: "testProp", value: "testValue" },
     { name: "document", value: /HTMLDocument \u2192 data:/ },
   ], { webconsole: hud });
 
   /* Check that a primitive value can be inspected, too */
--- a/devtools/server/actors/actor-registry.js
+++ b/devtools/server/actors/actor-registry.js
@@ -12,33 +12,33 @@ const Services = require("Services");
 const { DebuggerServer } = require("devtools/server/main");
 const { registerActor, unregisterActor } = require("devtools/server/actors/utils/actor-registry-utils");
 const { actorActorSpec, actorRegistrySpec } = require("devtools/shared/specs/actor-registry");
 
 /**
  * The ActorActor gives you a handle to an actor you've dynamically
  * registered and allows you to unregister it.
  */
-const ActorActor = protocol.ActorClass(actorActorSpec, {
+const ActorActor = protocol.ActorClassWithSpec(actorActorSpec, {
   initialize: function (conn, options) {
     protocol.Actor.prototype.initialize.call(this, conn);
 
     this.options = options;
   },
 
   unregister: function () {
     unregisterActor(this.options);
   }
 });
 
 /*
  * The ActorRegistryActor allows clients to define new actors on the
  * server. This is particularly useful for addons.
  */
-const ActorRegistryActor = protocol.ActorClass(actorRegistrySpec, {
+const ActorRegistryActor = protocol.ActorClassWithSpec(actorRegistrySpec, {
   initialize: function (conn) {
     protocol.Actor.prototype.initialize.call(this, conn);
   },
 
   registerActor: function (sourceText, fileName, options) {
     return registerActor(sourceText, fileName, options).then(() => {
       let { constructor, type } = options;
 
--- a/devtools/server/actors/addons.js
+++ b/devtools/server/actors/addons.js
@@ -5,17 +5,17 @@
 "use strict";
 
 const {AddonManager} = require("resource://gre/modules/AddonManager.jsm");
 const protocol = require("devtools/shared/protocol");
 const {FileUtils} = require("resource://gre/modules/FileUtils.jsm");
 const {Task} = require("devtools/shared/task");
 const {addonsSpec} = require("devtools/shared/specs/addons");
 
-const AddonsActor = protocol.ActorClass(addonsSpec, {
+const AddonsActor = protocol.ActorClassWithSpec(addonsSpec, {
 
   initialize: function (conn) {
     protocol.Actor.prototype.initialize.call(this, conn);
   },
 
   installTemporaryAddon: Task.async(function* (addonPath) {
     let addonFile;
     let addon;
--- a/devtools/server/actors/animation.js
+++ b/devtools/server/actors/animation.js
@@ -24,17 +24,17 @@
  * - WebAnimation WebIDL files:
  *   /dom/webidl/Animation*.webidl
  */
 
 const {Cu} = require("chrome");
 const promise = require("promise");
 const {Task} = require("devtools/shared/task");
 const protocol = require("devtools/shared/protocol");
-const {Actor, ActorClass} = protocol;
+const {Actor, ActorClassWithSpec} = protocol;
 const {animationPlayerSpec, animationsSpec} = require("devtools/shared/specs/animation");
 const events = require("sdk/event/core");
 
 // Types of animations.
 const ANIMATION_TYPES = {
   CSS_ANIMATION: "cssanimation",
   CSS_TRANSITION: "csstransition",
   SCRIPT_ANIMATION: "scriptanimation",
@@ -46,17 +46,17 @@ exports.ANIMATION_TYPES = ANIMATION_TYPE
  * The AnimationPlayerActor provides information about a given animation: its
  * startTime, currentTime, current state, etc.
  *
  * Since the state of a player changes as the animation progresses it is often
  * useful to call getCurrentState at regular intervals to get the current state.
  *
  * This actor also allows playing, pausing and seeking the animation.
  */
-var AnimationPlayerActor = protocol.ActorClass(animationPlayerSpec, {
+var AnimationPlayerActor = protocol.ActorClassWithSpec(animationPlayerSpec, {
   /**
    * @param {AnimationsActor} The main AnimationsActor instance
    * @param {AnimationPlayer} The player object returned by getAnimationPlayers
    */
   initialize: function (animationsActor, player) {
     Actor.prototype.initialize.call(this, animationsActor.conn);
 
     this.onAnimationMutation = this.onAnimationMutation.bind(this);
@@ -428,17 +428,17 @@ var AnimationPlayerActor = protocol.Acto
   }
 });
 
 exports.AnimationPlayerActor = AnimationPlayerActor;
 
 /**
  * The Animations actor lists animation players for a given node.
  */
-var AnimationsActor = exports.AnimationsActor = protocol.ActorClass(animationsSpec, {
+var AnimationsActor = exports.AnimationsActor = protocol.ActorClassWithSpec(animationsSpec, {
   initialize: function(conn, tabActor) {
     Actor.prototype.initialize.call(this, conn);
     this.tabActor = tabActor;
 
     this.onWillNavigate = this.onWillNavigate.bind(this);
     this.onNavigate = this.onNavigate.bind(this);
     this.onAnimationMutation = this.onAnimationMutation.bind(this);
 
--- a/devtools/server/actors/breakpoint.js
+++ b/devtools/server/actors/breakpoint.js
@@ -1,17 +1,17 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const { ActorClass } = require("devtools/shared/protocol");
+const { ActorClassWithSpec } = require("devtools/shared/protocol");
 const { breakpointSpec } = require("devtools/shared/specs/breakpoint");
 
 /**
  * Set breakpoints on all the given entry points with the given
  * BreakpointActor as the handler.
  *
  * @param BreakpointActor actor
  *        The actor handling the breakpoint hits.
@@ -29,17 +29,17 @@ function setBreakpointAtEntryPoints(acto
 
 exports.setBreakpointAtEntryPoints = setBreakpointAtEntryPoints;
 
 /**
  * BreakpointActors exist for the lifetime of their containing thread and are
  * responsible for deleting breakpoints, handling breakpoint hits and
  * associating breakpoints with scripts.
  */
-let BreakpointActor = ActorClass(breakpointSpec, {
+let BreakpointActor = ActorClassWithSpec(breakpointSpec, {
   /**
    * Create a Breakpoint actor.
    *
    * @param ThreadActor threadActor
    *        The parent thread actor that contains this breakpoint.
    * @param OriginalLocation originalLocation
    *        The original location of the breakpoint.
    */
--- a/devtools/server/actors/call-watcher.js
+++ b/devtools/server/actors/call-watcher.js
@@ -13,17 +13,17 @@ const {method, Arg, Option, RetVal} = pr
 
 const { functionCallSpec, callWatcherSpec } = require("devtools/shared/specs/call-watcher");
 const { CallWatcherFront } = require("devtools/shared/fronts/call-watcher");
 
 /**
  * This actor contains information about a function call, like the function
  * type, name, stack, arguments, returned value etc.
  */
-var FunctionCallActor = protocol.ActorClass(functionCallSpec, {
+var FunctionCallActor = protocol.ActorClassWithSpec(functionCallSpec, {
   /**
    * Creates the function call actor.
    *
    * @param DebuggerServerConnection conn
    *        The server connection.
    * @param DOMWindow window
    *        The content window.
    * @param string global
@@ -221,17 +221,17 @@ var FunctionCallActor = protocol.ActorCl
     }
     return data + "";
   }
 });
 
 /**
  * This actor observes function calls on certain objects or globals.
  */
-var CallWatcherActor = exports.CallWatcherActor = protocol.ActorClass(callWatcherSpec, {
+var CallWatcherActor = exports.CallWatcherActor = protocol.ActorClassWithSpec(callWatcherSpec, {
   initialize: function (conn, tabActor) {
     protocol.Actor.prototype.initialize.call(this, conn);
     this.tabActor = tabActor;
     this._onGlobalCreated = this._onGlobalCreated.bind(this);
     this._onGlobalDestroyed = this._onGlobalDestroyed.bind(this);
     this._onContentFunctionCall = this._onContentFunctionCall.bind(this);
     on(this.tabActor, "window-ready", this._onGlobalCreated);
     on(this.tabActor, "window-destroyed", this._onGlobalDestroyed);
--- a/devtools/server/actors/canvas.js
+++ b/devtools/server/actors/canvas.js
@@ -25,17 +25,17 @@ const {CanvasFront} = require("devtools/
 const {on, once, off, emit} = events;
 const {method, custom, Arg, Option, RetVal} = protocol;
 
 /**
  * This actor represents a recorded animation frame snapshot, along with
  * all the corresponding canvas' context methods invoked in that frame,
  * thumbnails for each draw call and a screenshot of the end result.
  */
-var FrameSnapshotActor = protocol.ActorClass(frameSnapshotSpec, {
+var FrameSnapshotActor = protocol.ActorClassWithSpec(frameSnapshotSpec, {
   /**
    * Creates the frame snapshot call actor.
    *
    * @param DebuggerServerConnection conn
    *        The server connection.
    * @param HTMLCanvasElement canvas
    *        A reference to the content canvas.
    * @param array calls
@@ -115,17 +115,17 @@ var FrameSnapshotActor = protocol.ActorC
   }
 });
 
 /**
  * This Canvas Actor handles simple instrumentation of all the methods
  * of a 2D or WebGL context, to provide information regarding all the calls
  * made when drawing frame inside an animation loop.
  */
-var CanvasActor = exports.CanvasActor = protocol.ActorClass(canvasSpec, {
+var CanvasActor = exports.CanvasActor = protocol.ActorClassWithSpec(canvasSpec, {
   // Reset for each recording, boolean indicating whether or not
   // any draw calls were called for a recording.
   _animationContainsDrawCall: false,
 
   initialize: function (conn, tabActor) {
     protocol.Actor.prototype.initialize.call(this, conn);
     this.tabActor = tabActor;
     this._webGLPrimitiveCounter = new WebGLPrimitiveCounter(tabActor);
--- a/devtools/server/actors/common.js
+++ b/devtools/server/actors/common.js
@@ -506,16 +506,16 @@ function actorBridge(methodName, definit
   return method(function () {
     return this.bridge[methodName].apply(this.bridge, arguments);
   }, definition);
 }
 exports.actorBridge = actorBridge;
 
 /**
  * Like `actorBridge`, but without a spec definition, for when the actor is
- * created with `ActorClass` rather than vanilla `ActorClass`.
+ * created with `ActorClassWithSpec` rather than vanilla `ActorClass`.
  */
 function actorBridgeWithSpec (methodName) {
   return method(function () {
     return this.bridge[methodName].apply(this.bridge, arguments);
   });
 }
 exports.actorBridgeWithSpec = actorBridgeWithSpec;
--- a/devtools/server/actors/css-properties.js
+++ b/devtools/server/actors/css-properties.js
@@ -10,22 +10,22 @@ loader.lazyGetter(this, "DOMUtils", () =
   return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
 });
 
 loader.lazyGetter(this, "appInfo", () => {
   return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo);
 });
 
 const protocol = require("devtools/shared/protocol");
-const { ActorClass, Actor } = protocol;
+const { ActorClassWithSpec, Actor } = protocol;
 const { cssPropertiesSpec } = require("devtools/shared/specs/css-properties");
 const { CSS_PROPERTIES, CSS_TYPES } = require("devtools/shared/css-properties-db");
 const { cssColors } = require("devtools/shared/css-color-db");
 
-exports.CssPropertiesActor = ActorClass(cssPropertiesSpec, {
+exports.CssPropertiesActor = ActorClassWithSpec(cssPropertiesSpec, {
   typeName: "cssProperties",
 
   initialize(conn, parent) {
     Actor.prototype.initialize.call(this, conn);
     this.parent = parent;
   },
 
   destroy() {
--- a/devtools/server/actors/csscoverage.js
+++ b/devtools/server/actors/csscoverage.js
@@ -62,17 +62,17 @@ const l10n = exports.l10n = {
  *         cssText: "p.quote { color: red; }",
  *         isUsed: true,
  *         presentOn: Set([ "http://eg.com/page1.html", ... ]),
  *         preLoadOn: Set([ "http://eg.com/page1.html" ]),
  *         isError: false,
  *       }, ...
  *     });
  */
-var CSSUsageActor = protocol.ActorClass(cssUsageSpec, {
+var CSSUsageActor = protocol.ActorClassWithSpec(cssUsageSpec, {
   initialize: function (conn, tabActor) {
     protocol.Actor.prototype.initialize.call(this, conn);
 
     this._tabActor = tabActor;
     this._running = false;
 
     this._onTabLoad = this._onTabLoad.bind(this);
     this._onChange = this._onChange.bind(this);
--- a/devtools/server/actors/device.js
+++ b/devtools/server/actors/device.js
@@ -10,17 +10,17 @@ const protocol = require("devtools/share
 const promise = require("promise");
 const {LongStringActor} = require("devtools/server/actors/string");
 const {DebuggerServer} = require("devtools/server/main");
 const {getSystemInfo, getSetting} = require("devtools/shared/system");
 const {deviceSpec} = require("devtools/shared/specs/device");
 const FileReader = require("FileReader");
 const {PermissionsTable} = require("resource://gre/modules/PermissionsTable.jsm");
 
-var DeviceActor = exports.DeviceActor = protocol.ActorClass(deviceSpec, {
+var DeviceActor = exports.DeviceActor = protocol.ActorClassWithSpec(deviceSpec, {
   _desc: null,
 
   getDescription: function () {
     return getSystemInfo();
   },
 
   getWallpaper: function () {
     let deferred = promise.defer();
--- a/devtools/server/actors/director-manager.js
+++ b/devtools/server/actors/director-manager.js
@@ -33,17 +33,17 @@ const ERR_MESSAGEPORT_FINALIZED = "messa
 
 const ERR_DIRECTOR_UNKNOWN_SCRIPTID = "unkown director-script id";
 const ERR_DIRECTOR_UNINSTALLED_SCRIPTID = "uninstalled director-script id";
 
 /**
  * A MessagePort Actor allowing communication through messageport events
  * over the remote debugging protocol.
  */
-var MessagePortActor = exports.MessagePortActor = protocol.ActorClass(messagePortSpec, {
+var MessagePortActor = exports.MessagePortActor = protocol.ActorClassWithSpec(messagePortSpec, {
   /**
    * Create a MessagePort actor.
    *
    * @param DebuggerServerConnection conn
    *        The server connection.
    * @param MessagePort port
    *        The wrapped MessagePort.
    */
@@ -144,17 +144,17 @@ var MessagePortActor = exports.MessagePo
  *
  * After retrieving an instance of this actor (from the tab director actor), you'll need to set it up
  * by calling setup().
  *
  * After the setup, this actor will automatically attach/detach the content script (and optionally a
  * directly connect the debugger client and the content script using a MessageChannel) on tab
  * navigation.
  */
-var DirectorScriptActor = exports.DirectorScriptActor = protocol.ActorClass(directorScriptSpec, {
+var DirectorScriptActor = exports.DirectorScriptActor = protocol.ActorClassWithSpec(directorScriptSpec, {
   /**
    * Creates the director script actor
    *
    * @param DebuggerServerConnection conn
    *        The server connection.
    * @param Actor tabActor
    *        The tab (or root) actor.
    * @param String scriptId
@@ -332,17 +332,17 @@ var DirectorScriptActor = exports.Direct
       port: this._messagePortActor
     });
   }
 });
 
 /**
  * The DirectorManager Actor is a tab actor which manages enabling/disabling director scripts.
  */
-const DirectorManagerActor = exports.DirectorManagerActor = protocol.ActorClass(directorManagerSpec, {
+const DirectorManagerActor = exports.DirectorManagerActor = protocol.ActorClassWithSpec(directorManagerSpec, {
   /* init & destroy methods */
   initialize: function (conn, tabActor) {
     protocol.Actor.prototype.initialize.call(this, conn);
     this.tabActor = tabActor;
     this._directorScriptActorsMap = new Map();
   },
   destroy: function (conn) {
     protocol.Actor.prototype.destroy.call(this, conn);
--- a/devtools/server/actors/director-registry.js
+++ b/devtools/server/actors/director-registry.js
@@ -143,17 +143,17 @@ exports.setupParentProcess = function se
     onDisconnected: () => setMessageManager(null),
   };
 };
 
 /**
  * The DirectorRegistry Actor is a global actor which manages install/uninstall of
  * director scripts definitions.
  */
-const DirectorRegistryActor = exports.DirectorRegistryActor = protocol.ActorClass(directorRegistrySpec, {
+const DirectorRegistryActor = exports.DirectorRegistryActor = protocol.ActorClassWithSpec(directorRegistrySpec, {
   /* init & destroy methods */
   initialize: function (conn, parentActor) {
     protocol.Actor.prototype.initialize.call(this, conn);
     this.maybeSetupChildProcess(conn);
   },
   destroy: function (conn) {
     protocol.Actor.prototype.destroy.call(this, conn);
     this.finalize();
--- a/devtools/server/actors/emulation.js
+++ b/devtools/server/actors/emulation.js
@@ -4,23 +4,25 @@
 
 "use strict";
 
 const { Ci } = require("chrome");
 const protocol = require("devtools/shared/protocol");
 const { emulationSpec } = require("devtools/shared/specs/emulation");
 const { SimulatorCore } = require("devtools/shared/touch/simulator-core");
 
-let EmulationActor = protocol.ActorClass(emulationSpec, {
+let EmulationActor = protocol.ActorClassWithSpec(emulationSpec, {
   initialize(conn, tabActor) {
     protocol.Actor.prototype.initialize.call(this, conn);
     this.docShell = tabActor.docShell;
     this.simulatorCore = new SimulatorCore(tabActor.chromeEventHandler);
   },
 
+  /* Touch events override */
+
   _previousTouchEventsOverride: null,
 
   setTouchEventsOverride(flag) {
     if (this.docShell.touchEventsOverride == flag) {
       return false;
     }
     if (this._previousTouchEventsOverride === null) {
       this._previousTouchEventsOverride = this.docShell.touchEventsOverride;
@@ -38,25 +40,53 @@ let EmulationActor = protocol.ActorClass
   },
 
   getTouchEventsOverride() {
     return this.docShell.touchEventsOverride;
   },
 
   clearTouchEventsOverride() {
     if (this._previousTouchEventsOverride !== null) {
-      this.setTouchEventsOverride(this._previousTouchEventsOverride);
+      return this.setTouchEventsOverride(this._previousTouchEventsOverride);
+    }
+    return false;
+  },
+
+  /* User agent override */
+
+  _previousUserAgentOverride: null,
+
+  setUserAgentOverride(userAgent) {
+    if (this.docShell.customUserAgent == userAgent) {
+      return false;
     }
+    if (this._previousUserAgentOverride === null) {
+      this._previousUserAgentOverride = this.docShell.customUserAgent;
+    }
+    this.docShell.customUserAgent = userAgent;
+    return true;
+  },
+
+  getUserAgentOverride() {
+    return this.docShell.customUserAgent;
+  },
+
+  clearUserAgentOverride() {
+    if (this._previousUserAgentOverride !== null) {
+      return this.setUserAgentOverride(this._previousUserAgentOverride);
+    }
+    return false;
   },
 
   disconnect() {
     this.destroy();
   },
 
   destroy() {
     this.clearTouchEventsOverride();
+    this.clearUserAgentOverride();
     this.docShell = null;
     this.simulatorCore = null;
     protocol.Actor.prototype.destroy.call(this);
   },
 });
 
 exports.EmulationActor = EmulationActor;
--- a/devtools/server/actors/environment.js
+++ b/devtools/server/actors/environment.js
@@ -1,30 +1,30 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
-const { ActorClass } = require("devtools/shared/protocol");
+const { ActorClassWithSpec } = require("devtools/shared/protocol");
 const { createValueGrip } = require("devtools/server/actors/object");
 const { environmentSpec } = require("devtools/shared/specs/environment");
 
 /**
  * Creates an EnvironmentActor. EnvironmentActors are responsible for listing
  * the bindings introduced by a lexical environment and assigning new values to
  * those identifier bindings.
  *
  * @param Debugger.Environment aEnvironment
  *        The lexical environment that will be used to create the actor.
  * @param ThreadActor aThreadActor
  *        The parent thread actor that contains this environment.
  */
-let EnvironmentActor = ActorClass(environmentSpec, {
+let EnvironmentActor = ActorClassWithSpec(environmentSpec, {
   initialize: function (environment, threadActor) {
     this.obj = environment;
     this.threadActor = threadActor;
   },
 
   /**
    * Return an environment form for use in a protocol message.
    */
--- a/devtools/server/actors/eventlooplag.js
+++ b/devtools/server/actors/eventlooplag.js
@@ -8,21 +8,21 @@
  * The eventLoopLag actor emits "event-loop-lag" events when the event
  * loop gets unresponsive. The event comes with a "time" property (the
  * duration of the lag in milliseconds).
  */
 
 const {Ci} = require("chrome");
 const Services = require("Services");
 const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
-const {Actor, ActorClass} = require("devtools/shared/protocol");
+const {Actor, ActorClassWithSpec} = require("devtools/shared/protocol");
 const events = require("sdk/event/core");
 const {eventLoopLagSpec} = require("devtools/shared/specs/eventlooplag");
 
-var EventLoopLagActor = exports.EventLoopLagActor = ActorClass(eventLoopLagSpec, {
+var EventLoopLagActor = exports.EventLoopLagActor = ActorClassWithSpec(eventLoopLagSpec, {
   _observerAdded: false,
 
   /**
    * Start tracking the event loop lags.
    */
   start: function () {
     if (!this._observerAdded) {
       Services.obs.addObserver(this, "event-loop-lag", false);
--- a/devtools/server/actors/frame.js
+++ b/devtools/server/actors/frame.js
@@ -3,23 +3,23 @@
 /* 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 { ActorPool } = require("devtools/server/actors/common");
 const { createValueGrip } = require("devtools/server/actors/object");
-const { ActorClass } = require("devtools/shared/protocol");
+const { ActorClassWithSpec } = require("devtools/shared/protocol");
 const { frameSpec } = require("devtools/shared/specs/frame");
 
 /**
  * An actor for a specified stack frame.
  */
-let FrameActor = ActorClass(frameSpec, {
+let FrameActor = ActorClassWithSpec(frameSpec, {
   /**
    * Creates the Frame actor.
    *
    * @param frame Debugger.Frame
    *        The debuggee frame.
    * @param threadActor ThreadActor
    *        The parent thread actor for this frame.
    */
--- a/devtools/server/actors/framerate.js
+++ b/devtools/server/actors/framerate.js
@@ -1,26 +1,26 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
-const { Actor, ActorClass } = require("devtools/shared/protocol");
+const { Actor, ActorClassWithSpec } = require("devtools/shared/protocol");
 const { actorBridgeWithSpec } = require("devtools/server/actors/common");
 const { on, once, off, emit } = require("sdk/event/core");
 const { Framerate } = require("devtools/server/performance/framerate");
 const { framerateSpec } = require("devtools/shared/specs/framerate");
 
 /**
  * An actor wrapper around Framerate. Uses exposed
  * methods via bridge and provides RDP definitions.
  *
  * @see devtools/server/performance/framerate.js for documentation.
  */
-var FramerateActor = exports.FramerateActor = ActorClass(framerateSpec, {
+var FramerateActor = exports.FramerateActor = ActorClassWithSpec(framerateSpec, {
   initialize: function (conn, tabActor) {
     Actor.prototype.initialize.call(this, conn);
     this.bridge = new Framerate(tabActor);
   },
   destroy: function (conn) {
     Actor.prototype.destroy.call(this, conn);
     this.bridge.destroy();
   },
--- a/devtools/server/actors/gcli.js
+++ b/devtools/server/actors/gcli.js
@@ -1,26 +1,26 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { Task } = require("devtools/shared/task");
 const {
-  method, Arg, Option, RetVal, Actor, ActorClass
+  method, Arg, Option, RetVal, Actor, ActorClassWithSpec
 } = require("devtools/shared/protocol");
 const { gcliSpec } = require("devtools/shared/specs/gcli");
 const events = require("sdk/event/core");
 const { createSystem } = require("gcli/system");
 
 /**
  * Manage remote connections that want to talk to GCLI
  */
-const GcliActor = ActorClass(gcliSpec, {
+const GcliActor = ActorClassWithSpec(gcliSpec, {
   initialize: function (conn, tabActor) {
     Actor.prototype.initialize.call(this, conn);
 
     this._commandsChanged = this._commandsChanged.bind(this);
 
     this._tabActor = tabActor;
     this._requisitionPromise = undefined; // see _getRequisition()
   },
--- a/devtools/server/actors/heap-snapshot-file.js
+++ b/devtools/server/actors/heap-snapshot-file.js
@@ -18,17 +18,17 @@ loader.lazyRequireGetter(this, "HeapSnap
                          "devtools/shared/heapsnapshot/HeapSnapshotFileUtils");
 
 /**
  * The HeapSnapshotFileActor handles transferring heap snapshot files from the
  * server to the client. This has to be a global actor in the parent process
  * because child processes are sandboxed and do not have access to the file
  * system.
  */
-exports.HeapSnapshotFileActor = protocol.ActorClass(heapSnapshotFileSpec, {
+exports.HeapSnapshotFileActor = protocol.ActorClassWithSpec(heapSnapshotFileSpec, {
   initialize: function (conn, parent) {
     if (Services.appinfo.processType !== Services.appinfo.PROCESS_TYPE_DEFAULT) {
       const err = new Error("Attempt to create a HeapSnapshotFileActor in a " +
                             "child process! The HeapSnapshotFileActor *MUST* " +
                             "be in the parent process!");
       DevToolsUtils.reportException(
         "HeapSnapshotFileActor.prototype.initialize", err);
       return;
--- a/devtools/server/actors/highlighters.js
+++ b/devtools/server/actors/highlighters.js
@@ -77,17 +77,17 @@ exports.register = register;
  *
  * Other types of highlighter actors exist and can be accessed via the
  * InspectorActor's 'getHighlighterByType' method.
  */
 
 /**
  * The HighlighterActor class
  */
-var HighlighterActor = exports.HighlighterActor = protocol.ActorClass(highlighterSpec, {
+var HighlighterActor = exports.HighlighterActor = protocol.ActorClassWithSpec(highlighterSpec, {
   initialize: function (inspector, autohide) {
     protocol.Actor.prototype.initialize.call(this, null);
 
     this._autohide = autohide;
     this._inspector = inspector;
     this._walker = this._inspector.walker;
     this._tabActor = this._inspector.tabActor;
     this._highlighterEnv = new HighlighterEnvironment();
@@ -402,17 +402,17 @@ var HighlighterActor = exports.Highlight
     }
   }
 });
 
 /**
  * A generic highlighter actor class that instantiate a highlighter given its
  * type name and allows to show/hide it.
  */
-var CustomHighlighterActor = exports.CustomHighlighterActor = protocol.ActorClass(customHighlighterSpec, {
+var CustomHighlighterActor = exports.CustomHighlighterActor = protocol.ActorClassWithSpec(customHighlighterSpec, {
   /**
    * Create a highlighter instance given its typename
    * The typename must be one of HIGHLIGHTER_CLASSES and the class must
    * implement constructor(tabActor), show(node), hide(), destroy()
    */
   initialize: function (inspector, typeName) {
     protocol.Actor.prototype.initialize.call(this, null);
 
--- a/devtools/server/actors/highlighters/css-grid.js
+++ b/devtools/server/actors/highlighters/css-grid.js
@@ -138,17 +138,17 @@ CssGridHighlighter.prototype = extend(Au
 
     this._showGrid();
 
     setIgnoreLayoutChanges(false, this.currentNode.ownerDocument.documentElement);
     return true;
   },
 
   clearCanvas() {
-    let ratio = this.win.devicePixelRatio || 1;
+    let ratio = parseFloat((this.win.devicePixelRatio || 1).toFixed(2));
     let width = this.win.innerWidth;
     let height = this.win.innerHeight;
 
     // Resize the canvas taking the dpr into account so as to have crisp lines.
     this.canvas.setAttribute("width", width * ratio);
     this.canvas.setAttribute("height", height * ratio);
     this.canvas.setAttribute("style", `width:${width}px;height:${height}px`);
     this.ctx.scale(ratio, ratio);
--- a/devtools/server/actors/inspector.js
+++ b/devtools/server/actors/inspector.js
@@ -192,17 +192,17 @@ exports.setInspectingNode = function (va
 const getNodeDisplayName = function (rawNode) {
   return (rawNode.prefix ? rawNode.prefix + ":" : "") + rawNode.localName;
 };
 exports.getNodeDisplayName = getNodeDisplayName;
 
 /**
  * Server side of the node actor.
  */
-var NodeActor = exports.NodeActor = protocol.ActorClass(nodeSpec, {
+var NodeActor = exports.NodeActor = protocol.ActorClassWithSpec(nodeSpec, {
   initialize: function (walker, node) {
     protocol.Actor.prototype.initialize.call(this, null);
     this.walker = walker;
     this.rawNode = node;
     this._eventParsers = new EventParsers().parsers;
 
     // Storing the original display of the node, to track changes when reflows
     // occur
@@ -703,17 +703,17 @@ var NodeActor = exports.NodeActor = prot
 
     return { data: LongStringActor(this.conn, dataURL), size: size };
   }
 });
 
 /**
  * Server side of a node list as returned by querySelectorAll()
  */
-var NodeListActor = exports.NodeListActor = protocol.ActorClass(nodeListSpec, {
+var NodeListActor = exports.NodeListActor = protocol.ActorClassWithSpec(nodeListSpec, {
   typeName: "domnodelist",
 
   initialize: function (walker, nodeList) {
     protocol.Actor.prototype.initialize.call(this);
     this.walker = walker;
     this.nodeList = nodeList || [];
   },
 
@@ -761,17 +761,17 @@ var NodeListActor = exports.NodeListActo
   },
 
   release: function () {}
 });
 
 /**
  * Server side of the DOM walker.
  */
-var WalkerActor = protocol.ActorClass(walkerSpec, {
+var WalkerActor = protocol.ActorClassWithSpec(walkerSpec, {
   /**
    * Create the WalkerActor
    * @param DebuggerServerConnection conn
    *    The server connection.
    */
   initialize: function (conn, tabActor, options) {
     protocol.Actor.prototype.initialize.call(this, conn);
     this.tabActor = tabActor;
@@ -2573,17 +2573,17 @@ var WalkerActor = protocol.ActorClass(wa
     return this.attachElement(obj);
   },
 });
 
 /**
  * Server side of the inspector actor, which is used to create
  * inspector-related actors, including the walker.
  */
-exports.InspectorActor = protocol.ActorClass(inspectorSpec, {
+exports.InspectorActor = protocol.ActorClassWithSpec(inspectorSpec, {
   initialize: function (conn, tabActor) {
     protocol.Actor.prototype.initialize.call(this, conn);
     this.tabActor = tabActor;
 
     this._onColorPicked = this._onColorPicked.bind(this);
     this._onColorPickCanceled = this._onColorPickCanceled.bind(this);
     this.destroyEyeDropper = this.destroyEyeDropper.bind(this);
   },
--- a/devtools/server/actors/layout.js
+++ b/devtools/server/actors/layout.js
@@ -31,17 +31,17 @@ const {method, Arg} = protocol;
 const events = require("sdk/event/core");
 const Heritage = require("sdk/core/heritage");
 const EventEmitter = require("devtools/shared/event-emitter");
 const {reflowSpec} = require("devtools/shared/specs/layout");
 
 /**
  * The reflow actor tracks reflows and emits events about them.
  */
-var ReflowActor = exports.ReflowActor = protocol.ActorClass(reflowSpec, {
+var ReflowActor = exports.ReflowActor = protocol.ActorClassWithSpec(reflowSpec, {
   initialize: function (conn, tabActor) {
     protocol.Actor.prototype.initialize.call(this, conn);
 
     this.tabActor = tabActor;
     this._onReflow = this._onReflow.bind(this);
     this.observer = getLayoutChangesObserver(tabActor);
     this._isStarted = false;
   },
--- a/devtools/server/actors/memory.js
+++ b/devtools/server/actors/memory.js
@@ -18,17 +18,17 @@ loader.lazyRequireGetter(this, "StackFra
  * parent tab. A global-scoped instance however, will measure the memory
  * footprint of the chrome window referenced by the root actor.
  *
  * This actor wraps the Memory module at devtools/server/performance/memory.js
  * and provides RDP definitions.
  *
  * @see devtools/server/performance/memory.js for documentation.
  */
-exports.MemoryActor = protocol.ActorClass(memorySpec, {
+exports.MemoryActor = protocol.ActorClassWithSpec(memorySpec, {
   initialize: function (conn, parent, frameCache = new StackFrameCache()) {
     protocol.Actor.prototype.initialize.call(this, conn);
 
     this._onGarbageCollection = this._onGarbageCollection.bind(this);
     this._onAllocations = this._onAllocations.bind(this);
     this.bridge = new Memory(parent, frameCache);
     this.bridge.on("garbage-collection", this._onGarbageCollection);
     this.bridge.on("allocations", this._onAllocations);
--- a/devtools/server/actors/performance-entries.js
+++ b/devtools/server/actors/performance-entries.js
@@ -4,21 +4,21 @@
 
 /**
  * The performanceEntries actor emits events corresponding to performance
  * entries. It receives `performanceentry` events containing the performance
  * entry details and emits an event containing the name, type, origin, and
  * epoch of the performance entry.
  */
 
-const { Actor, ActorClass } = require("devtools/shared/protocol");
+const { Actor, ActorClassWithSpec } = require("devtools/shared/protocol");
 const performanceSpec = require("devtools/shared/specs/performance-entries");
 const events = require("sdk/event/core");
 
-var PerformanceEntriesActor = ActorClass(performanceSpec, {
+var PerformanceEntriesActor = ActorClassWithSpec(performanceSpec, {
   listenerAdded: false,
 
   initialize: function (conn, tabActor) {
     Actor.prototype.initialize.call(this, conn);
     this.window = tabActor.window;
   },
 
   /**
--- a/devtools/server/actors/performance-recording.js
+++ b/devtools/server/actors/performance-recording.js
@@ -1,31 +1,31 @@
 /* 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 { Cu } = require("chrome");
-const { Actor, ActorClass } = require("devtools/shared/protocol");
+const { Actor, ActorClassWithSpec } = require("devtools/shared/protocol");
 const { performanceRecordingSpec } = require("devtools/shared/specs/performance-recording");
 
 loader.lazyRequireGetter(this, "merge", "sdk/util/object", true);
 loader.lazyRequireGetter(this, "RecordingUtils",
   "devtools/shared/performance/recording-utils");
 loader.lazyRequireGetter(this, "PerformanceRecordingCommon",
   "devtools/shared/performance/recording-common", true);
 
 /**
  * This actor wraps the Performance module at devtools/shared/shared/performance.js
  * and provides RDP definitions.
  *
  * @see devtools/shared/shared/performance.js for documentation.
  */
-const PerformanceRecordingActor = ActorClass(performanceRecordingSpec, merge({
+const PerformanceRecordingActor = ActorClassWithSpec(performanceRecordingSpec, merge({
   form: function (detail) {
     if (detail === "actorid") {
       return this.actorID;
     }
 
     let form = {
       actor: this.actorID,  // actorID is set when this is added to a pool
       configuration: this._configuration,
--- a/devtools/server/actors/performance.js
+++ b/devtools/server/actors/performance.js
@@ -1,17 +1,17 @@
 /* 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 { Cu } = require("chrome");
 const { Task } = require("devtools/shared/task");
-const { Actor, ActorClass } = require("devtools/shared/protocol");
+const { Actor, ActorClassWithSpec } = require("devtools/shared/protocol");
 const { actorBridgeWithSpec } = require("devtools/server/actors/common");
 const { performanceSpec } = require("devtools/shared/specs/performance");
 
 loader.lazyRequireGetter(this, "events", "sdk/event/core");
 loader.lazyRequireGetter(this, "extend", "sdk/util/object", true);
 
 loader.lazyRequireGetter(this, "PerformanceRecorder",
   "devtools/server/performance/recorder", true);
@@ -28,17 +28,17 @@ const RECORDING_STATE_CHANGE_EVENTS = ne
 ]);
 
 /**
  * This actor wraps the Performance module at devtools/shared/shared/performance.js
  * and provides RDP definitions.
  *
  * @see devtools/shared/shared/performance.js for documentation.
  */
-var PerformanceActor = ActorClass(performanceSpec, {
+var PerformanceActor = ActorClassWithSpec(performanceSpec, {
   traits: {
     features: {
       withMarkers: true,
       withTicks: true,
       withMemory: true,
       withFrames: true,
       withGCEvents: true,
       withDocLoadingEvents: true,
--- a/devtools/server/actors/preference.js
+++ b/devtools/server/actors/preference.js
@@ -10,17 +10,17 @@ const {preferenceSpec} = require("devtoo
 
 exports.register = function (handle) {
   handle.addGlobalActor(PreferenceActor, "preferenceActor");
 };
 
 exports.unregister = function (handle) {
 };
 
-var PreferenceActor = exports.PreferenceActor = protocol.ActorClass(preferenceSpec, {
+var PreferenceActor = exports.PreferenceActor = protocol.ActorClassWithSpec(preferenceSpec, {
   typeName: "preference",
 
   getBoolPref: function (name) {
     return Services.prefs.getBoolPref(name);
   },
 
   getCharPref: function (name) {
     return Services.prefs.getCharPref(name);
--- a/devtools/server/actors/profiler.js
+++ b/devtools/server/actors/profiler.js
@@ -1,27 +1,27 @@
 /* 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 { Actor, ActorClass } = require("devtools/shared/protocol");
+const { Actor, ActorClassWithSpec } = require("devtools/shared/protocol");
 const { Profiler } = require("devtools/server/performance/profiler");
 const { actorBridgeWithSpec } = require("devtools/server/actors/common");
 const { profilerSpec } = require("devtools/shared/specs/profiler");
 
 loader.lazyRequireGetter(this, "events", "sdk/event/core");
 
 /**
  * This actor wraps the Profiler module at devtools/server/performance/profiler.js
  * and provides RDP definitions.
  *
  * @see devtools/server/performance/profiler.js for documentation.
  */
-var ProfilerActor = exports.ProfilerActor = ActorClass(profilerSpec, {
+var ProfilerActor = exports.ProfilerActor = ActorClassWithSpec(profilerSpec, {
   initialize: function (conn) {
     Actor.prototype.initialize.call(this, conn);
     this._onProfilerEvent = this._onProfilerEvent.bind(this);
 
     this.bridge = new Profiler();
     events.on(this.bridge, "*", this._onProfilerEvent);
   },
 
--- a/devtools/server/actors/promises.js
+++ b/devtools/server/actors/promises.js
@@ -10,17 +10,17 @@ const { expectState, ActorPool } = requi
 const { ObjectActor, createValueGrip } = require("devtools/server/actors/object");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 loader.lazyRequireGetter(this, "events", "sdk/event/core");
 
 /**
  * The Promises Actor provides support for getting the list of live promises and
  * observing changes to their settlement state.
  */
-var PromisesActor = protocol.ActorClass(promisesSpec, {
+var PromisesActor = protocol.ActorClassWithSpec(promisesSpec, {
   /**
    * @param conn DebuggerServerConnection.
    * @param parent TabActor|RootActor
    */
   initialize: function (conn, parent) {
     protocol.Actor.prototype.initialize.call(this, conn);
 
     this.conn = conn;
--- a/devtools/server/actors/script.js
+++ b/devtools/server/actors/script.js
@@ -10,17 +10,17 @@ const Services = require("Services");
 const { Cc, Ci, Cu, Cr, components, ChromeWorker } = require("chrome");
 const { ActorPool, OriginalLocation, GeneratedLocation } = require("devtools/server/actors/common");
 const { BreakpointActor, setBreakpointAtEntryPoints } = require("devtools/server/actors/breakpoint");
 const { EnvironmentActor } = require("devtools/server/actors/environment");
 const { FrameActor } = require("devtools/server/actors/frame");
 const { ObjectActor, createValueGrip, longStringGrip } = require("devtools/server/actors/object");
 const { SourceActor, getSourceURL } = require("devtools/server/actors/source");
 const { DebuggerServer } = require("devtools/server/main");
-const { ActorClass } = require("devtools/shared/protocol");
+const { ActorClassWithSpec } = require("devtools/shared/protocol");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const flags = require("devtools/shared/flags");
 const { assert, dumpn, update, fetch } = DevToolsUtils;
 const promise = require("promise");
 const xpcInspector = require("xpcInspector");
 const { DevToolsWorker } = require("devtools/shared/worker/worker");
 const object = require("sdk/util/object");
 const { threadSpec } = require("devtools/shared/specs/script");
@@ -403,17 +403,17 @@ EventLoop.prototype = {
  *          - preNest: Function called before entering a nested event loop.
  *          - postNest: Function called after exiting a nested event loop.
  *          - makeDebugger: A function that takes no arguments and instantiates
  *            a Debugger that manages its globals on its own.
  * @param aGlobal object [optional]
  *        An optional (for content debugging only) reference to the content
  *        window.
  */
-const ThreadActor = ActorClass(threadSpec, {
+const ThreadActor = ActorClassWithSpec(threadSpec, {
   initialize: function (aParent, aGlobal) {
     this._state = "detached";
     this._frameActors = [];
     this._parent = aParent;
     this._dbg = null;
     this._gripDepth = 0;
     this._threadLifetimePool = null;
     this._tabClosed = false;
--- a/devtools/server/actors/settings.js
+++ b/devtools/server/actors/settings.js
@@ -59,17 +59,17 @@ function loadSettingsFile() {
     }
   }
 
   if (settingsFile.exists()) {
     getDefaultSettings();
   }
 }
 
-var SettingsActor = exports.SettingsActor = protocol.ActorClass(settingsSpec, {
+var SettingsActor = exports.SettingsActor = protocol.ActorClassWithSpec(settingsSpec, {
   _getSettingsService: function () {
     let win = Services.wm.getMostRecentWindow(DebuggerServer.chromeWindowType);
     return win.navigator.mozSettings;
   },
 
   getSetting: function (name) {
     let deferred = promise.defer();
     let lock = this._getSettingsService().createLock();
--- a/devtools/server/actors/source.js
+++ b/devtools/server/actors/source.js
@@ -6,17 +6,17 @@
 
 "use strict";
 
 const { Cc, Ci } = require("chrome");
 const Services = require("Services");
 const { BreakpointActor, setBreakpointAtEntryPoints } = require("devtools/server/actors/breakpoint");
 const { OriginalLocation, GeneratedLocation } = require("devtools/server/actors/common");
 const { createValueGrip } = require("devtools/server/actors/object");
-const { ActorClass, Arg, RetVal, method } = require("devtools/shared/protocol");
+const { ActorClassWithSpec, Arg, RetVal, method } = require("devtools/shared/protocol");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const { assert, fetch } = DevToolsUtils;
 const { joinURI } = require("devtools/shared/path");
 const promise = require("promise");
 const { defer, resolve, reject, all } = promise;
 const { sourceSpec } = require("devtools/shared/specs/source");
 
 loader.lazyRequireGetter(this, "SourceMapConsumer", "source-map", true);
@@ -136,17 +136,17 @@ function resolveURIToLocalPath(aURI) {
  * @param Debugger.Source generatedSource
  *        Optional, passed in when aSourceMap is also passed in. The generated
  *        source object that introduced this source.
  * @param Boolean isInlineSource
  *        Optional. True if this is an inline source from a HTML or XUL page.
  * @param String contentType
  *        Optional. The content type of this source, if immediately available.
  */
-let SourceActor = ActorClass(sourceSpec, {
+let SourceActor = ActorClassWithSpec(sourceSpec, {
   typeName: "source",
 
   initialize: function ({ source, thread, originalUrl, generatedSource,
                           isInlineSource, contentType }) {
     this._threadActor = thread;
     this._originalUrl = originalUrl;
     this._source = source;
     this._generatedSource = generatedSource;
--- a/devtools/server/actors/storage.js
+++ b/devtools/server/actors/storage.js
@@ -372,17 +372,17 @@ StorageActors.createActor = function (op
     options.typeName,
     options.observationTopic || null
   );
   for (let key in overrides) {
     actorObject[key] = overrides[key];
   }
 
   let actorSpec = specs.childSpecs[options.typeName];
-  let actor = protocol.ActorClass(actorSpec, actorObject);
+  let actor = protocol.ActorClassWithSpec(actorSpec, actorObject);
   storageTypePool.set(actorObject.typeName, actor);
 };
 
 /**
  * The Cookies actor and front.
  */
 StorageActors.createActor({
   typeName: "cookies"
@@ -2157,17 +2157,17 @@ exports.setupParentProcessForIndexedDB =
     onBrowserSwap: setMessageManager,
     onDisconnected: () => setMessageManager(null),
   };
 };
 
 /**
  * The main Storage Actor.
  */
-let StorageActor = protocol.ActorClass(specs.storageSpec, {
+let StorageActor = protocol.ActorClassWithSpec(specs.storageSpec, {
   typeName: "storage",
 
   get window() {
     return this.parentActor.window;
   },
 
   get document() {
     return this.parentActor.window.document;
--- a/devtools/server/actors/string.js
+++ b/devtools/server/actors/string.js
@@ -6,17 +6,17 @@
 
 var {DebuggerServer} = require("devtools/server/main");
 
 var promise = require("promise");
 
 var protocol = require("devtools/shared/protocol");
 const {longStringSpec} = require("devtools/shared/specs/string");
 
-exports.LongStringActor = protocol.ActorClass(longStringSpec, {
+exports.LongStringActor = protocol.ActorClassWithSpec(longStringSpec, {
   initialize: function (conn, str) {
     protocol.Actor.prototype.initialize.call(this, conn);
     this.str = str;
     this.short = (this.str.length < DebuggerServer.LONG_STRING_LENGTH);
   },
 
   destroy: function () {
     this.str = null;
--- a/devtools/server/actors/styleeditor.js
+++ b/devtools/server/actors/styleeditor.js
@@ -23,17 +23,17 @@ var TRANSITION_RULE = "\
 transition-duration: " + TRANSITION_DURATION_MS + "ms !important; \
 transition-delay: 0ms !important;\
 transition-timing-function: ease-out !important;\
 transition-property: all !important;\
 }";
 
 var LOAD_ERROR = "error-load";
 
-var OldStyleSheetActor = protocol.ActorClass(oldStyleSheetSpec, {
+var OldStyleSheetActor = protocol.ActorClassWithSpec(oldStyleSheetSpec, {
   toString: function() {
     return "[OldStyleSheetActor " + this.actorID + "]";
   },
 
   /**
    * Window of target
    */
   get window() {
@@ -316,17 +316,17 @@ var OldStyleSheetActor = protocol.ActorC
 });
 
 exports.OldStyleSheetActor = OldStyleSheetActor;
 
 /**
  * Creates a StyleEditorActor. StyleEditorActor provides remote access to the
  * stylesheets of a document.
  */
-var StyleEditorActor = exports.StyleEditorActor = protocol.ActorClass(styleEditorSpec, {
+var StyleEditorActor = exports.StyleEditorActor = protocol.ActorClassWithSpec(styleEditorSpec, {
   /**
    * The window we work with, taken from the parent actor.
    */
   get window() {
     return this.parentActor.window;
   },
 
   /**
--- a/devtools/server/actors/styles.js
+++ b/devtools/server/actors/styles.js
@@ -34,17 +34,17 @@ const NORMAL_FONT_WEIGHT = 400;
 const BOLD_FONT_WEIGHT = 700;
 // Offset (in px) to avoid cutting off text edges of italic fonts.
 const FONT_PREVIEW_OFFSET = 4;
 
 /**
  * The PageStyle actor lets the client look at the styles on a page, as
  * they are applied to a given node.
  */
-var PageStyleActor = protocol.ActorClass(pageStyleSpec, {
+var PageStyleActor = protocol.ActorClassWithSpec(pageStyleSpec, {
   /**
    * Create a PageStyleActor.
    *
    * @param inspector
    *    The InspectorActor that owns this PageStyleActor.
    *
    * @constructor
    */
@@ -920,17 +920,17 @@ exports.PageStyleActor = PageStyleActor;
 /**
  * An actor that represents a CSS style object on the protocol.
  *
  * We slightly flatten the CSSOM for this actor, it represents
  * both the CSSRule and CSSStyle objects in one actor.  For nodes
  * (which have a CSSStyle but no CSSRule) we create a StyleRuleActor
  * with a special rule type (100).
  */
-var StyleRuleActor = protocol.ActorClass(styleRuleSpec, {
+var StyleRuleActor = protocol.ActorClassWithSpec(styleRuleSpec, {
   initialize: function (pageStyle, item) {
     protocol.Actor.prototype.initialize.call(this, null);
     this.pageStyle = pageStyle;
     this.rawStyle = item.style;
     this._parentSheet = null;
     this._onStyleApplied = this._onStyleApplied.bind(this);
 
     if (item instanceof (Ci.nsIDOMCSSRule)) {
--- a/devtools/server/actors/stylesheets.js
+++ b/devtools/server/actors/stylesheets.js
@@ -55,17 +55,17 @@ exports.UPDATE_GENERAL = UPDATE_GENERAL;
 // is used so that navigation by the user will eventually cause the
 // edited text to be collected.
 let modifiedStyleSheets = new WeakMap();
 
 /**
  * Actor representing an original source of a style sheet that was specified
  * in a source map.
  */
-var OriginalSourceActor = protocol.ActorClass(originalSourceSpec, {
+var OriginalSourceActor = protocol.ActorClassWithSpec(originalSourceSpec, {
   initialize: function (aUrl, aSourceMap, aParentActor) {
     protocol.Actor.prototype.initialize.call(this, null);
 
     this.url = aUrl;
     this.sourceMap = aSourceMap;
     this.parentActor = aParentActor;
     this.conn = this.parentActor.conn;
 
@@ -108,17 +108,17 @@ var OriginalSourceActor = protocol.Actor
     });
   }
 });
 
 /**
  * A MediaRuleActor lives on the server and provides access to properties
  * of a DOM @media rule and emits events when it changes.
  */
-var MediaRuleActor = protocol.ActorClass(mediaRuleSpec, {
+var MediaRuleActor = protocol.ActorClassWithSpec(mediaRuleSpec, {
   get window() {
     return this.parentActor.window;
   },
 
   get document() {
     return this.window.document;
   },
 
@@ -178,17 +178,17 @@ var MediaRuleActor = protocol.ActorClass
   _matchesChange: function () {
     events.emit(this, "matches-change", this.matches);
   }
 });
 
 /**
  * A StyleSheetActor represents a stylesheet on the server.
  */
-var StyleSheetActor = protocol.ActorClass(styleSheetSpec, {
+var StyleSheetActor = protocol.ActorClassWithSpec(styleSheetSpec, {
   /* List of original sources that generated this stylesheet */
   _originalSources: null,
 
   toString: function () {
     return "[StyleSheetActor " + this.actorID + "]";
   },
 
   /**
@@ -781,17 +781,17 @@ var StyleSheetActor = protocol.ActorClas
 });
 
 exports.StyleSheetActor = StyleSheetActor;
 
 /**
  * Creates a StyleSheetsActor. StyleSheetsActor provides remote access to the
  * stylesheets of a document.
  */
-var StyleSheetsActor = protocol.ActorClass(styleSheetsSpec, {
+var StyleSheetsActor = protocol.ActorClassWithSpec(styleSheetsSpec, {
   /**
    * The window we work with, taken from the parent actor.
    */
   get window() {
     return this.parentActor.window;
   },
 
   /**
--- a/devtools/server/actors/timeline.js
+++ b/devtools/server/actors/timeline.js
@@ -21,17 +21,17 @@ const { Option, RetVal } = protocol;
 const { actorBridgeWithSpec } = require("devtools/server/actors/common");
 const { Timeline } = require("devtools/server/performance/timeline");
 const { timelineSpec } = require("devtools/shared/specs/timeline");
 const events = require("sdk/event/core");
 
 /**
  * The timeline actor pops and forwards timeline markers registered in docshells.
  */
-var TimelineActor = exports.TimelineActor = protocol.ActorClass(timelineSpec, {
+var TimelineActor = exports.TimelineActor = protocol.ActorClassWithSpec(timelineSpec, {
   /**
    * Initializes this actor with the provided connection and tab actor.
    */
   initialize: function (conn, tabActor) {
     protocol.Actor.prototype.initialize.call(this, conn);
     this.tabActor = tabActor;
     this.bridge = new Timeline(tabActor);
 
--- a/devtools/server/actors/webaudio.js
+++ b/devtools/server/actors/webaudio.js
@@ -33,17 +33,17 @@ const AUTOMATION_GRANULARITY_MAX = 6000;
 const AUDIO_GLOBALS = [
   "AudioContext", "AudioNode", "AudioParam"
 ];
 
 /**
  * An Audio Node actor allowing communication to a specific audio node in the
  * Audio Context graph.
  */
-var AudioNodeActor = exports.AudioNodeActor = protocol.ActorClass(audionodeSpec, {
+var AudioNodeActor = exports.AudioNodeActor = protocol.ActorClassWithSpec(audionodeSpec, {
   form: function (detail) {
     if (detail === "actorid") {
       return this.actorID;
     }
 
     return {
       actor: this.actorID, // actorID is set when this is added to a pool
       type: this.type,
@@ -399,17 +399,17 @@ var AudioNodeActor = exports.AudioNodeAc
   }
 });
 
 /**
  * The Web Audio Actor handles simple interaction with an AudioContext
  * high-level methods. After instantiating this actor, you'll need to set it
  * up by calling setup().
  */
-var WebAudioActor = exports.WebAudioActor = protocol.ActorClass(webAudioSpec, {
+var WebAudioActor = exports.WebAudioActor = protocol.ActorClassWithSpec(webAudioSpec, {
   initialize: function (conn, tabActor) {
     protocol.Actor.prototype.initialize.call(this, conn);
     this.tabActor = tabActor;
 
     this._onContentFunctionCall = this._onContentFunctionCall.bind(this);
 
     // Store ChromeOnly ID (`nativeID` property on AudioNodeActor) mapped
     // to the associated actorID, so we don't have to expose `nativeID`
--- a/devtools/server/actors/webbrowser.js
+++ b/devtools/server/actors/webbrowser.js
@@ -1672,22 +1672,16 @@ TabActor.prototype = {
     if ((typeof options.serviceWorkersTestingEnabled !== "undefined") &&
         (options.serviceWorkersTestingEnabled !==
          this._getServiceWorkersTestingEnabled())) {
       this._setServiceWorkersTestingEnabled(
         options.serviceWorkersTestingEnabled
       );
     }
 
-    if ((typeof options.customUserAgent !== "undefined") &&
-         options.customUserAgent !== this._getCustomUserAgent()) {
-      this._setCustomUserAgent(options.customUserAgent);
-      reload = true;
-    }
-
     // Reload if:
     //  - there's an explicit `performReload` flag and it's true
     //  - there's no `performReload` flag, but it makes sense to do so
     let hasExplicitReloadFlag = "performReload" in options;
     if ((hasExplicitReloadFlag && options.performReload) ||
        (!hasExplicitReloadFlag && reload)) {
       this.onReload();
     }
@@ -1696,17 +1690,16 @@ TabActor.prototype = {
   /**
    * Opposite of the _toggleDevToolsSettings method, that reset document state
    * when closing the toolbox.
    */
   _restoreDocumentSettings() {
     this._restoreJavascript();
     this._setCacheDisabled(false);
     this._setServiceWorkersTestingEnabled(false);
-    this._restoreUserAgent();
   },
 
   /**
    * Disable or enable the cache via docShell.
    */
   _setCacheDisabled(disabled) {
     let enable = Ci.nsIRequest.LOAD_NORMAL;
     let disable = Ci.nsIRequest.LOAD_BYPASS_CACHE |
@@ -1780,48 +1773,16 @@ TabActor.prototype = {
       return null;
     }
 
     let windowUtils = this.window.QueryInterface(Ci.nsIInterfaceRequestor)
                                  .getInterface(Ci.nsIDOMWindowUtils);
     return windowUtils.serviceWorkersTestingEnabled;
   },
 
-  _previousCustomUserAgent: null,
-
-  /**
-   * Return custom user agent.
-   */
-  _getCustomUserAgent() {
-    if (!this.docShell) {
-      // The tab is already closed.
-      return null;
-    }
-    return this.docShell.customUserAgent;
-  },
-
-  /**
-   * Sets custom user agent for the current tab
-   */
-  _setCustomUserAgent(userAgent) {
-    if (this._previousCustomUserAgent === null) {
-      this._previousCustomUserAgent = this.docShell.customUserAgent;
-    }
-    this.docShell.customUserAgent = userAgent;
-  },
-
-  /**
-   * Restore the user agent, before the actor modified it
-   */
-  _restoreUserAgent() {
-    if (this._previousCustomUserAgent !== null) {
-      this.docShell.customUserAgent = this._previousCustomUserAgent;
-    }
-  },
-
   /**
    * Prepare to enter a nested event loop by disabling debuggee events.
    */
   preNest() {
     if (!this.window) {
       // The tab is already closed.
       return;
     }
--- a/devtools/server/actors/webgl.js
+++ b/devtools/server/actors/webgl.js
@@ -23,17 +23,17 @@ const PROGRAM_DEFAULT_TRAITS = 0;
 const PROGRAM_BLACKBOX_TRAIT = 1;
 const PROGRAM_HIGHLIGHT_TRAIT = 2;
 
 /**
  * A WebGL Shader contributing to building a WebGL Program.
  * You can either retrieve, or compile the source of a shader, which will
  * automatically inflict the necessary changes to the WebGL state.
  */
-var ShaderActor = protocol.ActorClass(shaderSpec, {
+var ShaderActor = protocol.ActorClassWithSpec(shaderSpec, {
   /**
    * Create the shader actor.
    *
    * @param DebuggerServerConnection conn
    *        The server connection.
    * @param WebGLProgram program
    *        The WebGL program being linked.
    * @param WebGLShader shader
@@ -78,17 +78,17 @@ var ShaderActor = protocol.ActorClass(sh
     return undefined;
   }
 });
 
 /**
  * A WebGL program is composed (at the moment, analogue to OpenGL ES 2.0)
  * of two shaders: a vertex shader and a fragment shader.
  */
-var ProgramActor = protocol.ActorClass(programSpec, {
+var ProgramActor = protocol.ActorClassWithSpec(programSpec, {
   /**
    * Create the program actor.
    *
    * @param DebuggerServerConnection conn
    *        The server connection.
    * @param WebGLProgram program
    *        The WebGL program being linked.
    * @param WebGLShader[] shaders
@@ -179,17 +179,17 @@ var ProgramActor = protocol.ActorClass(p
   }
 });
 
 /**
  * The WebGL Actor handles simple interaction with a WebGL context via a few
  * high-level methods. After instantiating this actor, you'll need to set it
  * up by calling setup().
  */
-var WebGLActor = exports.WebGLActor = protocol.ActorClass(webGLSpec, {
+var WebGLActor = exports.WebGLActor = protocol.ActorClassWithSpec(webGLSpec, {
   initialize: function (conn, tabActor) {
     protocol.Actor.prototype.initialize.call(this, conn);
     this.tabActor = tabActor;
     this._onGlobalCreated = this._onGlobalCreated.bind(this);
     this._onGlobalDestroyed = this._onGlobalDestroyed.bind(this);
     this._onProgramLinked = this._onProgramLinked.bind(this);
   },
   destroy: function (conn) {
--- a/devtools/server/actors/worker.js
+++ b/devtools/server/actors/worker.js
@@ -50,17 +50,17 @@ function matchWorkerDebugger(dbg, option
     if (window !== options.window) {
       return false;
     }
   }
 
   return true;
 }
 
-let WorkerActor = protocol.ActorClass(workerSpec, {
+let WorkerActor = protocol.ActorClassWithSpec(workerSpec, {
   initialize(conn, dbg) {
     protocol.Actor.prototype.initialize.call(this, conn);
     this._dbg = dbg;
     this._attached = false;
     this._threadActor = null;
     this._transport = null;
   },
 
@@ -308,17 +308,17 @@ WorkerActorList.prototype = {
     if (matchWorkerDebugger(dbg, this._options)) {
       this._notifyListChanged();
     }
   }
 };
 
 exports.WorkerActorList = WorkerActorList;
 
-let PushSubscriptionActor = protocol.ActorClass(pushSubscriptionSpec, {
+let PushSubscriptionActor = protocol.ActorClassWithSpec(pushSubscriptionSpec, {
   initialize(conn, subscription) {
     protocol.Actor.prototype.initialize.call(this, conn);
     this._subscription = subscription;
   },
 
   form(detail) {
     if (detail === "actorid") {
       return this.actorID;
@@ -338,17 +338,17 @@ let PushSubscriptionActor = protocol.Act
     this._subscription = null;
   },
 });
 
 // Lazily load the service-worker-child.js process script only once.
 let _serviceWorkerProcessScriptLoaded = false;
 
 let ServiceWorkerRegistrationActor =
-protocol.ActorClass(serviceWorkerRegistrationSpec, {
+protocol.ActorClassWithSpec(serviceWorkerRegistrationSpec, {
   /**
    * Create the ServiceWorkerRegistrationActor
    * @param DebuggerServerConnection conn
    *   The server connection.
    * @param ServiceWorkerRegistrationInfo registration
    *   The registration's information.
    */
   initialize(conn, registration) {
--- a/devtools/server/tests/mochitest/hello-actor.js
+++ b/devtools/server/tests/mochitest/hello-actor.js
@@ -9,17 +9,17 @@ const helloSpec = protocol.generateActor
   methods: {
     count: {
       request: {},
       response: {count: protocol.RetVal("number")}
     }
   }
 });
 
-var HelloActor = protocol.ActorClass(helloSpec, {
+var HelloActor = protocol.ActorClassWithSpec(helloSpec, {
   initialize: function () {
     protocol.Actor.prototype.initialize.apply(this, arguments);
     this.counter = 0;
   },
 
   count: function () {
     return ++this.counter;
   }
--- a/devtools/server/tests/unit/hello-actor.js
+++ b/devtools/server/tests/unit/hello-actor.js
@@ -6,13 +6,13 @@ const protocol = require("devtools/share
 const helloSpec = protocol.generateActorSpec({
   typeName: "helloActor",
 
   methods: {
     hello: {}
   }
 });
 
-var HelloActor = protocol.ActorClass(helloSpec, {
+var HelloActor = protocol.ActorClassWithSpec(helloSpec, {
   hello: function () {
     return;
   }
 });
--- a/devtools/server/tests/unit/registertestactors-03.js
+++ b/devtools/server/tests/unit/registertestactors-03.js
@@ -1,40 +1,40 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-var {method, RetVal, Actor, ActorClass, Front, FrontClass,
+var {method, RetVal, Actor, ActorClassWithSpec, Front, FrontClassWithSpec,
      generateActorSpec} = require("devtools/shared/protocol");
 var Services = require("Services");
 
 const lazySpec = generateActorSpec({
   typeName: "lazy",
 
   methods: {
     hello: {
       response: { str: RetVal("string") }
     }
   }
 });
 
-exports.LazyActor = ActorClass(lazySpec, {
+exports.LazyActor = ActorClassWithSpec(lazySpec, {
   initialize: function (conn, id) {
     Actor.prototype.initialize.call(this, conn);
 
     Services.obs.notifyObservers(null, "actor", "instantiated");
   },
 
   hello: function (str) {
     return "world";
   }
 });
 
 Services.obs.notifyObservers(null, "actor", "loaded");
 
-exports.LazyFront = FrontClass(lazySpec, {
+exports.LazyFront = FrontClassWithSpec(lazySpec, {
   initialize: function (client, form) {
     Front.prototype.initialize.call(this, client);
     this.actorID = form.lazyActor;
 
     client.addActorPool(this);
     this.manage(this);
   }
 });
--- a/devtools/server/tests/unit/test_protocol_abort.js
+++ b/devtools/server/tests/unit/test_protocol_abort.js
@@ -23,34 +23,34 @@ const rootSpec = protocol.generateActorS
 
   methods: {
     simpleReturn: {
       response: { value: RetVal() }
     }
   }
 });
 
-var RootActor = protocol.ActorClass(rootSpec, {
+var RootActor = protocol.ActorClassWithSpec(rootSpec, {
   typeName: "root",
   initialize: function (conn) {
     protocol.Actor.prototype.initialize.call(this, conn);
     // Root actor owns itself.
     this.manage(this);
     this.actorID = "root";
     this.sequence = 0;
   },
 
   sayHello: simpleHello,
 
   simpleReturn: function () {
     return this.sequence++;
   }
 });
 
-var RootFront = protocol.FrontClass(rootSpec, {
+var RootFront = protocol.FrontClassWithSpec(rootSpec, {
   initialize: function (client) {
     this.actorID = "root";
     protocol.Front.prototype.initialize.call(this, client);
     // Root owns itself.
     this.manage(this);
   }
 });
 
--- a/devtools/server/tests/unit/test_protocol_async.js
+++ b/devtools/server/tests/unit/test_protocol_async.js
@@ -34,17 +34,17 @@ const rootSpec = protocol.generateActorS
       response: { value: RetVal("number") }
     },
     promiseThrow: {
       response: { value: RetVal("number") },
     }
   }
 });
 
-var RootActor = protocol.ActorClass(rootSpec, {
+var RootActor = protocol.ActorClassWithSpec(rootSpec, {
   initialize: function (conn) {
     protocol.Actor.prototype.initialize.call(this, conn);
     // Root actor owns itself.
     this.manage(this);
     this.actorID = "root";
     this.sequence = 0;
   },
 
@@ -84,17 +84,17 @@ var RootActor = protocol.ActorClass(root
     // This should be enough to force a failure if the code is broken.
     do_timeout(150, () => {
       deferred.reject(sequence++);
     });
     return deferred.promise;
   }
 });
 
-var RootFront = protocol.FrontClass(rootSpec, {
+var RootFront = protocol.FrontClassWithSpec(rootSpec, {
   initialize: function (client) {
     this.actorID = "root";
     protocol.Front.prototype.initialize.call(this, client);
     // Root owns itself.
     this.manage(this);
   }
 });
 
--- a/devtools/server/tests/unit/test_protocol_children.js
+++ b/devtools/server/tests/unit/test_protocol_children.js
@@ -83,17 +83,17 @@ const childSpec = protocol.generateActor
       response: { value: "correct response" },
     },
     release: {
       release: true
     }
   }
 });
 
-var ChildActor = protocol.ActorClass(childSpec, {
+var ChildActor = protocol.ActorClassWithSpec(childSpec, {
   // Actors returned by this actor should be owned by the root actor.
   marshallPool: function () { return this.parent(); },
 
   toString: function () { return "[ChildActor " + this.childID + "]"; },
 
   initialize: function (conn, id) {
     protocol.Actor.prototype.initialize.call(this, conn);
     this.childID = id;
@@ -151,17 +151,17 @@ var ChildActor = protocol.ActorClass(chi
     events.emit(this, "named-event", 1, 2, 3);
     events.emit(this, "object-event", this);
     events.emit(this, "array-object-event", [this]);
   },
 
   release: function () { },
 });
 
-var ChildFront = protocol.FrontClass(childSpec, {
+var ChildFront = protocol.FrontClassWithSpec(childSpec, {
   initialize: function (client, form) {
     protocol.Front.prototype.initialize.call(this, client, form);
   },
 
   destroy: function () {
     this.destroyed = true;
     protocol.Front.prototype.destroy.call(this);
   },
@@ -221,17 +221,17 @@ const rootSpec = protocol.generateActorS
       request: { id: Arg(0) },
       response: { child: RetVal("temp:childActor") }
     },
     clearTemporaryChildren: {}
   }
 });
 
 var rootActor = null;
-var RootActor = protocol.ActorClass(rootSpec, {
+var RootActor = protocol.ActorClassWithSpec(rootSpec, {
   toString: function () { return "[root actor]"; },
 
   initialize: function (conn) {
     rootActor = this;
     this.actorID = "root";
     this._children = {};
     protocol.Actor.prototype.initialize.call(this, conn);
     // Root actor owns itself.
@@ -282,17 +282,17 @@ var RootActor = protocol.ActorClass(root
     if (!this._temporaryHolder) {
       return;
     }
     this._temporaryHolder.destroy();
     delete this._temporaryHolder;
   }
 });
 
-var RootFront = protocol.FrontClass(rootSpec, {
+var RootFront = protocol.FrontClassWithSpec(rootSpec, {
   toString: function () { return "[root front]"; },
   initialize: function (client) {
     this.actorID = "root";
     protocol.Front.prototype.initialize.call(this, client);
     // Root actor owns itself.
     this.manage(this);
   },
 
--- a/devtools/server/tests/unit/test_protocol_formtype.js
+++ b/devtools/server/tests/unit/test_protocol_formtype.js
@@ -10,34 +10,34 @@ const childSpec = protocol.generateActor
   methods: {
     getChild: {
       response: RetVal("child")
     }
   }
 });
 
 // The child actor doesn't provide a form description
-var ChildActor = protocol.ActorClass(childSpec, {
+var ChildActor = protocol.ActorClassWithSpec(childSpec, {
   initialize(conn) {
     protocol.Actor.prototype.initialize.call(this, conn);
   },
 
   form(detail) {
     return {
       actor: this.actorID,
       extra: "extra"
     };
   },
 
   getChild: function () {
     return this;
   }
 });
 
-var ChildFront = protocol.FrontClass(childSpec, {
+var ChildFront = protocol.FrontClassWithSpec(childSpec, {
   initialize(client) {
     protocol.Front.prototype.initialize.call(this, client);
   },
 
   form(v, ctx, detail) {
     this.extra = v.extra;
   }
 });
@@ -72,17 +72,17 @@ const rootSpec = protocol.generateActorS
     },
     getUnknownDetail: {
       response: RetVal("root#unknownDetail")
     }
   }
 });
 
 // The root actor does provide a form description.
-var RootActor = protocol.ActorClass(rootSpec, {
+var RootActor = protocol.ActorClassWithSpec(rootSpec, {
   initialize(conn) {
     protocol.Actor.prototype.initialize.call(this, conn);
     this.manage(this);
     this.child = new ChildActor();
   },
 
   sayHello() {
     return {
@@ -120,17 +120,17 @@ var RootActor = protocol.ActorClass(root
     return this;
   },
 
   getUnknownDetail: function () {
     return this;
   }
 });
 
-var RootFront = protocol.FrontClass(rootSpec, {
+var RootFront = protocol.FrontClassWithSpec(rootSpec, {
   initialize(client) {
     this.actorID = "root";
     protocol.Front.prototype.initialize.call(this, client);
 
     // Root owns itself.
     this.manage(this);
   },
 
--- a/devtools/server/tests/unit/test_protocol_longstring.js
+++ b/devtools/server/tests/unit/test_protocol_longstring.js
@@ -47,17 +47,17 @@ const rootSpec = protocol.generateActorS
       oneway: true,
     },
     emitLongString: {
       oneway: true,
     }
   }
 });
 
-var RootActor = protocol.ActorClass(rootSpec, {
+var RootActor = protocol.ActorClassWithSpec(rootSpec, {
   initialize: function (conn) {
     rootActor = this;
     protocol.Actor.prototype.initialize.call(this, conn);
     // Root actor owns itself.
     this.manage(this);
     this.actorID = "root";
   },
 
@@ -75,17 +75,17 @@ var RootActor = protocol.ActorClass(root
     events.emit(this, "string-event", new LongStringActor(this.conn, SHORT_STR));
   },
 
   emitLongString: function () {
     events.emit(this, "string-event", new LongStringActor(this.conn, LONG_STR));
   },
 });
 
-var RootFront = protocol.FrontClass(rootSpec, {
+var RootFront = protocol.FrontClassWithSpec(rootSpec, {
   initialize: function (client) {
     this.actorID = "root";
     protocol.Front.prototype.initialize.call(this, client);
     // Root owns itself.
     this.manage(this);
   }
 });
 
--- a/devtools/server/tests/unit/test_protocol_simple.js
+++ b/devtools/server/tests/unit/test_protocol_simple.js
@@ -96,17 +96,17 @@ const rootSpec = protocol.generateActorS
       oneway: true
     },
     emitFalsyOptions: {
       oneway: true
     }
   }
 });
 
-var RootActor = protocol.ActorClass(rootSpec, {
+var RootActor = protocol.ActorClassWithSpec(rootSpec, {
   initialize: function (conn) {
     protocol.Actor.prototype.initialize.call(this, conn);
     // Root actor owns itself.
     this.manage(this);
     this.actorID = "root";
   },
 
   sayHello: simpleHello,
@@ -159,34 +159,34 @@ var RootActor = protocol.ActorClass(root
     events.emit(this, "oneway", a);
   },
 
   emitFalsyOptions: function () {
     events.emit(this, "falsyOptions", { zero: 0, farce: false });
   }
 });
 
-var RootFront = protocol.FrontClass(rootSpec, {
+var RootFront = protocol.FrontClassWithSpec(rootSpec, {
   initialize: function (client) {
     this.actorID = "root";
     protocol.Front.prototype.initialize.call(this, client);
     // Root owns itself.
     this.manage(this);
   }
 });
 
 function run_test()
 {
   DebuggerServer.createRootActor = (conn => {
     return RootActor(conn);
   });
   DebuggerServer.init();
 
   check_except(() => {
-    let badActor = ActorClass({}, {
+    let badActor = ActorClassWithSpec({}, {
       missing: preEvent("missing-event", function () {
       })
     });
   });
 
   protocol.types.getType("array:array:array:number");
   protocol.types.getType("array:array:array:number");
 
--- a/devtools/server/tests/unit/test_protocol_stack.js
+++ b/devtools/server/tests/unit/test_protocol_stack.js
@@ -27,33 +27,33 @@ const rootSpec = protocol.generateActorS
 
   methods: {
     simpleReturn: {
       response: { value: RetVal() },
     }
   }
 });
 
-var RootActor = protocol.ActorClass(rootSpec, {
+var RootActor = protocol.ActorClassWithSpec(rootSpec, {
   initialize: function (conn) {
     protocol.Actor.prototype.initialize.call(this, conn);
     // Root actor owns itself.
     this.manage(this);
     this.actorID = "root";
     this.sequence = 0;
   },
 
   sayHello: simpleHello,
 
   simpleReturn: function () {
     return this.sequence++;
   }
 });
 
-var RootFront = protocol.FrontClass(rootSpec, {
+var RootFront = protocol.FrontClassWithSpec(rootSpec, {
   initialize: function (client) {
     this.actorID = "root";
     protocol.Front.prototype.initialize.call(this, client);
     // Root owns itself.
     this.manage(this);
   }
 });
 
--- a/devtools/shared/fronts/actor-registry.js
+++ b/devtools/shared/fronts/actor-registry.js
@@ -6,17 +6,17 @@
 const { components } = require("chrome");
 const Services = require("Services");
 const { actorActorSpec, actorRegistrySpec } = require("devtools/shared/specs/actor-registry");
 const protocol = require("devtools/shared/protocol");
 const { custom } = protocol;
 
 loader.lazyImporter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
 
-const ActorActorFront = protocol.FrontClass(actorActorSpec, {
+const ActorActorFront = protocol.FrontClassWithSpec(actorActorSpec, {
   initialize: function (client, form) {
     protocol.Front.prototype.initialize.call(this, client, form);
   }
 });
 
 exports.ActorActorFront = ActorActorFront;
 
 function request(uri) {
@@ -41,17 +41,17 @@ function request(uri) {
 
       let source = NetUtil.readInputStreamToString(stream, stream.available());
       stream.close();
       resolve(source);
     });
   });
 }
 
-const ActorRegistryFront = protocol.FrontClass(actorRegistrySpec, {
+const ActorRegistryFront = protocol.FrontClassWithSpec(actorRegistrySpec, {
   initialize: function (client, form) {
     protocol.Front.prototype.initialize.call(this, client,
                                              { actor: form.actorRegistryActor });
 
     this.manage(this);
   },
 
   registerActor: custom(function (uri, options) {
--- a/devtools/shared/fronts/addons.js
+++ b/devtools/shared/fronts/addons.js
@@ -1,17 +1,17 @@
 /* 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 {addonsSpec} = require("devtools/shared/specs/addons");
 const protocol = require("devtools/shared/protocol");
 
-const AddonsFront = protocol.FrontClass(addonsSpec, {
+const AddonsFront = protocol.FrontClassWithSpec(addonsSpec, {
   initialize: function (client, {addonsActor}) {
     protocol.Front.prototype.initialize.call(this, client);
     this.actorID = addonsActor;
     this.manage(this);
   }
 });
 
 exports.AddonsFront = AddonsFront;
--- a/devtools/shared/fronts/animation.js
+++ b/devtools/shared/fronts/animation.js
@@ -1,26 +1,26 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const {
   Front,
-  FrontClass,
+  FrontClassWithSpec,
   custom,
   preEvent
 } = require("devtools/shared/protocol");
 const {
   animationPlayerSpec,
   animationsSpec
 } = require("devtools/shared/specs/animation");
 const { Task } = require("devtools/shared/task");
 
-const AnimationPlayerFront = FrontClass(animationPlayerSpec, {
+const AnimationPlayerFront = FrontClassWithSpec(animationPlayerSpec, {
   initialize: function (conn, form, detail, ctx) {
     Front.prototype.initialize.call(this, conn, form, detail, ctx);
 
     this.state = {};
   },
 
   form: function (form, detail) {
     if (detail === "actorid") {
@@ -118,17 +118,17 @@ const AnimationPlayerFront = FrontClass(
     }
 
     return {state: data, hasChanged};
   }
 });
 
 exports.AnimationPlayerFront = AnimationPlayerFront;
 
-const AnimationsFront = FrontClass(animationsSpec, {
+const AnimationsFront = FrontClassWithSpec(animationsSpec, {
   initialize: function (client, {animationsActor}) {
     Front.prototype.initialize.call(this, client, {actor: animationsActor});
     this.manage(this);
   },
 
   destroy: function () {
     Front.prototype.destroy.call(this);
   }
--- a/devtools/shared/fronts/call-watcher.js
+++ b/devtools/shared/fronts/call-watcher.js
@@ -4,17 +4,17 @@
 "use strict";
 
 const { functionCallSpec, callWatcherSpec } = require("devtools/shared/specs/call-watcher");
 const protocol = require("devtools/shared/protocol");
 
 /**
  * The corresponding Front object for the FunctionCallActor.
  */
-const FunctionCallFront = protocol.FrontClass(functionCallSpec, {
+const FunctionCallFront = protocol.FrontClassWithSpec(functionCallSpec, {
   initialize: function (client, form) {
     protocol.Front.prototype.initialize.call(this, client, form);
   },
 
   /**
    * Adds some generic information directly to this instance,
    * to avoid extra roundtrips.
    */
@@ -33,17 +33,17 @@ const FunctionCallFront = protocol.Front
 
 exports.FunctionCallFront = FunctionCallFront;
 
 /**
  * The corresponding Front object for the CallWatcherActor.
  */
 var CallWatcherFront =
 exports.CallWatcherFront =
-protocol.FrontClass(callWatcherSpec, {
+protocol.FrontClassWithSpec(callWatcherSpec, {
   initialize: function (client, { callWatcherActor }) {
     protocol.Front.prototype.initialize.call(this, client, { actor: callWatcherActor });
     this.manage(this);
   }
 });
 
 /**
  * Constants.
--- a/devtools/shared/fronts/canvas.js
+++ b/devtools/shared/fronts/canvas.js
@@ -13,17 +13,17 @@ const {
   INTERESTING_CALLS,
 } = require("devtools/shared/specs/canvas");
 const protocol = require("devtools/shared/protocol");
 const promise = require("promise");
 
 /**
  * The corresponding Front object for the FrameSnapshotActor.
  */
-const FrameSnapshotFront = protocol.FrontClass(frameSnapshotSpec, {
+const FrameSnapshotFront = protocol.FrontClassWithSpec(frameSnapshotSpec, {
   initialize: function (client, form) {
     protocol.Front.prototype.initialize.call(this, client, form);
     this._animationFrameEndScreenshot = null;
     this._cachedScreenshots = new WeakMap();
   },
 
   /**
    * This implementation caches the animation frame end screenshot to optimize
@@ -59,17 +59,17 @@ const FrameSnapshotFront = protocol.Fron
   })
 });
 
 exports.FrameSnapshotFront = FrameSnapshotFront;
 
 /**
  * The corresponding Front object for the CanvasActor.
  */
-const CanvasFront = protocol.FrontClass(canvasSpec, {
+const CanvasFront = protocol.FrontClassWithSpec(canvasSpec, {
   initialize: function (client, { canvasActor }) {
     protocol.Front.prototype.initialize.call(this, client, { actor: canvasActor });
     this.manage(this);
   }
 });
 
 /**
  * Constants.
--- a/devtools/shared/fronts/css-properties.js
+++ b/devtools/shared/fronts/css-properties.js
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
-const { FrontClass, Front } = require("devtools/shared/protocol");
+const { FrontClassWithSpec, Front } = require("devtools/shared/protocol");
 const { cssPropertiesSpec } = require("devtools/shared/specs/css-properties");
 const { Task } = require("devtools/shared/task");
 const { CSS_PROPERTIES_DB } = require("devtools/shared/css-properties-db");
 const { cssColors } = require("devtools/shared/css-color-db");
 
 /**
  * Build up a regular expression that matches a CSS variable token. This is an
  * ident token that starts with two dashes "--".
@@ -34,17 +34,17 @@ function isCssVariable(input) {
 var cachedCssProperties = new WeakMap();
 
 /**
  * The CssProperties front provides a mechanism to have a one-time asynchronous
  * load of a CSS properties database. This is then fed into the CssProperties
  * interface that provides synchronous methods for finding out what CSS
  * properties the current server supports.
  */
-const CssPropertiesFront = FrontClass(cssPropertiesSpec, {
+const CssPropertiesFront = FrontClassWithSpec(cssPropertiesSpec, {
   initialize: function (client, { cssPropertiesActor }) {
     Front.prototype.initialize.call(this, client, {actor: cssPropertiesActor});
     this.manage(this);
   }
 });
 
 /**
  * Ask questions to a CSS database. This class does not care how the database
--- a/devtools/shared/fronts/csscoverage.js
+++ b/devtools/shared/fronts/csscoverage.js
@@ -31,17 +31,17 @@ const l10n = exports.l10n = {
 var isRunning = false;
 var notification;
 var target;
 var chromeWindow;
 
 /**
  * Front for CSSUsageActor
  */
-const CSSUsageFront = protocol.FrontClass(cssUsageSpec, {
+const CSSUsageFront = protocol.FrontClassWithSpec(cssUsageSpec, {
   initialize: function (client, form) {
     protocol.Front.prototype.initialize.call(this, client, form);
     this.actorID = form.cssUsageActor;
     this.manage(this);
   },
 
   _onStateChange: protocol.preEvent("state-change", function (ev) {
     isRunning = ev.isRunning;
--- a/devtools/shared/fronts/device.js
+++ b/devtools/shared/fronts/device.js
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const {Cc, Ci, Cu} = require("chrome");
 const {deviceSpec} = require("devtools/shared/specs/device");
 const protocol = require("devtools/shared/protocol");
 const defer = require("devtools/shared/defer");
 
-const DeviceFront = protocol.FrontClass(deviceSpec, {
+const DeviceFront = protocol.FrontClassWithSpec(deviceSpec, {
   initialize: function (client, form) {
     protocol.Front.prototype.initialize.call(this, client);
     this.actorID = form.deviceActor;
     this.manage(this);
   },
 
   screenshotToBlob: function () {
     return this.screenshotToDataURL().then(longstr => {
--- a/devtools/shared/fronts/director-manager.js
+++ b/devtools/shared/fronts/director-manager.js
@@ -8,39 +8,39 @@ const {
   directorScriptSpec,
   directorManagerSpec,
 } = require("devtools/shared/specs/director-manager");
 const protocol = require("devtools/shared/protocol");
 
 /**
  * The corresponding Front object for the MessagePortActor.
  */
-const MessagePortFront = protocol.FrontClass(messagePortSpec, {
+const MessagePortFront = protocol.FrontClassWithSpec(messagePortSpec, {
   initialize: function (client, form) {
     protocol.Front.prototype.initialize.call(this, client, form);
   }
 });
 
 exports.MessagePortFront = MessagePortFront;
 
 /**
  * The corresponding Front object for the DirectorScriptActor.
  */
-const DirectorScriptFront = protocol.FrontClass(directorScriptSpec, {
+const DirectorScriptFront = protocol.FrontClassWithSpec(directorScriptSpec, {
   initialize: function (client, form) {
     protocol.Front.prototype.initialize.call(this, client, form);
   }
 });
 
 exports.DirectorScriptFront = DirectorScriptFront;
 
 /**
  * The corresponding Front object for the DirectorManagerActor.
  */
-const DirectorManagerFront = protocol.FrontClass(directorManagerSpec, {
+const DirectorManagerFront = protocol.FrontClassWithSpec(directorManagerSpec, {
   initialize: function (client, { directorManagerActor }) {
     protocol.Front.prototype.initialize.call(this, client, {
       actor: directorManagerActor
     });
     this.manage(this);
   }
 });
 
--- a/devtools/shared/fronts/director-registry.js
+++ b/devtools/shared/fronts/director-registry.js
@@ -4,17 +4,17 @@
 "use strict";
 
 const {directorRegistrySpec} = require("devtools/shared/specs/director-registry");
 const protocol = require("devtools/shared/protocol");
 
 /**
  * The corresponding Front object for the DirectorRegistryActor.
  */
-const DirectorRegistryFront = protocol.FrontClass(directorRegistrySpec, {
+const DirectorRegistryFront = protocol.FrontClassWithSpec(directorRegistrySpec, {
   initialize: function (client, { directorRegistryActor }) {
     protocol.Front.prototype.initialize.call(this, client, {
       actor: directorRegistryActor
     });
     this.manage(this);
   }
 });
 
--- a/devtools/shared/fronts/emulation.js
+++ b/devtools/shared/fronts/emulation.js
@@ -1,20 +1,20 @@
 /* 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 { Front, FrontClass } = require("devtools/shared/protocol");
+const { Front, FrontClassWithSpec } = require("devtools/shared/protocol");
 const { emulationSpec } = require("devtools/shared/specs/emulation");
 
 /**
  * The corresponding Front object for the EmulationActor.
  */
-const EmulationFront = FrontClass(emulationSpec, {
+const EmulationFront = FrontClassWithSpec(emulationSpec, {
   initialize: function (client, form) {
     Front.prototype.initialize.call(this, client);
     this.actorID = form.emulationActor;
     this.manage(this);
   },
 
   destroy: function () {
     Front.prototype.destroy.call(this);
--- a/devtools/shared/fronts/eventlooplag.js
+++ b/devtools/shared/fronts/eventlooplag.js
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
-const { Front, FrontClass } = require("devtools/shared/protocol");
+const { Front, FrontClassWithSpec } = require("devtools/shared/protocol");
 const { eventLoopLagSpec } = require("devtools/shared/specs/eventlooplag");
 
-exports.EventLoopLagFront = FrontClass(eventLoopLagSpec, {
+exports.EventLoopLagFront = FrontClassWithSpec(eventLoopLagSpec, {
   initialize: function (client, form) {
     Front.prototype.initialize.call(this, client);
     this.actorID = form.eventLoopLagActor;
     this.manage(this);
   },
 });
--- a/devtools/shared/fronts/framerate.js
+++ b/devtools/shared/fronts/framerate.js
@@ -1,19 +1,19 @@
 /* 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 { Front, FrontClass } = require("devtools/shared/protocol");
+const { Front, FrontClassWithSpec } = require("devtools/shared/protocol");
 const { framerateSpec } = require("devtools/shared/specs/framerate");
 
 /**
  * The corresponding Front object for the FramerateActor.
  */
-var FramerateFront = exports.FramerateFront = FrontClass(framerateSpec, {
+var FramerateFront = exports.FramerateFront = FrontClassWithSpec(framerateSpec, {
   initialize: function (client, { framerateActor }) {
     Front.prototype.initialize.call(this, client, { actor: framerateActor });
     this.manage(this);
   }
 });
 
 exports.FramerateFront = FramerateFront;
--- a/devtools/shared/fronts/gcli.js
+++ b/devtools/shared/fronts/gcli.js
@@ -1,20 +1,20 @@
 /* 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 { Front, FrontClass } = require("devtools/shared/protocol");
+const { Front, FrontClassWithSpec } = require("devtools/shared/protocol");
 const { gcliSpec } = require("devtools/shared/specs/gcli");
 
 /**
  *
  */
-const GcliFront = exports.GcliFront = FrontClass(gcliSpec, {
+const GcliFront = exports.GcliFront = FrontClassWithSpec(gcliSpec, {
   initialize: function (client, tabForm) {
     Front.prototype.initialize.call(this, client);
     this.actorID = tabForm.gcliActor;
 
     // XXX: This is the first actor type in its hierarchy to use the protocol
     // library, so we're going to self-own on the client side for now.
     this.manage(this);
   },
--- a/devtools/shared/fronts/highlighters.js
+++ b/devtools/shared/fronts/highlighters.js
@@ -1,25 +1,25 @@
 /* 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 { FrontClass } = require("devtools/shared/protocol");
+const { FrontClassWithSpec } = require("devtools/shared/protocol");
 const {
   customHighlighterSpec,
   highlighterSpec
 } = require("devtools/shared/specs/highlighters");
 
-const HighlighterFront = FrontClass(highlighterSpec, {
+const HighlighterFront = FrontClassWithSpec(highlighterSpec, {
   // Update the object given a form representation off the wire.
   form: function (json) {
     this.actorID = json.actor;
     // FF42+ HighlighterActors starts exposing custom form, with traits object
     this.traits = json.traits || {};
   }
 });
 
 exports.HighlighterFront = HighlighterFront;
 
-const CustomHighlighterFront = FrontClass(customHighlighterSpec, {});
+const CustomHighlighterFront = FrontClassWithSpec(customHighlighterSpec, {});
 
 exports.CustomHighlighterFront = CustomHighlighterFront;
--- a/devtools/shared/fronts/inspector.js
+++ b/devtools/shared/fronts/inspector.js
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 require("devtools/shared/fronts/styles");
 require("devtools/shared/fronts/highlighters");
 const { SimpleStringFront } = require("devtools/shared/fronts/string");
 const {
   Front,
-  FrontClass,
+  FrontClassWithSpec,
   custom,
   preEvent,
   types
 } = require("devtools/shared/protocol.js");
 const {
   inspectorSpec,
   nodeSpec,
   nodeListSpec,
@@ -79,17 +79,17 @@ const AttributeModificationList = Class(
  *  - The order of children isn't guaranteed to be the same as the DOM.
  * Children are stored in a doubly-linked list, to make addition/removal
  * and traversal quick.
  *
  * Due to the order/incompleteness of the child list, it is safe to use
  * the parent node from clients, but the `children` request should be used
  * to traverse children.
  */
-const NodeFront = FrontClass(nodeSpec, {
+const NodeFront = FrontClassWithSpec(nodeSpec, {
   initialize: function (conn, form, detail, ctx) {
     // The parent node
     this._parent = null;
     // The first child of this node.
     this._child = null;
     // The next sibling of this node.
     this._next = null;
     // The previous sibling of this node.
@@ -439,17 +439,17 @@ const NodeFront = FrontClass(nodeSpec, {
   }
 });
 
 exports.NodeFront = NodeFront;
 
 /**
  * Client side of a node list as returned by querySelectorAll()
  */
-const NodeListFront = FrontClass(nodeListSpec, {
+const NodeListFront = FrontClassWithSpec(nodeListSpec, {
   initialize: function (client, form) {
     Front.prototype.initialize.call(this, client, form);
   },
 
   destroy: function () {
     Front.prototype.destroy.call(this);
   },
 
@@ -479,17 +479,17 @@ const NodeListFront = FrontClass(nodeLis
   })
 });
 
 exports.NodeListFront = NodeListFront;
 
 /**
  * Client side of the DOM walker.
  */
-const WalkerFront = FrontClass(walkerSpec, {
+const WalkerFront = FrontClassWithSpec(walkerSpec, {
   // Set to true if cleanup should be requested after every mutation list.
   autoCleanup: true,
 
   /**
    * This is kept for backward-compatibility reasons with older remote target.
    * Targets previous to bug 916443
    */
   pick: custom(function () {
@@ -935,17 +935,17 @@ const WalkerFront = FrontClass(walkerSpe
 });
 
 exports.WalkerFront = WalkerFront;
 
 /**
  * Client side of the inspector actor, which is used to create
  * inspector-related actors, including the walker.
  */
-var InspectorFront = FrontClass(inspectorSpec, {
+var InspectorFront = FrontClassWithSpec(inspectorSpec, {
   initialize: function (client, tabForm) {
     Front.prototype.initialize.call(this, client);
     this.actorID = tabForm.inspectorActor;
 
     // XXX: This is the first actor type in its hierarchy to use the protocol
     // library, so we're going to self-own on the client side for now.
     this.manage(this);
   },
--- a/devtools/shared/fronts/layout.js
+++ b/devtools/shared/fronts/layout.js
@@ -9,17 +9,17 @@ const protocol = require("devtools/share
 /**
  * Usage example of the reflow front:
  *
  * let front = ReflowFront(toolbox.target.client, toolbox.target.form);
  * front.on("reflows", this._onReflows);
  * front.start();
  * // now wait for events to come
  */
-const ReflowFront = protocol.FrontClass(reflowSpec, {
+const ReflowFront = protocol.FrontClassWithSpec(reflowSpec, {
   initialize: function (client, {reflowActor}) {
     protocol.Front.prototype.initialize.call(this, client, {actor: reflowActor});
     this.manage(this);
   },
 
   destroy: function () {
     protocol.Front.prototype.destroy.call(this);
   },
--- a/devtools/shared/fronts/memory.js
+++ b/devtools/shared/fronts/memory.js
@@ -7,17 +7,17 @@ const { memorySpec } = require("devtools
 const { Task } = require("devtools/shared/task");
 const protocol = require("devtools/shared/protocol");
 
 loader.lazyRequireGetter(this, "FileUtils",
                          "resource://gre/modules/FileUtils.jsm", true);
 loader.lazyRequireGetter(this, "HeapSnapshotFileUtils",
                          "devtools/shared/heapsnapshot/HeapSnapshotFileUtils");
 
-const MemoryFront = protocol.FrontClass(memorySpec, {
+const MemoryFront = protocol.FrontClassWithSpec(memorySpec, {
   initialize: function (client, form, rootForm = null) {
     protocol.Front.prototype.initialize.call(this, client, form);
     this._client = client;
     this.actorID = form.memoryActor;
     this.heapSnapshotFileActorID = rootForm
       ? rootForm.heapSnapshotFileActor
       : null;
     this.manage(this);
--- a/devtools/shared/fronts/performance-entries.js
+++ b/devtools/shared/fronts/performance-entries.js
@@ -1,17 +1,17 @@
 /* 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 { Front, FrontClass } = require("devtools/shared/protocol");
+const { Front, FrontClassWithSpec } = require("devtools/shared/protocol");
 const performanceSpec = require("devtools/shared/specs/performance-entries");
 
-var PerformanceEntriesFront = FrontClass(performanceSpec, {
+var PerformanceEntriesFront = FrontClassWithSpec(performanceSpec, {
   initialize: function (client, form) {
     Front.prototype.initialize.call(this, client);
     this.actorID = form.performanceEntriesActor;
     this.manage(this);
   },
 });
 
 exports.PerformanceEntriesFront = PerformanceEntriesFront;
--- a/devtools/shared/fronts/performance-recording.js
+++ b/devtools/shared/fronts/performance-recording.js
@@ -1,29 +1,29 @@
 /* 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 { Front, FrontClass } = require("devtools/shared/protocol");
+const { Front, FrontClassWithSpec } = require("devtools/shared/protocol");
 const { performanceRecordingSpec } = require("devtools/shared/specs/performance-recording");
 
 loader.lazyRequireGetter(this, "PerformanceIO",
   "devtools/client/performance/modules/io");
 loader.lazyRequireGetter(this, "PerformanceRecordingCommon",
   "devtools/shared/performance/recording-common", true);
 loader.lazyRequireGetter(this, "RecordingUtils",
   "devtools/shared/performance/recording-utils");
 loader.lazyRequireGetter(this, "merge", "sdk/util/object", true);
 
 /**
  * This can be used on older Profiler implementations, but the methods cannot
  * be changed -- you must introduce a new method, and detect the server.
  */
-const PerformanceRecordingFront = FrontClass(performanceRecordingSpec, merge({
+const PerformanceRecordingFront = FrontClassWithSpec(performanceRecordingSpec, merge({
   form: function (form, detail) {
     if (detail === "actorid") {
       this.actorID = form;
       return;
     }
     this.actorID = form.actor;
     this._form = form;
     this._configuration = form.configuration;
--- a/devtools/shared/fronts/performance.js
+++ b/devtools/shared/fronts/performance.js
@@ -1,27 +1,27 @@
 /* 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 { Cu } = require("chrome");
-const { Front, FrontClass, custom, preEvent } = require("devtools/shared/protocol");
+const { Front, FrontClassWithSpec, custom, preEvent } = require("devtools/shared/protocol");
 const { PerformanceRecordingFront } = require("devtools/shared/fronts/performance-recording");
 const { performanceSpec } = require("devtools/shared/specs/performance");
 const { Task } = require("devtools/shared/task");
 
 loader.lazyRequireGetter(this, "PerformanceIO",
   "devtools/client/performance/modules/io");
 loader.lazyRequireGetter(this, "LegacyPerformanceFront",
   "devtools/client/performance/legacy/front", true);
 loader.lazyRequireGetter(this, "getSystemInfo",
   "devtools/shared/system", true);
 
-const PerformanceFront = FrontClass(performanceSpec, {
+const PerformanceFront = FrontClassWithSpec(performanceSpec, {
   initialize: function (client, form) {
     Front.prototype.initialize.call(this, client, form);
     this.actorID = form.performanceActor;
     this.manage(this);
   },
 
   destroy: function () {
     Front.prototype.destroy.call(this);
--- a/devtools/shared/fronts/preference.js
+++ b/devtools/shared/fronts/preference.js
@@ -1,17 +1,17 @@
 /* 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 {preferenceSpec} = require("devtools/shared/specs/preference");
 const protocol = require("devtools/shared/protocol");
 
-const PreferenceFront = protocol.FrontClass(preferenceSpec, {
+const PreferenceFront = protocol.FrontClassWithSpec(preferenceSpec, {
   initialize: function (client, form) {
     protocol.Front.prototype.initialize.call(this, client);
     this.actorID = form.preferenceActor;
     this.manage(this);
   },
 });
 
 const _knownPreferenceFronts = new WeakMap();
--- a/devtools/shared/fronts/profiler.js
+++ b/devtools/shared/fronts/profiler.js
@@ -1,29 +1,29 @@
 /* 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 { Cu } = require("chrome");
 const {
   Front,
-  FrontClass,
+  FrontClassWithSpec,
   custom
 } = require("devtools/shared/protocol");
 const { profilerSpec } = require("devtools/shared/specs/profiler");
 
 loader.lazyRequireGetter(this, "events", "sdk/event/core");
 loader.lazyRequireGetter(this, "extend", "sdk/util/object", true);
 
 /**
  * This can be used on older Profiler implementations, but the methods cannot
  * be changed -- you must introduce a new method, and detect the server.
  */
-exports.ProfilerFront = FrontClass(profilerSpec, {
+exports.ProfilerFront = FrontClassWithSpec(profilerSpec, {
   initialize: function (client, form) {
     Front.prototype.initialize.call(this, client, form);
     this.actorID = form.profilerActor;
     this.manage(this);
 
     this._onProfilerEvent = this._onProfilerEvent.bind(this);
     events.on(this, "*", this._onProfilerEvent);
   },
--- a/devtools/shared/fronts/promises.js
+++ b/devtools/shared/fronts/promises.js
@@ -1,23 +1,23 @@
 /* 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 {
   Front,
-  FrontClass,
+  FrontClassWithSpec,
 } = require("devtools/shared/protocol");
 const { promisesSpec } = require("devtools/shared/specs/promises");
 
 /**
  * PromisesFront, the front for the PromisesActor.
  */
-const PromisesFront = FrontClass(promisesSpec, {
+const PromisesFront = FrontClassWithSpec(promisesSpec, {
   initialize: function (client, form) {
     Front.prototype.initialize.call(this, client, form);
     this.actorID = form.promisesActor;
     this.manage(this);
   },
 
   destroy: function () {
     Front.prototype.destroy.call(this);
--- a/devtools/shared/fronts/settings.js
+++ b/devtools/shared/fronts/settings.js
@@ -1,17 +1,17 @@
 /* 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 {settingsSpec} = require("devtools/shared/specs/settings");
 const protocol = require("devtools/shared/protocol");
 
-const SettingsFront = protocol.FrontClass(settingsSpec, {
+const SettingsFront = protocol.FrontClassWithSpec(settingsSpec, {
   initialize: function (client, form) {
     protocol.Front.prototype.initialize.call(this, client);
     this.actorID = form.settingsActor;
     this.manage(this);
   },
 });
 
 const _knownSettingsFronts = new WeakMap();
--- a/devtools/shared/fronts/storage.js
+++ b/devtools/shared/fronts/storage.js
@@ -2,31 +2,31 @@
  * 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 protocol = require("devtools/shared/protocol");
 const specs = require("devtools/shared/specs/storage");
 
 for (let childSpec of Object.values(specs.childSpecs)) {
-  protocol.FrontClass(childSpec, {
+  protocol.FrontClassWithSpec(childSpec, {
     form(form, detail) {
       if (detail === "actorid") {
         this.actorID = form;
         return null;
       }
 
       this.actorID = form.actor;
       this.hosts = form.hosts;
       return null;
     }
   });
 }
 
-const StorageFront = protocol.FrontClass(specs.storageSpec, {
+const StorageFront = protocol.FrontClassWithSpec(specs.storageSpec, {
   initialize(client, tabForm) {
     protocol.Front.prototype.initialize.call(this, client);
     this.actorID = tabForm.storageActor;
     this.manage(this);
   }
 });
 
 exports.StorageFront = StorageFront;
--- a/devtools/shared/fronts/string.js
+++ b/devtools/shared/fronts/string.js
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const {DebuggerServer} = require("devtools/server/main");
 const promise = require("promise");
 const {longStringSpec, SimpleStringFront} = require("devtools/shared/specs/string");
 const protocol = require("devtools/shared/protocol");
 
-const LongStringFront = protocol.FrontClass(longStringSpec, {
+const LongStringFront = protocol.FrontClassWithSpec(longStringSpec, {
   initialize: function (client) {
     protocol.Front.prototype.initialize.call(this, client);
   },
 
   destroy: function () {
     this.initial = null;
     this.length = null;
     this.strPromise = null;
--- a/devtools/shared/fronts/styleeditor.js
+++ b/devtools/shared/fronts/styleeditor.js
@@ -1,27 +1,27 @@
 /* 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 { SimpleStringFront } = require("devtools/shared/fronts/string");
-const { Front, FrontClass } = require("devtools/shared/protocol");
+const { Front, FrontClassWithSpec } = require("devtools/shared/protocol");
 const {
   oldStyleSheetSpec,
   styleEditorSpec
 } = require("devtools/shared/specs/styleeditor");
 const promise = require("promise");
 const defer = require("devtools/shared/defer");
 const events = require("sdk/event/core");
 
 /**
  * StyleSheetFront is the client-side counterpart to a StyleSheetActor.
  */
-const OldStyleSheetFront = FrontClass(oldStyleSheetSpec, {
+const OldStyleSheetFront = FrontClassWithSpec(oldStyleSheetSpec, {
   initialize: function (conn, form, ctx, detail) {
     Front.prototype.initialize.call(this, conn, form, ctx, detail);
 
     this._onPropertyChange = this._onPropertyChange.bind(this);
     events.on(this, "property-change", this._onPropertyChange);
   },
 
   destroy: function () {
@@ -82,17 +82,17 @@ const OldStyleSheetFront = FrontClass(ol
   }
 });
 
 exports.OldStyleSheetFront = OldStyleSheetFront;
 
 /**
  * The corresponding Front object for the StyleEditorActor.
  */
-const StyleEditorFront = FrontClass(styleEditorSpec, {
+const StyleEditorFront = FrontClassWithSpec(styleEditorSpec, {
   initialize: function (client, tabForm) {
     Front.prototype.initialize.call(this, client);
     this.actorID = tabForm.styleEditorActor;
     this.manage(this);
   },
 
   getStyleSheets: function () {
     let deferred = defer();
--- a/devtools/shared/fronts/styles.js
+++ b/devtools/shared/fronts/styles.js
@@ -1,33 +1,33 @@
 /* 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";
 
 require("devtools/shared/fronts/stylesheets");
 const {
   Front,
-  FrontClass,
+  FrontClassWithSpec,
   custom,
   preEvent
 } = require("devtools/shared/protocol");
 const {
   pageStyleSpec,
   styleRuleSpec
 } = require("devtools/shared/specs/styles");
 const promise = require("promise");
 const { Task } = require("devtools/shared/task");
 const { Class } = require("sdk/core/heritage");
 const { RuleRewriter } = require("devtools/shared/css-parsing-utils");
 
 /**
  * PageStyleFront, the front object for the PageStyleActor
  */
-const PageStyleFront = FrontClass(pageStyleSpec, {
+const PageStyleFront = FrontClassWithSpec(pageStyleSpec, {
   initialize: function (conn, form, ctx, detail) {
     Front.prototype.initialize.call(this, conn, form, ctx, detail);
     this.inspector = this.parent();
   },
 
   form: function (form, detail) {
     if (detail === "actorid") {
       this.actorID = form;
@@ -85,17 +85,17 @@ const PageStyleFront = FrontClass(pageSt
   })
 });
 
 exports.PageStyleFront = PageStyleFront;
 
 /**
  * StyleRuleFront, the front for the StyleRule actor.
  */
-const StyleRuleFront = FrontClass(styleRuleSpec, {
+const StyleRuleFront = FrontClassWithSpec(styleRuleSpec, {
   initialize: function (client, form, ctx, detail) {
     Front.prototype.initialize.call(this, client, form, ctx, detail);
   },
 
   destroy: function () {
     Front.prototype.destroy.call(this);
   },
 
--- a/devtools/shared/fronts/stylesheets.js
+++ b/devtools/shared/fronts/stylesheets.js
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
-const { Front, FrontClass } = require("devtools/shared/protocol");
+const { Front, FrontClassWithSpec } = require("devtools/shared/protocol");
 const {
   getIndentationFromPrefs,
   getIndentationFromString
 } = require("devtools/shared/indentation");
 const {
   originalSourceSpec,
   mediaRuleSpec,
   styleSheetSpec,
@@ -16,17 +16,17 @@ const {
 } = require("devtools/shared/specs/stylesheets");
 const promise = require("promise");
 const { Task } = require("devtools/shared/task");
 const events = require("sdk/event/core");
 
 /**
  * The client-side counterpart for an OriginalSourceActor.
  */
-const OriginalSourceFront = FrontClass(originalSourceSpec, {
+const OriginalSourceFront = FrontClassWithSpec(originalSourceSpec, {
   initialize: function (client, form) {
     Front.prototype.initialize.call(this, client, form);
 
     this.isOriginalSource = true;
   },
 
   form: function (form, detail) {
     if (detail === "actorid") {
@@ -45,17 +45,17 @@ const OriginalSourceFront = FrontClass(o
   }
 });
 
 exports.OriginalSourceFront = OriginalSourceFront;
 
 /**
  * Corresponding client-side front for a MediaRuleActor.
  */
-const MediaRuleFront = FrontClass(mediaRuleSpec, {
+const MediaRuleFront = FrontClassWithSpec(mediaRuleSpec, {
   initialize: function (client, form) {
     Front.prototype.initialize.call(this, client, form);
 
     this._onMatchesChange = this._onMatchesChange.bind(this);
     events.on(this, "matches-change", this._onMatchesChange);
   },
 
   _onMatchesChange: function (matches) {
@@ -91,17 +91,17 @@ const MediaRuleFront = FrontClass(mediaR
   }
 });
 
 exports.MediaRuleFront = MediaRuleFront;
 
 /**
  * StyleSheetFront is the client-side counterpart to a StyleSheetActor.
  */
-const StyleSheetFront = FrontClass(styleSheetSpec, {
+const StyleSheetFront = FrontClassWithSpec(styleSheetSpec, {
   initialize: function (conn, form) {
     Front.prototype.initialize.call(this, conn, form);
 
     this._onPropertyChange = this._onPropertyChange.bind(this);
     events.on(this, "property-change", this._onPropertyChange);
   },
 
   destroy: function () {
@@ -168,17 +168,17 @@ const StyleSheetFront = FrontClass(style
   }
 });
 
 exports.StyleSheetFront = StyleSheetFront;
 
 /**
  * The corresponding Front object for the StyleSheetsActor.
  */
-const StyleSheetsFront = FrontClass(styleSheetsSpec, {
+const StyleSheetsFront = FrontClassWithSpec(styleSheetsSpec, {
   initialize: function (client, tabForm) {
     Front.prototype.initialize.call(this, client);
     this.actorID = tabForm.styleSheetsActor;
     this.manage(this);
   }
 });
 
 exports.StyleSheetsFront = StyleSheetsFront;
--- a/devtools/shared/fronts/timeline.js
+++ b/devtools/shared/fronts/timeline.js
@@ -1,23 +1,23 @@
 /* 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 {
   Front,
-  FrontClass,
+  FrontClassWithSpec,
 } = require("devtools/shared/protocol");
 const { timelineSpec } = require("devtools/shared/specs/timeline");
 
 /**
  * TimelineFront, the front for the TimelineActor.
  */
-const TimelineFront = FrontClass(timelineSpec, {
+const TimelineFront = FrontClassWithSpec(timelineSpec, {
   initialize: function (client, { timelineActor }) {
     Front.prototype.initialize.call(this, client, { actor: timelineActor });
     this.manage(this);
   },
   destroy: function () {
     Front.prototype.destroy.call(this);
   },
 });
--- a/devtools/shared/fronts/webaudio.js
+++ b/devtools/shared/fronts/webaudio.js
@@ -20,17 +20,17 @@ const AUDIO_NODE_DEFINITION = require("d
  *            The type of audio node, like "OscillatorNode", "MediaElementAudioSourceNode"
  * @attribute {Boolean} source
  *            Boolean indicating if the node is a source node, like BufferSourceNode,
  *            MediaElementAudioSourceNode, OscillatorNode, etc.
  * @attribute {Boolean} bypassable
  *            Boolean indicating if the audio node is bypassable (splitter,
  *            merger and destination nodes, for example, are not)
  */
-const AudioNodeFront = protocol.FrontClass(audionodeSpec, {
+const AudioNodeFront = protocol.FrontClassWithSpec(audionodeSpec, {
   form: function (form, detail) {
     if (detail === "actorid") {
       this.actorID = form;
       return;
     }
 
     this.actorID = form.actor;
     this.type = form.type;
@@ -48,17 +48,17 @@ const AudioNodeFront = protocol.FrontCla
   }
 });
 
 exports.AudioNodeFront = AudioNodeFront;
 
 /**
  * The corresponding Front object for the WebAudioActor.
  */
-const WebAudioFront = protocol.FrontClass(webAudioSpec, {
+const WebAudioFront = protocol.FrontClassWithSpec(webAudioSpec, {
   initialize: function (client, { webaudioActor }) {
     protocol.Front.prototype.initialize.call(this, client, { actor: webaudioActor });
     this.manage(this);
   },
 
   /**
    * If connecting to older geckos (<Fx43), where audio node actor's do not
    * contain `type`, `source` and `bypassable` properties, fetch
--- a/devtools/shared/fronts/webgl.js
+++ b/devtools/shared/fronts/webgl.js
@@ -8,38 +8,38 @@ const {
   programSpec,
   webGLSpec,
 } = require("devtools/shared/specs/webgl");
 const protocol = require("devtools/shared/protocol");
 
 /**
  * The corresponding Front object for the ShaderActor.
  */
-const ShaderFront = protocol.FrontClass(shaderSpec, {
+const ShaderFront = protocol.FrontClassWithSpec(shaderSpec, {
   initialize: function (client, form) {
     protocol.Front.prototype.initialize.call(this, client, form);
   }
 });
 
 exports.ShaderFront = ShaderFront;
 
 /**
  * The corresponding Front object for the ProgramActor.
  */
-const ProgramFront = protocol.FrontClass(programSpec, {
+const ProgramFront = protocol.FrontClassWithSpec(programSpec, {
   initialize: function (client, form) {
     protocol.Front.prototype.initialize.call(this, client, form);
   }
 });
 
 exports.ProgramFront = ProgramFront;
 
 /**
  * The corresponding Front object for the WebGLActor.
  */
-const WebGLFront = protocol.FrontClass(webGLSpec, {
+const WebGLFront = protocol.FrontClassWithSpec(webGLSpec, {
   initialize: function (client, { webglActor }) {
     protocol.Front.prototype.initialize.call(this, client, { actor: webglActor });
     this.manage(this);
   }
 });
 
 exports.WebGLFront = WebGLFront;
--- a/devtools/shared/protocol.js
+++ b/devtools/shared/protocol.js
@@ -1085,35 +1085,52 @@ var generateRequestHandlers = function (
   });
 
   actorProto._actorSpec = actorSpec;
 
   return actorProto;
 };
 
 /**
+ * THIS METHOD IS DEPRECATED, AND PRESERVED ONLY FOR ADD-ONS. IT SHOULD NOT BE
+ * USED INSIDE THE TREE.
+ *
+ * Create an actor class for the given actor prototype.
+ *
+ * @param object actorProto
+ *    The actor prototype.  Must have a 'typeName' property,
+ *    should have method definitions, can have event definitions.
+ */
+exports.ActorClass = function (actorProto) {
+  return ActorClassWithSpec(generateActorSpec(actorProto), actorProto);
+};
+
+/**
+ * THIS METHOD IS DEPRECATED, AND PRESERVED ONLY FOR ADD-ONS. IT SHOULD NOT BE
+ * USED INSIDE THE TREE.
+ *
  * Create an actor class for the given actor specification and prototype.
  *
  * @param object actorSpec
  *    The actor specification. Must have a 'typeName' property.
  * @param object actorProto
  *    The actor prototype. Should have method definitions, can have event
  *    definitions.
  */
-var ActorClass = function (actorSpec, actorProto) {
+var ActorClassWithSpec = function (actorSpec, actorProto) {
   if (!actorSpec.typeName) {
     throw Error("Actor specification must have a typeName member.");
   }
 
   actorProto.extends = Actor;
   let cls = Class(generateRequestHandlers(actorSpec, actorProto));
 
   return cls;
 };
-exports.ActorClass = ActorClass;
+exports.ActorClassWithSpec = ActorClassWithSpec;
 
 /**
  * Base class for client-side actor fronts.
  */
 var Front = Class({
   extends: Pool,
 
   actorID: null,
@@ -1406,36 +1423,49 @@ var generateRequestMethods = function (a
   }
 
   frontProto._actorSpec = actorSpec;
 
   return frontProto;
 };
 
 /**
+ * Create a front class for the given actor class and front prototype.
+ *
+ * @param ActorClass actorType
+ *    The actor class you're creating a front for.
+ * @param object frontProto
+ *    The front prototype.  Must have a 'typeName' property,
+ *    should have method definitions, can have event definitions.
+ */
+exports.FrontClass = function (actorType, frontProto) {
+  return FrontClassWithSpec(prototypeOf(actorType)._actorSpec, frontProto);
+};
+
+/**
  * Create a front class for the given actor specification and front prototype.
  *
  * @param object actorSpec
  *    The actor specification you're creating a front for.
  * @param object proto
  *    The object prototype.  Must have a 'typeName' property,
  *    should have method definitions, can have event definitions.
  */
-var FrontClass = function (actorSpec, frontProto) {
+var FrontClassWithSpec = function (actorSpec, frontProto) {
   frontProto.extends = Front;
   let cls = Class(generateRequestMethods(actorSpec, frontProto));
 
   if (!registeredTypes.has(actorSpec.typeName)) {
     types.addActorType(actorSpec.typeName);
   }
   registeredTypes.get(actorSpec.typeName).frontClass = cls;
 
   return cls;
 };
-exports.FrontClass = FrontClass;
+exports.FrontClassWithSpec = FrontClassWithSpec;
 
 exports.dumpActorSpec = function (type) {
   let actorSpec = type.actorSpec;
   let ret = {
     category: "actor",
     typeName: type.name,
     methods: [],
     events: {}
--- a/devtools/shared/specs/emulation.js
+++ b/devtools/shared/specs/emulation.js
@@ -9,27 +9,52 @@ const emulationSpec = generateActorSpec(
   typeName: "emulation",
 
   methods: {
     setTouchEventsOverride: {
       request: {
         flag: Arg(0, "number")
       },
       response: {
-        reload: RetVal("boolean")
+        valueChanged: RetVal("boolean")
       }
     },
 
     getTouchEventsOverride: {
       request: {},
       response: {
         flag: RetVal("number")
       }
     },
 
     clearTouchEventsOverride: {
       request: {},
-      response: {}
+      response: {
+        valueChanged: RetVal("boolean")
+      }
+    },
+
+    setUserAgentOverride: {
+      request: {
+        flag: Arg(0, "string")
+      },
+      response: {
+        valueChanged: RetVal("boolean")
+      }
+    },
+
+    getUserAgentOverride: {
+      request: {},
+      response: {
+        userAgent: RetVal("string")
+      }
+    },
+
+    clearUserAgentOverride: {
+      request: {},
+      response: {
+        valueChanged: RetVal("boolean")
+      }
     },
   }
 });
 
 exports.emulationSpec = emulationSpec;
--- a/dom/base/StructuredCloneHolder.cpp
+++ b/dom/base/StructuredCloneHolder.cpp
@@ -128,27 +128,27 @@ StructuredCloneCallbacksFreeTransfer(uin
 
 void
 StructuredCloneCallbacksError(JSContext* aCx,
                               uint32_t aErrorId)
 {
   NS_WARNING("Failed to clone data.");
 }
 
-const JSStructuredCloneCallbacks gCallbacks = {
+} // anonymous namespace
+
+const JSStructuredCloneCallbacks StructuredCloneHolder::sCallbacks = {
   StructuredCloneCallbacksRead,
   StructuredCloneCallbacksWrite,
   StructuredCloneCallbacksError,
   StructuredCloneCallbacksReadTransfer,
   StructuredCloneCallbacksWriteTransfer,
   StructuredCloneCallbacksFreeTransfer
 };
 
-} // anonymous namespace
-
 // StructuredCloneHolderBase class
 
 StructuredCloneHolderBase::StructuredCloneHolderBase(StructuredCloneScope aScope)
   : mStructuredCloneScope(aScope)
 #ifdef DEBUG
   , mClearCalled(false)
 #endif
 {}
@@ -180,34 +180,34 @@ StructuredCloneHolderBase::Write(JSConte
 bool
 StructuredCloneHolderBase::Write(JSContext* aCx,
                                  JS::Handle<JS::Value> aValue,
                                  JS::Handle<JS::Value> aTransfer)
 {
   MOZ_ASSERT(!mBuffer, "Double Write is not allowed");
   MOZ_ASSERT(!mClearCalled, "This method cannot be called after Clear.");
 
-  mBuffer = new JSAutoStructuredCloneBuffer(mStructuredCloneScope, &gCallbacks, this);
+  mBuffer = MakeUnique<JSAutoStructuredCloneBuffer>(mStructuredCloneScope, &StructuredCloneHolder::sCallbacks, this);
 
-  if (!mBuffer->write(aCx, aValue, aTransfer, &gCallbacks, this)) {
+  if (!mBuffer->write(aCx, aValue, aTransfer, &StructuredCloneHolder::sCallbacks, this)) {
     mBuffer = nullptr;
     return false;
   }
 
   return true;
 }
 
 bool
 StructuredCloneHolderBase::Read(JSContext* aCx,
                                 JS::MutableHandle<JS::Value> aValue)
 {
   MOZ_ASSERT(mBuffer, "Read() without Write() is not allowed.");
   MOZ_ASSERT(!mClearCalled, "This method cannot be called after Clear.");
 
-  bool ok = mBuffer->read(aCx, aValue, &gCallbacks, this);
+  bool ok = mBuffer->read(aCx, aValue, &StructuredCloneHolder::sCallbacks, this);
   return ok;
 }
 
 bool
 StructuredCloneHolderBase::CustomReadTransferHandler(JSContext* aCx,
                                                      JSStructuredCloneReader* aReader,
                                                      uint32_t aTag,
                                                      void* aContent,
@@ -306,85 +306,48 @@ StructuredCloneHolder::Read(nsISupports*
     mClonedSurfaces.Clear();
     Clear();
   }
 }
 
 void
 StructuredCloneHolder::ReadFromBuffer(nsISupports* aParent,
                                       JSContext* aCx,
-                                      uint64_t* aBuffer,
-                                      size_t aBufferLength,
+                                      JSStructuredCloneData& aBuffer,
                                       JS::MutableHandle<JS::Value> aValue,
                                       ErrorResult& aRv)
 {
-  ReadFromBuffer(aParent, aCx, aBuffer, aBufferLength,
+  ReadFromBuffer(aParent, aCx, aBuffer,
                  JS_STRUCTURED_CLONE_VERSION, aValue, aRv);
 }
 
 void
 StructuredCloneHolder::ReadFromBuffer(nsISupports* aParent,
                                       JSContext* aCx,
-                                      uint64_t* aBuffer,
-                                      size_t aBufferLength,
+                                      JSStructuredCloneData& aBuffer,
                                       uint32_t aAlgorithmVersion,
                                       JS::MutableHandle<JS::Value> aValue,
                                       ErrorResult& aRv)
 {
   MOZ_ASSERT_IF(mStructuredCloneScope == StructuredCloneScope::SameProcessSameThread,
                 mCreationThread == NS_GetCurrentThread());
 
   MOZ_ASSERT(!mBuffer, "ReadFromBuffer() must be called without a Write().");
-  MOZ_ASSERT(aBuffer);
 
   mozilla::AutoRestore<nsISupports*> guard(mParent);
   mParent = aParent;
 
-  if (!JS_ReadStructuredClone(aCx, aBuffer, aBufferLength, aAlgorithmVersion,
-                              mStructuredCloneScope, aValue, &gCallbacks,
+  if (!JS_ReadStructuredClone(aCx, aBuffer, aAlgorithmVersion,
+                              mStructuredCloneScope, aValue, &sCallbacks,
                               this)) {
     JS_ClearPendingException(aCx);
     aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
   }
 }
 
-void
-StructuredCloneHolder::MoveBufferDataToArray(FallibleTArray<uint8_t>& aArray,
-                                             ErrorResult& aRv)
-{
-  MOZ_ASSERT_IF(mStructuredCloneScope == StructuredCloneScope::SameProcessSameThread,
-                mCreationThread == NS_GetCurrentThread());
-
-  MOZ_ASSERT(mBuffer, "MoveBuffer() cannot be called without a Write().");
-
-  if (NS_WARN_IF(!aArray.SetLength(BufferSize(), mozilla::fallible))) {
-    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
-    return;
-  }
-
-  uint64_t* buffer;
-  size_t size;
-  mBuffer->steal(&buffer, &size);
-  mBuffer = nullptr;
-
-  memcpy(aArray.Elements(), buffer, size);
-  js_free(buffer);
-}
-
-void
-StructuredCloneHolder::FreeBuffer(uint64_t* aBuffer,
-                                  size_t aBufferLength)
-{
-  MOZ_ASSERT(!mBuffer, "FreeBuffer() must be called without a Write().");
-  MOZ_ASSERT(aBuffer);
-  MOZ_ASSERT(aBufferLength);
-
-  JS_ClearStructuredClone(aBuffer, aBufferLength, &gCallbacks, this, false);
-}
-
 /* static */ JSObject*
 StructuredCloneHolder::ReadFullySerializableObjects(JSContext* aCx,
                                                     JSStructuredCloneReader* aReader,
                                                     uint32_t aTag)
 {
   if (aTag == SCTAG_DOM_IMAGEDATA) {
     return ReadStructuredCloneImageData(aCx, aReader);
   }
--- a/dom/base/StructuredCloneHolder.h
+++ b/dom/base/StructuredCloneHolder.h
@@ -3,17 +3,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_StructuredCloneHolder_h
 #define mozilla_dom_StructuredCloneHolder_h
 
 #include "js/StructuredClone.h"
 #include "mozilla/Move.h"
-#include "nsAutoPtr.h"
+#include "mozilla/UniquePtr.h"
 #include "nsISupports.h"
 #include "nsTArray.h"
 
 #ifdef DEBUG
 #include "nsIThread.h"
 #endif
 
 namespace mozilla {
@@ -31,16 +31,18 @@ namespace dom {
 class StructuredCloneHolderBase
 {
 public:
   typedef JS::StructuredCloneScope StructuredCloneScope;
 
   StructuredCloneHolderBase(StructuredCloneScope aScope = StructuredCloneScope::SameProcessSameThread);
   virtual ~StructuredCloneHolderBase();
 
+  StructuredCloneHolderBase(StructuredCloneHolderBase&& aOther) = default;
+
   // These methods should be implemented in order to clone data.
   // Read more documentation in js/public/StructuredClone.h.
 
   virtual JSObject* CustomReadHandler(JSContext* aCx,
                                       JSStructuredCloneReader* aReader,
                                       uint32_t aTag,
                                       uint32_t aIndex) = 0;
 
@@ -97,30 +99,24 @@ public:
   bool Read(JSContext* aCx,
             JS::MutableHandle<JS::Value> aValue);
 
   bool HasData() const
   {
     return !!mBuffer;
   }
 
-  uint64_t* BufferData() const
+  JSStructuredCloneData& BufferData() const
   {
     MOZ_ASSERT(mBuffer, "Write() has never been called.");
     return mBuffer->data();
   }
 
-  size_t BufferSize() const
-  {
-    MOZ_ASSERT(mBuffer, "Write() has never been called.");
-    return mBuffer->nbytes();
-  }
-
 protected:
-  nsAutoPtr<JSAutoStructuredCloneBuffer> mBuffer;
+  UniquePtr<JSAutoStructuredCloneBuffer> mBuffer;
 
   StructuredCloneScope mStructuredCloneScope;
 
 #ifdef DEBUG
   bool mClearCalled;
 #endif
 };
 
@@ -151,38 +147,34 @@ public:
   // data can be read and written. Additional checks about the nature of the
   // objects will be done based on this scope value because not all the
   // objects can be sent between threads or processes.
   explicit StructuredCloneHolder(CloningSupport aSupportsCloning,
                                  TransferringSupport aSupportsTransferring,
                                  StructuredCloneScope aStructuredCloneScope);
   virtual ~StructuredCloneHolder();
 
+  StructuredCloneHolder(StructuredCloneHolder&& aOther) = default;
+
   // Normally you should just use Write() and Read().
 
   void Write(JSContext* aCx,
              JS::Handle<JS::Value> aValue,
              ErrorResult &aRv);
 
   void Write(JSContext* aCx,
              JS::Handle<JS::Value> aValue,
              JS::Handle<JS::Value> aTransfer,
              ErrorResult &aRv);
 
   void Read(nsISupports* aParent,
             JSContext* aCx,
             JS::MutableHandle<JS::Value> aValue,
             ErrorResult &aRv);
 
-  // Sometimes, when IPC is involved, you must send a buffer after a Write().
-  // This method 'steals' the internal data from this class.
-  // You should free this buffer with StructuredCloneHolder::FreeBuffer().
-  void MoveBufferDataToArray(FallibleTArray<uint8_t>& aArray,
-                             ErrorResult& aRv);
-
   // Call this method to know if this object is keeping some DOM object alive.
   bool HasClonedDOMObjects() const
   {
     return !mBlobImplArray.IsEmpty() ||
            !mClonedSurfaces.IsEmpty();
   }
 
   nsTArray<RefPtr<BlobImpl>>& BlobImpls()
@@ -261,39 +253,35 @@ public:
   static JSObject* ReadFullySerializableObjects(JSContext* aCx,
                                                 JSStructuredCloneReader* aReader,
                                                 uint32_t aTag);
 
   static bool  WriteFullySerializableObjects(JSContext* aCx,
                                              JSStructuredCloneWriter* aWriter,
                                              JS::Handle<JSObject*> aObj);
 
+  static const JSStructuredCloneCallbacks sCallbacks;
+
 protected:
   // If you receive a buffer from IPC, you can use this method to retrieve a
   // JS::Value. It can happen that you want to pre-populate the array of Blobs
   // and/or the PortIdentifiers.
   void ReadFromBuffer(nsISupports* aParent,
                       JSContext* aCx,
-                      uint64_t* aBuffer,
-                      size_t aBufferLength,
+                      JSStructuredCloneData& aBuffer,
                       JS::MutableHandle<JS::Value> aValue,
                       ErrorResult &aRv);
 
   void ReadFromBuffer(nsISupports* aParent,
                       JSContext* aCx,
-                      uint64_t* aBuffer,
-                      size_t aBufferLength,
+                      JSStructuredCloneData& aBuffer,
                       uint32_t aAlgorithmVersion,
                       JS::MutableHandle<JS::Value> aValue,
                       ErrorResult &aRv);
 
-  // Use this method to free a buffer generated by MoveToBuffer().
-  void FreeBuffer(uint64_t* aBuffer,
-                  size_t aBufferLength);
-
   bool mSupportsCloning;
   bool mSupportsTransferring;
 
   // Used for cloning blobs in the structured cloning algorithm.
   nsTArray<RefPtr<BlobImpl>> mBlobImplArray;
 
   // This is used for sharing the backend of ImageBitmaps.
   // The DataSourceSurface object must be thread-safely reference-counted.
--- a/dom/base/nsAttrValue.cpp
+++ b/dom/base/nsAttrValue.cpp
@@ -12,17 +12,17 @@
 #include "mozilla/DebugOnly.h"
 #include "mozilla/HashFunctions.h"
 
 #include "nsAttrValue.h"
 #include "nsAttrValueInlines.h"
 #include "nsIAtom.h"
 #include "nsUnicharUtils.h"
 #include "mozilla/MemoryReporting.h"
-#include "mozilla/ServoBindings.h"
+#include "mozilla/ServoBindingHelpers.h"
 #include "mozilla/css/Declaration.h"
 #include "nsContentUtils.h"
 #include "nsReadableUtils.h"
 #include "prprf.h"
 #include "nsHTMLCSSStyleSheet.h"
 #include "nsCSSParser.h"
 #include "nsStyledElement.h"
 #include "nsIURI.h"
@@ -458,22 +458,22 @@ nsAttrValue::SetTo(css::Declaration* aVa
   NS_ADDREF(cont->mValue.mGeckoCSSDeclaration = aValue);
   cont->mType = eGeckoCSSDeclaration;
   NS_ADDREF(cont);
   SetMiscAtomOrString(aSerialized);
   MOZ_ASSERT(cont->mValue.mRefCount == 1);
 }
 
 void
-nsAttrValue::SetTo(ServoDeclarationBlock* aValue,
+nsAttrValue::SetTo(already_AddRefed<ServoDeclarationBlock> aValue,
                    const nsAString* aSerialized)
 {
   MiscContainer* cont = EnsureEmptyMiscContainer();
   MOZ_ASSERT(cont->mValue.mRefCount == 0);
-  cont->mValue.mServoCSSDeclaration = aValue;
+  cont->mValue.mServoCSSDeclaration = aValue.take();
   cont->mType = eServoCSSDeclaration;
   NS_ADDREF(cont);
   SetMiscAtomOrString(aSerialized);
   MOZ_ASSERT(cont->mValue.mRefCount == 1);
 }
 
 void
 nsAttrValue::SetTo(css::URLValue* aValue, const nsAString* aSerialized)
@@ -1740,22 +1740,21 @@ nsAttrValue::ParseStyleAttribute(const n
       NS_ADDREF(cont);
       SetPtrValueAndType(cont, eOtherBase);
       return true;
     }
   }
 
   if (ownerDoc->GetStyleBackendType() == StyleBackendType::Servo) {
     NS_ConvertUTF16toUTF8 value(aString);
-    ServoDeclarationBlock* decl = Servo_ParseStyleAttribute(
+    RefPtr<ServoDeclarationBlock> decl = Servo_ParseStyleAttribute(
         reinterpret_cast<const uint8_t*>(value.get()),
-        value.Length(),
-        sheet);
+        value.Length(), sheet).Consume();
     MOZ_ASSERT(decl);
-    SetTo(decl, &aString);
+    SetTo(decl.forget(), &aString);
   } else {
     css::Loader* cssLoader = ownerDoc->CSSLoader();
     nsCSSParser cssParser(cssLoader);
 
     RefPtr<css::Declaration> declaration =
       cssParser.ParseStyleAttribute(aString, docURI, baseURI,
                                     aElement->NodePrincipal());
     if (!declaration) {
@@ -1857,17 +1856,17 @@ nsAttrValue::ClearMiscContainer()
         case eServoCSSDeclaration:
         {
           MOZ_ASSERT(cont->mValue.mRefCount == 1);
           cont->Release();
           cont->Evict();
           if (cont->mType == eGeckoCSSDeclaration) {
             NS_RELEASE(cont->mValue.mGeckoCSSDeclaration);
           } else {
-            Servo_DropDeclarationBlock(cont->mValue.mServoCSSDeclaration);
+            Servo_DeclarationBlock_Release(cont->mValue.mServoCSSDeclaration);
           }
           break;
         }
         case eURL:
         {
           NS_RELEASE(cont->mValue.mURL);
           break;
         }
--- a/dom/base/nsAttrValue.h
+++ b/dom/base/nsAttrValue.h
@@ -143,17 +143,17 @@ public:
 
   void SetTo(const nsAttrValue& aOther);
   void SetTo(const nsAString& aValue);
   void SetTo(nsIAtom* aValue);
   void SetTo(int16_t aInt);
   void SetTo(int32_t aInt, const nsAString* aSerialized);
   void SetTo(double aValue, const nsAString* aSerialized);
   void SetTo(mozilla::css::Declaration* aValue, const nsAString* aSerialized);
-  void SetTo(ServoDeclarationBlock* aDeclarationBlock,
+  void SetTo(already_AddRefed<ServoDeclarationBlock> aDeclarationBlock,
              const nsAString* aSerialized);
   void SetTo(mozilla::css::URLValue* aValue, const nsAString* aSerialized);
   void SetTo(const nsIntMargin& aValue);
   void SetTo(const nsSVGAngle& aValue, const nsAString* aSerialized);
   void SetTo(const nsSVGIntegerPair& aValue, const nsAString* aSerialized);
   void SetTo(const nsSVGLength2& aValue, const nsAString* aSerialized);
   void SetTo(const mozilla::SVGLengthList& aValue,
              const nsAString* aSerialized);
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -1016,16 +1016,18 @@ nsFrameLoader::SwapWithOtherRemoteLoader
   }
 
   rv = ourFrameFrame->BeginSwapDocShells(otherFrame);
   if (NS_FAILED(rv)) {
     mInSwap = aOther->mInSwap = false;
     return rv;
   }
 
+  mRemoteBrowser->SwapLayerTreeObservers(aOther->mRemoteBrowser);
+
   nsCOMPtr<nsIBrowserDOMWindow> otherBrowserDOMWindow =
     aOther->mRemoteBrowser->GetBrowserDOMWindow();
   nsCOMPtr<nsIBrowserDOMWindow> browserDOMWindow =
     mRemoteBrowser->GetBrowserDOMWindow();
 
   if (!!otherBrowserDOMWindow != !!browserDOMWindow) {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
@@ -3197,16 +3199,56 @@ nsFrameLoader::RequestNotifyAfterRemoteP
   if (mRemoteBrowser) {
     Unused << mRemoteBrowser->SendRequestNotifyAfterRemotePaint();
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsFrameLoader::RequestNotifyLayerTreeReady()
+{
+  if (mRemoteBrowser) {
+    return mRemoteBrowser->RequestNotifyLayerTreeReady() ? NS_OK : NS_ERROR_NOT_AVAILABLE;
+  }
+
+  if (!mOwnerContent) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  RefPtr<AsyncEventDispatcher> event =
+    new AsyncEventDispatcher(mOwnerContent,
+                             NS_LITERAL_STRING("MozLayerTreeReady"),
+                             true, false);
+  event->PostDOMEvent();
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFrameLoader::RequestNotifyLayerTreeCleared()
+{
+  if (mRemoteBrowser) {
+    return mRemoteBrowser->RequestNotifyLayerTreeCleared() ? NS_OK : NS_ERROR_NOT_AVAILABLE;
+  }
+
+  if (!mOwnerContent) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  RefPtr<AsyncEventDispatcher> event =
+    new AsyncEventDispatcher(mOwnerContent,
+                             NS_LITERAL_STRING("MozLayerTreeCleared"),
+                             true, false);
+  event->PostDOMEvent();
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsFrameLoader::Print(uint64_t aOuterWindowID,
                      nsIPrintSettings* aPrintSettings,
                      nsIWebProgressListener* aProgressListener)
 {
 #if defined(NS_PRINTING)
   if (mRemoteBrowser) {
     RefPtr<embedding::PrintingParent> printingParent =
       mRemoteBrowser->Manager()->AsContentParent()->GetPrintingParent();
--- a/dom/base/nsFrameMessageManager.cpp
+++ b/dom/base/nsFrameMessageManager.cpp
@@ -271,18 +271,23 @@ struct DataBlobs<Child>
 
 template<ActorFlavorEnum Flavor>
 static bool
 BuildClonedMessageData(typename BlobTraits<Flavor>::ConcreteContentManagerType* aManager,
                        StructuredCloneData& aData,
                        ClonedMessageData& aClonedData)
 {
   SerializedStructuredCloneBuffer& buffer = aClonedData.data();
-  buffer.data = aData.Data();
-  buffer.dataLength = aData.DataLength();
+  auto iter = aData.Data().Iter();
+  size_t size = aData.Data().Size();
+  bool success;
+  buffer.data = aData.Data().Borrow<js::SystemAllocPolicy>(iter, size, &success);
+  if (NS_WARN_IF(!success)) {
+    return false;
+  }
   aClonedData.identfiers().AppendElements(aData.PortIdentifiers());
 
   const nsTArray<RefPtr<BlobImpl>>& blobImpls = aData.BlobImpls();
 
   if (!blobImpls.IsEmpty()) {
     typedef typename BlobTraits<Flavor>::ProtocolType ProtocolType;
     InfallibleTArray<ProtocolType*>& blobList = DataBlobs<Flavor>::Blobs(aClonedData);
     uint32_t length = blobImpls.Length();
@@ -320,17 +325,17 @@ static void
 UnpackClonedMessageData(const ClonedMessageData& aClonedData,
                         StructuredCloneData& aData)
 {
   const SerializedStructuredCloneBuffer& buffer = aClonedData.data();
   typedef typename BlobTraits<Flavor>::ProtocolType ProtocolType;
   const InfallibleTArray<ProtocolType*>& blobs = DataBlobs<Flavor>::Blobs(aClonedData);
   const InfallibleTArray<MessagePortIdentifier>& identifiers = aClonedData.identfiers();
 
-  aData.UseExternalData(buffer.data, buffer.dataLength);
+  aData.UseExternalData(buffer.data);
 
   aData.PortIdentifiers().AppendElements(identifiers);
 
   if (!blobs.IsEmpty()) {
     uint32_t length = blobs.Length();
     aData.BlobImpls().SetCapacity(length);
     for (uint32_t i = 0; i < length; ++i) {
       auto* blob =
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -11116,24 +11116,24 @@ nsGlobalWindow::ShowSlowScriptDialog()
                          (nsIPrompt::BUTTON_TITLE_IS_STRING *
                           (nsIPrompt::BUTTON_POS_0 + nsIPrompt::BUTTON_POS_1));
 
   // Add a third button if necessary.
   if (showDebugButton)
     buttonFlags += nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_2;
 
   // Null out the operation callback while we're re-entering JS here.
-  bool old = JS_DisableInterruptCallback(cx);
+  JSInterruptCallback old = JS_SetInterruptCallback(cx, nullptr);
 
   // Open the dialog.
   rv = prompt->ConfirmEx(title, msg, buttonFlags, waitButton, stopButton,
                          debugButton, neverShowDlg, &neverShowDlgChk,
                          &buttonPressed);
 
-  JS_ResetInterruptCallback(cx, old);
+  JS_SetInterruptCallback(cx, old);
 
   if (NS_SUCCEEDED(rv) && (buttonPressed == 0)) {
     return neverShowDlgChk ? AlwaysContinueSlowScript : ContinueSlowScript;
   }
   if (buttonPressed == 2) {
     if (debugCallback) {
       rv = debugCallback->HandleSlowScriptDebug(this);
       return NS_SUCCEEDED(rv) ? ContinueSlowScript : KillSlowScript;
--- a/dom/base/nsIFrameLoader.idl
+++ b/dom/base/nsIFrameLoader.idl
@@ -134,16 +134,24 @@ interface nsIFrameLoader : nsISupports
   /**
    * Request that the next time a remote layer transaction has been
    * received by the Compositor, a MozAfterRemoteFrame event be sent
    * to the window.
    */
   void requestNotifyAfterRemotePaint();
 
   /**
+   * Request an event when the layer tree from the remote tab becomes
+   * available or unavailable. When this happens, a mozLayerTreeReady
+   * or mozLayerTreeCleared event is fired.
+   */
+  void requestNotifyLayerTreeReady();
+  void requestNotifyLayerTreeCleared();
+
+  /**
    * Print the current document.
    *
    * @param aOuterWindowID the ID of the outer window to print
    * @param aPrintSettings optional print settings to use; printSilent can be
    *                       set to prevent prompting.
    * @param aProgressListener optional print progress listener.
    */
   void print(in unsigned long long aOuterWindowID,
--- a/dom/base/nsStructuredCloneContainer.cpp
+++ b/dom/base/nsStructuredCloneContainer.cpp
@@ -132,17 +132,21 @@ nsStructuredCloneContainer::GetDataAsBas
   if (!DataLength()) {
     return NS_ERROR_FAILURE;
   }
 
   if (HasClonedDOMObjects()) {
     return NS_ERROR_FAILURE;
   }
 
-  nsAutoCString binaryData(reinterpret_cast<char*>(Data()), DataLength());
+  auto iter = Data().Iter();
+  size_t size = Data().Size();
+  nsAutoCString binaryData;
+  binaryData.SetLength(size);
+  Data().ReadBytes(iter, binaryData.BeginWriting(), size);
   nsAutoCString base64Data;
   nsresult rv = Base64Encode(binaryData, base64Data);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   CopyASCIItoUTF16(base64Data, aOut);
   return NS_OK;
--- a/dom/base/test/unit/test_range.js
+++ b/dom/base/test/unit/test_range.js
@@ -168,17 +168,20 @@ function getRange(aSourceNode, aFragment
 }
 
 /**
  * Get the document for a given path, and clean it up for our tests.
  *
  * @param aPath The path to the local document.
  */
 function getParsedDocument(aPath) {
-  var doc = do_parse_document(aPath, "application/xml");
+  return do_parse_document(aPath, "application/xml").then(processParsedDocument);
+}
+
+function processParsedDocument(doc) {
   do_check_true(doc.documentElement.localName != "parsererror");
   do_check_true(doc instanceof C_i.nsIDOMXPathEvaluator);
   do_check_true(doc instanceof C_i.nsIDOMDocument);
 
   // Clean out whitespace.
   var walker = doc.createTreeWalker(doc,
                                     C_i.nsIDOMNodeFilter.SHOW_TEXT |
                                     C_i.nsIDOMNodeFilter.SHOW_CDATA_SECTION,
@@ -209,17 +212,20 @@ function getParsedDocument(aPath) {
   return doc;
 }
 
 /**
  * Run the extraction tests.
  */
 function run_extract_test() {
   var filePath = "test_delete_range.xml";
-  var doc = getParsedDocument(filePath);
+  getParsedDocument(filePath).then(do_extract_test);
+}
+
+function do_extract_test(doc) {
   var tests = doc.getElementsByTagName("test");
 
   // Run our deletion, extraction tests.
   for (var i = 0; i < tests.length; i++) {
     dump("Configuring for test " + i + "\n");
     var currentTest = tests.item(i);
 
     // Validate the test is properly formatted for what this harness expects.
@@ -328,17 +334,20 @@ function run_extract_test() {
   }
 }
 
 /**
  * Miscellaneous tests not covered above.
  */
 function run_miscellaneous_tests() {
   var filePath = "test_delete_range.xml";
-  var doc = getParsedDocument(filePath);
+  getParsedDocument(filePath).then(do_miscellaneous_tests);
+}
+
+function do_miscellaneous_tests(doc) {
   var tests = doc.getElementsByTagName("test");
 
   // Let's try some invalid inputs to our DOM range and see what happens.
   var currentTest = tests.item(0);
   var baseSource = currentTest.firstChild;
   var baseResult = baseSource.nextSibling;
   var baseExtract = baseResult.nextSibling;
 
--- a/dom/base/test/unit/test_xmlserializer.js
+++ b/dom/base/test/unit/test_xmlserializer.js
@@ -2,69 +2,81 @@
 /* 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/. */
 
 function xmlEncode(aFile, aFlags, aCharset) {
     if(aFlags == undefined) aFlags = 0;
     if(aCharset == undefined) aCharset = "UTF-8";
 
-    var doc = do_parse_document(aFile, "text/xml");
-
-    var encoder = Components.classes["@mozilla.org/layout/documentEncoder;1?type=text/xml"]
-                   .createInstance(nsIDocumentEncoder);
-    encoder.setCharset(aCharset);
-    encoder.init(doc, "text/xml", aFlags);
-    return encoder.encodeToString();
+    return do_parse_document(aFile, "text/xml").then(doc => {
+      var encoder = Components.classes["@mozilla.org/layout/documentEncoder;1?type=text/xml"]
+                     .createInstance(nsIDocumentEncoder);
+      encoder.setCharset(aCharset);
+      encoder.init(doc, "text/xml", aFlags);
+      return encoder.encodeToString();
+    });
 }
 
 function run_test()
 {
     var result, expected;
     const de = Components.interfaces.nsIDocumentEncoder;
 
-    result = xmlEncode("1_original.xml", de.OutputLFLineBreak);
-    expected =loadContentFile("1_result.xml");
-    do_check_eq(expected, result);
+    xmlEncode("1_original.xml", de.OutputLFLineBreak).then(result => {
+      expected = loadContentFile("1_result.xml");
+      do_check_eq(expected, result);
+    });
 
-    result =  xmlEncode("2_original.xml", de.OutputLFLineBreak);
-    expected = loadContentFile("2_result_1.xml");
-    do_check_eq(expected, result);
+    xmlEncode("2_original.xml", de.OutputLFLineBreak).then(result => {
+      expected = loadContentFile("2_result_1.xml");
+      do_check_eq(expected, result);
+    });
 
-    result =  xmlEncode("2_original.xml", de.OutputCRLineBreak);
-    expected = expected.replace(/\n/g, "\r");
-    do_check_eq(expected, result);
+    xmlEncode("2_original.xml", de.OutputCRLineBreak).then(result => {
+      expected = expected.replace(/\n/g, "\r");
+      do_check_eq(expected, result);
+    });
 
-    result = xmlEncode("2_original.xml", de.OutputLFLineBreak | de.OutputCRLineBreak);
-    expected = expected.replace(/\r/mg, "\r\n");
-    do_check_eq(expected, result);
+    xmlEncode("2_original.xml", de.OutputLFLineBreak | de.OutputCRLineBreak).then(result => {
+      expected = expected.replace(/\r/mg, "\r\n");
+      do_check_eq(expected, result);
+    });
 
-    result =  xmlEncode("2_original.xml", de.OutputLFLineBreak | de.OutputFormatted);
-    expected = loadContentFile("2_result_2.xml");
-    do_check_eq(expected, result);
+    xmlEncode("2_original.xml", de.OutputLFLineBreak | de.OutputFormatted).then(result => {
+      expected = loadContentFile("2_result_2.xml");
+      do_check_eq(expected, result);
+    });
 
-    result =  xmlEncode("2_original.xml", de.OutputLFLineBreak | de.OutputFormatted | de.OutputWrap);
-    expected = loadContentFile("2_result_3.xml");
-    do_check_eq(expected, result);
+    xmlEncode("2_original.xml", de.OutputLFLineBreak | de.OutputFormatted | de.OutputWrap).then(result => {
+      expected = loadContentFile("2_result_3.xml");
+      do_check_eq(expected, result);
+    });
 
-    result =  xmlEncode("2_original.xml", de.OutputLFLineBreak | de.OutputWrap);
-    expected = loadContentFile("2_result_4.xml");
-    do_check_eq(expected, result);
+    xmlEncode("2_original.xml", de.OutputLFLineBreak | de.OutputWrap).then(result => {
+      expected = loadContentFile("2_result_4.xml");
+      do_check_eq(expected, result);
+    });
 
-    result =  xmlEncode("3_original.xml", de.OutputLFLineBreak | de.OutputFormatted | de.OutputWrap);
-    expected = loadContentFile("3_result.xml");
-    do_check_eq(expected, result);
+    xmlEncode("3_original.xml", de.OutputLFLineBreak | de.OutputFormatted | de.OutputWrap).then(result => {
+      expected = loadContentFile("3_result.xml");
+      do_check_eq(expected, result);
+    });
 
-    result =  xmlEncode("3_original.xml", de.OutputLFLineBreak | de.OutputWrap);
-    expected = loadContentFile("3_result_2.xml");
-    do_check_eq(expected, result);
+    xmlEncode("3_original.xml", de.OutputLFLineBreak | de.OutputWrap).then(result => {
+      expected = loadContentFile("3_result_2.xml");
+      do_check_eq(expected, result);
+    });
 
     // tests on namespaces
-    var doc = do_parse_document("4_original.xml", "text/xml");
+    do_parse_document("4_original.xml", "text/xml").then(run_namespace_tests);
+}
 
+function run_namespace_tests(doc) {
+    const de = Components.interfaces.nsIDocumentEncoder;
     var encoder = Components.classes["@mozilla.org/layout/documentEncoder;1?type=text/xml"]
                    .createInstance(nsIDocumentEncoder);
     encoder.setCharset("UTF-8");
     encoder.init(doc, "text/xml", de.OutputLFLineBreak);
 
     result = encoder.encodeToString();
     expected = loadContentFile("4_result_1.xml");
     do_check_eq(expected, result);
@@ -92,10 +104,9 @@ function run_test()
     expected = loadContentFile("4_result_5.xml");
     do_check_eq(expected, result);
 
     encoder.init(doc, "text/xml",  de.OutputLFLineBreak | de.OutputWrap);
     encoder.setWrapColumn(40);
     result = encoder.encodeToString();
     expected = loadContentFile("4_result_6.xml");
     do_check_eq(expected, result);
-
 }
--- a/dom/broadcastchannel/BroadcastChannel.cpp
+++ b/dom/broadcastchannel/BroadcastChannel.cpp
@@ -147,19 +147,23 @@ public:
   {
     MOZ_ASSERT(mActor);
     if (mActor->IsActorDestroyed()) {
       return NS_OK;
     }
 
     ClonedMessageData message;
 
+    bool success;
     SerializedStructuredCloneBuffer& buffer = message.data();
-    buffer.data = mData->BufferData();
-    buffer.dataLength = mData->BufferSize();
+    auto iter = mData->BufferData().Iter();
+    buffer.data = mData->BufferData().Borrow<js::SystemAllocPolicy>(iter, mData->BufferData().Size(), &success);
+    if (NS_WARN_IF(!success)) {
+      return NS_OK;
+    }
 
     PBackgroundChild* backgroundManager = mActor->Manager();
     MOZ_ASSERT(backgroundManager);
 
     const nsTArray<RefPtr<BlobImpl>>& blobImpls = mData->BlobImpls();
 
     if (!blobImpls.IsEmpty()) {
       message.blobsChild().SetCapacity(blobImpls.Length());
--- a/dom/broadcastchannel/BroadcastChannelChild.cpp
+++ b/dom/broadcastchannel/BroadcastChannelChild.cpp
@@ -86,22 +86,21 @@ BroadcastChannelChild::RecvNotify(const 
     NS_WARNING("Failed to initialize AutoJSAPI object.");
     return true;
   }
 
   ipc::StructuredCloneData cloneData;
   cloneData.BlobImpls().AppendElements(blobs);
 
   const SerializedStructuredCloneBuffer& buffer = aData.data();
-  cloneData.UseExternalData(buffer.data, buffer.dataLength);
-
   JSContext* cx = jsapi.cx();
   JS::Rooted<JS::Value> value(cx, JS::NullValue());
-  if (buffer.dataLength) {
+  if (buffer.data.Size()) {
     ErrorResult rv;
+    cloneData.UseExternalData(buffer.data);
     cloneData.Read(cx, &value, rv);
     if (NS_WARN_IF(rv.Failed())) {
       rv.SuppressException();
       return true;
     }
   }
 
   RootedDictionary<MessageEventInit> init(cx);
--- a/dom/browser-element/BrowserElementChildPreload.js
+++ b/dom/browser-element/BrowserElementChildPreload.js
@@ -1,15 +1,19 @@
 /* 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";
 
-dump("######################## BrowserElementChildPreload.js loaded\n");
+function debug(msg) {
+  // dump("BrowserElementChildPreload - " + msg + "\n");
+}
+
+debug("loaded");
 
 var BrowserElementIsReady = false;
 
 var { classes: Cc, interfaces: Ci, results: Cr, utils: Cu }  = Components;
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/BrowserElementPromptService.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
@@ -25,20 +29,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 
 
 var kLongestReturnedString = 128;
 
 const Timer = Components.Constructor("@mozilla.org/timer;1",
                                      "nsITimer",
                                      "initWithCallback");
 
-function debug(msg) {
-  //dump("BrowserElementChildPreload - " + msg + "\n");
-}
-
 function sendAsyncMsg(msg, data) {
   // Ensure that we don't send any messages before BrowserElementChild.js
   // finishes loading.
   if (!BrowserElementIsReady)
     return;
 
   if (!data) {
     data = { };
--- a/dom/browser-element/BrowserElementCopyPaste.js
+++ b/dom/browser-element/BrowserElementCopyPaste.js
@@ -1,17 +1,21 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- /
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 /* 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";
 
-dump("###################################### BrowserElementCopyPaste.js loaded\n");
+function debug(msg) {
+  // dump("BrowserElementCopyPaste - " + msg + "\n");
+}
+
+debug("loaded");
 
 var { classes: Cc, interfaces: Ci, results: Cr, utils: Cu }  = Components;
 
 var CopyPasteAssistent = {
   COMMAND_MAP: {
     'cut': 'cmd_cut',
     'copy': 'cmd_copyAndCollapseToEnd',
     'paste': 'cmd_paste',
--- a/dom/browser-element/BrowserElementPanning.js
+++ b/dom/browser-element/BrowserElementPanning.js
@@ -1,17 +1,22 @@
 /* -*- tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ts=2 sw=2 sts=2 et: */
 
 /* 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";
-dump("############################### browserElementPanning.js loaded\n");
+
+function debug(msg) {
+  // dump("BrowserElementPanning - " + msg + "\n");
+}
+
+debug("loaded");
 
 var { classes: Cc, interfaces: Ci, results: Cr, utils: Cu }  = Components;
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Geometry.jsm");
 
 const kObservedEvents = [
   "BEC:ShownModalPrompt",
   "Activity:Success",
@@ -146,27 +151,27 @@ const ContentPanning = {
 
   _zoomOut: function() {
     let rect = new Rect(0, 0, 0, 0);
     Services.obs.notifyObservers(docShell, 'browser-zoom-to-rect', JSON.stringify(rect));
   },
 
   _isRectZoomedIn: function(aRect, aViewport) {
     // This function checks to see if the area of the rect visible in the
-    // viewport (i.e. the "overlapArea" variable below) is approximately 
+    // viewport (i.e. the "overlapArea" variable below) is approximately
     // the max area of the rect we can show.
     let vRect = new Rect(aViewport.x, aViewport.y, aViewport.width, aViewport.height);
     let overlap = vRect.intersect(aRect);
     let overlapArea = overlap.width * overlap.height;
     let availHeight = Math.min(aRect.width * vRect.height / vRect.width, aRect.height);
     let showing = overlapArea / (aRect.width * availHeight);
     let ratioW = (aRect.width / vRect.width);
     let ratioH = (aRect.height / vRect.height);
 
-    return (showing > 0.9 && (ratioW > 0.9 || ratioH > 0.9)); 
+    return (showing > 0.9 && (ratioW > 0.9 || ratioH > 0.9));
   },
 
   _unloadHandler: function() {
     kObservedEvents.forEach((topic) => {
       Services.obs.removeObserver(this, topic);
     });
   }
 };
--- a/dom/events/test/pointerevents/mochitest.ini
+++ b/dom/events/test/pointerevents/mochitest.ini
@@ -1,44 +1,55 @@
 [DEFAULT]
 skip-if = (toolkit == 'gonk') || (os == 'android') # Bug 1178701 - Issue on 'B2G ICS Emulator' and 'Android'
 support-files =
   mochitest_support_external.js
   mochitest_support_internal.js
   pointerevent_styles.css
   pointerevent_support.js
 
-[test_pointerevent_button_attribute_mouse-manual.html]
-  support-files = pointerevent_button_attribute_mouse-manual.html
+[test_pointerevent_attributes_mouse-manual.html]
+  support-files = pointerevent_attributes_mouse-manual.html
+  disabled = should be investigated
 [test_pointerevent_capture_mouse-manual.html]
   support-files = pointerevent_capture_mouse-manual.html
 [test_pointerevent_capture_suppressing_mouse-manual.html]
   support-files = pointerevent_capture_suppressing_mouse-manual.html
 [test_pointerevent_change-touch-action-onpointerdown_touch-manual.html]
   support-files = pointerevent_change-touch-action-onpointerdown_touch-manual.html
   disabled = disabled
 [test_pointerevent_constructor.html]
   support-files = pointerevent_constructor.html
+  disabled = should be investigated
 [test_pointerevent_element_haspointercapture.html]
   support-files = pointerevent_element_haspointercapture.html
 [test_pointerevent_gotpointercapture_before_first_pointerevent-manual.html]
   support-files = pointerevent_gotpointercapture_before_first_pointerevent-manual.html
   disabled = should be investigated
 [test_pointerevent_lostpointercapture_for_disconnected_node-manual.html]
   support-files = pointerevent_lostpointercapture_for_disconnected_node-manual.html
 [test_pointerevent_lostpointercapture_is_first-manual.html]
   support-files = pointerevent_lostpointercapture_is_first-manual.html
+[test_pointerevent_multiple_primary_pointers_boundary_events-manual.html]
+  support-files = pointerevent_multiple_primary_pointers_boundary_events-manual.html
+  disabled = should be investigated
 [test_pointerevent_pointercancel_touch-manual.html]
   support-files = pointerevent_pointercancel_touch-manual.html
 [test_pointerevent_pointerdown-manual.html]
   support-files = pointerevent_pointerdown-manual.html
+  disabled = should be investigated
 [test_pointerevent_pointerenter_does_not_bubble-manual.html]
   support-files = pointerevent_pointerenter_does_not_bubble-manual.html
 [test_pointerevent_pointerenter_nohover-manual.html]
   support-files = pointerevent_pointerenter_nohover-manual.html
+[test_pointerevent_pointerId_scope-manual.html]
+  support-files =
+    test_pointerevent_pointerId_scope-manual.html
+    ./resources/pointerevent_pointerId_scope-iframe.html
+  disabled = should be investigated
 [test_pointerevent_pointerenter-manual.html]
   support-files = pointerevent_pointerenter-manual.html
 [test_pointerevent_pointerleave_after_pointercancel_touch-manual.html]
   support-files = pointerevent_pointerleave_after_pointercancel_touch-manual.html
 [test_pointerevent_pointerleave_after_pointerup_nohover-manual.html]
   support-files = pointerevent_pointerleave_after_pointerup_nohover-manual.html
 [test_pointerevent_pointerleave_descendant_over-manual.html]
   support-files = pointerevent_pointerleave_descendant_over-manual.html
@@ -52,16 +63,19 @@ support-files =
   support-files = pointerevent_pointerleave_pen-manual.html
   disabled = should be investigated
 [test_pointerevent_pointerleave_touch-manual.html]
   support-files = pointerevent_pointerleave_touch-manual.html
 [test_pointerevent_pointermove-manual.html]
   support-files = pointerevent_pointermove-manual.html
 [test_pointerevent_pointermove_isprimary_same_as_pointerdown-manual.html]
   support-files = pointerevent_pointermove_isprimary_same_as_pointerdown-manual.html
+[test_pointerevent_pointermove-on-chorded-mouse-button.html]
+  support-files = pointerevent_pointermove-on-chorded-mouse-button.html
+  disabled = should be investigated
 [test_pointerevent_pointermove_pointertype-manual.html]
   support-files = pointerevent_pointermove_pointertype-manual.html
 [test_pointerevent_pointerout-manual.html]
   support-files = pointerevent_pointerout-manual.html
 [test_pointerevent_pointerout_after_pointercancel_touch-manual.html]
   support-files = pointerevent_pointerout_after_pointercancel_touch-manual.html
 [test_pointerevent_pointerout_after_pointerup_nohover-manual.html]
   support-files = pointerevent_pointerout_after_pointerup_nohover-manual.html
@@ -75,36 +89,44 @@ support-files =
 [test_pointerevent_pointertype_mouse-manual.html]
   support-files = pointerevent_pointertype_mouse-manual.html
 [test_pointerevent_pointertype_pen-manual.html]
   support-files = pointerevent_pointertype_pen-manual.html
 [test_pointerevent_pointertype_touch-manual.html]
   support-files = pointerevent_pointertype_touch-manual.html
 [test_pointerevent_pointerup-manual.html]
   support-files = pointerevent_pointerup-manual.html
+  disabled = should be investigated
 [test_pointerevent_pointerup_isprimary_same_as_pointerdown-manual.html]
   support-files = pointerevent_pointerup_isprimary_same_as_pointerdown-manual.html
 [test_pointerevent_pointerup_pointertype-manual.html]
   support-files = pointerevent_pointerup_pointertype-manual.html
 [test_pointerevent_releasepointercapture_events_to_original_target-manual.html]
   support-files = pointerevent_releasepointercapture_events_to_original_target-manual.html
 [test_pointerevent_releasepointercapture_invalid_pointerid-manual.html]
   support-files = pointerevent_releasepointercapture_invalid_pointerid-manual.html
 [test_pointerevent_releasepointercapture_onpointercancel_touch-manual.html]
   support-files = pointerevent_releasepointercapture_onpointercancel_touch-manual.html
 [test_pointerevent_releasepointercapture_onpointerup_mouse-manual.html]
   support-files = pointerevent_releasepointercapture_onpointerup_mouse-manual.html
 [test_pointerevent_setpointercapture_disconnected-manual.html]
   support-files = pointerevent_setpointercapture_disconnected-manual.html
 [test_pointerevent_setpointercapture_inactive_button_mouse-manual.html]
   support-files = pointerevent_setpointercapture_inactive_button_mouse-manual.html
+  disabled = should be investigated
 [test_pointerevent_setpointercapture_invalid_pointerid-manual.html]
   support-files = pointerevent_setpointercapture_invalid_pointerid-manual.html
 [test_pointerevent_setpointercapture_relatedtarget-manual.html]
   support-files = pointerevent_setpointercapture_relatedtarget-manual.html
+[test_pointerevent_suppress_compat_events_on_click.html]
+  support-files = pointerevent_suppress_compat_events_on_click.html
+  disabled = should be investigated
+[test_pointerevent_suppress_compat_events_on_drag_mouse.html]
+  support-files = pointerevent_suppress_compat_events_on_drag_mouse.html
+  disabled = should be investigated
 [test_touch_action.html]
   # Windows touch injection doesn't work in automation, but this test can be run locally on a windows touch device.
   skip-if = (toolkit == 'windows')
   support-files =
     ../../../../gfx/layers/apz/test/mochitest/apz_test_utils.js
     ../../../../gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js
     touch_action_helpers.js
     pointerevent_touch-action-auto-css_touch-manual.html
new file mode 100644
--- /dev/null
+++ b/dom/events/test/pointerevents/pointerevent_attributes_mouse-manual.html
@@ -0,0 +1,105 @@
+<!doctype html>
+<html>
+    <head>
+        <title>Pointer Events properties tests</title>
+        <meta name="viewport" content="width=device-width">
+        <link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
+        <script src="/resources/testharness.js"></script>
+        <!--script src="/resources/testharnessreport.js"></script-->
+        <!-- Additional helper script for common checks across event types -->
+        <script type="text/javascript" src="pointerevent_support.js"></script>
+        <script type="text/javascript" src="mochitest_support_internal.js"></script>
+        <script>
+            var detected_pointertypes = {};
+            var detected_eventTypes = {};
+            var test_pointerEvent = async_test("pointerevent attributes");
+            // showPointerTypes is defined in pointerevent_support.js
+            // Requirements: the callback function will reference the test_pointerEvent object and
+            // will fail unless the async_test is created with the var name "test_pointerEvent".
+            add_completion_callback(showPointerTypes);
+
+            function run() {
+                var square1 = document.getElementById("square1");
+                var rectSquare1 = square1.getBoundingClientRect();
+                var pointerover_event;
+
+                var eventList = ['pointerenter', 'pointerover', 'pointermove', 'pointerdown', 'pointerup', 'pointerout', 'pointerleave'];
+                eventList.forEach(function(eventName) {
+                    on_event(square1, eventName, function (event) {
+                        if (detected_eventTypes[event.type])
+                            return;
+                        detected_pointertypes[event.pointerType] = true;
+                        test(function () {
+                            assert_equals(event.pointerType, 'mouse', 'pointerType should be mouse');
+                        }, event.type + ".pointerType attribute is correct.");
+
+                        // Test button and buttons
+                        if (event.type == 'pointerdown') {
+                            test(function() {
+                                assert_true(event.button == 0, "If left mouse button is pressed button attribute is 0")
+                            }, event.type + "'s button attribute is 0 when left mouse button is pressed.");
+                            test(function() {
+                                assert_true(event.buttons == 1, "If left mouse button is pressed buttons attribute is 1")
+                            }, event.type + "'s buttons attribute is 1 when left mouse button is pressed.");
+                        } else if (event.type == 'pointerup') {
+                            test(function() {
+                                assert_true(event.button == 0, "If left mouse button is just released button attribute is 0")
+                            }, event.type + "'s button attribute is 0 when left mouse button is just released.");
+                            test(function() {
+                                assert_true(event.buttons == 0, "If left mouse button is just released buttons attribute is 0")
+                            }, event.type + "'s buttons attribute is 0 when left mouse button is just released.");
+                        } else {
+                            test(function() {
+                                assert_true(event.button == -1, "If mouse buttons are released button attribute is -1")
+                            }, event.type + "'s button is -1 when mouse buttons are released.");
+                            test(function() {
+                                assert_true(event.buttons == 0, "If mouse buttons are released buttons attribute is 0")
+                            }, event.type + "'s buttons is 0 when mouse buttons are released.");
+                        }
+
+                        // Test clientX and clientY
+                        if (event.type != 'pointerout' && event.type != 'pointerleave' ) {
+                            test(function () {
+                                assert_true(event.clientX >= rectSquare1.left && event.clientX < rectSquare1.right, "ClientX should be in the boundaries of the black box");
+                            }, event.type + ".clientX attribute is correct.");
+                            test(function () {
+                              assert_true(event.clientY >= rectSquare1.top && event.clientY < rectSquare1.bottom, "ClientY should be in the boundaries of the black box");
+                            }, event.type + ".clientY attribute is correct.");
+                        } else {
+                            test(function () {
+                                assert_true(event.clientX < rectSquare1.left || event.clientX > rectSquare1.right - 1 || event.clientY < rectSquare1.top || event.clientY > rectSquare1.bottom - 1, "ClientX/Y should be out of the boundaries of the black box");
+                            }, event.type + "'s ClientX and ClientY attributes are correct.");
+                        }
+
+                        // Test isPrimary
+                        test(function () {
+                            assert_equals(event.isPrimary, true, "isPrimary should be true");
+                        }, event.type + ".isPrimary attribute is correct.");
+
+                        check_PointerEvent(event);
+                        detected_eventTypes[event.type] = true;
+                        if (Object.keys(detected_eventTypes).length == eventList.length)
+                            test_pointerEvent.done();
+                    });
+                });
+            }
+        </script>
+    </head>
+    <body onload="run()">
+        <h1>Pointer Events pointerdown tests</h1>
+        <!--
+        <h4>
+            Test Description: This test checks the properties of mouse pointer events. Move your mouse over the black square and click on it. Then move it off the black square.
+        </h4>
+        -->
+        Test passes if the proper behavior of the events is observed.
+        <div id="square1" class="square"></div>
+        <div class="spacer"></div>
+        <div id="complete-notice">
+            <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p>
+            <p>Refresh the page to run the tests again with a different pointer type.</p>
+        </div>
+        <div id="log"></div>
+    </body>
+</html>
+
deleted file mode 100644
--- a/dom/events/test/pointerevents/pointerevent_button_attribute_mouse-manual.html
+++ /dev/null
@@ -1,61 +0,0 @@
-<!doctype html>
-<html>
-    <head>
-        <title>Button and buttons attribute test for mouse</title>
-        <meta name="viewport" content="width=device-width">
-        <link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
-        <script src="/resources/testharness.js"></script>
-        <!--script src="/resources/testharnessreport.js"></script-->
-        <script type="text/javascript" src="pointerevent_support.js"></script>
-        <script type="text/javascript" src="mochitest_support_internal.js"></script>
-    </head>
-    <body onload="run()">
-        <h1>Button attribute test for mouse</h1>
-        <!--
-        <h2>This test is for mouse only</h2>
-        <h4>
-            Test Description: This test checks if button attribute for mouse handled properly.
-            <p>Put your mouse over the black rectangle</p>
-        </h4>
-        <p>
-        -->
-        <div id="target0" style="background:black"></div>
-        <div id="target1" style="background:yellow"></div>
-        <script>
-            var eventTested = false;
-            var detected_pointertypes = {};
-
-            setup({ explicit_done: true });
-            add_completion_callback(showPointerTypes);
-
-            function run() {
-                var target0 = document.getElementById("target0");
-
-                // If pointerType is "mouse" and no mouse button is depressed, then the button attribute of the pointermove event must be -1 and the buttons attribute must be 0.
-                // TA: 5.8
-                on_event(target0, "pointerover", function (event) {
-                    detected_pointertypes[event.pointerType] = true;
-                    if(event.pointerType != "mouse") {
-                        alert("Use mouse for this test please!");
-                        return;
-                    }
-                    if (eventTested == false) {
-                        test(function() {
-                            assert_true(event.button == -1, "If mouse buttons are released button attribute is -1")
-                        }, "If mouse buttons are released button attribute is -1");
-                        test(function() {
-                            assert_true(event.buttons == 0, "If mouse buttons are released buttons attribute is 0")
-                        }, "If mouse buttons are released buttons attribute is 0");
-                        eventTested = true;
-                        done();
-                    }
-                });
-            }
-        </script>
-        <h1>Pointer Events button attribute test for mouse test</h1>
-        <div id="complete-notice">
-            <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p>
-        </div>
-        <div id="log"></div>
-    </body>
-</html>
\ No newline at end of file
--- a/dom/events/test/pointerevents/pointerevent_capture_suppressing_mouse-manual.html
+++ b/dom/events/test/pointerevents/pointerevent_capture_suppressing_mouse-manual.html
@@ -32,16 +32,17 @@
         <div id="target0"></div>
         <br>
         <div id="target1"></div>
         <br>
         <input type="button" id="btnCapture" value="Set Capture">
         <script type='text/javascript'>
             var isPointerCapture = false;
             var isRelatedTargetValueTested = false;
+            var isTargetAuthenticityTested = false;
             var count = 0;
 
             var detected_pointertypes = {};
             add_completion_callback(showPointerTypes);
 
             var target0 = document.getElementById('target0');
             var target1 = document.getElementById('target1');
             var captureButton = document.getElementById('btnCapture');
@@ -90,16 +91,23 @@
                     if(isPointerCapture) {
                         test_pointerover_capture.done();
                         if (!isRelatedTargetValueTested) {
                             test(function() {
                                 assert_true(event.relatedTarget==null, "relatedTarget is null when the capture is set")
                             }, "relatedTarget is null when the capture is set. relatedTarget is " + event.relatedTarget);
                             isRelatedTargetValueTested = true;
                         }
+                        var hitTest = document.elementFromPoint(event.clientX, event.clientY);
+                        if(event.target !== hitTest && !isTargetAuthenticityTested) {
+                            test(function () {
+                                assert_unreached("pointerover for this target shouldn't trigger events on capture target");
+                            }, "pointerover should only trigger over the black rectangle");
+                            isTargetAuthenticityTested = true;
+                        }
                     }
                     else {
                         test_pointerover_no_capture.done();
                     }
                 });
 
                 on_event(target0, "pointerout", function (event) {
                     log("pointerout", target0);
--- a/dom/events/test/pointerevents/pointerevent_constructor.html
+++ b/dom/events/test/pointerevents/pointerevent_constructor.html
@@ -10,39 +10,36 @@
         <script type="text/javascript" src="pointerevent_support.js"></script>
         <script type="text/javascript" src="mochitest_support_internal.js"></script>
     </head>
     <body onload="run()">
     <h1>PointerEvent: Dispatch custom event</h1>
     <h4>Test Description: This test checks if PointerEvent constructor works properly using synthetic pointerover and pointerout events. For valid results, this test must be run without generating real (trusted) pointerover or pointerout events on the black rectangle below.</h4>
     <div id="target0"></div>
     <script>
-        var eventTested = false;
         var detected_pointertypes = {};
-        setup({ explicit_done: true });
         add_completion_callback(showPointerTypes);
-        function run() {
+
+        async_test(function() {
             var target0 = document.getElementById("target0");
             // set values for non-default constructor
             var testBubbles = true;
             var testCancelable = true;
             var testPointerId = 42;
             var testPointerType = 'pen';
             var testClientX = 300;
             var testClientY = 500;
             var testWidth = 3;
             var testHeight = 5;
             var testTiltX = -45;
             var testTiltY = 30;
             var testPressure = 0.4;
             var testIsPrimary = true;
-            var pointerEventCustom;
-            var pointerEventDefault;
 
-            on_event(target0, "pointerover", function(event) {
+            on_event(target0, "pointerover", this.step_func(function(event) {
                 detected_pointertypes[ event.pointerType ] = true;
                 generate_tests(assert_equals, [
                     ["custom bubbles", event.bubbles, testBubbles],
                     ["custom cancelable", event.cancelable, testCancelable],
                     ["custom pointerId", event.pointerId, testPointerId],
                     ["custom pointerType", event.pointerType, testPointerType],
                     ["custom width", event.width, testWidth],
                     ["custom height", event.height, testHeight],
@@ -50,35 +47,35 @@
                     ["custom clientY", event.clientY, testClientY],
                     ["custom tiltX", event.tiltX, testTiltX],
                     ["custom tiltY", event.tiltY, testTiltY],
                     ["custom isPrimary", event.isPrimary, testIsPrimary]
                 ]);
                 test(function() {
                     assert_approx_equals(event.pressure, testPressure, 0.00000001, "custom pressure: ");
                 }, "custom pressure: ");
-            });
+            }));
 
-            on_event(target0, "pointerout", function(event) {
+            on_event(target0, "pointerout", this.step_func(function(event) {
                 generate_tests(assert_equals, [
                     ["default pointerId", event.pointerId, 0],
                     ["default pointerType", event.pointerType, ""],
-                    ["default width", event.width, 0],
-                    ["default height", event.height, 0],
+                    ["default width", event.width, 1],
+                    ["default height", event.height, 1],
                     ["default tiltX", event.tiltX, 0],
                     ["default tiltY", event.tiltY, 0],
                     ["default pressure", event.pressure, 0],
                     ["default isPrimary", event.isPrimary, false]
                 ]);
-            });
+            }));
 
-            test(function() {
+            on_event(window, "load", this.step_func_done(function() {
                 assert_not_equals(window.PointerEvent, undefined);
 
-                pointerEventCustom = new PointerEvent("pointerover",
+                var pointerEventCustom = new PointerEvent("pointerover",
                 {bubbles: testBubbles,
                 cancelable: testCancelable,
                 pointerId: testPointerId,
                 pointerType: testPointerType,
                 width: testWidth,
                 height: testHeight,
                 clientX: testClientX,
                 clientY: testClientY,
@@ -86,20 +83,19 @@
                 tiltY: testTiltY,
                 pressure: testPressure,
                 isPrimary: testIsPrimary
                 });
                 // A PointerEvent created with a PointerEvent constructor must have all its attributes set to the corresponding values provided to the constructor.
                 // For attributes where values are not provided to the constructor, the corresponding default values must be used.
                 // TA: 12.1
                 target0.dispatchEvent(pointerEventCustom);
-                pointerEventDefault = new PointerEvent("pointerout");
+                var pointerEventDefault = new PointerEvent("pointerout");
                 target0.dispatchEvent(pointerEventDefault);
-                done();
-            }, "PointerEvent constructor");
-        }
+            }, "PointerEvent constructor"));
+        })
     </script>
     <div id="complete-notice">
     <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p>
     </div>
     <div id="log"></div>
     </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/dom/events/test/pointerevents/pointerevent_multiple_primary_pointers_boundary_events-manual.html
@@ -0,0 +1,148 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Pointer Event: Boundary compatibility events for multiple primary pointers</title>
+    <meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
+    <link rel="author" title="Google" href="http://www.google.com "/>
+    <meta name="assert" content="When more than one primary pointers are active, each will have an independent sequence of pointer boundary events but the compatibilty mouse boundary events have their own sequence."/>
+    <link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
+    <script src="/resources/testharness.js"></script>
+    <!--script src="/resources/testharnessreport.js"></script-->
+    <script type="text/javascript" src="pointerevent_support.js"></script>
+    <script type="text/javascript" src="mochitest_support_internal.js"></script>
+    <script type="text/javascript">
+      var test_pointerEvent = async_test("Multi-pointer boundary compat events");
+      add_completion_callback(end_of_test);
+
+      var detected_pointertypes = {};
+      var event_log = [];
+
+      // These two ids help us detect two different pointing devices.
+      var first_entry_pointer_id = -1;
+      var second_entry_pointer_id = -1;
+
+      // Current node for each pointer id
+      var current_node_for_id = {};
+
+      function end_of_test() {
+          showLoggedEvents();
+          showPointerTypes();
+      }
+
+      function end_of_interaction() {
+          test(function () {
+              assert_equals(event_log.join(", "),
+                  "mouseover@target0, mouseenter@target0, mouseout@target0, mouseleave@target0, " +
+                  "mouseover@target1, mouseenter@target1, mouseout@target1, mouseleave@target1, " +
+                  "mouseover@target0, mouseenter@target0, mouseout@target0, mouseleave@target0"
+              );
+          }, "Event log");
+
+          test_pointerEvent.done(); // complete test
+      }
+
+      function log_event(label) {
+          event_log.push(label);
+      }
+
+      function run() {
+          on_event(document.getElementById("done"), "click", end_of_interaction);
+
+          var target_list = ["target0", "target1"];
+          var pointer_event_list = ["pointerenter", "pointerleave", "pointerover", "pointerout", "pointerdown"];
+          var mouse_event_list = ["mouseenter", "mouseleave", "mouseover", "mouseout"];
+
+          target_list.forEach(function(targetId) {
+              var target = document.getElementById(targetId);
+
+              pointer_event_list.forEach(function(eventName) {
+                  on_event(target, eventName, function (event) {
+                      var label = event.type + "@" + targetId;
+
+                      detected_pointertypes[event.pointerType] = true;
+
+                      if (!event.isPrimary) {
+                          test(function () {
+                              assert_unreached("Non-primary pointer " + label);
+                          }, "Non-primary pointer " + label);
+                      }
+
+                      if (event.type === "pointerenter") {
+                          var pointer_id = event.pointerId;
+                          if (current_node_for_id[pointer_id] !== undefined) {
+                              test(function () {
+                                  assert_unreached("Double entry " + label);
+                              }, "Double entry " + label);
+                          }
+                          current_node_for_id[pointer_id] = event.target;
+
+                          // Test that two different pointing devices are used
+                          if (first_entry_pointer_id === -1) {
+                              first_entry_pointer_id = pointer_id;
+                          } else if (second_entry_pointer_id === -1) {
+                              second_entry_pointer_id = pointer_id;
+                              test(function () {
+                                  assert_true(first_entry_pointer_id !== second_entry_pointer_id);
+                              }, "Different pointing devices");
+                          }
+                      } else if (event.type === "pointerleave") {
+                          var pointer_id = event.pointerId;
+                          if (current_node_for_id[pointer_id] !== event.target) {
+                              test(function () {
+                                  assert_unreached("Double exit " + label);
+                              }, "Double exit " + label);
+                          }
+                          current_node_for_id[pointer_id] = undefined;
+                      }
+                  });
+              });
+
+              mouse_event_list.forEach(function(eventName) {
+                  on_event(target, eventName, function (event) {
+                      log_event(event.type + "@" + targetId);
+                  });
+              });
+          });
+      }
+    </script>
+    <style>
+      #target0, #target1 {
+        margin: 20px;
+      }
+
+      #done {
+        margin: 20px;
+        border: 2px solid black;
+      }
+  </style>
+  </head>
+  <body onload="run()">
+    <h1>Pointer Event: Boundary compatibility events for multiple primary pointers</h1>
+	<!--
+    <h4>
+      When more than one primary pointers are active, each will have an independent sequence of pointer boundary events but the compatibilty mouse boundary events have their own sequence.
+    </h4>
+    Instruction:
+    <ol>
+      <li>Move the mouse directly into Target0 (without going through Target1), and then leave the mouse there unmoved.</li>
+      <li>Tap directly on Target1 with a finger or a stylus, and then lift the finger/stylus off the screen/digitizer without crossing Target1 boundary.</li>
+      <li>Move the mouse into Target0 (if not there already) and move inside it.</li>
+      <li>Click Done (without passing over Target1).</li>
+    </ol>
+	-->
+    <div id="done">
+      Done
+    </div>
+    <div id="target0">
+      Target0
+    </div>
+    <div id="target1">
+      Target1
+    </div>
+    <div id="complete-notice">
+      <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p>
+      <p>The following events were logged: <span id="event-log"></span>.</p>
+    </div>
+    <div id="log"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/events/test/pointerevents/pointerevent_pointerId_scope-manual.html
@@ -0,0 +1,85 @@
+<!doctype html>
+<html>
+    <!--
+Test cases for Pointer Events v1 spec
+This document references Test Assertions (abbrev TA below) written by Cathy Chan
+http://www.w3.org/wiki/PointerEvents/TestAssertions
+-->
+    <head>
+        <title>Pointer Events pointerdown tests</title>
+        <meta name="viewport" content="width=device-width">
+        <link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
+        <script src="/resources/testharness.js"></script>
+        <!--script src="/resources/testharnessreport.js"></script-->
+        <!-- Additional helper script for common checks across event types -->
+        <script type="text/javascript" src="pointerevent_support.js"></script>
+        <script type="text/javascript" src="mochitest_support_internal.js"></script>
+        <script>
+            var detected_pointertypes = {};
+            var test_pointerEvent = async_test("pointerId of an active pointer is the same across iframes");
+            // showPointerTypes is defined in pointerevent_support.js
+            // Requirements: the callback function will reference the test_pointerEvent object and
+            // will fail unless the async_test is created with the var name "test_pointerEvent".
+            add_completion_callback(showPointerTypes);
+            var detected_pointertypes = {};
+
+            function run() {
+                var target0 = document.getElementById("target0");
+                var pointerover_pointerId = null;
+                var pointerover_pointerType = null;
+
+                var eventList = ['pointerenter', 'pointerover', 'pointermove', 'pointerout', 'pointerleave'];
+                var receivedEvents = {};
+                var receivedEventsInnerFrame = {};
+
+
+                function checkPointerId(event, inner) {
+                    detected_pointertypes[event.pointerType] = true;
+                    var eventName = (inner ? "inner frame " : "" ) + event.type;
+                    test_pointerEvent.step(function() {
+                        assert_equals(event.pointerId, pointerover_pointerId, "PointerId of " + eventName + " is not correct");
+                        assert_equals(event.pointerType, pointerover_pointerType, "PointerType of " + eventName + " is not correct");
+                    }, eventName + ".pointerId were the same as first pointerover");
+                }
+
+                on_event(window, "message", function(event) {
+                    var pe_event = JSON.parse(event.data);
+                    receivedEventsInnerFrame[pe_event.type] = 1;
+                    checkPointerId(pe_event, true);
+                    if (Object.keys(receivedEvents).length == eventList.length && Object.keys(receivedEventsInnerFrame).length == eventList.length)
+                        test_pointerEvent.done();
+                });
+
+                eventList.forEach(function(eventName) {
+                    on_event(target0, eventName, function (event) {
+                        if (pointerover_pointerId === null && event.type == 'pointerover') {
+                            pointerover_pointerId = event.pointerId;
+                            pointerover_pointerType = event.pointerType;
+                        } else {
+                            checkPointerId(event, false);
+                        }
+                        receivedEvents[event.type] = 1;
+                    });
+               });
+            }
+        </script>
+    </head>
+    <body onload="run()">
+        <h1>Pointer Events pointerdown tests</h1>
+        Complete the following actions:
+        <!--
+        <ol>
+            <li>Start with your pointing device outside of black box, then move it into black box. If using touch just press in black box and don't release.
+            <li>Move your pointing device into purple box (without leaving the digitizer range if you are using hover supported pen or without releasing touch if using touch). Then move it out of the purple box.
+        </ol>
+        -->
+        <div id="target0" class="touchActionNone">
+        </div>
+        <iframe src="resources/pointerevent_pointerId_scope-iframe.html" id="innerframe"></iframe>
+        <div id="complete-notice">
+            <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p>
+            <p>Refresh the page to run the tests again with a different pointer type.</p>
+        </div>
+        <div id="log"></div>
+    </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/events/test/pointerevents/pointerevent_pointermove-on-chorded-mouse-button.html
@@ -0,0 +1,77 @@
+<!doctype html>
+<html>
+    <head>
+        <title>Pointermove on button state changes</title>
+        <meta name="viewport" content="width=device-width">
+        <meta name="assert" content="When a pointer changes button state and does not produce a different event, the pointermove event must be dispatched."/>
+        <link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
+        <script src="/resources/testharness.js"></script>
+        <!--script src="/resources/testharnessreport.js"></script-->
+        <!-- Additional helper script for common checks across event types -->
+        <script type="text/javascript" src="pointerevent_support.js"></script>
+        <script type="text/javascript" src="mochitest_support_internal.js"></script>
+    </head>
+    <body onload="run()">
+        <h2>PointerMove</h2>
+        <h4>Test Description: This test checks if pointermove event are triggered by button state changes
+            <ol>
+                <li>Put your mouse over the black rectangle</li>
+                <li>Press a button and hold it</li>
+                <li>Press a second button</li>
+                <li>Release the second button</li>
+                <li>Release the first button to complete the test</li>
+            </ol>
+        </h4>
+        <div id="target0" style="background:black"></div>
+        <script>
+            var eventTested = false;
+            var detected_pointertypes = {};
+            var test_pointermove = async_test("pointermove events received for button state changes");
+            add_completion_callback(showPointerTypes);
+
+            var step = 0;
+            var firstButton = 0;
+
+            function run() {
+                var target0 = document.getElementById("target0");
+
+                // When a pointer changes button state and the circumstances produce no other pointer event, the pointermove event must be dispatched.
+                // 5.2.6
+
+                on_event(target0, "pointerdown", function (event) {
+                    detected_pointertypes[event.pointerType] = true;
+                    test_pointermove.step(function() {assert_true(step === 0, "There must not be more than one pointer down event.");});
+                    if (step == 0) {
+                        step = 1;
+                        firstButton = event.buttons;
+                    }
+                });
+                on_event(target0, "pointermove", function (event) {
+                    detected_pointertypes[event.pointerType] = true;
+
+                    if (step == 1 && event.button != -1) { // second button pressed
+                        test_pointermove.step(function() {assert_true(event.buttons !== firstButton, "The pointermove event must be triggered by pressing a second button.");});
+                        test_pointermove.step(function() {assert_true((event.buttons & firstButton) != 0, "The first button must still be reported pressed.");});
+                        step = 2;
+                    } else if (step == 2 && event.button != -1) { // second button released
+                        test_pointermove.step(function() {assert_true(event.buttons === firstButton, "The pointermove event must be triggered by releasing the second button.");});
+                        step = 3;
+                    }
+                });
+                on_event(target0, "pointerup", function (event) {
+                    detected_pointertypes[event.pointerType] = true;
+                    test_pointermove.step(function() {assert_true(step === 3, "The pointerup event must be triggered after pressing and releasing the second button.");});
+                    test_pointermove.step(function() {assert_true(event.buttons === 0, "The pointerup event must be triggered by releasing the last pressed button.");});
+                    test_pointermove.done();
+                    eventTested = true;
+                });
+            }
+        </script>
+        <h1>Pointer Events pointermove on button state changes Tests</h1>
+        <div id="complete-notice">
+            <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p>
+            <p>Refresh the page to run the tests again.</p>
+        </div>
+        <div id="log"></div>
+    </body>
+</html>
--- a/dom/events/test/pointerevents/pointerevent_pointertype_pen-manual.html
+++ b/dom/events/test/pointerevents/pointerevent_pointertype_pen-manual.html
@@ -19,18 +19,16 @@
             // showPointerTypes is defined in pointerevent_support.js
             // Requirements: the callback function will reference the test_pointerEvent object and
             // will fail unless the async_test is created with the var name "test_pointerEvent".
             add_completion_callback(showPointerTypes);
 
             function eventHandler(event) {
                 detected_pointertypes[event.pointerType] = true;
                 if(!eventTested) {
-                    if("pen" != event.pointerType)
-                        return;
                     check_PointerEvent(event);
                     test_pointerEvent.step(function () {
                         assert_equals(event.pointerType, "pen", "Verify event.pointerType is 'pen'.");
                     });
                     eventTested = true;
                 }
                 if (event.type == "pointerup") {
                     test_pointerEvent.done(); // complete test
--- a/dom/events/test/pointerevents/pointerevent_pointertype_touch-manual.html
+++ b/dom/events/test/pointerevents/pointerevent_pointertype_touch-manual.html
@@ -19,18 +19,16 @@
             // showPointerTypes is defined in pointerevent_support.js
             // Requirements: the callback function will reference the test_pointerEvent object and
             // will fail unless the async_test is created with the var name "test_pointerEvent".
             add_completion_callback(showPointerTypes);
 
             function eventHandler(event) {
                 detected_pointertypes[event.pointerType] = true;
                 if(!eventTested) {
-                    if("touch" != event.pointerType)
-                        return;
                     check_PointerEvent(event);
                     test_pointerEvent.step(function () {
                         assert_equals(event.pointerType, "touch", "Verify event.pointerType is 'touch'.");
                     });
                     eventTested = true;
                 }
                 if (event.type == "pointerup") {
                     test_pointerEvent.done(); // complete test
--- a/dom/events/test/pointerevents/pointerevent_setpointercapture_inactive_button_mouse-manual.html
+++ b/dom/events/test/pointerevents/pointerevent_setpointercapture_inactive_button_mouse-manual.html
@@ -17,17 +17,16 @@
             <ol>
                 <li>Put your mouse over the black rectangle
                 <li>Move you mouse out to complete the test
             </ol>
         </h4>
         <p>
         -->
         <div id="target0" style="background:black; color:white;"></div>
-        <div id="target1" style="background:yellow;"></div>
         <script>
             var detected_pointertypes = {};
 
             var captureGot = false;
 
             setup({ explicit_done: true });
             add_completion_callback(showPointerTypes);
 
--- a/dom/events/test/pointerevents/pointerevent_styles.css
+++ b/dom/events/test/pointerevents/pointerevent_styles.css
@@ -1,24 +1,52 @@
+.spacer {
+height: 100px;
+}
+
+#square1 {
+background: black;
+top: 150px;
+left: 100px;
+}
+
+.square {
+height: 20px;
+width: 20px;
+position: absolute;
+padding: 0px;
+}
+
 #target0 {
 background: black;
 color: white;
 white-space: nowrap;
 overflow-y: auto;
 overflow-x: auto;
 }
 
 #target1 {
 background: purple;
 color: white;
 white-space: nowrap;
 overflow-y: auto;
 overflow-x: auto;
 }
 
+.touchActionNone {
+touch-action: none;
+}
+
+#innerframe {
+width: 90%;
+margin: 10px;
+margin-left: 10%;
+height: 200px;
+}
+
 .scroller {
 width: 700px;
 height: 430px;
 margin: 20px;
 overflow: auto;
 background: black;
 }
 
@@ -44,16 +72,20 @@ background: #afa;
 border: 1px solid #0a0;
 display: none;
 }
 
 #pointertype-log {
 font-weight: bold;
 }
 
+#event-log {
+font-weight: bold;
+}
+
 #listener {
 background: orange;
 border: 1px solid orange;
 position: absolute;
 top: -100px;
 }
 
 body.scrollable {
--- a/dom/events/test/pointerevents/pointerevent_support.js
+++ b/dom/events/test/pointerevents/pointerevent_support.js
@@ -8,19 +8,20 @@ var All_Pointer_Events = [
         "pointerenter",
         "pointerleave",
         "gotpointercapture",
         "lostpointercapture"];
 
 // Check for conformance to PointerEvent interface
 // TA: 1.1, 1.2, 1.6, 1.7, 1.8, 1.9, 1.10, 1.11, 1.12, 1.13
 function check_PointerEvent(event) {
+    var pointerTestName = event.pointerType + ' ' + event.type;
     test(function () {
         assert_true(event instanceof PointerEvent, "event is a PointerEvent event");
-    }, event.type + " event is a PointerEvent event");
+    }, pointerTestName + " event is a PointerEvent event");
 
 
     // Check attributes for conformance to WebIDL:
     // * attribute exists
     // * has proper type
     // * if the attribute is "readonly", it cannot be changed
     // TA: 1.1, 1.2
     var idl_type_check = {
@@ -32,41 +33,47 @@ function check_PointerEvent(event) {
     [
         ["readonly", "long", "pointerId"],
         ["readonly", "float", "width"],
         ["readonly", "float", "height"],
         ["readonly", "float", "pressure"],
         ["readonly", "long", "tiltX"],
         ["readonly", "long", "tiltY"],
         ["readonly", "string", "pointerType"],
-        ["readonly", "boolean", "isPrimary"]
+        ["readonly", "boolean", "isPrimary"],
+        ["readonly", "long", "detail", 0]
     ].forEach(function (attr) {
         var readonly = attr[0];
         var type = attr[1];
         var name = attr[2];
-
+        var value = attr[3];
 
         // existence check
         test(function () {
             assert_true(name in event, name + " attribute in " + event.type + " event");
-        }, event.type + "." + name + " attribute exists");
-
+        }, pointerTestName + "." + name + " attribute exists");
 
         // readonly check
         if (readonly === "readonly") {
             test(function () {
                 assert_readonly(event.type, name, event.type + "." + name + " cannot be changed");
-            }, event.type + "." + name + " is readonly");
+            }, pointerTestName + "." + name + " is readonly");
         }
 
-
         // type check
         test(function () {
             assert_true(idl_type_check[type](event[name]), name + " attribute of type " + type);
-        }, event.type + "." + name + " IDL type " + type + " (JS type was " + typeof event[name] + ")");
+        }, pointerTestName + "." + name + " IDL type " + type + " (JS type was " + typeof event[name] + ")");
+
+        // value check if defined
+        if (value != undefined) {
+            test(function () {
+                assert_equals(event[name], value, name + " attribute value");
+            }, pointerTestName + "." + name + " value is " + value + ".");
+        }
     });
 
 
     // Check the pressure value
     // TA: 1.6, 1.7, 1.8
     test(function () {
         // TA: 1.6
         assert_greater_than_equal(event.pressure, 0, "pressure is greater than or equal to 0");
@@ -76,40 +83,48 @@ function check_PointerEvent(event) {
         // TA: 1.7, 1.8
         if (event.pointerType === "mouse") {
             if (event.buttons === 0) {
                 assert_equals(event.pressure, 0, "pressure is 0 for mouse with no buttons pressed");
             } else {
                 assert_equals(event.pressure, 0.5, "pressure is 0.5 for mouse with a button pressed");
             }
         }
-    }, event.type + ".pressure value is valid");
+    }, pointerTestName + ".pressure value is valid");
 
 
     // Check mouse-specific properties
     if (event.pointerType === "mouse") {
         // TA: 1.9, 1.10, 1.13
         test(function () {
             assert_equals(event.tiltX, 0, event.type + ".tiltX is 0 for mouse");
             assert_equals(event.tiltY, 0, event.type + ".tiltY is 0 for mouse");
             assert_true(event.isPrimary, event.type + ".isPrimary is true for mouse");
-        }, event.type + " properties for pointerType = mouse");
+        }, pointerTestName + " properties for pointerType = mouse");
         // Check properties for pointers other than mouse
     }
 }
 
 function showPointerTypes() {
     var complete_notice = document.getElementById("complete-notice");
     var pointertype_log = document.getElementById("pointertype-log");
     var pointertypes = Object.keys(detected_pointertypes);
     pointertype_log.innerHTML = pointertypes.length ?
         pointertypes.join(",") : "(none)";
     complete_notice.style.display = "block";
 }
 
+function showLoggedEvents() {
+    var event_log_elem = document.getElementById("event-log");
+    event_log_elem.innerHTML = event_log.length ? event_log.join(", ") : "(none)";
+
+    var complete_notice = document.getElementById("complete-notice");
+    complete_notice.style.display = "block";
+}
+
 function log(msg, el) {
     if (++count > 10){
       count = 0;
       el.innerHTML = ' ';
     }
     el.innerHTML = msg + '; ' + el.innerHTML;
 }
 
@@ -149,40 +164,22 @@ function objectScroller(target, directio
         target.scrollTop = 0;
     } else if (direction == 'left') {
         target.scrollLeft = 0;
     }
 }
 
 function sPointerCapture(e) {
     try {
-        if(target0.setPointerCapture)
-            target0.setPointerCapture(e.pointerId);
-        else
-            test(function() {
-                assert_equals(typeof(target0.setPointerCapture), "function", "target0 should have function setPointerCapture");
-            }, "target0 should have function setPointerCapture");
+        target0.setPointerCapture(e.pointerId);
     }
     catch(e) {
-        console.log("catch exception: " + e);
-        test(function() {
-            assert_true(false, "Exception in function setPointerCapture");
-        }, "Exception in function setPointerCapture");
     }
 }
 
 function rPointerCapture(e) {
     try {
         captureButton.value = 'Set Capture';
-        if(target0.releasePointerCapture)
-            target0.releasePointerCapture(e.pointerId);
-        else
-            test(function() {
-                assert_equals(typeof(target0.releasePointerCapture), "function", "target0 should have function releasePointerCapture");
-            }, "target0 should have function releasePointerCapture");
+        target0.releasePointerCapture(e.pointerId);
     }
     catch(e) {
-        console.log("catch exception: " + e);
-        test(function() {
-            assert_true(false, "Exception in function releasePointerCapture");
-        }, "Exception in function releasePointerCapture");
     }
 }
new file mode 100644
--- /dev/null
+++ b/dom/events/test/pointerevents/pointerevent_suppress_compat_events_on_click.html
@@ -0,0 +1,104 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Pointer Event: Suppress compatibility mouse events on click</title>
+    <meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
+    <link rel="author" title="Google" href="http://www.google.com "/>
+    <meta name="assert" content="When a pointerdown is canceled, a click/tap shouldn't fire any compatibility mouse events."/>
+    <link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
+    <script src="/resources/testharness.js"></script>
+    <!--script src="/resources/testharnessreport.js"></script-->
+    <script type="text/javascript" src="pointerevent_support.js"></script>
+    <script type="text/javascript" src="mochitest_support_internal.js"></script>
+    <script type="text/javascript">
+      var test_pointerEvent = async_test("Suppress compat mouse events on click");
+      add_completion_callback(end_of_test);
+
+      var detected_pointertypes = {};
+      var event_log = [];
+
+      function end_of_test() {
+          showLoggedEvents();
+          showPointerTypes();
+      }
+
+      function end_of_interaction() {
+          test(function () {
+              assert_equals(event_log.join(", "),
+                  "mousedown@target1, mouseup@target1");
+          }, "Event log");
+
+          test_pointerEvent.done(); // complete test
+      }
+
+      function run() {
+          on_event(document.getElementById("done"), "click", end_of_interaction);
+
+          var target_list = ["target0", "target1"];
+          var pointer_event_list = ["pointerdown"];
+          var mouse_event_list = ["mousedown", "mouseup"];
+
+          target_list.forEach(function(targetId) {
+              var target = document.getElementById(targetId);
+
+              pointer_event_list.forEach(function(eventName) {
+                  on_event(target, eventName, function (event) {
+                      detected_pointertypes[event.pointerType] = true;
+                      var label = event.type + "@" + targetId;
+
+                      test(function () {
+                          assert_true(event.isPrimary);
+                      }, "primary pointer " + label);
+
+                      if (label === "pointerdown@target0")
+                          event.preventDefault();
+                  });
+              });
+
+              mouse_event_list.forEach(function(eventName) {
+                  on_event(target, eventName, function (event) {
+                      event_log.push(event.type + "@" + targetId);
+                  });
+              });
+          });
+      }
+    </script>
+    <style>
+      #target0, #target1 {
+        margin: 20px;
+      }
+
+      #done {
+        margin: 20px;
+        border: 2px solid black;
+      }
+    </style>
+  </head>
+  <body onload="run()">
+    <h1>Pointer Event: Suppress compatibility mouse events on click</h1>
+    <h4>
+      When a pointerdown is canceled, a click/tap shouldn't fire any compatibility mouse events.
+    </h4>
+    <!--
+    <ol>
+      <li> Click or tap on Target0.</li>
+      <li> Click or tap on Target1.</li>
+      <li> Click Done.</li>
+    </ol>
+    -->
+    <div id="target0">
+      Target0
+    </div>
+    <div id="target1">
+      Target1
+    </div>
+    <div id="done">
+      Done
+    </div>
+    <div id="complete-notice">
+      <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p>
+      <p>The following events were logged: <span id="event-log"></span>.</p>
+    </div>
+    <div id="log"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/events/test/pointerevents/pointerevent_suppress_compat_events_on_drag_mouse.html
@@ -0,0 +1,117 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Pointer Event: Suppress compatibility mouse events on drag</title>
+    <meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
+    <link rel="author" title="Google" href="http://www.google.com "/>
+    <meta name="assert" content="When a pointerdown is canceled, a mouse drag shouldn't fire any compatibility mouse events."/>
+    <link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
+    <script src="/resources/testharness.js"></script>
+    <!--script src="/resources/testharnessreport.js"></script-->
+    <script type="text/javascript" src="pointerevent_support.js"></script>
+    <script type="text/javascript" src="mochitest_support_internal.js"></script>
+    <script type="text/javascript">
+      var test_pointerEvent = async_test("Suppress compat mouse events on drag");
+      add_completion_callback(end_of_test);
+
+      var detected_pointertypes = {};
+      var event_log = [];
+
+      function end_of_test() {
+          showLoggedEvents();
+          showPointerTypes();
+      }
+
+      var include_next_mousemove = false;
+
+      // Limits logging/testing of mousemove.
+      function drop_event(event_type) {
+          return (event_type == "mousemove" && !include_next_mousemove);
+      }
+
+      function end_of_interaction() {
+          test(function () {
+              assert_equals(event_log.join(", "),
+                  "mousedown@target1, mousemove@target1, mouseup@target1");
+          }, "Event log");
+
+          test_pointerEvent.done(); // complete test
+      }
+
+      function run() {
+          on_event(document.getElementById("done"), "click", end_of_interaction);
+
+          var target_list = ["target0", "target1"];
+          var pointer_event_list = ["pointerdown"];
+          var mouse_event_list = ["mousedown", "mouseup", "mousemove"];
+
+          target_list.forEach(function(targetId) {
+              var target = document.getElementById(targetId);
+
+              pointer_event_list.forEach(function(eventName) {
+                  on_event(target, eventName, function (event) {
+                      detected_pointertypes[event.pointerType] = true;
+                      var label = event.type + "@" + targetId;
+
+                      test(function () {
+                          assert_true(event.isPrimary);
+                      }, "primary pointer " + label);
+
+                      if (label === "pointerdown@target0")
+                          event.preventDefault();
+                  });
+              });
+
+              mouse_event_list.forEach(function(eventName) {
+                  on_event(target, eventName, function (event) {
+                      if (drop_event(event.type))
+                          return;
+
+                      event_log.push(event.type + "@" + targetId);
+
+                      include_next_mousemove = (event.type == "mousedown");
+                  });
+              });
+          });
+      }
+    </script>
+    <style>
+      #target0, #target1 {
+        margin: 20px;
+        touch-action: none;
+      }
+
+      #done {
+        margin: 20px;
+        border: 2px solid black;
+      }
+    </style>
+  </head>
+  <body onload="run()">
+    <h1>Pointer Event: Suppress compatibility mouse events on drag</h1>
+    <!--
+    <h4>
+      When a pointerdown is canceled, a mouse drag shouldn't fire any compatibility mouse events.
+    </h4>
+    <ol>
+      <li> Drag mouse within Target0 &amp; release.</li>
+      <li> Drag mouse within Target1 &amp; release.</li>
+      <li> Click Done.</li>
+    </ol>
+	-->
+    <div id="target0">
+      Target0
+    </div>
+    <div id="target1">
+      Target1
+    </div>
+    <div id="done">
+      Done
+    </div>
+    <div id="complete-notice">
+      <p>The following pointer types were detected: <span id="pointertype-log"></span>.</p>
+      <p>The following events were logged: <span id="event-log"></span>.</p>
+    </div>
+    <div id="log"></div>
+  </body>
+</html>
--- a/dom/events/test/pointerevents/pointerevent_touch-action-button-test_touch-manual.html
+++ b/dom/events/test/pointerevents/pointerevent_touch-action-button-test_touch-manual.html
@@ -25,17 +25,17 @@
             }
         </style>
     </head>
     <body onload="run()">
         <h2>Pointer Events touch-action attribute support</h2>
         <h4 id="desc">Test Description: Try to scroll black element DOWN moving your touch outside of the red border. Wait for description update.</h4>
         <p>Note: this test is for touch only</p>
         <div id="target0">
-            <button>Test Button</button>
+            <button id="testButton">Test Button</button>
         </div>
         <br>
         <input type="button" id="btnComplete" value="Complete test">
 
         <script type='text/javascript'>
             var detected_pointertypes = {};
             var xScrollIsReceived = false;
             var yScrollIsReceived = false;
--- a/dom/events/test/pointerevents/pointerevent_touch-action-svg-test_touch-manual.html
+++ b/dom/events/test/pointerevents/pointerevent_touch-action-svg-test_touch-manual.html
@@ -18,17 +18,17 @@
             }
         </style>
     </head>
     <body onload="run()">
         <h2>Pointer Events touch-action attribute support</h2>
         <h4 id="desc">Test Description: Try to scroll black element DOWN moving your touch outside of the red border. Wait for description update.</h4>
         <p>Note: this test is for touch only</p>
         <div id="target0">
-            <svg width="555" height="555" style="touch-action: none;  border: 4px double red;">
+            <svg id="testSvg" width="555" height="555" style="touch-action: none;  border: 4px double red;">
                 <circle cx="305" cy="305" r="250" stroke="green" stroke-width="4" fill="yellow" />
                 Sorry, your browser does not support inline SVG.
             </svg>
         </div>
         <br>
         <input type="button" id="btnComplete" value="Complete test">
         <script type='text/javascript'>
             var detected_pointertypes = {};
new file mode 100644
--- /dev/null
+++ b/dom/events/test/pointerevents/resources/pointerevent_pointerId_scope-iframe.html
@@ -0,0 +1,37 @@
+<!doctype html>
+<html>
+    <!--
+Test cases for Pointer Events v1 spec
+This document references Test Assertions (abbrev TA below) written by Cathy Chan
+http://www.w3.org/wiki/PointerEvents/TestAssertions
+-->
+    <head>
+        <title>Pointer Events pointerdown tests</title>
+        <meta name="viewport" content="width=device-width">
+        <link rel="stylesheet" type="text/css" href="../pointerevent_styles.css">
+        <script>
+            function run() {
+                var target1 = document.getElementById("target1");
+                var pointerover_event;
+                var ponterId = null;
+
+                var eventList = ['pointerenter', 'pointerover', 'pointermove', 'pointerout', 'pointerleave'];
+
+                eventList.forEach(function(eventName) {
+                    target1.addEventListener(eventName, function (event) {
+                        var pass_data = {
+                            'pointerId' : event.pointerId,
+                            'type' : event.type,
+                            'pointerType' : event.pointerType
+                        };
+                        top.postMessage(JSON.stringify(pass_data), "*");
+                    });
+               });
+            }
+        </script>
+    </head>
+    <body onload="run()">
+        <div id="target1" class="touchActionNone">
+        </div>
+    </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/events/test/pointerevents/test_pointerevent_attributes_mouse-manual.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1000870
+-->
+  <head>
+    <meta charset="utf-8">
+    <title>Test for Bug 1000870</title>
+    <meta name="author" content="Maksim Lebedev" />
+    <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+    <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+    <script type="text/javascript" src="mochitest_support_external.js"></script>
+    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+    <script type="text/javascript">
+      SimpleTest.waitForExplicitFinish();
+      function startTest() {
+        var iframe = document.getElementById("testFrame");
+        iframe.src = "pointerevent_attributes_mouse-manual.html";
+      }
+      function executeTest(int_win) {
+        var square1 = int_win.document.getElementById("square1");
+        var rect = square1.getBoundingClientRect();
+        var x = rect.left + rect.width / 4;
+        var y = rect.top + rect.height / 2
+        synthesizeMouseAtPoint(x, y, {type: "mousemove", button:-1}, int_win);
+        synthesizeMouseAtPoint(x, y, {type: "mousedown", button:0},  int_win);
+        synthesizeMouseAtPoint(x, y, {type: "mouseup",   button:0},  int_win);
+        synthesizeMouseAtPoint(x, y, {type: "mousemove", button:-1}, int_win);
+        synthesizeMouseAtPoint(rect.left-1, rect.top-1, {type: "mousemove", button:-1}, int_win);
+      }
+    </script>
+  </head>
+  <body>
+    <iframe id="testFrame" height="800" width="1000"></iframe>
+  </body>
+</html>
+
deleted file mode 100644
--- a/dom/events/test/pointerevents/test_pointerevent_button_attribute_mouse-manual.html
+++ /dev/null
@@ -1,29 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=1000870
--->
-  <head>
-    <meta charset="utf-8">
-    <title>Test for Bug 1000870</title>
-    <meta name="author" content="Maksim Lebedev" />
-    <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-    <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
-    <script type="text/javascript" src="mochitest_support_external.js"></script>
-    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-    <script type="text/javascript">
-      SimpleTest.waitForExplicitFinish();
-      function startTest() {
-        var iframe = document.getElementById("testFrame");
-        iframe.src = "pointerevent_button_attribute_mouse-manual.html";
-      }
-      function executeTest(int_win) {
-        sendPointerEvent(int_win, "target1", "pointermove", MouseEvent.MOZ_SOURCE_MOUSE);
-        sendPointerEvent(int_win, "target0", "pointermove", MouseEvent.MOZ_SOURCE_MOUSE, {button:-1});
-      }
-    </script>
-  </head>
-  <body>
-    <iframe id="testFrame" height="800" width="1000"></iframe>
-  </body>
-</html>
new file mode 100644
--- /dev/null
+++ b/dom/events/test/pointerevents/test_pointerevent_multiple_primary_pointers_boundary_events-manual.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1000870
+-->
+  <head>
+    <meta charset="utf-8">
+    <title>Test for Bug 1000870</title>
+    <meta name="author" content="Maksim Lebedev" />
+    <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+    <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+    <script type="text/javascript" src="mochitest_support_external.js"></script>
+    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+    <script type="text/javascript">
+      SimpleTest.waitForExplicitFinish();
+      function startTest() {
+        var iframe = document.getElementById("testFrame");
+        iframe.src = "pointerevent_multiple_primary_pointers_boundary_events-manual.html";
+      }
+      function executeTest(int_win) {
+        sendMouseEvent(int_win, "target0", "mousemove");
+        sendTouchEvent(int_win, "target1", "touchstart");
+        sendTouchEvent(int_win, "target1", "touchend");
+        sendMouseEvent(int_win, "target0", "mousemove");
+        sendMouseEvent(int_win, "done",    "mousedown", {button:0});
+        sendMouseEvent(int_win, "done",    "mouseup",   {button:0});
+      }
+    </script>
+  </head>
+  <body>
+    <iframe id="testFrame" height="800" width="1000"></iframe>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/events/test/pointerevents/test_pointerevent_pointerId_scope-manual.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1000870
+-->
+  <head>
+    <meta charset="utf-8">
+    <title>Test for Bug 1000870</title>
+    <meta name="author" content="Maksim Lebedev" />
+    <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+    <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+    <script type="text/javascript" src="mochitest_support_external.js"></script>
+    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+    <script type="text/javascript">
+      SimpleTest.waitForExplicitFinish();
+      function startTest() {
+        var iframe = document.getElementById("testFrame");
+        iframe.src = "pointerevent_pointerId_scope-manual.html";
+      }
+      function executeTest(int_win) {
+        sendTouchEvent(int_win, "target0", "touchstart");
+        sendTouchEvent(int_win, "target0", "touchend");
+      }
+    </script>
+  </head>
+  <body>
+    <iframe id="testFrame" height="800" width="1000"></iframe>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/events/test/pointerevents/test_pointerevent_pointermove-on-chorded-mouse-button.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1000870
+-->
+  <head>
+    <meta charset="utf-8">
+    <title>Test for Bug 1000870</title>
+    <meta name="author" content="Maksim Lebedev" />
+    <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+    <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+    <script type="text/javascript" src="mochitest_support_external.js"></script>
+    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+    <script type="text/javascript">
+      SimpleTest.waitForExplicitFinish();
+      function startTest() {
+        var iframe = document.getElementById("testFrame");
+        iframe.src = "pointerevent_pointermove-on-chorded-mouse-button.html";
+      }
+      function executeTest(int_win) {
+        sendMouseEvent(int_win, "target0", "mousedown");
+        sendMouseEvent(int_win, "target0", "mouseup");
+      }
+    </script>
+  </head>
+  <body>
+    <iframe id="testFrame" height="800" width="1000"></iframe>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/events/test/pointerevents/test_pointerevent_suppress_compat_events_on_click.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1000870
+-->
+  <head>
+    <meta charset="utf-8">
+    <title>Test for Bug 1000870</title>
+    <meta name="author" content="Maksim Lebedev" />
+    <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+    <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+    <script type="text/javascript" src="mochitest_support_external.js"></script>
+    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+    <script type="text/javascript">
+      SimpleTest.waitForExplicitFinish();
+      function startTest() {
+        var iframe = document.getElementById("testFrame");
+        iframe.src = "pointerevent_suppress_compat_events_on_click.html";
+      }
+      function executeTest(int_win) {
+        sendMouseEvent(int_win, "target0", "mousedown", {button:0});
+        sendMouseEvent(int_win, "target0", "mouseup",   {button:0});
+        sendMouseEvent(int_win, "target1", "mousedown", {button:0});
+        sendMouseEvent(int_win, "target1", "mouseup",   {button:0});
+        sendMouseEvent(int_win, "done",    "mousedown", {button:0});
+        sendMouseEvent(int_win, "done",    "mouseup",   {button:0});
+      }
+    </script>
+  </head>
+  <body>
+    <iframe id="testFrame" height="800" width="1000"></iframe>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/events/test/pointerevents/test_pointerevent_suppress_compat_events_on_drag_mouse.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1000870
+-->
+  <head>
+    <meta charset="utf-8">
+    <title>Test for Bug 1000870</title>
+    <meta name="author" content="Maksim Lebedev" />
+    <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+    <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+    <script type="text/javascript" src="mochitest_support_external.js"></script>
+    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+    <script type="text/javascript">
+      SimpleTest.waitForExplicitFinish();
+      function startTest() {
+        var iframe = document.getElementById("testFrame");
+        iframe.src = "pointerevent_suppress_compat_events_on_drag_mouse.html";
+      }
+      function executeTest(int_win) {
+        sendMouseEvent(int_win, "target0", "mousedown", {button:0});
+        sendMouseEvent(int_win, "target0", "mousemove", {button:0});
+        sendMouseEvent(int_win, "target0", "mouseup",   {button:0});
+        sendMouseEvent(int_win, "target1", "mousedown", {button:0});
+        sendMouseEvent(int_win, "target1", "mousemove", {button:0});
+        sendMouseEvent(int_win, "target1", "mouseup",   {button:0});
+        sendMouseEvent(int_win, "done",    "mousedown", {button:0});
+        sendMouseEvent(int_win, "done",    "mouseup",   {button:0});
+      }
+    </script>
+  </head>
+  <body>
+    <iframe id="testFrame" height="800" width="1000"></iframe>
+  </body>
+</html>
--- a/dom/indexedDB/ActorsChild.cpp
+++ b/dom/indexedDB/ActorsChild.cpp
@@ -425,18 +425,16 @@ private:
 
   nsresult
   GetResult(JSContext* aCx,
             StructuredCloneReadInfo* aCloneInfo,
             JS::MutableHandle<JS::Value> aResult)
   {
     bool ok = IDBObjectStore::DeserializeValue(aCx, *aCloneInfo, aResult);
 
-    aCloneInfo->mCloneBuffer.clear();
-
     if (NS_WARN_IF(!ok)) {
       return NS_ERROR_DOM_DATA_CLONE_ERR;
     }
 
     return NS_OK;
   }
 
   nsresult
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -14835,38 +14835,38 @@ TransactionBase::VerifyRequestParams(con
 
   RefPtr<FullObjectStoreMetadata> objMetadata =
     GetMetadataForObjectStoreId(aParams.objectStoreId());
   if (NS_WARN_IF(!objMetadata)) {
     ASSERT_UNLESS_FUZZING();
     return false;
   }
 
-  if (NS_WARN_IF(aParams.cloneInfo().data().IsEmpty())) {
+  if (NS_WARN_IF(!aParams.cloneInfo().data().data.Size())) {
     ASSERT_UNLESS_FUZZING();
     return false;
   }
 
   if (objMetadata->mCommonMetadata.autoIncrement() &&
       objMetadata->mCommonMetadata.keyPath().IsValid() &&
       aParams.key().IsUnset()) {
     const SerializedStructuredCloneWriteInfo cloneInfo = aParams.cloneInfo();
 
     if (NS_WARN_IF(!cloneInfo.offsetToKeyProp())) {
       ASSERT_UNLESS_FUZZING();
       return false;
     }
 
-    if (NS_WARN_IF(cloneInfo.data().Length() < sizeof(uint64_t))) {
+    if (NS_WARN_IF(cloneInfo.data().data.Size() < sizeof(uint64_t))) {
       ASSERT_UNLESS_FUZZING();
       return false;
     }
 
     if (NS_WARN_IF(cloneInfo.offsetToKeyProp() >
-                   (cloneInfo.data().Length() - sizeof(uint64_t)))) {
+                   (cloneInfo.data().data.Size() - sizeof(uint64_t)))) {
       ASSERT_UNLESS_FUZZING();
       return false;
     }
   } else if (NS_WARN_IF(aParams.cloneInfo().offsetToKeyProp())) {
     ASSERT_UNLESS_FUZZING();
     return false;
   }
 
@@ -19060,17 +19060,19 @@ DatabaseOperationBase::GetStructuredClon
 
   char* uncompressedBuffer = reinterpret_cast<char*>(uncompressed.Elements());
 
   if (NS_WARN_IF(!snappy::RawUncompress(compressed, compressedLength,
                                         uncompressedBuffer))) {
     return NS_ERROR_FILE_CORRUPTED;
   }
 
-  aInfo->mData.SwapElements(uncompressed);
+  if (!aInfo->mData.WriteBytes(uncompressedBuffer, uncompressed.Length())) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
 
   if (!aFileIds.IsVoid()) {
     AutoTArray<int64_t, 10> array;
     nsresult rv = ConvertFileIdsToArray(aFileIds, array);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
@@ -25422,16 +25424,23 @@ ObjectStoreAddOrPutRequestOp::DoDatabase
   rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"), osid);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   MOZ_ASSERT(!keyUnset || mMetadata->mCommonMetadata.autoIncrement(),
              "Should have key unless autoIncrement");
 
+  const JSStructuredCloneData& data = mParams.cloneInfo().data().data;
+  size_t cloneDataSize = data.Size();
+  nsCString cloneData;
+  cloneData.SetLength(cloneDataSize);
+  auto iter = data.Iter();
+  data.ReadBytes(iter, cloneData.BeginWriting(), cloneDataSize);
+
   int64_t autoIncrementNum = 0;
 
   if (mMetadata->mCommonMetadata.autoIncrement()) {
     if (keyUnset) {
       autoIncrementNum = mMetadata->mNextAutoIncrementId;
 
       MOZ_ASSERT(autoIncrementNum > 0);
 
@@ -25443,39 +25452,36 @@ ObjectStoreAddOrPutRequestOp::DoDatabase
     } else if (key.IsFloat() &&
                key.ToFloat() >= mMetadata->mNextAutoIncrementId) {
       autoIncrementNum = floor(key.ToFloat());
     }
 
     if (keyUnset && keyPath.IsValid()) {
       const SerializedStructuredCloneWriteInfo& cloneInfo = mParams.cloneInfo();
       MOZ_ASSERT(cloneInfo.offsetToKeyProp());
-      MOZ_ASSERT(cloneInfo.data().Length() > sizeof(uint64_t));
+      MOZ_ASSERT(cloneDataSize > sizeof(uint64_t));
       MOZ_ASSERT(cloneInfo.offsetToKeyProp() <=
-                 (cloneInfo.data().Length() - sizeof(uint64_t)));
+                 (cloneDataSize - sizeof(uint64_t)));
 
       // Special case where someone put an object into an autoIncrement'ing
       // objectStore with no key in its keyPath set. We needed to figure out
       // which row id we would get above before we could set that properly.
-      uint8_t* keyPropPointer =
-        const_cast<uint8_t*>(cloneInfo.data().Elements() +
-                             cloneInfo.offsetToKeyProp());
+      char* keyPropPointer = cloneData.BeginWriting() + cloneInfo.offsetToKeyProp();
       uint64_t keyPropValue =
         ReinterpretDoubleAsUInt64(static_cast<double>(autoIncrementNum));
 
       LittleEndian::writeUint64(keyPropPointer, keyPropValue);
     }
   }
 
   key.BindToStatement(stmt, NS_LITERAL_CSTRING("key"));
 
   // Compress the bytes before adding into the database.
-  const char* uncompressed =
-    reinterpret_cast<const char*>(mParams.cloneInfo().data().Elements());
-  size_t uncompressedLength = mParams.cloneInfo().data().Length();
+  const char* uncompressed = cloneData.BeginReading();
+  size_t uncompressedLength = cloneDataSize;
 
   // We don't have a smart pointer class that calls free, so we need to
   // manage | compressed | manually.
   {
     size_t compressedLength = snappy::MaxCompressedLength(uncompressedLength);
 
     char* compressed = static_cast<char*>(malloc(compressedLength));
     if (NS_WARN_IF(!compressed)) {
@@ -25797,17 +25803,17 @@ nsresult
 ObjectStoreGetRequestOp::ConvertResponse(
                              uint32_t aIndex,
                              SerializedStructuredCloneReadInfo& aSerializedInfo)
 {
   MOZ_ASSERT(aIndex < mResponse.Length());
 
   StructuredCloneReadInfo& info = mResponse[aIndex];
 
-  info.mData.SwapElements(aSerializedInfo.data());
+  aSerializedInfo.data().data = Move(info.mData);
 
   FallibleTArray<BlobOrMutableFile> blobs;
   nsresult rv = ConvertBlobsToActors(mBackgroundParent,
                                      mDatabase,
                                      info.mFiles,
                                      blobs);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
@@ -26520,17 +26526,17 @@ IndexGetRequestOp::GetResponse(RequestRe
       for (uint32_t count = mResponse.Length(), index = 0;
            index < count;
            index++) {
         StructuredCloneReadInfo& info = mResponse[index];
 
         SerializedStructuredCloneReadInfo& serializedInfo =
           fallibleCloneInfos[index];
 
-        info.mData.SwapElements(serializedInfo.data());
+        serializedInfo.data().data = Move(info.mData);
 
         FallibleTArray<BlobOrMutableFile> blobs;
         nsresult rv = ConvertBlobsToActors(mBackgroundParent,
                                            mDatabase,
                                            info.mFiles,
                                            blobs);
         if (NS_WARN_IF(NS_FAILED(rv))) {
           aResponse = rv;
@@ -26554,17 +26560,17 @@ IndexGetRequestOp::GetResponse(RequestRe
   aResponse = IndexGetResponse();
 
   if (!mResponse.IsEmpty()) {
     StructuredCloneReadInfo& info = mResponse[0];
 
     SerializedStructuredCloneReadInfo& serializedInfo =
       aResponse.get_IndexGetResponse().cloneInfo();
 
-    info.mData.SwapElements(serializedInfo.data());
+    serializedInfo.data().data = Move(info.mData);
 
     FallibleTArray<BlobOrMutableFile> blobs;
     nsresult rv =
       ConvertBlobsToActors(mBackgroundParent,
                            mDatabase,
                            info.mFiles,
                            blobs);
     if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -26868,17 +26874,17 @@ CursorOpBase::PopulateResponseFromStatem
         mResponse = nsTArray<ObjectStoreCursorResponse>();
       } else {
         MOZ_ASSERT(mResponse.type() ==
                      CursorResponse::TArrayOfObjectStoreCursorResponse);
       }
 
       auto& responses = mResponse.get_ArrayOfObjectStoreCursorResponse();
       auto& response = *responses.AppendElement();
-      response.cloneInfo().data().SwapElements(cloneInfo.mData);
+      response.cloneInfo().data().data = Move(cloneInfo.mData);
       response.key() = mCursor->mKey;
 
       mFiles.AppendElement(Move(cloneInfo.mFiles));
       break;
     }
 
     case OpenCursorParams::TObjectStoreOpenKeyCursorParams: {
       MOZ_ASSERT(aInitializeResponse);
@@ -26906,17 +26912,17 @@ CursorOpBase::PopulateResponseFromStatem
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
 
       MOZ_ASSERT(aInitializeResponse);
       mResponse = IndexCursorResponse();
 
       auto& response = mResponse.get_IndexCursorResponse();
-      response.cloneInfo().data().SwapElements(cloneInfo.mData);
+      response.cloneInfo().data().data = Move(cloneInfo.mData);
       response.key() = mCursor->mKey;
       response.sortKey() = mCursor->mSortKey;
       response.objectKey() = mCursor->mObjectKey;
 
       mFiles.AppendElement(Move(cloneInfo.mFiles));
       break;
     }
 
--- a/dom/indexedDB/IDBObjectStore.cpp
+++ b/dom/indexedDB/IDBObjectStore.cpp
@@ -102,46 +102,16 @@ struct IDBObjectStore::StructuredCloneWr
     mBlobOrMutableFiles.SwapElements(aCloneWriteInfo.mBlobOrMutableFiles);
     aCloneWriteInfo.mOffsetToKeyProp = 0;
   }
 
   ~StructuredCloneWriteInfo()
   {
     MOZ_COUNT_DTOR(StructuredCloneWriteInfo);
   }
-
-  bool
-  operator==(const StructuredCloneWriteInfo& aOther) const
-  {
-    return this->mCloneBuffer.nbytes() == aOther.mCloneBuffer.nbytes() &&
-           this->mCloneBuffer.data() == aOther.mCloneBuffer.data() &&
-           this->mBlobOrMutableFiles == aOther.mBlobOrMutableFiles &&
-           this->mDatabase == aOther.mDatabase &&
-           this->mOffsetToKeyProp == aOther.mOffsetToKeyProp;
-  }
-
-  bool
-  SetFromSerialized(const SerializedStructuredCloneWriteInfo& aOther)
-  {
-    if (aOther.data().IsEmpty()) {
-      mCloneBuffer.clear();
-    } else {
-      auto* aOtherBuffer =
-        reinterpret_cast<uint64_t*>(
-          const_cast<uint8_t*>(aOther.data().Elements()));
-      if (!mCloneBuffer.copy(aOtherBuffer, aOther.data().Length())) {
-        return false;
-      }
-    }
-
-    mBlobOrMutableFiles.Clear();
-
-    mOffsetToKeyProp = aOther.offsetToKeyProp();
-    return true;
-  }
 };
 
 namespace {
 
 struct MOZ_STACK_CLASS MutableFileData final
 {
   nsString type;
   nsString name;
@@ -892,25 +862,16 @@ CommonStructuredCloneReadCallback(JSCont
 
     return result;
   }
 
   return StructuredCloneHolder::ReadFullySerializableObjects(aCx, aReader,
                                                              aTag);
 }
 
-// static
-void
-ClearStructuredCloneBuffer(JSAutoStructuredCloneBuffer& aBuffer)
-{
-  if (aBuffer.data()) {
-    aBuffer.clear();
-  }
-}
-
 } // namespace
 
 const JSClass IDBObjectStore::sDummyPropJSClass = {
   "IDBObjectStore Dummy",
   0 /* flags */
 };
 
 IDBObjectStore::IDBObjectStore(IDBTransaction* aTransaction,
@@ -1067,56 +1028,52 @@ IDBObjectStore::AppendIndexUpdateInfo(
 
 // static
 void
 IDBObjectStore::ClearCloneReadInfo(StructuredCloneReadInfo& aReadInfo)
 {
   // This is kind of tricky, we only want to release stuff on the main thread,
   // but we can end up being called on other threads if we have already been
   // cleared on the main thread.
-  if (!aReadInfo.mCloneBuffer.data() && !aReadInfo.mFiles.Length()) {
+  if (!aReadInfo.mFiles.Length()) {
     return;
   }
 
-  ClearStructuredCloneBuffer(aReadInfo.mCloneBuffer);
   aReadInfo.mFiles.Clear();
 }
 
 // static
 bool
 IDBObjectStore::DeserializeValue(JSContext* aCx,
                                  StructuredCloneReadInfo& aCloneReadInfo,
                                  JS::MutableHandle<JS::Value> aValue)
 {
   MOZ_ASSERT(aCx);
 
-  if (aCloneReadInfo.mData.IsEmpty()) {
+  if (!aCloneReadInfo.mData.Size()) {
     aValue.setUndefined();
     return true;
   }
 
-  auto* data = reinterpret_cast<uint64_t*>(aCloneReadInfo.mData.Elements());
-  size_t dataLen = aCloneReadInfo.mData.Length();
-
-  MOZ_ASSERT(!(dataLen % sizeof(*data)));
+  MOZ_ASSERT(!(aCloneReadInfo.mData.Size() % sizeof(uint64_t)));
 
   JSAutoRequest ar(aCx);
 
   static const JSStructuredCloneCallbacks callbacks = {
     CommonStructuredCloneReadCallback<ValueDeserializationHelper>,
     nullptr,
     nullptr,
     nullptr,
     nullptr,
     nullptr
   };
 
   // FIXME: Consider to use StructuredCloneHolder here and in other
   //        deserializing methods.
-  if (!JS_ReadStructuredClone(aCx, data, dataLen, JS_STRUCTURED_CLONE_VERSION,
+  if (!JS_ReadStructuredClone(aCx, aCloneReadInfo.mData, JS_STRUCTURED_CLONE_VERSION,
                               JS::StructuredCloneScope::SameProcessSameThread,
                               aValue, &callbacks, &aCloneReadInfo)) {
     return false;
   }
 
   return true;
 }
 
@@ -1124,38 +1081,32 @@ IDBObjectStore::DeserializeValue(JSConte
 bool
 IDBObjectStore::DeserializeIndexValue(JSContext* aCx,
                                       StructuredCloneReadInfo& aCloneReadInfo,
                                       JS::MutableHandle<JS::Value> aValue)
 {
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(aCx);
 
-  if (aCloneReadInfo.mData.IsEmpty()) {
+  if (!aCloneReadInfo.mData.Size()) {
     aValue.setUndefined();
     return true;
   }
 
-  size_t dataLen = aCloneReadInfo.mData.Length();
-
-  uint64_t* data =
-    const_cast<uint64_t*>(reinterpret_cast<uint64_t*>(
-      aCloneReadInfo.mData.Elements()));
-
-  MOZ_ASSERT(!(dataLen % sizeof(*data)));
+  MOZ_ASSERT(!(aCloneReadInfo.mData.Size() % sizeof(uint64_t)));
 
   JSAutoRequest ar(aCx);
 
   static const JSStructuredCloneCallbacks callbacks = {
     CommonStructuredCloneReadCallback<IndexDeserializationHelper>,
     nullptr,
     nullptr
   };
 
-  if (!JS_ReadStructuredClone(aCx, data, dataLen, JS_STRUCTURED_CLONE_VERSION,
+  if (!JS_ReadStructuredClone(aCx, aCloneReadInfo.mData, JS_STRUCTURED_CLONE_VERSION,
                               JS::StructuredCloneScope::SameProcessSameThread,
                               aValue, &callbacks, &aCloneReadInfo)) {
     return false;
   }
 
   return true;
 }
 
@@ -1165,41 +1116,35 @@ IDBObjectStore::DeserializeIndexValue(JS
 bool
 IDBObjectStore::DeserializeUpgradeValue(JSContext* aCx,
                                         StructuredCloneReadInfo& aCloneReadInfo,
                                         JS::MutableHandle<JS::Value> aValue)
 {
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(aCx);
 
-  if (aCloneReadInfo.mData.IsEmpty()) {
+  if (!aCloneReadInfo.mData.Size()) {
     aValue.setUndefined();
     return true;
   }
 
-  size_t dataLen = aCloneReadInfo.mData.Length();
-
-  uint64_t* data =
-    const_cast<uint64_t*>(reinterpret_cast<uint64_t*>(
-      aCloneReadInfo.mData.Elements()));
-
-  MOZ_ASSERT(!(dataLen % sizeof(*data)));
+  MOZ_ASSERT(!(aCloneReadInfo.mData.Size() % sizeof(uint64_t)));
 
   JSAutoRequest ar(aCx);
 
   static JSStructuredCloneCallbacks callbacks = {
     CommonStructuredCloneReadCallback<UpgradeDeserializationHelper>,
     nullptr,
     nullptr,
     nullptr,
     nullptr,
     nullptr
   };
 
-  if (!JS_ReadStructuredClone(aCx, data, dataLen, JS_STRUCTURED_CLONE_VERSION,
+  if (!JS_ReadStructuredClone(aCx, aCloneReadInfo.mData, JS_STRUCTURED_CLONE_VERSION,
                               JS::StructuredCloneScope::SameProcessSameThread,
                               aValue, &callbacks, &aCloneReadInfo)) {
     return false;
   }
 
   return true;
 }
 
@@ -1321,32 +1266,19 @@ IDBObjectStore::AddOrPut(JSContext* aCx,
   StructuredCloneWriteInfo cloneWriteInfo(mTransaction->Database());
   nsTArray<IndexUpdateInfo> updateInfo;
 
   aRv = GetAddInfo(aCx, value, aKey, cloneWriteInfo, key, updateInfo);
   if (aRv.Failed()) {
     return nullptr;
   }
 
-  FallibleTArray<uint8_t> cloneData;
-  if (NS_WARN_IF(!cloneData.SetLength(cloneWriteInfo.mCloneBuffer.nbytes(),
-                                      fallible))) {
-    aRv = NS_ERROR_OUT_OF_MEMORY;
-    return nullptr;
-  }
-
-  // XXX Remove this
-  memcpy(cloneData.Elements(), cloneWriteInfo.mCloneBuffer.data(),
-         cloneWriteInfo.mCloneBuffer.nbytes());
-
-  cloneWriteInfo.mCloneBuffer.clear();
-
   ObjectStoreAddPutParams commonParams;
   commonParams.objectStoreId() = Id();
-  commonParams.cloneInfo().data().SwapElements(cloneData);
+  commonParams.cloneInfo().data().data = Move(cloneWriteInfo.mCloneBuffer.data());
   commonParams.cloneInfo().offsetToKeyProp() = cloneWriteInfo.mOffsetToKeyProp;
   commonParams.key() = key;
   commonParams.indexUpdateInfos().SwapElements(updateInfo);
 
   // Convert any blobs or mutable files into DatabaseOrMutableFile.
   nsTArray<StructuredCloneWriteInfo::BlobOrMutableFile>& blobOrMutableFiles =
     cloneWriteInfo.mBlobOrMutableFiles;
 
--- a/dom/indexedDB/IndexedDatabase.h
+++ b/dom/indexedDB/IndexedDatabase.h
@@ -40,32 +40,33 @@ struct StructuredCloneFile
 
   // In IndexedDatabaseInlines.h
   inline bool
   operator==(const StructuredCloneFile& aOther) const;
 };
 
 struct StructuredCloneReadInfo
 {
-  nsTArray<uint8_t> mData;
+  JSStructuredCloneData mData;
   nsTArray<StructuredCloneFile> mFiles;
   IDBDatabase* mDatabase;
 
-  // XXX Remove!
-  JSAutoStructuredCloneBuffer mCloneBuffer;
-
   // In IndexedDatabaseInlines.h
   inline
   StructuredCloneReadInfo();
 
   // In IndexedDatabaseInlines.h
   inline
   ~StructuredCloneReadInfo();
 
   // In IndexedDatabaseInlines.h
+  inline
+  StructuredCloneReadInfo(StructuredCloneReadInfo&& aOther);
+
+  // In IndexedDatabaseInlines.h
   inline StructuredCloneReadInfo&
   operator=(StructuredCloneReadInfo&& aOther);
 
   // In IndexedDatabaseInlines.h
   inline
   MOZ_IMPLICIT StructuredCloneReadInfo(SerializedStructuredCloneReadInfo&& aOther);
 };
 
--- a/dom/indexedDB/IndexedDatabaseInlines.h
+++ b/dom/indexedDB/IndexedDatabaseInlines.h
@@ -42,46 +42,55 @@ StructuredCloneFile::operator==(const St
          this->mMutableFile == aOther.mMutableFile &&
          this->mFileInfo == aOther.mFileInfo &&
          this->mMutable == aOther.mMutable;
 }
 
 inline
 StructuredCloneReadInfo::StructuredCloneReadInfo()
   : mDatabase(nullptr)
-  , mCloneBuffer(JS::StructuredCloneScope::SameProcessSameThread, nullptr,
-                 nullptr)
 {
   MOZ_COUNT_CTOR(StructuredCloneReadInfo);
 }
 
 inline
 StructuredCloneReadInfo::StructuredCloneReadInfo(
+                             StructuredCloneReadInfo&& aCloneReadInfo)
+  : mData(Move(aCloneReadInfo.mData))
+{
+  MOZ_ASSERT(&aCloneReadInfo != this);
+  MOZ_COUNT_CTOR(StructuredCloneReadInfo);
+
+  mFiles.Clear();
+  mFiles.SwapElements(aCloneReadInfo.mFiles);
+  mDatabase = aCloneReadInfo.mDatabase;
+  aCloneReadInfo.mDatabase = nullptr;
+}
+
+inline
+StructuredCloneReadInfo::StructuredCloneReadInfo(
                              SerializedStructuredCloneReadInfo&& aCloneReadInfo)
-  : mData(Move(aCloneReadInfo.data()))
+  : mData(Move(aCloneReadInfo.data().data))
   , mDatabase(nullptr)
-  , mCloneBuffer(JS::StructuredCloneScope::SameProcessSameThread, nullptr,
-                 nullptr)
 {
   MOZ_COUNT_CTOR(StructuredCloneReadInfo);
 }
 
 inline
 StructuredCloneReadInfo::~StructuredCloneReadInfo()
 {
   MOZ_COUNT_DTOR(StructuredCloneReadInfo);
 }
 
 inline StructuredCloneReadInfo&
 StructuredCloneReadInfo::operator=(StructuredCloneReadInfo&& aCloneReadInfo)
 {
   MOZ_ASSERT(&aCloneReadInfo != this);
 
   mData = Move(aCloneReadInfo.mData);
-  mCloneBuffer = Move(aCloneReadInfo.mCloneBuffer);
   mFiles.Clear();
   mFiles.SwapElements(aCloneReadInfo.mFiles);
   mDatabase = aCloneReadInfo.mDatabase;
   aCloneReadInfo.mDatabase = nullptr;
   return *this;
 }
 
 } // namespace indexedDB
--- a/dom/indexedDB/PBackgroundIDBSharedTypes.ipdlh
+++ b/dom/indexedDB/PBackgroundIDBSharedTypes.ipdlh
@@ -25,16 +25,19 @@ using class mozilla::dom::indexedDB::Key
   from "mozilla/dom/indexedDB/Key.h";
 
 using class mozilla::dom::indexedDB::KeyPath
   from "mozilla/dom/indexedDB/KeyPath.h";
 
 using mozilla::dom::quota::PersistenceType
   from "mozilla/dom/quota/PersistenceType.h";
 
+using mozilla::SerializedStructuredCloneBuffer
+  from "ipc/IPCMessageUtils.h";
+
 namespace mozilla {
 namespace dom {
 namespace indexedDB {
 
 struct SerializedKeyRange
 {
   Key lower;
   Key upper;
@@ -52,23 +55,23 @@ union NullableMutableFile
 union BlobOrMutableFile
 {
   PBlob;
   NullableMutableFile;
 };
 
 struct SerializedStructuredCloneReadInfo
 {
-  uint8_t[] data;
+  SerializedStructuredCloneBuffer data;
   BlobOrMutableFile[] blobs;
 };
 
 struct SerializedStructuredCloneWriteInfo
 {
-  uint8_t[] data;
+  SerializedStructuredCloneBuffer data;
   uint64_t offsetToKeyProp;
 };
 
 struct IndexUpdateInfo
 {
   int64_t indexId;
   Key value;
   Key localizedValue;
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -5496,17 +5496,8 @@ ContentParent::SendGetFilesResponseAndFo
                                              const GetFilesResponseResult& aResult)
 {
   GetFilesHelper* helper = mGetFilesPendingRequests.GetWeak(aUUID);
   if (helper) {
     mGetFilesPendingRequests.Remove(aUUID);
     Unused << SendGetFilesResponse(aUUID, aResult);
   }
 }
-
-void
-ContentParent::ForceTabPaint(TabParent* aTabParent, uint64_t aLayerObserverEpoch)
-{
-  if (!mHangMonitorActor) {
-    return;
-  }
-  ProcessHangMonitor::ForcePaint(mHangMonitorActor, aTabParent, aLayerObserverEpoch);
-}
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -558,19 +558,16 @@ public:
                                            PBlobParent* aBlobParent,
                                            const Principal& aPrincipal) override;
 
   virtual bool
   RecvUnstoreAndBroadcastBlobURLUnregistration(const nsCString& aURI) override;
 
   virtual int32_t Pid() const override;
 
-  // Use the PHangMonitor channel to ask the child to repaint a tab.
-  void ForceTabPaint(TabParent* aTabParent, uint64_t aLayerObserverEpoch);
-
 protected:
   void OnChannelConnected(int32_t pid) override;
 
   virtual void ActorDestroy(ActorDestroyReason why) override;
 
   bool ShouldContinueFromReplyTimeout() override;
 
   void OnVarChanged(const GfxVarUpdate& aVar) override;
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -60,17 +60,18 @@ using struct mozilla::dom::RemoteDOMEven
 using mozilla::dom::ScreenOrientationInternal from "mozilla/dom/ScreenOrientation.h";
 using struct mozilla::layers::TextureFactoryIdentifier from "mozilla/layers/CompositorTypes.h";
 using mozilla::CSSToScreenScale from "Units.h";
 using mozilla::CommandInt from "mozilla/EventForwards.h";
 using mozilla::WritingMode from "mozilla/WritingModes.h";
 using mozilla::layers::TouchBehaviorFlags from "mozilla/layers/APZUtils.h";
 using nsIWidget::TouchPointerState from "nsIWidget.h";
 using struct LookAndFeelInt from "mozilla/widget/WidgetMessageUtils.h";
-using class mozilla::dom::ipc::StructuredCloneData from "ipc/IPCMessageUtils.h";
+using class mozilla::dom::MessagePort from "mozilla/dom/MessagePort.h";
+using class mozilla::dom::ipc::StructuredCloneData from "mozilla/dom/ipc/StructuredCloneData.h";
 using mozilla::EventMessage from "mozilla/EventForwards.h";
 using nsEventStatus from "mozilla/EventForwards.h";
 using nsSizeMode from "nsIWidgetListener.h";
 using mozilla::widget::CandidateWindowPosition from "ipc/nsGUIEventIPC.h";
 using class mozilla::NativeEventData from "ipc/nsGUIEventIPC.h";
 using mozilla::FontRange from "ipc/nsGUIEventIPC.h";
 
 namespace mozilla {
@@ -546,21 +547,16 @@ parent:
      * compositing.  This is sent when all pending changes have been
      * sent to the compositor and are ready to be shown on the next composite.
      * @see PCompositor
      * @see RequestNotifyAfterRemotePaint
      */
     async RemotePaintIsReady();
 
     /**
-     * Child informs the parent that the layer tree is already available.
-     */
-    async ForcePaintNoOp(uint64_t aLayerObserverEpoch);
-
-    /**
      * Sent by the child to the parent to inform it that an update to the
      * dimensions has been requested, likely through win.moveTo or resizeTo
      */
     async SetDimensions(uint32_t aFlags, int32_t aX, int32_t aY, int32_t aCx, int32_t aCy);
 
     prio(high) sync DispatchWheelEvent(WidgetWheelEvent event);
     prio(high) sync DispatchMouseEvent(WidgetMouseEvent event);
     prio(high) sync DispatchKeyboardEvent(WidgetKeyboardEvent event);
@@ -721,17 +717,17 @@ child:
      * Tell the child side if it has to update it's touchable region
      * to the parent.
      */
     async SetUpdateHitRegion(bool aEnabled);
 
     /**
      * Update the child side docShell active (resource use) state.
      */
-    async SetDocShellIsActive(bool aIsActive, bool aIsHidden, uint64_t aLayerObserverEpoch);
+    async SetDocShellIsActive(bool aIsActive, bool aIsHidden);
 
     /**
      * Notify the child that it shouldn't paint the offscreen displayport.
      * This is useful to speed up interactive operations over async
      * scrolling performance like resize, tabswitch, pageload.
      *
      * Each enable call must be matched with a disable call. The child
      * will remain in the suppress mode as long as there's
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -90,17 +90,18 @@ using struct mozilla::null_t from "ipc/I
 using struct mozilla::void_t from "ipc/IPCMessageUtils.h";
 using mozilla::dom::NativeThreadId from "mozilla/dom/TabMessageUtils.h";
 using mozilla::hal::ProcessPriority from "mozilla/HalTypes.h";
 using mozilla::gfx::IntSize from "mozilla/gfx/2D.h";
 using mozilla::dom::TabId from "mozilla/dom/ipc/IdType.h";
 using mozilla::dom::ContentParentId from "mozilla/dom/ipc/IdType.h";
 using mozilla::LayoutDeviceIntPoint from "Units.h";
 using struct LookAndFeelInt from "mozilla/widget/WidgetMessageUtils.h";
-using class mozilla::dom::ipc::StructuredCloneData from "ipc/IPCMessageUtils.h";
+using class mozilla::dom::MessagePort from "mozilla/dom/MessagePort.h";
+using class mozilla::dom::ipc::StructuredCloneData from "mozilla/dom/ipc/StructuredCloneData.h";
 using mozilla::DataStorageType from "ipc/DataStorageIPCUtils.h";
 using mozilla::DocShellOriginAttributes from "mozilla/ipc/BackgroundUtils.h";
 using struct mozilla::layers::TextureFactoryIdentifier from "mozilla/layers/CompositorTypes.h";
 using struct mozilla::dom::FlyWebPublishOptions from "mozilla/dom/FlyWebPublishOptionsIPCSerializer.h";
 
 union ChromeRegistryItem
 {
     ChromePackage;
--- a/dom/ipc/PContentBridge.ipdl
+++ b/dom/ipc/PContentBridge.ipdl
@@ -11,17 +11,18 @@ include protocol PJavaScript;
 
 include DOMTypes;
 include JavaScriptTypes;
 include PTabContext;
 
 using class IPC::Principal from "mozilla/dom/PermissionMessageUtils.h";
 using mozilla::dom::TabId from "mozilla/dom/ipc/IdType.h";
 using mozilla::dom::ContentParentId from "mozilla/dom/ipc/IdType.h";
-using class mozilla::dom::ipc::StructuredCloneData from "ipc/IPCMessageUtils.h";
+using class mozilla::dom::MessagePort from "mozilla/dom/MessagePort.h";
+using class mozilla::dom::ipc::StructuredCloneData from "mozilla/dom/ipc/StructuredCloneData.h";
 
 namespace mozilla {
 namespace dom {
 
 /*
  * PContentBridge allows us to represent a parent/child relationship between two
  * child processes.  When a child process wants to open its own child, it asks
  * the root process to create a new process and then bridge them.  The first
--- a/dom/ipc/PProcessHangMonitor.ipdl
+++ b/dom/ipc/PProcessHangMonitor.ipdl
@@ -35,13 +35,11 @@ parent:
   async HangEvidence(HangData data);
   async ClearHang();
 
 child:
   async TerminateScript();
 
   async BeginStartingDebugger();
   async EndStartingDebugger();
-
-  async ForcePaint(TabId tabId, uint64_t aLayerObserverEpoch);
 };
 
 } // namespace mozilla
--- a/dom/ipc/ProcessHangMonitor.cpp
+++ b/dom/ipc/ProcessHangMonitor.cpp
@@ -2,23 +2,19 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/ProcessHangMonitor.h"
 #include "mozilla/ProcessHangMonitorIPC.h"
 
-#include "jsapi.h"
-#include "js/GCAPI.h"
-
 #include "mozilla/Atomics.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/Element.h"
-#include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/TabChild.h"
 #include "mozilla/dom/TabParent.h"
 #include "mozilla/Monitor.h"
 #include "mozilla/plugins/PluginBridge.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/unused.h"
 
 #include "nsIFrameLoader.h"
@@ -95,21 +91,18 @@ class HangMonitorChild
 
   void ClearHang();
   void ClearHangAsync();
 
   virtual bool RecvTerminateScript() override;
   virtual bool RecvBeginStartingDebugger() override;
   virtual bool RecvEndStartingDebugger() override;
 
-  virtual bool RecvForcePaint(const TabId& aTabId, const uint64_t& aLayerObserverEpoch) override;
-
   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
 
-  void InterruptCallback();
   void Shutdown();
 
   static HangMonitorChild* Get() { return sInstance; }
 
   MessageLoop* MonitorLoop() { return mHangMonitor->MonitorLoop(); }
 
  private:
   void ShutdownOnThread();
@@ -121,20 +114,16 @@ class HangMonitorChild
 
   // Main thread-only.
   bool mSentReport;
 
   // These fields must be accessed with mMonitor held.
   bool mTerminateScript;
   bool mStartDebugger;
   bool mFinishedStartingDebugger;
-  bool mForcePaint;
-  TabId mForcePaintTab;
-  uint64_t mForcePaintEpoch;
-  JSContext* mContext;
   bool mShutdownDone;
 
   // This field is only accessed on the hang thread.
   bool mIPCOpen;
 };
 
 Atomic<HangMonitorChild*> HangMonitorChild::sInstance;
 
@@ -214,37 +203,32 @@ public:
   virtual bool RecvClearHang() override;
 
   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
 
   void SetProcess(HangMonitoredProcess* aProcess) { mProcess = aProcess; }
 
   void Shutdown();
 
-  void ForcePaint(dom::TabParent* aTabParent, uint64_t aLayerObserverEpoch);
-
   void TerminateScript();
   void BeginStartingDebugger();
   void EndStartingDebugger();
   void CleanupPluginHang(uint32_t aPluginId, bool aRemoveFiles);
 
   /**
    * Update the dump for the specified plugin. This method is thread-safe and
    * is used to replace a browser minidump with a full minidump. If aDumpId is
    * empty this is a no-op.
    */
   void UpdateMinidump(uint32_t aPluginId, const nsString& aDumpId);
 
   MessageLoop* MonitorLoop() { return mHangMonitor->MonitorLoop(); }
 
 private:
   bool TakeBrowserMinidump(const PluginHangData& aPhd, nsString& aCrashId);
-
-  void ForcePaintOnThread(TabId aTabId, uint64_t aLayerObserverEpoch);
-
   void ShutdownOnThread();
 
   const RefPtr<ProcessHangMonitor> mHangMonitor;
 
   // This field is read-only after construction.
   bool mReportHangs;
 
   // This field is only accessed on the hang thread.
@@ -266,59 +250,30 @@ private:
 
 HangMonitorChild::HangMonitorChild(ProcessHangMonitor* aMonitor)
  : mHangMonitor(aMonitor),
    mMonitor("HangMonitorChild lock"),
    mSentReport(false),
    mTerminateScript(false),
    mStartDebugger(false),
    mFinishedStartingDebugger(false),
-   mForcePaint(false),
    mShutdownDone(false),
    mIPCOpen(true)
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
-  mContext = danger::GetJSContext();
 }
 
 HangMonitorChild::~HangMonitorChild()
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(sInstance == this);
   sInstance = nullptr;
 }
 
 void
-HangMonitorChild::InterruptCallback()
-{
-  MOZ_RELEASE_ASSERT(NS_IsMainThread());
-
-  bool forcePaint;
-  TabId forcePaintTab;
-  uint64_t forcePaintEpoch;
-
-  {
-    MonitorAutoLock lock(mMonitor);
-    forcePaint = mForcePaint;
-    forcePaintTab = mForcePaintTab;
-    forcePaintEpoch = mForcePaintEpoch;
-
-    mForcePaint = false;
-  }
-
-  if (forcePaint) {
-    RefPtr<TabChild> tabChild = TabChild::FindTabChild(forcePaintTab);
-    if (tabChild) {
-      JS::AutoAssertOnGC aaogc(mContext);
-      tabChild->ForcePaint(forcePaintEpoch);
-    }
-  }
-}
-
-void
 HangMonitorChild::Shutdown()
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
   MonitorAutoLock lock(mMonitor);
   while (!mShutdownDone) {
     mMonitor.Wait();
   }
@@ -371,33 +326,16 @@ HangMonitorChild::RecvEndStartingDebugge
 {
   MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop());
 
   MonitorAutoLock lock(mMonitor);
   mFinishedStartingDebugger = true;
   return true;
 }
 
-bool
-HangMonitorChild::RecvForcePaint(const TabId& aTabId, const uint64_t& aLayerObserverEpoch)
-{
-  MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop());
-
-  {
-    MonitorAutoLock lock(mMonitor);
-    mForcePaint = true;
-    mForcePaintTab = aTabId;
-    mForcePaintEpoch = aLayerObserverEpoch;
-  }
-
-  JS_RequestInterruptCallback(mContext);
-
-  return true;
-}
-
 void
 HangMonitorChild::Open(Transport* aTransport, ProcessId aPid,
                        MessageLoop* aIOLoop)
 {
   MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop());
 
   MOZ_ASSERT(!sInstance);
   sInstance = this;
@@ -582,35 +520,16 @@ HangMonitorParent::ShutdownOnThread()
   }
 
   MonitorAutoLock lock(mMonitor);
   mShutdownDone = true;
   mMonitor.Notify();
 }
 
 void
-HangMonitorParent::ForcePaint(dom::TabParent* aTab, uint64_t aLayerObserverEpoch)
-{
-  MOZ_RELEASE_ASSERT(NS_IsMainThread());
-  TabId id = aTab->GetTabId();
-  MonitorLoop()->PostTask(NewNonOwningRunnableMethod<TabId, uint64_t>(
-                            this, &HangMonitorParent::ForcePaintOnThread, id, aLayerObserverEpoch));
-}
-
-void
-HangMonitorParent::ForcePaintOnThread(TabId aTabId, uint64_t aLayerObserverEpoch)
-{
-  MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop());
-
-  if (mIPCOpen) {
-    Unused << SendForcePaint(aTabId, aLayerObserverEpoch);
-  }
-}
-
-void
 HangMonitorParent::ActorDestroy(ActorDestroyReason aWhy)
 {
   MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop());
   mIPCOpen = false;
 }
 
 void
 HangMonitorParent::Open(Transport* aTransport, ProcessId aPid,
@@ -1035,26 +954,16 @@ HangMonitoredProcess::UserCanceled()
 
   if (mActor) {
     uint32_t id = mHangData.get_PluginHangData().pluginId();
     mActor->CleanupPluginHang(id, true);
   }
   return NS_OK;
 }
 
-static bool
-InterruptCallback(JSContext* cx)
-{
-  if (HangMonitorChild* child = HangMonitorChild::Get()) {
-    child->InterruptCallback();
-  }
-
-  return true;
-}
-
 ProcessHangMonitor* ProcessHangMonitor::sInstance;
 
 ProcessHangMonitor::ProcessHangMonitor()
  : mCPOWTimeout(false)
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
   MOZ_COUNT_CTOR(ProcessHangMonitor);
@@ -1180,19 +1089,16 @@ mozilla::CreateHangMonitorParent(Content
 }
 
 PProcessHangMonitorChild*
 mozilla::CreateHangMonitorChild(mozilla::ipc::Transport* aTransport,
                                 base::ProcessId aOtherPid)
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
-  JSContext* cx = danger::GetJSContext();
-  JS_AddInterruptCallback(cx, InterruptCallback);
-
   ProcessHangMonitor* monitor = ProcessHangMonitor::GetOrCreate();
   HangMonitorChild* child = new HangMonitorChild(monitor);
 
   monitor->MonitorLoop()->PostTask(NewNonOwningRunnableMethod
                                    <mozilla::ipc::Transport*,
                                     base::ProcessId,
                                     MessageLoop*>(child,
                                                   &HangMonitorChild::Open,
@@ -1231,18 +1137,8 @@ ProcessHangMonitor::RemoveProcess(PProce
 /* static */ void
 ProcessHangMonitor::ClearHang()
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (HangMonitorChild* child = HangMonitorChild::Get()) {
     child->ClearHang();
   }
 }
-
-/* static */ void
-ProcessHangMonitor::ForcePaint(PProcessHangMonitorParent* aParent,
-                               dom::TabParent* aTabParent,
-                               uint64_t aLayerObserverEpoch)
-{
-  MOZ_RELEASE_ASSERT(NS_IsMainThread());
-  auto parent = static_cast<HangMonitorParent*>(aParent);
-  parent->ForcePaint(aTabParent, aLayerObserverEpoch);
-}
--- a/dom/ipc/ProcessHangMonitor.h
+++ b/dom/ipc/ProcessHangMonitor.h
@@ -17,17 +17,16 @@ class MessageLoop;
 namespace base {
 class Thread;
 } // namespace base
 
 namespace mozilla {
 
 namespace dom {
 class ContentParent;
-class TabParent;
 } // namespace dom
 
 class PProcessHangMonitorParent;
 
 class ProcessHangMonitor final
   : public nsIObserver
 {
  private:
@@ -41,20 +40,16 @@ class ProcessHangMonitor final
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
   static void AddProcess(dom::ContentParent* aContentParent);
   static void RemoveProcess(PProcessHangMonitorParent* aParent);
 
   static void ClearHang();
 
-  static void ForcePaint(PProcessHangMonitorParent* aParent,
-                         dom::TabParent* aTab,
-                         uint64_t aLayerObserverEpoch);
-
   enum SlowScriptAction {
     Continue,
     Terminate,
     StartDebugger
   };
   SlowScriptAction NotifySlowScript(nsITabChild* aTabChild,
                                     const char* aFileName,
                                     unsigned aLineNo);
--- a/dom/ipc/StructuredCloneData.cpp
+++ b/dom/ipc/StructuredCloneData.cpp
@@ -23,122 +23,109 @@
 
 namespace mozilla {
 namespace dom {
 namespace ipc {
 
 bool
 StructuredCloneData::Copy(const StructuredCloneData& aData)
 {
-  if (!aData.Data()) {
+  if (!aData.mInitialized) {
     return true;
   }
 
   if (aData.SharedData()) {
     mSharedData = aData.SharedData();
   } else {
     mSharedData =
-      SharedJSAllocatedData::CreateFromExternalData(aData.Data(),
-                                                    aData.DataLength());
+      SharedJSAllocatedData::CreateFromExternalData(aData.Data());
     NS_ENSURE_TRUE(mSharedData, false);
   }
 
   PortIdentifiers().AppendElements(aData.PortIdentifiers());
 
   MOZ_ASSERT(BlobImpls().IsEmpty());
   BlobImpls().AppendElements(aData.BlobImpls());
 
   MOZ_ASSERT(GetSurfaces().IsEmpty());
 
+  mInitialized = true;
+
   return true;
 }
 
 void
 StructuredCloneData::Read(JSContext* aCx,
                           JS::MutableHandle<JS::Value> aValue,
                           ErrorResult &aRv)
 {
-  MOZ_ASSERT(Data());
+  MOZ_ASSERT(mInitialized);
 
   nsIGlobalObject *global = xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx));
   MOZ_ASSERT(global);
 
-  ReadFromBuffer(global, aCx, Data(), DataLength(), aValue, aRv);
+  ReadFromBuffer(global, aCx, Data(), aValue, aRv);
 }
 
 void
 StructuredCloneData::Write(JSContext* aCx,
                            JS::Handle<JS::Value> aValue,
                            ErrorResult &aRv)
 {
   Write(aCx, aValue, JS::UndefinedHandleValue, aRv);
 }
 
 void
 StructuredCloneData::Write(JSContext* aCx,
                            JS::Handle<JS::Value> aValue,
                            JS::Handle<JS::Value> aTransfer,
                            ErrorResult &aRv)
 {
-  MOZ_ASSERT(!Data());
+  MOZ_ASSERT(!mInitialized);
 
   StructuredCloneHolder::Write(aCx, aValue, aTransfer, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
-  uint64_t* data = nullptr;
-  size_t dataLength = 0;
-  mBuffer->steal(&data, &dataLength);
+  JSStructuredCloneData data;
+  mBuffer->abandon();
+  mBuffer->steal(&data);
   mBuffer = nullptr;
-  mSharedData = new SharedJSAllocatedData(data, dataLength);
+  mSharedData = new SharedJSAllocatedData(Move(data));
+  mInitialized = true;
 }
 
 void
 StructuredCloneData::WriteIPCParams(IPC::Message* aMsg) const
 {
-  WriteParam(aMsg, DataLength());
-
-  if (DataLength()) {
-    aMsg->WriteBytes(Data(), DataLength());
-  }
+  WriteParam(aMsg, Data());
 }
 
 bool
 StructuredCloneData::ReadIPCParams(const IPC::Message* aMsg,
                                    PickleIterator* aIter)
 {
-  MOZ_ASSERT(!Data());
-
-  size_t dataLength = 0;
-  if (!ReadParam(aMsg, aIter, &dataLength)) {
+  MOZ_ASSERT(!mInitialized);
+  JSStructuredCloneData data;
+  if (!ReadParam(aMsg, aIter, &data)) {
     return false;
   }
-
-  if (!dataLength) {
-    return true;
-  }
-
-  mSharedData = SharedJSAllocatedData::AllocateForExternalData(dataLength);
-  NS_ENSURE_TRUE(mSharedData, false);
-
-  if (!aMsg->ReadBytesInto(aIter, mSharedData->Data(), dataLength)) {
-    mSharedData = nullptr;
-    return false;
-  }
-
+  mSharedData = new SharedJSAllocatedData(Move(data));
+  mInitialized = true;
   return true;
 }
 
 bool
-StructuredCloneData::CopyExternalData(const void* aData,
+StructuredCloneData::CopyExternalData(const char* aData,
                                       size_t aDataLength)
 {
-  MOZ_ASSERT(!Data());
+  MOZ_ASSERT(!mInitialized);
   mSharedData = SharedJSAllocatedData::CreateFromExternalData(aData,
                                                               aDataLength);
   NS_ENSURE_TRUE(mSharedData, false);
+  mInitialized = true;
   return true;
 }
 
 } // namespace ipc
 } // namespace dom
 } // namespace mozilla
--- a/dom/ipc/StructuredCloneData.h
+++ b/dom/ipc/StructuredCloneData.h
@@ -19,87 +19,78 @@ class PickleIterator;
 
 namespace mozilla {
 namespace dom {
 namespace ipc {
 
 class SharedJSAllocatedData final
 {
 public:
-  SharedJSAllocatedData(uint64_t* aData, size_t aDataLength)
-  : mData(aData), mDataLength(aDataLength)
-  {
-    MOZ_ASSERT(mData);
-  }
+  explicit SharedJSAllocatedData(JSStructuredCloneData&& aData)
+    : mData(Move(aData))
+  { }
 
   static already_AddRefed<SharedJSAllocatedData>
-  AllocateForExternalData(size_t aDataLength)
+  CreateFromExternalData(const char* aData, size_t aDataLength)
   {
-    uint64_t* data = Allocate64bitSafely(aDataLength);
-    if (!data) {
-      return nullptr;
-    }
-
+    JSStructuredCloneData buf;
+    buf.WriteBytes(aData, aDataLength);
     RefPtr<SharedJSAllocatedData> sharedData =
-      new SharedJSAllocatedData(data, aDataLength);
+      new SharedJSAllocatedData(Move(buf));
     return sharedData.forget();
   }
 
   static already_AddRefed<SharedJSAllocatedData>
-  CreateFromExternalData(const void* aData, size_t aDataLength)
+  CreateFromExternalData(const JSStructuredCloneData& aData)
   {
+    JSStructuredCloneData buf;
+    auto iter = aData.Iter();
+    while (!iter.Done()) {
+      buf.WriteBytes(iter.Data(), iter.RemainingInSegment());
+      iter.Advance(aData, iter.RemainingInSegment());
+    }
     RefPtr<SharedJSAllocatedData> sharedData =
-      AllocateForExternalData(aDataLength);
-    memcpy(sharedData->Data(), aData, aDataLength);
+      new SharedJSAllocatedData(Move(buf));
     return sharedData.forget();
   }
 
   NS_INLINE_DECL_REFCOUNTING(SharedJSAllocatedData)
 
-  uint64_t* Data() const { return mData; }
-  size_t DataLength() const { return mDataLength; }
+  JSStructuredCloneData& Data() { return mData; }
+  size_t DataLength() const { return mData.Size(); }
 
 private:
-  ~SharedJSAllocatedData()
-  {
-    js_free(mData);
-  }
+  ~SharedJSAllocatedData() { }
 
-  static uint64_t*
-  Allocate64bitSafely(size_t aSize)
-  {
-    // Structured cloning requires 64-bit aligment.
-    return static_cast<uint64_t*>(js_malloc(std::max(sizeof(uint64_t), aSize)));
-  }
-
-  uint64_t* mData;
-  size_t mDataLength;
+  JSStructuredCloneData mData;
 };
 
 class StructuredCloneData : public StructuredCloneHolder
 {
 public:
   StructuredCloneData()
     : StructuredCloneHolder(StructuredCloneHolder::CloningSupported,
                             StructuredCloneHolder::TransferringSupported,
                             StructuredCloneHolder::StructuredCloneScope::DifferentProcess)
-    , mExternalData(nullptr)
-    , mExternalDataLength(0)
+    , mInitialized(false)
   {}
 
   StructuredCloneData(const StructuredCloneData&) = delete;
 
+  StructuredCloneData(StructuredCloneData&& aOther) = default;
+
   ~StructuredCloneData()
-  {
-    MOZ_ASSERT(!(mExternalData && mSharedData));
-  }
+  {}
 
   StructuredCloneData&
   operator=(const StructuredCloneData& aOther) = delete;
 
+  StructuredCloneData&
+  operator=(StructuredCloneData&& aOther) = default;
+
   const nsTArray<RefPtr<BlobImpl>>& BlobImpls() const
   {
     return mBlobImplArray;
   }
 
   nsTArray<RefPtr<BlobImpl>>& BlobImpls()
   {
     return mBlobImplArray;
@@ -115,48 +106,55 @@ public:
              JS::Handle<JS::Value> aValue,
              ErrorResult &aRv);
 
   void Write(JSContext* aCx,
              JS::Handle<JS::Value> aValue,
              JS::Handle<JS::Value> aTransfers,
              ErrorResult &aRv);
 
-  void UseExternalData(uint64_t* aData, size_t aDataLength)
+  bool UseExternalData(const JSStructuredCloneData& aData)
   {
-    MOZ_ASSERT(!Data());
-    mExternalData = aData;
-    mExternalDataLength = aDataLength;
+    auto iter = aData.Iter();
+    bool success = false;
+    mExternalData =
+      aData.Borrow<js::SystemAllocPolicy>(iter, aData.Size(), &success);
+    mInitialized = true;
+    return success;
   }
 
-  bool CopyExternalData(const void* aData, size_t aDataLength);
+  bool CopyExternalData(const char* aData, size_t aDataLength);
 
-  uint64_t* Data() const
+  JSStructuredCloneData& Data()
+  {
+    return mSharedData ? mSharedData->Data() : mExternalData;
+  }
+
+  const JSStructuredCloneData& Data() const
   {
     return mSharedData ? mSharedData->Data() : mExternalData;
   }
 
   size_t DataLength() const
   {
-    return mSharedData ? mSharedData->DataLength() : mExternalDataLength;
+    return mSharedData ? mSharedData->DataLength() : mExternalData.Size();
   }
 
   SharedJSAllocatedData* SharedData() const
   {
     return mSharedData;
   }
 
   // For IPC serialization
   void WriteIPCParams(IPC::Message* aMessage) const;
   bool ReadIPCParams(const IPC::Message* aMessage, PickleIterator* aIter);
 
 private:
-  uint64_t* MOZ_NON_OWNING_REF mExternalData;
-  size_t mExternalDataLength;
-
+  JSStructuredCloneData mExternalData;
   RefPtr<SharedJSAllocatedData> mSharedData;
+  bool mInitialized;
 };
 
 } // namespace ipc
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_ipc_StructuredCloneData_h
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -78,17 +78,16 @@
 #include "nsIWebBrowserSetup.h"
 #include "nsIWebProgress.h"
 #include "nsIXULRuntime.h"
 #include "nsPIDOMWindow.h"
 #include "nsPIWindowRoot.h"
 #include "nsLayoutUtils.h"
 #include "nsPrintfCString.h"
 #include "nsThreadUtils.h"
-#include "nsViewManager.h"
 #include "nsWeakReference.h"
 #include "nsWindowWatcher.h"
 #include "PermissionMessageUtils.h"
 #include "PuppetWidget.h"
 #include "StructuredCloneData.h"
 #include "nsViewportInfo.h"
 #include "nsILoadContext.h"
 #include "ipc/nsGUIEventIPC.h"
@@ -540,17 +539,16 @@ TabChild::TabChild(nsIContentChild* aMan
   , mDPI(0)
   , mDefaultScale(0)
   , mIsTransparent(false)
   , mIPCOpen(true)
   , mParentIsActive(false)
   , mDidSetRealShowInfo(false)
   , mDidLoadURLInit(false)
   , mAPZChild(nullptr)
-  , mLayerObserverEpoch(0)
 {
   // In the general case having the TabParent tell us if APZ is enabled or not
   // doesn't really work because the TabParent itself may not have a reference
   // to the owning widget during initialization. Instead we assume that this
   // TabChild corresponds to a widget type that would have APZ enabled, and just
   // check the other conditions necessary for enabling APZ.
   mAsyncPanZoomEnabled = gfxPlatform::AsyncPanZoomEnabled();
 
@@ -2580,85 +2578,29 @@ TabChild::RecvSetUpdateHitRegion(const b
     RefPtr<nsPresContext> presContext = presShell->GetPresContext();
     NS_ENSURE_TRUE(presContext, true);
     presContext->InvalidatePaintedLayers();
 
     return true;
 }
 
 bool
-TabChild::RecvSetDocShellIsActive(const bool& aIsActive, const bool& aIsHidden, const uint64_t& aLayerObserverEpoch)
+TabChild::RecvSetDocShellIsActive(const bool& aIsActive, const bool& aIsHidden)
 {
-  // Since SetDocShellIsActive requests come in from both the hang monitor
-  // channel and the PContent channel, we have an ordering problem. This code
-  // ensures that we respect the order in which the requests were made and
-  // ignore stale requests.
-  if (mLayerObserverEpoch > aLayerObserverEpoch) {
-    return true;
-  }
-  mLayerObserverEpoch = aLayerObserverEpoch;
-
-  if (aIsActive && WebWidget()->IsVisible()) {
-    // This request is a no-op. In this case, we still want a MozLayerTreeReady
-    // notification to fire in the parent (so that it knows that the child has
-    // updated its epoch). ForcePaintNoOp does that.
-    if (IPCOpen()) {
-      Unused << SendForcePaintNoOp(aLayerObserverEpoch);
+    // docshell is consider prerendered only if not active yet
+    mIsPrerendered &= !aIsActive;
+    nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation());
+    if (docShell) {
+      if (aIsHidden) {
+        docShell->SetIsActive(aIsActive);
+      } else {
+        docShell->SetIsActiveAndForeground(aIsActive);
+      }
     }
     return true;
-  }
-
-  MOZ_ASSERT(mPuppetWidget);
-  MOZ_ASSERT(mPuppetWidget->GetLayerManager());
-  MOZ_ASSERT(mPuppetWidget->GetLayerManager()->GetBackendType() ==
-             LayersBackend::LAYERS_CLIENT);
-
-  // We send the current layer observer epoch to the compositor so that
-  // TabParent knows whether a layer update notification corresponds to the
-  // latest SetDocShellIsActive request that was made.
-  ClientLayerManager *manager = mPuppetWidget->GetLayerManager()->AsClientLayerManager();
-  manager->SetLayerObserverEpoch(aLayerObserverEpoch);
-
-  // docshell is consider prerendered only if not active yet
-  mIsPrerendered &= !aIsActive;
-  nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation());
-  if (docShell) {
-    if (aIsHidden) {
-      docShell->SetIsActive(aIsActive);
-    } else {
-      docShell->SetIsActiveAndForeground(aIsActive);
-    }
-  }
-
-  if (aIsActive) {
-    // We don't use GetPresShell() here because that would create a content viewer
-    // if one doesn't exist yet. Creating a content viewer can cause JS to run,
-    // which we want to avoid. nsIDocShell::GetPresShell returns null if no
-    // content viewer exists yet.
-    if (nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell()) {
-      // If we need to repaint, let's do that right away. No sense waiting until
-      // we get back to the event loop again. We suppress the display port so that
-      // we only paint what's visible. This ensures that the tab we're switching
-      // to paints as quickly as possible.
-      APZCCallbackHelper::SuppressDisplayport(true, presShell);
-      if (nsContentUtils::IsSafeToRunScript()) {
-        WebWidget()->PaintNowIfNeeded();
-      } else {
-        RefPtr<nsViewManager> vm = presShell->GetViewManager();
-        if (nsView* view = vm->GetRootView()) {
-          presShell->Paint(view, view->GetBounds(),
-                           nsIPresShell::PAINT_LAYERS |
-                           nsIPresShell::PAINT_SYNC_DECODE_IMAGES);
-        }
-      }
-      APZCCallbackHelper::SuppressDisplayport(false, presShell);
-    }
-  }
-
-  return true;
 }
 
 bool
 TabChild::RecvNavigateByKey(const bool& aForward, const bool& aForDocumentNavigation)
 {
   nsIFocusManager* fm = nsFocusManager::GetFocusManager();
   if (fm) {
     nsCOMPtr<nsIDOMElement> result;
@@ -2869,32 +2811,24 @@ TabChild::NotifyPainted()
         mRemoteFrame->SendNotifyCompositorTransaction();
         mNotified = true;
     }
 }
 
 void
 TabChild::MakeVisible()
 {
-  if (mPuppetWidget && mPuppetWidget->IsVisible()) {
-    return;
-  }
-
   if (mPuppetWidget) {
     mPuppetWidget->Show(true);
   }
 }
 
 void
 TabChild::MakeHidden()
 {
-  if (mPuppetWidget && !mPuppetWidget->IsVisible()) {
-    return;
-  }
-
   CompositorBridgeChild* compositor = CompositorBridgeChild::Get();
 
   // Clear cached resources directly. This avoids one extra IPC
   // round-trip from CompositorBridgeChild to CompositorBridgeParent.
   compositor->RecvClearCachedResources(mLayersId);
 
   if (mPuppetWidget) {
     mPuppetWidget->Show(false);
@@ -3243,23 +3177,16 @@ TabChild::GetInnerSize()
 ScreenIntRect
 TabChild::GetOuterRect()
 {
   LayoutDeviceIntRect outerRect =
     RoundedToInt(mUnscaledOuterRect * mPuppetWidget->GetDefaultScale());
   return ViewAs<ScreenPixel>(outerRect, PixelCastJustification::LayoutDeviceIsScreenForTabDims);
 }
 
-void
-TabChild::ForcePaint(uint64_t aLayerObserverEpoch)
-{
-  nsAutoScriptBlocker scriptBlocker;
-  RecvSetDocShellIsActive(true, false, aLayerObserverEpoch);
-}
-
 TabChildGlobal::TabChildGlobal(TabChildBase* aTabChild)
 : mTabChild(aTabChild)
 {
   SetIsNotDOMBinding();
 }
 
 TabChildGlobal::~TabChildGlobal()
 {
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -641,33 +641,29 @@ public:
                   const CSSRect& aRect,
                   const uint32_t& aFlags);
 
   void SetAPZChild(layers::APZChild* aAPZChild)
   {
       mAPZChild = aAPZChild;
   }
 
-  // Request that the docshell be marked as active.
-  void ForcePaint(uint64_t aLayerObserverEpoch);
-
 protected:
   virtual ~TabChild();
 
   virtual PRenderFrameChild* AllocPRenderFrameChild() override;
 
   virtual bool DeallocPRenderFrameChild(PRenderFrameChild* aFrame) override;
 
   virtual bool RecvDestroy() override;
 
   virtual bool RecvSetUpdateHitRegion(const bool& aEnabled) override;
 
   virtual bool RecvSetDocShellIsActive(const bool& aIsActive,
-                                       const bool& aIsHidden,
-                                       const uint64_t& aLayerObserverEpoch) override;
+                                       const bool& aIsHidden) override;
 
   virtual bool RecvNavigateByKey(const bool& aForward,
                                  const bool& aForDocumentNavigation) override;
 
   virtual bool RecvRequestNotifyAfterRemotePaint() override;
 
   virtual bool RecvSuppressDisplayport(const bool& aEnabled) override;
 
@@ -777,18 +773,15 @@ private:
   bool mDidLoadURLInit;
 
   AutoTArray<bool, NUMBER_OF_AUDIO_CHANNELS> mAudioChannelsActive;
 
   // APZChild clears this pointer from its destructor, so it shouldn't be a
   // dangling pointer.
   layers::APZChild* mAPZChild;
 
-  // The most recently seen layer observer epoch in RecvSetDocShellIsActive.
-  uint64_t mLayerObserverEpoch;
-
   DISALLOW_EVIL_CONSTRUCTORS(TabChild);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_TabChild_h
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -287,23 +287,23 @@ TabParent::TabParent(nsIContentParent* a
   , mAppPackageFileDescriptorSent(false)
   , mSendOfflineStatus(true)
   , mChromeFlags(aChromeFlags)
   , mDragAreaX(0)
   , mDragAreaY(0)
   , mInitedByParent(false)
   , mTabId(aTabId)
   , mCreatingWindow(false)
+  , mNeedLayerTreeReadyNotification(false)
   , mCursor(nsCursor(-1))
   , mTabSetsCursor(false)
   , mHasContentOpener(false)
 #ifdef DEBUG
   , mActiveSupressDisplayportCount(0)
 #endif
-  , mLayerTreeEpoch(0)
 {
   MOZ_ASSERT(aManager);
 }
 
 TabParent::~TabParent()
 {
 }
 
@@ -488,16 +488,23 @@ TabParent::DestroyInternal()
   // If this fails, it's most likely due to a content-process crash,
   // and auto-cleanup will kick in.  Otherwise, the child side will
   // destroy itself and send back __delete__().
   Unused << SendDestroy();
 
   if (RenderFrameParent* frame = GetRenderFrame()) {
     RemoveTabParentFromTable(frame->GetLayersId());
     frame->Destroy();
+
+    // Notify our layer tree update observer that we're going away. It's
+    // possible that we race with a notification and there can be an
+    // LayerTreeUpdateRunnable on the main thread's event queue with a pointer
+    // to us. However, our actual destruction won't be until yet another event
+    // *after* that one is processed, so this should be safe.
+    mLayerUpdateObserver->TabParentDestroyed();
   }
 
   // Let all PluginWidgets know we are tearing down. Prevents
   // these objects from sending async events after the child side
   // is shut down.
   const ManagedContainer<PPluginWidgetParent>& kids =
     ManagedPPluginWidgetParent();
   for (auto iter = kids.ConstIter(); !iter.Done(); iter.Next()) {
@@ -2268,20 +2275,22 @@ TabParent::GetTabIdFrom(nsIDocShell *doc
     return static_cast<TabChild*>(tabChild.get())->GetTabId();
   }
   return TabId(0);
 }
 
 RenderFrameParent*
 TabParent::GetRenderFrame()
 {
+  if (!mLayerUpdateObserver) {
+    mLayerUpdateObserver = new LayerTreeUpdateObserver(this);
+  }
+
   PRenderFrameParent* p = LoneManagedOrNullAsserts(ManagedPRenderFrameParent());
-  RenderFrameParent* frame = static_cast<RenderFrameParent*>(p);
-
-  return frame;
+  return static_cast<RenderFrameParent*>(p);
 }
 
 bool
 TabParent::RecvRequestIMEToCommitComposition(const bool& aCancel,
                                              bool* aIsCommitted,
                                              nsString* aCommittedString)
 {
   nsCOMPtr<nsIWidget> widget = GetWidget();
@@ -2622,16 +2631,21 @@ TabParent::SetRenderFrame(PRenderFramePa
   bool success = renderFrame->Init(frameLoader);
   if (!success) {
     return false;
   }
 
   uint64_t layersId = renderFrame->GetLayersId();
   AddTabParentToTable(layersId, this);
 
+  if (mNeedLayerTreeReadyNotification) {
+    RequestNotifyLayerTreeReady();
+    mNeedLayerTreeReadyNotification = false;
+  }
+
   return true;
 }
 
 bool
 TabParent::GetRenderFrameInfo(TextureFactoryIdentifier* aTextureFactoryIdentifier,
                               uint64_t* aLayersId)
 {
   RenderFrameParent* rfp = GetRenderFrame();
@@ -2866,32 +2880,20 @@ TabParent::GetUseAsyncPanZoom(bool* useA
   *useAsyncPanZoom = AsyncPanZoomEnabled();
   return NS_OK;
 }
 
 // defined in nsITabParent
 NS_IMETHODIMP
 TabParent::SetDocShellIsActive(bool isActive)
 {
-  // Increment the epoch so that layer tree updates from previous
-  // SetDocShellIsActive requests are ignored.
-  mLayerTreeEpoch++;
-
   // docshell is consider prerendered only if not active yet
   mIsPrerendered &= !isActive;
   mDocShellIsActive = isActive;
-  Unused << SendSetDocShellIsActive(isActive, true, mLayerTreeEpoch);
-
-  // Ask the child to repaint using the PHangMonitor channel/thread (which may
-  // be less congested).
-  if (isActive) {
-    ContentParent* cp = Manager()->AsContentParent();
-    cp->ForceTabPaint(this, mLayerTreeEpoch);
-  }
-
+  Unused << SendSetDocShellIsActive(isActive, true);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TabParent::GetDocShellIsActive(bool* aIsActive)
 {
   *aIsActive = mDocShellIsActive;
   return NS_OK;
@@ -2905,17 +2907,17 @@ TabParent::GetIsPrerendered(bool* aIsPre
 }
 
 NS_IMETHODIMP
 TabParent::SetDocShellIsActiveAndForeground(bool isActive)
 {
   // docshell is consider prerendered only if not active yet
   mIsPrerendered &= !isActive;
   mDocShellIsActive = isActive;
-  Unused << SendSetDocShellIsActive(isActive, false, mLayerTreeEpoch);
+  Unused << SendSetDocShellIsActive(isActive, false);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TabParent::SuppressDisplayport(bool aEnabled)
 {
   if (IsDestroyed()) {
     return NS_OK;
@@ -2966,83 +2968,121 @@ TabParent::NavigateByKey(bool aForward, 
 {
   Unused << SendNavigateByKey(aForward, aForDocumentNavigation);
   return NS_OK;
 }
 
 class LayerTreeUpdateRunnable final
   : public mozilla::Runnable
 {
-  uint64_t mLayersId;
-  uint64_t mEpoch;
+  RefPtr<LayerTreeUpdateObserver> mUpdateObserver;
   bool mActive;
 
 public:
-  explicit LayerTreeUpdateRunnable(uint64_t aLayersId, uint64_t aEpoch, bool aActive)
-    : mLayersId(aLayersId), mEpoch(aEpoch), mActive(aActive)
+  explicit LayerTreeUpdateRunnable(LayerTreeUpdateObserver* aObs, bool aActive)
+    : mUpdateObserver(aObs), mActive(aActive)
   {
     MOZ_ASSERT(!NS_IsMainThread());
   }
 
 private:
   NS_IMETHOD Run() override {
     MOZ_ASSERT(NS_IsMainThread());
-    if (RefPtr<TabParent> tabParent = TabParent::GetTabParentFromLayersId(mLayersId)) {
-      tabParent->LayerTreeUpdate(mEpoch, mActive);
+    if (RefPtr<TabParent> tabParent = mUpdateObserver->GetTabParent()) {
+      tabParent->LayerTreeUpdate(mActive);
     }
     return NS_OK;
   }
 };
 
-/* static */ void
-TabParent::ObserveLayerUpdate(uint64_t aLayersId, uint64_t aEpoch, bool aActive)
+void
+LayerTreeUpdateObserver::ObserveUpdate(uint64_t aLayersId, bool aActive)
 {
   MOZ_ASSERT(!NS_IsMainThread());
 
   RefPtr<LayerTreeUpdateRunnable> runnable =
-    new LayerTreeUpdateRunnable(aLayersId, aEpoch, aActive);
+    new LayerTreeUpdateRunnable(this, aActive);
   NS_DispatchToMainThread(runnable);
 }
 
-void
-TabParent::LayerTreeUpdate(uint64_t aEpoch, bool aActive)
+
+bool
+TabParent::RequestNotifyLayerTreeReady()
 {
-  // Ignore updates from old epochs. They might tell us that layers are
-  // available when we've already sent a message to clear them. We can't trust
-  // the update in that case since layers could disappear anytime after that.
-  if (aEpoch != mLayerTreeEpoch || mIsDestroyed) {
-    return;
+  RenderFrameParent* frame = GetRenderFrame();
+  if (!frame || !frame->IsInitted()) {
+    mNeedLayerTreeReadyNotification = true;
+  } else {
+    GPUProcessManager::Get()->RequestNotifyLayerTreeReady(
+      frame->GetLayersId(),
+      mLayerUpdateObserver);
   }
-
+  return true;
+}
+
+bool
+TabParent::RequestNotifyLayerTreeCleared()
+{
+  RenderFrameParent* frame = GetRenderFrame();
+  if (!frame) {
+    return false;
+  }
+
+  GPUProcessManager::Get()->RequestNotifyLayerTreeCleared(
+    frame->GetLayersId(),
+    mLayerUpdateObserver);
+  return true;
+}
+
+bool
+TabParent::LayerTreeUpdate(bool aActive)
+{
   nsCOMPtr<mozilla::dom::EventTarget> target = do_QueryInterface(mFrameElement);
   if (!target) {
     NS_WARNING("Could not locate target for layer tree message.");
-    return;
+    return true;
   }
 
   RefPtr<Event> event = NS_NewDOMEvent(mFrameElement, nullptr, nullptr);
   if (aActive) {
     event->InitEvent(NS_LITERAL_STRING("MozLayerTreeReady"), true, false);
   } else {
     event->InitEvent(NS_LITERAL_STRING("MozLayerTreeCleared"), true, false);
   }
   event->SetTrusted(true);
   event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
   bool dummy;
   mFrameElement->DispatchEvent(event, &dummy);
+  return true;
 }
 
-bool
-TabParent::RecvForcePaintNoOp(const uint64_t& aLayerObserverEpoch)
+void
+TabParent::SwapLayerTreeObservers(TabParent* aOther)
 {
-  // We sent a ForcePaint message when layers were already visible. In this
-  // case, we should act as if an update occurred even though we already have
-  // the layers.
-  LayerTreeUpdate(aLayerObserverEpoch, true);
-  return true;
+  if (IsDestroyed() || aOther->IsDestroyed()) {
+    return;
+  }
+
+  RenderFrameParent* rfp = GetRenderFrame();
+  RenderFrameParent* otherRfp = aOther->GetRenderFrame();
+  if (!rfp || !otherRfp) {
+    return;
+  }
+
+  // The swap that happens for the observers in GPUProcessManager has to
+  // happen in a lock so that an update being processed on the compositor thread
+  // can't grab the layer update observer for the wrong tab parent.
+  GPUProcessManager::Get()->SwapLayerTreeObservers(
+    rfp->GetLayersId(),
+    otherRfp->GetLayersId());
+
+  // No need for a lock, destruction can only happen on the main thread and we
+  // only read mLayerUpdateObserver::mTabParent on the main thread.
+  Swap(mLayerUpdateObserver, aOther->mLayerUpdateObserver);
+  mLayerUpdateObserver->SwapTabParent(aOther->mLayerUpdateObserver);
 }
 
 bool
 TabParent::RecvRemotePaintIsReady()
 {
   nsCOMPtr<mozilla::dom::EventTarget> target = do_QueryInterface(mFrameElement);
   if (!target) {
     NS_WARNING("Could not locate target for MozAfterRemotePaint message.");
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -75,16 +75,49 @@ class ClonedMessageData;
 class nsIContentParent;
 class Element;
 class DataTransfer;
 
 namespace ipc {
 class StructuredCloneData;
 } // ipc namespace
 
+// This observer runs on the compositor thread, so we dispatch a runnable to the
+// main thread to actually dispatch the event.
+class LayerTreeUpdateObserver : public layers::CompositorUpdateObserver
+{
+public:
+  explicit LayerTreeUpdateObserver(TabParent* aTabParent)
+    : mTabParent(aTabParent)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+  }
+
+  virtual void ObserveUpdate(uint64_t aLayersId, bool aActive) override;
+
+  virtual void SwapTabParent(LayerTreeUpdateObserver* aOther) {
+    MOZ_ASSERT(NS_IsMainThread());
+    Swap(mTabParent, aOther->mTabParent);
+  }
+
+  void TabParentDestroyed() {
+    MOZ_ASSERT(NS_IsMainThread());
+    mTabParent = nullptr;
+  }
+
+  TabParent* GetTabParent() {
+    MOZ_ASSERT(NS_IsMainThread());
+    return mTabParent;
+  }
+
+private:
+  // NB: Should never be touched off the main thread!
+  TabParent* mTabParent;
+};
+
 class TabParent final : public PBrowserParent
                       , public nsIDOMEventListener
                       , public nsITabParent
                       , public nsIAuthPromptProvider
                       , public nsISecureBrowserUI
                       , public nsIKeyEventInPluginCallback
                       , public nsSupportsWeakReference
                       , public TabContext
@@ -549,18 +582,24 @@ public:
 
   bool IsInitedByParent() const { return mInitedByParent; }
 
   static TabParent* GetNextTabParent();
 
   bool SendLoadRemoteScript(const nsString& aURL,
                             const bool& aRunInGlobalScope);
 
-  static void ObserveLayerUpdate(uint64_t aLayersId, uint64_t aEpoch, bool aActive);
-  void LayerTreeUpdate(uint64_t aEpoch, bool aActive);
+  // See nsIFrameLoader requestNotifyLayerTreeReady.
+  bool RequestNotifyLayerTreeReady();
+
+  bool RequestNotifyLayerTreeCleared();
+
+  bool LayerTreeUpdate(bool aActive);
+
+  void SwapLayerTreeObservers(TabParent* aOther);
 
   virtual bool
   RecvInvokeDragSession(nsTArray<IPCDataTransfer>&& aTransfers,