Bug 1067145 - Make responsive design e10s-ready. r=ochameau
authorPaul Rouget <paul@mozilla.com>
Tue, 20 Jan 2015 00:57:00 -0500
changeset 251920 ae9cc9b5f5dda503e877a55b54ad1ca40484a18e
parent 251919 a242901a534718f9ff7f95b953144264456ad321
child 251921 f526a385989aa1d31b4a677fd412b461389f1020
push id4610
push userjlund@mozilla.com
push dateMon, 30 Mar 2015 18:32:55 +0000
treeherdermozilla-beta@4df54044d9ef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersochameau
bugs1067145
milestone38.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1067145 - Make responsive design e10s-ready. r=ochameau
b2g/chrome/content/desktop.js
b2g/chrome/content/screen.js
browser/devtools/responsivedesign/moz.build
browser/devtools/responsivedesign/responsivedesign-child.js
browser/devtools/responsivedesign/responsivedesign.jsm
browser/devtools/responsivedesign/test/browser.ini
browser/devtools/responsivedesign/test/browser_responsive_cmd.js
browser/devtools/responsivedesign/test/browser_responsive_devicewidth.js
browser/devtools/responsivedesign/test/browser_responsivecomputedview.js
browser/devtools/responsivedesign/test/browser_responsiveruleview.js
browser/devtools/responsivedesign/test/browser_responsiveui.js
browser/devtools/responsivedesign/test/browser_responsiveui_touch.js
browser/devtools/responsivedesign/test/browser_responsiveuiaddcustompreset.js
browser/devtools/responsivedesign/test/head.js
browser/devtools/shared/FloatingScrollbars.jsm
browser/devtools/shared/moz.build
--- a/b2g/chrome/content/desktop.js
+++ b/b2g/chrome/content/desktop.js
@@ -112,17 +112,17 @@ function checkDebuggerPort() {
       {'debugger.remote-mode': 'adb-devtools'});
   }
 }
 
 
 function initResponsiveDesign() {
   Cu.import('resource:///modules/devtools/responsivedesign.jsm');
   ResponsiveUIManager.on('on', function(event, {tab:tab}) {
-    let responsive = tab.__responsiveUI;
+    let responsive = ResponsiveUIManager.getResponsiveUIForTab(tab);
     let document = tab.ownerDocument;
 
     // Only tweak reponsive mode for shell.html tabs.
     if (tab.linkedBrowser.contentWindow != window) {
       return;
     }
 
     // Disable transition as they mess up with screen size handler
@@ -132,17 +132,17 @@ function initResponsiveDesign() {
 
     responsive.rotatebutton.addEventListener('command', function (evt) {
       GlobalSimulatorScreen.flipScreen();
       evt.stopImmediatePropagation();
       evt.preventDefault();
     }, true);
 
     // Enable touch events
-    browserWindow.gBrowser.selectedTab.__responsiveUI.enableTouch();
+    responsive.enableTouch();
   });
 
   // Automatically toggle responsive design mode
   let width = 320, height = 480;
   // We have to take into account padding and border introduced with the
   // device look'n feel:
   width += 15*2; // Horizontal padding
   width += 1*2; // Vertical border
--- a/b2g/chrome/content/screen.js
+++ b/b2g/chrome/content/screen.js
@@ -143,17 +143,18 @@ window.addEventListener('ContentStart', 
     let controls = document.getElementById('controls');
     let controlsHeight = 0;
     if (controls) {
       controlsHeight = controls.getBoundingClientRect().height;
     }
     let chromewidth = window.outerWidth - window.innerWidth;
     let chromeheight = window.outerHeight - window.innerHeight + controlsHeight;
     if (isMulet) {
-      let responsive = browserWindow.gBrowser.selectedTab.__responsiveUI;
+      let tab = browserWindow.gBrowser.selectedTab;
+      let responsive = ResponsiveUIManager.getResponsiveUIForTab(tab);
       responsive.setSize((Math.round(width * scale) + 16*2),
                         (Math.round(height * scale) + controlsHeight + 61));
     } else {
       window.resizeTo(Math.round(width * scale) + chromewidth,
                       Math.round(height * scale) + chromeheight);
     }
 
     let frameWidth = width, frameHeight = height;
--- a/browser/devtools/responsivedesign/moz.build
+++ b/browser/devtools/responsivedesign/moz.build
@@ -1,10 +1,11 @@
 # 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/.
 
 EXTRA_JS_MODULES.devtools += [
     'resize-commands.js',
-    'responsivedesign.jsm',
+    'responsivedesign-child.js',
+    'responsivedesign.jsm'
 ]
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
new file mode 100644
--- /dev/null
+++ b/browser/devtools/responsivedesign/responsivedesign-child.js
@@ -0,0 +1,129 @@
+/* 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/. */
+
+const Ci = Components.interfaces;
+const gDeviceSizeWasPageSize = docShell.deviceSizeIsPageSize;
+const gFloatingScrollbarsStylesheet = Services.io.newURI("chrome://browser/skin/devtools/floating-scrollbars.css", null, null);
+let gRequiresFloatingScrollbars;
+
+let active = false;
+
+addMessageListener("ResponsiveMode:Start", startResponsiveMode);
+addMessageListener("ResponsiveMode:Stop", stopResponsiveMode);
+
+function startResponsiveMode({data:data}) {
+  if (active) {
+    return;
+  }
+  addMessageListener("ResponsiveMode:RequestScreenshot", screenshot);
+  addMessageListener("ResponsiveMode:NotifyOnResize", notifiyOnResize);
+  let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebProgress);
+  webProgress.addProgressListener(WebProgressListener, Ci.nsIWebProgress.NOTIFY_ALL);
+  docShell.deviceSizeIsPageSize = true;
+  gRequiresFloatingScrollbars = data.requiresFloatingScrollbars;
+
+  // At this point, a content viewer might not be loaded for this
+  // docshell. makeScrollbarsFloating will be triggered by onLocationChange.
+  if (docShell.contentViewer) {
+    makeScrollbarsFloating();
+  }
+  active = true;
+  sendAsyncMessage("ResponsiveMode:Start:Done");
+}
+
+function notifiyOnResize() {
+  content.addEventListener("resize", () => {
+    sendAsyncMessage("ResponsiveMode:OnContentResize");
+  }, false);
+  sendAsyncMessage("ResponsiveMode:NotifyOnResize:Done");
+}
+
+function stopResponsiveMode() {
+  if (!active) {
+    return;
+  }
+  active = false;
+  removeMessageListener("ResponsiveMode:RequestScreenshot", screenshot);
+  removeMessageListener("ResponsiveMode:NotifyOnResize", notifiyOnResize);
+  let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebProgress);
+  webProgress.removeProgressListener(WebProgressListener);
+  docShell.deviceSizeIsPageSize = gDeviceSizeWasPageSize;
+  restoreScrollbars();
+  sendAsyncMessage("ResponsiveMode:Stop:Done");
+}
+
+function makeScrollbarsFloating() {
+  if (!gRequiresFloatingScrollbars) {
+    return;
+  }
+
+  let allDocShells = [docShell];
+
+  for (let i = 0; i < docShell.childCount; i++) {
+    let child = docShell.getChildAt(i).QueryInterface(Ci.nsIDocShell);
+    allDocShells.push(child);
+  }
+
+  for (let d of allDocShells) {
+    let win = d.contentViewer.DOMDocument.defaultView;
+    let winUtils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
+    try {
+      winUtils.loadSheet(gFloatingScrollbarsStylesheet, win.AGENT_SHEET);
+    } catch(e) { }
+  }
+
+  flushStyle();
+}
+
+function restoreScrollbars() {
+  let allDocShells = [docShell];
+  for (let i = 0; i < docShell.childCount; i++) {
+    allDocShells.push(docShell.getChildAt(i).QueryInterface(Ci.nsIDocShell));
+  }
+  for (let d of allDocShells) {
+    let win = d.contentViewer.DOMDocument.defaultView;
+    let winUtils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
+    try {
+      winUtils.removeSheet(gFloatingScrollbarsStylesheet, win.AGENT_SHEET);
+    } catch(e) { }
+  }
+  flushStyle();
+}
+
+function flushStyle() {
+  // Force presContext destruction
+  let isSticky = docShell.contentViewer.sticky;
+  docShell.contentViewer.sticky = false;
+  docShell.contentViewer.hide();
+  docShell.contentViewer.show();
+  docShell.contentViewer.sticky = isSticky;
+}
+
+function screenshot() {
+  let canvas = content.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
+  let width = content.innerWidth;
+  let height = content.innerHeight;
+  canvas.mozOpaque = true;
+  canvas.width = width;
+  canvas.height = height;
+  let ctx = canvas.getContext("2d");
+  ctx.drawWindow(content, content.scrollX, content.scrollY, width, height, "#fff");
+  sendAsyncMessage("ResponsiveMode:RequestScreenshot:Done", canvas.toDataURL());
+}
+
+let WebProgressListener = {
+  onLocationChange: function onLocationChange(aWebProgress) {
+    makeScrollbarsFloating();
+  },
+  QueryInterface: function QueryInterface(aIID) {
+    if (aIID.equals(Ci.nsIWebProgressListener) ||
+        aIID.equals(Ci.nsISupportsWeakReference) ||
+        aIID.equals(Ci.nsISupports)) {
+        return this;
+    }
+    throw Components.results.NS_ERROR_NO_INTERFACE;
+  }
+};
+
+sendAsyncMessage("ResponsiveMode:ChildScriptReady");
--- a/browser/devtools/responsivedesign/responsivedesign.jsm
+++ b/browser/devtools/responsivedesign/responsivedesign.jsm
@@ -5,18 +5,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource:///modules/devtools/gDevTools.jsm");
-Cu.import("resource:///modules/devtools/FloatingScrollbars.jsm");
 Cu.import("resource://gre/modules/devtools/event-emitter.js");
+let { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
 XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
                                   "resource://gre/modules/SystemAppProxy.jsm");
 
 var require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 let Telemetry = require("devtools/shared/telemetry");
 let {showDoorhanger} = require("devtools/shared/doorhanger");
 let {TouchEventHandler} = require("devtools/touch-events");
 
@@ -28,66 +28,75 @@ const MIN_HEIGHT = 50;
 const MAX_WIDTH = 10000;
 const MAX_HEIGHT = 10000;
 
 const SLOW_RATIO = 6;
 const ROUND_RATIO = 10;
 
 const INPUT_PARSER = /(\d+)[^\d]+(\d+)/;
 
+let ActiveTabs = new Map();
+
 this.ResponsiveUIManager = {
   /**
    * Check if the a tab is in a responsive mode.
    * Leave the responsive mode if active,
    * active the responsive mode if not active.
    *
    * @param aWindow the main window.
    * @param aTab the tab targeted.
    */
   toggle: function(aWindow, aTab) {
-    if (aTab.__responsiveUI) {
-      aTab.__responsiveUI.close();
+    if (this.isActiveForTab(aTab)) {
+      ActiveTabs.get(aTab).close();
     } else {
       new ResponsiveUI(aWindow, aTab);
     }
   },
 
   /**
    * Returns true if responsive view is active for the provided tab.
    *
    * @param aTab the tab targeted.
    */
   isActiveForTab: function(aTab) {
-    return !!aTab.__responsiveUI;
+    return ActiveTabs.has(aTab);
+  },
+
+  /**
+   * Return the responsive UI controller for a tab.
+   */
+  getResponsiveUIForTab: function(aTab) {
+    return ActiveTabs.get(aTab);
   },
 
   /**
    * Handle gcli commands.
    *
    * @param aWindow the browser window.
    * @param aTab the tab targeted.
    * @param aCommand the command name.
    * @param aArgs command arguments.
    */
   handleGcliCommand: function(aWindow, aTab, aCommand, aArgs) {
     switch (aCommand) {
       case "resize to":
-        if (!aTab.__responsiveUI) {
+        if (!this.isActiveForTab(aTab)) {
           new ResponsiveUI(aWindow, aTab);
         }
-        aTab.__responsiveUI.setSize(aArgs.width, aArgs.height);
+        ActiveTabs.get(aTab).setSize(aArgs.width, aArgs.height);
         break;
       case "resize on":
-        if (!aTab.__responsiveUI) {
+        if (!this.isActiveForTab(aTab)) {
           new ResponsiveUI(aWindow, aTab);
         }
         break;
       case "resize off":
-        if (aTab.__responsiveUI) {
-          aTab.__responsiveUI.close();
+        if (this.isActiveForTab(aTab)) {
+          ActiveTabs.get(aTab).close();
         }
         break;
       case "resize toggle":
           this.toggle(aWindow, aTab);
       default:
     }
   }
 }
@@ -110,23 +119,38 @@ let presets = [
   {key: "1280x600", width: 1280, height: 600},
   {key: "1920x900", width: 1920, height: 900},
 ];
 
 function ResponsiveUI(aWindow, aTab)
 {
   this.mainWindow = aWindow;
   this.tab = aTab;
+  this.mm = this.tab.linkedBrowser.messageManager;
   this.tabContainer = aWindow.gBrowser.tabContainer;
   this.browser = aTab.linkedBrowser;
   this.chromeDoc = aWindow.document;
   this.container = aWindow.gBrowser.getBrowserContainer(this.browser);
   this.stack = this.container.querySelector(".browserStack");
   this._telemetry = new Telemetry();
-  this._floatingScrollbars = !this.mainWindow.matchMedia("(-moz-overlay-scrollbars)").matches;
+  this.e10s = !this.browser.contentWindow;
+
+  let childOn = () => {
+    this.mm.removeMessageListener("ResponsiveMode:Start:Done", childOn);
+    ResponsiveUIManager.emit("on", { tab: this.tab });
+  }
+  this.mm.addMessageListener("ResponsiveMode:Start:Done", childOn);
+
+  let requiresFloatingScrollbars = !this.mainWindow.matchMedia("(-moz-overlay-scrollbars)").matches;
+  this.mm.loadFrameScript("resource:///modules/devtools/responsivedesign-child.js", true);
+  this.mm.addMessageListener("ResponsiveMode:ChildScriptReady", () => {
+    this.mm.sendAsyncMessage("ResponsiveMode:Start", {
+      requiresFloatingScrollbars: requiresFloatingScrollbars
+    });
+  });
 
   // Try to load presets from prefs
   if (Services.prefs.prefHasUserValue("devtools.responsiveUI.presets")) {
     try {
       presets = JSON.parse(Services.prefs.getCharPref("devtools.responsiveUI.presets"));
     } catch(e) {
       // User pref is malformated.
       Cu.reportError("Could not parse pref `devtools.responsiveUI.presets`: " + e);
@@ -158,18 +182,16 @@ function ResponsiveUI(aWindow, aTab)
 
     this.currentPresetKey = this.presets[1].key; // most common preset
   }
 
   this.container.setAttribute("responsivemode", "true");
   this.stack.setAttribute("responsivemode", "true");
 
   // Let's bind some callbacks.
-  this.bound_onPageLoad = this.onPageLoad.bind(this);
-  this.bound_onPageUnload = this.onPageUnload.bind(this);
   this.bound_presetSelected = this.presetSelected.bind(this);
   this.bound_handleManualInput = this.handleManualInput.bind(this);
   this.bound_addPreset = this.addPreset.bind(this);
   this.bound_removePreset = this.removePreset.bind(this);
   this.bound_rotate = this.rotate.bind(this);
   this.bound_screenshot = () => this.screenshot();
   this.bound_touch = this.toggleTouch.bind(this);
   this.bound_close = this.close.bind(this);
@@ -179,51 +201,32 @@ function ResponsiveUI(aWindow, aTab)
 
   // Events
   this.tab.addEventListener("TabClose", this);
   this.tabContainer.addEventListener("TabSelect", this);
 
   this.buildUI();
   this.checkMenus();
 
-  this.docShell = this.browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
-                      .getInterface(Ci.nsIWebNavigation)
-                      .QueryInterface(Ci.nsIDocShell);
-
-  this._deviceSizeWasPageSize = this.docShell.deviceSizeIsPageSize;
-  this.docShell.deviceSizeIsPageSize = true;
-
   try {
     if (Services.prefs.getBoolPref("devtools.responsiveUI.rotate")) {
       this.rotate();
     }
   } catch(e) {}
 
-  if (this._floatingScrollbars)
-    switchToFloatingScrollbars(this.tab);
-
-  this.tab.__responsiveUI = this;
+  ActiveTabs.set(aTab, this);
 
   this._telemetry.toolOpened("responsive");
 
-  // Touch events support
-  this.touchEnableBefore = false;
-  this.touchEventHandler = new TouchEventHandler(this.browser);
-
-  this.browser.addEventListener("load", this.bound_onPageLoad, true);
-  this.browser.addEventListener("unload", this.bound_onPageUnload, true);
-
-  if (this.browser.contentWindow.document &&
-      this.browser.contentWindow.document.readyState == "complete") {
-    this.onPageLoad();
+  if (!this.e10s) {
+    // Touch events support
+    this.touchEnableBefore = false;
+    this.touchEventHandler = new TouchEventHandler(this.browser);
   }
 
-  // E10S: We should be using target here. See bug 1028234
-  ResponsiveUIManager.emit("on", { tab: this.tab });
-
   // Hook to display promotional Developer Edition doorhanger. Only displayed once.
   showDoorhanger({
     window: this.mainWindow,
     type: "deveditionpromo",
     anchor: this.chromeDoc.querySelector("#content")
   });
 }
 
@@ -235,54 +238,23 @@ ResponsiveUI.prototype = {
     if (aValue && !this._resizing && this.stack.hasAttribute("responsivemode")) {
       this.stack.removeAttribute("notransition");
     } else if (!aValue) {
       this.stack.setAttribute("notransition", "true");
     }
   },
 
   /**
-   * Window onload / onunload
-   */
-   onPageLoad: function() {
-     this.touchEventHandler = new TouchEventHandler(this.browser);
-     if (this.touchEnableBefore) {
-       this.enableTouch();
-     }
-   },
-
-   onPageUnload: function(evt) {
-     // Ignore sub frames unload events
-     if (evt.target != this.browser.contentDocument)
-       return;
-     if (this.closing)
-       return;
-     if (this.touchEventHandler) {
-       this.touchEnableBefore = this.touchEventHandler.enabled;
-       this.disableTouch();
-       delete this.touchEventHandler;
-     }
-   },
-
-  /**
    * Destroy the nodes. Remove listeners. Reset the style.
    */
-  close: function RUI_unload() {
+  close: function RUI_close() {
     if (this.closing)
       return;
     this.closing = true;
 
-    this.docShell.deviceSizeIsPageSize = this._deviceSizeWasPageSize;
-
-    this.browser.removeEventListener("load", this.bound_onPageLoad, true);
-    this.browser.removeEventListener("unload", this.bound_onPageUnload, true);
-
-    if (this._floatingScrollbars)
-      switchToNativeScrollbars(this.tab);
-
     this.unCheckMenus();
     // Reset style of the stack.
     let style = "max-width: none;" +
                 "min-width: 0;" +
                 "max-height: none;" +
                 "min-height: 0;";
     this.stack.setAttribute("style", style);
 
@@ -291,20 +263,22 @@ ResponsiveUI.prototype = {
 
     // Remove listeners.
     this.menulist.removeEventListener("select", this.bound_presetSelected, true);
     this.menulist.removeEventListener("change", this.bound_handleManualInput, true);
     this.tab.removeEventListener("TabClose", this);
     this.tabContainer.removeEventListener("TabSelect", this);
     this.rotatebutton.removeEventListener("command", this.bound_rotate, true);
     this.screenshotbutton.removeEventListener("command", this.bound_screenshot, true);
-    this.touchbutton.removeEventListener("command", this.bound_touch, true);
     this.closebutton.removeEventListener("command", this.bound_close, true);
     this.addbutton.removeEventListener("command", this.bound_addPreset, true);
     this.removebutton.removeEventListener("command", this.bound_removePreset, true);
+    if (!this.e10s) {
+      this.touchbutton.removeEventListener("command", this.bound_touch, true);
+    }
 
     // Removed elements.
     this.container.removeChild(this.toolbar);
     if (this.bottomToolbar) {
       this.bottomToolbar.remove();
       delete this.bottomToolbar;
     }
     this.stack.removeChild(this.resizer);
@@ -312,23 +286,50 @@ ResponsiveUI.prototype = {
     this.stack.removeChild(this.resizeBarH);
 
     this.stack.classList.remove("fxos-mode");
 
     // Unset the responsive mode.
     this.container.removeAttribute("responsivemode");
     this.stack.removeAttribute("responsivemode");
 
-    delete this.docShell;
-    delete this.tab.__responsiveUI;
-    if (this.touchEventHandler)
+    ActiveTabs.delete(this.tab);
+    if (!this.e10s && this.touchEventHandler) {
       this.touchEventHandler.stop();
+    }
     this._telemetry.toolClosed("responsive");
-    // E10S: We should be using target here. See bug 1028234
-    ResponsiveUIManager.emit("off", { tab: this.tab });
+    let childOff = () => {
+      this.mm.removeMessageListener("ResponsiveMode:Stop:Done", childOff);
+      ResponsiveUIManager.emit("off", { tab: this.tab });
+    }
+    this.mm.addMessageListener("ResponsiveMode:Stop:Done", childOff);
+    this.tab.linkedBrowser.messageManager.sendAsyncMessage("ResponsiveMode:Stop");
+  },
+
+  /**
+   * Notify when the content has been resized. Only used in tests.
+   */
+  _test_notifyOnResize: function() {
+    let deferred = promise.defer();
+    let mm = this.mm;
+
+    this.bound_onContentResize = this.onContentResize.bind(this);
+
+    mm.addMessageListener("ResponsiveMode:OnContentResize", this.bound_onContentResize);
+
+    mm.sendAsyncMessage("ResponsiveMode:NotifyOnResize");
+    mm.addMessageListener("ResponsiveMode:NotifyOnResize:Done", function onListeningResize() {
+      mm.removeMessageListener("ResponsiveMode:NotifyOnResize:Done", onListeningResize);
+      deferred.resolve();
+    });
+    return deferred.promise;
+  },
+
+  onContentResize: function() {
+    ResponsiveUIManager.emit("contentResize", { tab: this.tab });
   },
 
   /**
    * Handle events
    */
   handleEvent: function (aEvent) {
     switch (aEvent.type) {
       case "TabClose":
@@ -422,32 +423,35 @@ ResponsiveUI.prototype = {
     this.rotatebutton.addEventListener("command", this.bound_rotate, true);
 
     this.screenshotbutton = this.chromeDoc.createElement("toolbarbutton");
     this.screenshotbutton.setAttribute("tabindex", "0");
     this.screenshotbutton.setAttribute("tooltiptext", this.strings.GetStringFromName("responsiveUI.screenshot"));
     this.screenshotbutton.className = "devtools-responsiveui-toolbarbutton devtools-responsiveui-screenshot";
     this.screenshotbutton.addEventListener("command", this.bound_screenshot, true);
 
-    this.touchbutton = this.chromeDoc.createElement("toolbarbutton");
-    this.touchbutton.setAttribute("tabindex", "0");
-    this.touchbutton.setAttribute("tooltiptext", this.strings.GetStringFromName("responsiveUI.touch"));
-    this.touchbutton.className = "devtools-responsiveui-toolbarbutton devtools-responsiveui-touch";
-    this.touchbutton.addEventListener("command", this.bound_touch, true);
-
     this.closebutton = this.chromeDoc.createElement("toolbarbutton");
     this.closebutton.setAttribute("tabindex", "0");
     this.closebutton.className = "devtools-responsiveui-toolbarbutton devtools-responsiveui-close";
     this.closebutton.setAttribute("tooltiptext", this.strings.GetStringFromName("responsiveUI.close"));
     this.closebutton.addEventListener("command", this.bound_close, true);
 
     this.toolbar.appendChild(this.closebutton);
     this.toolbar.appendChild(this.menulist);
     this.toolbar.appendChild(this.rotatebutton);
-    this.toolbar.appendChild(this.touchbutton);
+
+    if (!this.e10s) {
+      this.touchbutton = this.chromeDoc.createElement("toolbarbutton");
+      this.touchbutton.setAttribute("tabindex", "0");
+      this.touchbutton.setAttribute("tooltiptext", this.strings.GetStringFromName("responsiveUI.touch"));
+      this.touchbutton.className = "devtools-responsiveui-toolbarbutton devtools-responsiveui-touch";
+      this.touchbutton.addEventListener("command", this.bound_touch, true);
+      this.toolbar.appendChild(this.touchbutton);
+    }
+
     this.toolbar.appendChild(this.screenshotbutton);
 
     // Resizers
     let resizerTooltip = this.strings.GetStringFromName("responsiveUI.resizerTooltip");
     this.resizer = this.chromeDoc.createElement("box");
     this.resizer.className = "devtools-responsiveui-resizehandle";
     this.resizer.setAttribute("right", "0");
     this.resizer.setAttribute("bottom", "0");
@@ -578,18 +582,19 @@ ResponsiveUI.prototype = {
       menuitem.setAttribute("ispreset", true);
       this.menuitems.set(menuitem, preset);
 
       if (preset.key === this.currentPresetKey) {
         menuitem.setAttribute("selected", "true");
         this.selectedItem = menuitem;
       }
 
-      if (preset.custom)
+      if (preset.custom) {
         this.customMenuitem = menuitem;
+      }
 
       this.setMenuLabel(menuitem, preset);
       fragment.appendChild(menuitem);
     }
     aParent.appendChild(fragment);
   },
 
   /**
@@ -657,19 +662,17 @@ ResponsiveUI.prototype = {
     let newName = {};
 
     let title = this.strings.GetStringFromName("responsiveUI.customNamePromptTitle");
     let message = this.strings.formatStringFromName("responsiveUI.customNamePromptMsg", [w, h], 2);
     let promptOk = Services.prompt.prompt(null, title, message, newName, null, {});
 
     if (!promptOk) {
       // Prompt has been cancelled
-      let menuitem = this.customMenuitem;
-      this.menulist.selectedItem = menuitem;
-      this.currentPresetKey = this.customPreset.key;
+      this.menulist.selectedItem = this.selectedItem;
       return;
     }
 
     let newPreset = {
       key: w + "x" + h,
       name: newName.value,
       width: w,
       height: h
@@ -757,45 +760,34 @@ ResponsiveUI.prototype = {
   },
 
   /**
    * Take a screenshot of the page.
    *
    * @param aFileName name of the screenshot file (used for tests).
    */
   screenshot: function RUI_screenshot(aFileName) {
-    let window = this.browser.contentWindow;
-    let document = window.document;
-    let canvas = this.chromeDoc.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
-
-    let width = window.innerWidth;
-    let height = window.innerHeight;
-
-    canvas.width = width;
-    canvas.height = height;
-
-    let ctx = canvas.getContext("2d");
-    ctx.drawWindow(window, window.scrollX, window.scrollY, width, height, "#fff");
-
     let filename = aFileName;
-
     if (!filename) {
       let date = new Date();
       let month = ("0" + (date.getMonth() + 1)).substr(-2, 2);
       let day = ("0" + date.getDate()).substr(-2, 2);
       let dateString = [date.getFullYear(), month, day].join("-");
       let timeString = date.toTimeString().replace(/:/g, ".").split(" ")[0];
       filename = this.strings.formatStringFromName("responsiveUI.screenshotGeneratedFilename", [dateString, timeString], 2);
     }
-
-    canvas.toBlob(blob => {
-      let chromeWindow = this.chromeDoc.defaultView;
-      let url = chromeWindow.URL.createObjectURL(blob);
-      chromeWindow.saveURL(url, filename + ".png", null, true, true, document.documentURIObject, document);
-    });
+    let mm = this.tab.linkedBrowser.messageManager;
+    let chromeWindow = this.chromeDoc.defaultView;
+    let doc = chromeWindow.document;
+    function onScreenshot(aMessage) {
+      mm.removeMessageListener("ResponsiveMode:RequestScreenshot:Done", onScreenshot);
+      chromeWindow.saveURL(aMessage.data, filename + ".png", null, true, true, doc.documentURIObject, doc);
+    }
+    mm.addMessageListener("ResponsiveMode:RequestScreenshot:Done", onScreenshot);
+    mm.sendAsyncMessage("ResponsiveMode:RequestScreenshot");
   },
 
   /**
    * Enable/Disable mouse -> touch events translation.
    */
    enableTouch: function RUI_enableTouch() {
      if (!this.touchEventHandler.enabled) {
        let isReloadNeeded = this.touchEventHandler.start();
--- a/browser/devtools/responsivedesign/test/browser.ini
+++ b/browser/devtools/responsivedesign/test/browser.ini
@@ -1,14 +1,16 @@
 [DEFAULT]
-skip-if = e10s # Bug ?????? - devtools tests disabled with e10s
 subsuite = devtools
 support-files =
   head.js
   touch.html
 
 [browser_responsive_cmd.js]
 [browser_responsivecomputedview.js]
+skip-if = e10s # Bug ??????
 [browser_responsiveruleview.js]
+skip-if = e10s # Bug ??????
 [browser_responsiveui.js]
 [browser_responsiveui_touch.js]
+skip-if = e10s # Bug ?????? - [e10s] re-introduce touch feature in responsive mode
 [browser_responsiveuiaddcustompreset.js]
 [browser_responsive_devicewidth.js]
--- a/browser/devtools/responsivedesign/test/browser_responsive_cmd.js
+++ b/browser/devtools/responsivedesign/test/browser_responsive_cmd.js
@@ -5,20 +5,21 @@
 //
 // Whitelisting this test.
 // As part of bug 1077403, the leaking uncaught rejection should be fixed.
 //
 thisTestLeaksUncaughtRejectionsAndShouldBeFixed("destroy");
 
 function test() {
   function isOpen() {
-    return !!gBrowser.selectedTab.__responsiveUI;
+    return gBrowser.getBrowserContainer(gBrowser.selectedTab.linkedBrowser)
+                   .hasAttribute("responsivemode");
   }
 
-  helpers.addTabWithToolbar("about:blank", function(options) {
+  helpers.addTabWithToolbar("data:text/html;charset=utf-8,hi", function(options) {
     return helpers.audit(options, [
       {
         setup: "resize toggle",
         check: {
           input:  'resize toggle',
           hints:               '',
           markup: 'VVVVVVVVVVVVV',
           status: 'VALID'
--- a/browser/devtools/responsivedesign/test/browser_responsive_devicewidth.js
+++ b/browser/devtools/responsivedesign/test/browser_responsive_devicewidth.js
@@ -2,31 +2,29 @@
 http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function test() {
   let instance;
   let mgr = ResponsiveUI.ResponsiveUIManager;
 
   waitForExplicitFinish();
 
-  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedTab = gBrowser.addTab("about:logo");
   gBrowser.selectedBrowser.addEventListener("load", function onload() {
     gBrowser.selectedBrowser.removeEventListener("load", onload, true);
-    waitForFocus(startTest, content);
+    startTest();
   }, true);
 
-  content.location = "data:text/html,mop";
-
   function startTest() {
     mgr.once("on", function() {executeSoon(onUIOpen)});
     document.getElementById("Tools:ResponsiveUI").doCommand();
   }
 
   function onUIOpen() {
-    instance = gBrowser.selectedTab.__responsiveUI;
+    instance = mgr.getResponsiveUIForTab(gBrowser.selectedTab);
     instance.stack.setAttribute("notransition", "true");
     ok(instance, "instance of the module is attached to the tab.");
 
     instance.setSize(110, 500);
     ok(content.innerWidth, 110, "initial width is valid");
 
     let mql = content.matchMedia("(max-device-width:100px)")
 
--- a/browser/devtools/responsivedesign/test/browser_responsivecomputedview.js
+++ b/browser/devtools/responsivedesign/test/browser_responsivecomputedview.js
@@ -3,21 +3,22 @@
 
 function test() {
   let instance;
 
   let computedView;
   let inspector;
 
   waitForExplicitFinish();
+  let mgr = ResponsiveUI.ResponsiveUIManager;
 
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function onload() {
     gBrowser.selectedBrowser.removeEventListener("load", onload, true);
-    waitForFocus(startTest, content);
+    startTest();
   }, true);
 
   content.location = "data:text/html;charset=utf-8,<html><style>" +
     "div {" +
     "  width: 500px;" +
     "  height: 10px;" +
     "  background: purple;" +
     "} " +
@@ -38,17 +39,17 @@ function test() {
   }
 
   function startTest() {
     document.getElementById("Tools:ResponsiveUI").doCommand();
     executeSoon(onUIOpen);
   }
 
   function onUIOpen() {
-    instance = gBrowser.selectedTab.__responsiveUI;
+    instance = mgr.getResponsiveUIForTab(gBrowser.selectedTab);
     ok(instance, "instance of the module is attached to the tab.");
 
     instance.stack.setAttribute("notransition", "true");
     registerCleanupFunction(function() {
       instance.stack.removeAttribute("notransition");
     });
 
     instance.setSize(500, 500);
--- a/browser/devtools/responsivedesign/test/browser_responsiveruleview.js
+++ b/browser/devtools/responsivedesign/test/browser_responsiveruleview.js
@@ -6,20 +6,17 @@ function test() {
 
   let ruleView;
   let inspector;
   let mgr = ResponsiveUI.ResponsiveUIManager;
 
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
-  gBrowser.selectedBrowser.addEventListener("load", function onload() {
-    gBrowser.selectedBrowser.removeEventListener("load", onload, true);
-    waitForFocus(startTest, content);
-  }, true);
+  gBrowser.selectedBrowser.addEventListener("load", startTest, true);
 
   content.location = "data:text/html;charset=utf-8,<html><style>" +
     "div {" +
     "  width: 500px;" +
     "  height: 10px;" +
     "  background: purple;" +
     "} " +
     "@media screen and (max-width: 200px) {" +
@@ -29,22 +26,23 @@ function test() {
     "};" +
     "</style><div></div></html>"
 
   function numberOfRules() {
     return ruleView.element.querySelectorAll(".ruleview-code").length;
   }
 
   function startTest() {
+    gBrowser.selectedBrowser.removeEventListener("load", startTest, true);
     document.getElementById("Tools:ResponsiveUI").doCommand();
     executeSoon(onUIOpen);
   }
 
   function onUIOpen() {
-    instance = gBrowser.selectedTab.__responsiveUI;
+    instance = mgr.getResponsiveUIForTab(gBrowser.selectedTab);
     ok(instance, "instance of the module is attached to the tab.");
 
     instance.stack.setAttribute("notransition", "true");
     registerCleanupFunction(function() {
       instance.stack.removeAttribute("notransition");
     });
 
     instance.setSize(500, 500);
--- a/browser/devtools/responsivedesign/test/browser_responsiveui.js
+++ b/browser/devtools/responsivedesign/test/browser_responsiveui.js
@@ -1,353 +1,305 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Protocol error (unknownError): Error: Got an invalid root window in DocumentWalker");
+
 function test() {
-  let instance, widthBeforeClose, heightBeforeClose;
-  let mgr = ResponsiveUI.ResponsiveUIManager;
-
   waitForExplicitFinish();
+  SimpleTest.requestCompleteLog();
+  Task.spawn(function() {
 
-  gBrowser.selectedTab = gBrowser.addTab();
-  gBrowser.selectedBrowser.addEventListener("load", function onload() {
-    gBrowser.selectedBrowser.removeEventListener("load", onload, true);
-    waitForFocus(startTest, content);
-  }, true);
-
-  content.location = "data:text/html,mop";
+    function extractSizeFromString(str) {
+      let numbers = str.match(/(\d+)[^\d]*(\d+)/);
+      if (numbers) {
+        return [numbers[1], numbers[2]];
+      } else {
+        return [null, null];
+      }
+    }
 
-  function startTest() {
-    document.getElementById("Tools:ResponsiveUI").removeAttribute("disabled");
-    mgr.once("on", function() {executeSoon(onUIOpen)});
+    function processStringAsKey(str) {
+      for (let i = 0, l = str.length; i < l; i++) {
+        EventUtils.synthesizeKey(str.charAt(i), {});
+      }
+    }
+
+    yield addTab("data:text/html,mop");
+
+    let mgr = ResponsiveUI.ResponsiveUIManager;
+
     synthesizeKeyFromKeyTag("key_responsiveUI");
-  }
 
-  function onUIOpen() {
+    yield once(mgr, "on");
+
     // Is it open?
     let container = gBrowser.getBrowserContainer();
     is(container.getAttribute("responsivemode"), "true", "In responsive mode.");
 
     // Menus are correctly updated?
     is(document.getElementById("Tools:ResponsiveUI").getAttribute("checked"), "true", "menus checked");
 
-    instance = gBrowser.selectedTab.__responsiveUI;
+    let instance = mgr.getResponsiveUIForTab(gBrowser.selectedTab);
     ok(instance, "instance of the module is attached to the tab.");
 
-    if (instance._floatingScrollbars) {
-      ensureScrollbarsAreFloating();
-    }
+    let originalWidth = content.innerWidth;
+    content.location = "data:text/html;charset=utf-8,mop<div style%3D'height%3A5000px'><%2Fdiv>";
+    let newWidth = content.innerWidth;
+    is(originalWidth, newWidth, "Floating scrollbars are presents");
+
+    yield instance._test_notifyOnResize();
+
+    yield nextTick();
 
     instance.transitionsEnabled = false;
 
-    testPresets();
-  }
-
-  function ensureScrollbarsAreFloating() {
-    let body = gBrowser.contentDocument.body;
-    let html = gBrowser.contentDocument.documentElement;
-
-    let originalWidth = body.getBoundingClientRect().width;
-
-    html.style.overflowY = "scroll"; // Force scrollbars
-    // Flush. Should not be needed as getBoundingClientRect() should flush,
-    // but just in case.
-    gBrowser.contentWindow.getComputedStyle(html).overflowY;
-    let newWidth = body.getBoundingClientRect().width;
-    is(originalWidth, newWidth, "Floating scrollbars are presents");
-  }
-
-  function testPresets() {
-    function testOnePreset(c) {
-      if (c == 0) {
-        executeSoon(testCustom);
-        return;
-      }
-      instance.menulist.selectedIndex = c;
+    // Starting from length - 4 because last 3 items are not presets : separator, addbutton and removebutton
+    for (let c = instance.menulist.firstChild.childNodes.length - 4; c >= 0; c--) {
       let item = instance.menulist.firstChild.childNodes[c];
       let [width, height] = extractSizeFromString(item.getAttribute("label"));
+      let onContentResize = once(mgr, "contentResize");
+      instance.menulist.selectedIndex = c;
+      yield onContentResize;
       is(content.innerWidth, width, "preset " + c + ": dimension valid (width)");
       is(content.innerHeight, height, "preset " + c + ": dimension valid (height)");
+    }
 
-      testOnePreset(c - 1);
-    }
-    // Starting from length - 4 because last 3 items are not presets : separator, addbutton and removebutton
-    testOnePreset(instance.menulist.firstChild.childNodes.length - 4);
-  }
+    // test custom
 
-  function extractSizeFromString(str) {
-    let numbers = str.match(/(\d+)[^\d]*(\d+)/);
-    if (numbers) {
-      return [numbers[1], numbers[2]];
-    } else {
-      return [null, null];
-    }
-  }
+    instance.setSize(100, 100);
 
-  function testCustom() {
+    yield once(mgr, "contentResize");
+
     let initialWidth = content.innerWidth;
     let initialHeight = content.innerHeight;
 
+    is(initialWidth, 100, "Width reset to 100");
+    is(initialHeight, 100, "Height reset to 100");
+
     let x = 2, y = 2;
     EventUtils.synthesizeMouse(instance.resizer, x, y, {type: "mousedown"}, window);
     x += 20; y += 10;
     EventUtils.synthesizeMouse(instance.resizer, x, y, {type: "mousemove"}, window);
     EventUtils.synthesizeMouse(instance.resizer, x, y, {type: "mouseup"}, window);
 
+    yield once(mgr, "contentResize");
+
     let expectedWidth = initialWidth + 20;
     let expectedHeight = initialHeight + 10;
     info("initial width: " + initialWidth);
     info("initial height: " + initialHeight);
-    is(content.innerWidth, expectedWidth, "Size correcty updated (width).");
-    is(content.innerHeight, expectedHeight, "Size correcty updated (height).");
+    is(content.innerWidth, expectedWidth, "Size correctly updated (width).");
+    is(content.innerHeight, expectedHeight, "Size correctly updated (height).");
+
     is(instance.menulist.selectedIndex, -1, "Custom menuitem cannot be selected");
     let label = instance.menulist.firstChild.firstChild.getAttribute("label");
     let value = instance.menulist.value;
     isnot(label, value, "Label from the menulist item is different than the value of the menulist")
     let [width, height] = extractSizeFromString(label);
     is(width, expectedWidth, "Label updated (width).");
     is(height, expectedHeight, "Label updated (height).");
     [width, height] = extractSizeFromString(value);
     is(width, expectedWidth, "Value updated (width).");
     is(height, expectedHeight, "Value updated (height).");
-    testCustom2();
-  }
+
+    // With "shift" key pressed
+
+    instance.setSize(100, 100);
 
-  function testCustom2() {
-    let initialWidth = content.innerWidth;
-    let initialHeight = content.innerHeight;
+    yield once(mgr, "contentResize");
 
-    let x = 2, y = 2;
+    initialWidth = content.innerWidth;
+    initialHeight = content.innerHeight;
+
+    x = 2; y = 2;
     EventUtils.synthesizeMouse(instance.resizer, x, y, {type: "mousedown"}, window);
     x += 23; y += 13;
     EventUtils.synthesizeMouse(instance.resizer, x, y, {type: "mousemove", shiftKey: true}, window);
     EventUtils.synthesizeMouse(instance.resizer, x, y, {type: "mouseup"}, window);
 
-    let expectedWidth = initialWidth + 20;
-    let expectedHeight = initialHeight + 10;
-    is(content.innerWidth, expectedWidth, "with shift: Size correcty updated (width).");
-    is(content.innerHeight, expectedHeight, "with shift: Size correcty updated (height).");
-    is(instance.menulist.selectedIndex, -1, "with shift: Custom menuitem cannot be selected");
-    let label = instance.menulist.firstChild.firstChild.getAttribute("label");
-    let value = instance.menulist.value;
-    isnot(label, value, "Label from the menulist item is different than the value of the menulist")
-    let [width, height] = extractSizeFromString(label);
-    is(width, expectedWidth, "Label updated (width).");
-    is(height, expectedHeight, "Label updated (height).");
-    [width, height] = extractSizeFromString(value);
-    is(width, expectedWidth, "Value updated (width).");
-    is(height, expectedHeight, "Value updated (height).");
-    testCustom3();
-  }
+    yield once(mgr, "contentResize");
 
-  function testCustom3() {
-    let initialWidth = content.innerWidth;
-    let initialHeight = content.innerHeight;
-
-    let x = 2, y = 2;
-    EventUtils.synthesizeMouse(instance.resizer, x, y, {type: "mousedown"}, window);
-    x += 60; y += 30;
-    EventUtils.synthesizeMouse(instance.resizer, x, y, {type: "mousemove", ctrlKey: true}, window);
-    EventUtils.synthesizeMouse(instance.resizer, x, y, {type: "mouseup"}, window);
-
-    let expectedWidth = initialWidth + 10;
-    let expectedHeight = initialHeight + 5;
-    is(content.innerWidth, expectedWidth, "with ctrl: Size correcty updated (width).");
-    is(content.innerHeight, expectedHeight, "with ctrl: Size correcty updated (height).");
-    is(instance.menulist.selectedIndex, -1, "with ctrl: Custom menuitem cannot be selected");
-    let label = instance.menulist.firstChild.firstChild.getAttribute("label");
-    let value = instance.menulist.value;
-    isnot(label, value, "Label from the menulist item is different than the value of the menulist")
-    let [width, height] = extractSizeFromString(label);
+    expectedWidth = initialWidth + 20;
+    expectedHeight = initialHeight + 10;
+    is(content.innerWidth, expectedWidth, "with shift: Size correctly updated (width).");
+    is(content.innerHeight, expectedHeight, "with shift: Size correctly updated (height).");
+    is(instance.menulist.selectedIndex, -1, "with shift: Custom menuitem cannot be selected");
+    label = instance.menulist.firstChild.firstChild.getAttribute("label");
+    value = instance.menulist.value;
+    isnot(label, value, "Label from the menulist item is different than the value of the menulist");
+    [width, height] = extractSizeFromString(label);
     is(width, expectedWidth, "Label updated (width).");
     is(height, expectedHeight, "Label updated (height).");
     [width, height] = extractSizeFromString(value);
     is(width, expectedWidth, "Value updated (width).");
     is(height, expectedHeight, "Value updated (height).");
 
-    testCustomInput();
-  }
+
+    // With "ctrl" key pressed
+
+    instance.setSize(100, 100);
+
+    yield once(mgr, "contentResize");
+
+    initialWidth = content.innerWidth;
+    initialHeight = content.innerHeight;
+
+    x = 2; y = 2;
+    EventUtils.synthesizeMouse(instance.resizer, x, y, {type: "mousedown"}, window);
+    x += 60; y += 30;
+    EventUtils.synthesizeMouse(instance.resizer, x, y, {type: "mousemove", ctrlKey: true}, window);
+    EventUtils.synthesizeMouse(instance.resizer, x, y, {type: "mouseup"}, window);
+
+    yield once(mgr, "contentResize");
 
-  function testCustomInput() {
-    let initialWidth = content.innerWidth;
-    let initialHeight = content.innerHeight;
-    let expectedWidth = initialWidth - 20;
-    let expectedHeight = initialHeight - 10;
+    expectedWidth = initialWidth + 10;
+    expectedHeight = initialHeight + 5;
+    is(content.innerWidth, expectedWidth, "with ctrl: Size correctly updated (width).");
+    is(content.innerHeight, expectedHeight, "with ctrl: Size correctly updated (height).");
+    is(instance.menulist.selectedIndex, -1, "with ctrl: Custom menuitem cannot be selected");
+    label = instance.menulist.firstChild.firstChild.getAttribute("label");
+    value = instance.menulist.value;
+    isnot(label, value, "Label from the menulist item is different than the value of the menulist");
+    [width, height] = extractSizeFromString(label);
+    is(width, expectedWidth, "Label updated (width).");
+    is(height, expectedHeight, "Label updated (height).");
+    [width, height] = extractSizeFromString(value);
+    is(width, expectedWidth, "Value updated (width).");
+    is(height, expectedHeight, "Value updated (height).");
+
+
+    // Test custom input
+
+    initialWidth = content.innerWidth;
+    initialHeight = content.innerHeight;
+    expectedWidth = initialWidth - 20;
+    expectedHeight = initialHeight - 10;
     let index = instance.menulist.selectedIndex;
-    let label, value, width, height;
 
     let userInput = expectedWidth + " x " + expectedHeight;
 
     instance.menulist.inputField.value = "";
     instance.menulist.focus();
     processStringAsKey(userInput);
 
     // While typing, the size should not change
     is(content.innerWidth, initialWidth, "Size hasn't changed (width).");
     is(content.innerHeight, initialHeight, "Size hasn't changed (height).");
 
     // Only the `change` event must change the size
     EventUtils.synthesizeKey("VK_RETURN", {});
 
+    yield once(mgr, "contentResize");
+
     is(content.innerWidth, expectedWidth, "Size correctly updated (width).");
     is(content.innerHeight, expectedHeight, "Size correctly updated (height).");
     is(instance.menulist.selectedIndex, -1, "Custom menuitem cannot be selected");
     label = instance.menulist.firstChild.firstChild.getAttribute("label");
     value = instance.menulist.value;
     isnot(label, value, "Label from the menulist item is different than the value of the menulist");
     [width, height] = extractSizeFromString(label);
     is(width, expectedWidth, "Label updated (width).");
     is(height, expectedHeight, "Label updated (height).");
     [width, height] = extractSizeFromString(value);
     is(width, expectedWidth, "Value updated (width).");
     is(height, expectedHeight, "Value updated (height).");
 
-    testCustomInput2();
-  }
+
+    // Invalid input
+
 
-  function testCustomInput2() {
-    let initialWidth = content.innerWidth;
-    let initialHeight = content.innerHeight;
-    let index = instance.menulist.selectedIndex;
+    initialWidth = content.innerWidth;
+    initialHeight = content.innerHeight;
+    index = instance.menulist.selectedIndex;
     let expectedValue = initialWidth + "x" + initialHeight;
     let expectedLabel = instance.menulist.firstChild.firstChild.getAttribute("label");
 
-    let userInput = "I'm wrong";
+    userInput = "I'm wrong";
 
     instance.menulist.inputField.value = "";
     instance.menulist.focus();
     processStringAsKey(userInput);
     EventUtils.synthesizeKey("VK_RETURN", {});
 
     is(content.innerWidth, initialWidth, "Size hasn't changed (width).");
     is(content.innerHeight, initialHeight, "Size hasn't changed (height).");
     is(instance.menulist.selectedIndex, index, "Selected item hasn't changed.");
     is(instance.menulist.value, expectedValue, "Value has been reset")
-    let label = instance.menulist.firstChild.firstChild.getAttribute("label");
+    label = instance.menulist.firstChild.firstChild.getAttribute("label");
     is(label, expectedLabel, "Custom menuitem's label hasn't changed");
 
-    rotate();
-  }
+
+    // Rotate
 
-  function rotate() {
-    let initialWidth = content.innerWidth;
-    let initialHeight = content.innerHeight;
+    initialWidth = content.innerWidth;
+    initialHeight = content.innerHeight;
 
     info("rotate");
     instance.rotate();
 
+    yield once(mgr, "contentResize");
+
     is(content.innerWidth, initialHeight, "The width is now the height.");
     is(content.innerHeight, initialWidth, "The height is now the width.");
-    let [width, height] = extractSizeFromString(instance.menulist.firstChild.firstChild.getAttribute("label"));
+    [width, height] = extractSizeFromString(instance.menulist.firstChild.firstChild.getAttribute("label"));
     is(width, initialHeight, "Label updated (width).");
     is(height, initialWidth, "Label updated (height).");
 
-    widthBeforeClose = content.innerWidth;
-    heightBeforeClose = content.innerHeight;
-
-    info("XXX BUG 851296: instance.closing: " + !!instance.closing);
+    let widthBeforeClose = content.innerWidth;
+    let heightBeforeClose = content.innerHeight;
 
-    mgr.once("off", function() {
-      info("XXX BUG 851296: 'off' received.");
-      executeSoon(restart);
-    });
+    // Restart
+
     mgr.toggle(window, gBrowser.selectedTab);
-  }
 
-  function restart() {
-    info("XXX BUG 851296: restarting.");
-    info("XXX BUG 851296: __responsiveUI: " + gBrowser.selectedTab.__responsiveUI);
-    mgr.once("on", function() {
-      info("XXX BUG 851296: 'on' received.");
-      executeSoon(onUIOpen2);
-    });
-    //XXX BUG 851296: synthesizeKeyFromKeyTag("key_responsiveUI");
+    yield once(mgr, "off");
+
     mgr.toggle(window, gBrowser.selectedTab);
-    info("XXX BUG 851296: restart() finished.");
-  }
 
-  function onUIOpen2() {
-    info("XXX BUG 851296: onUIOpen2.");
-    let container = gBrowser.getBrowserContainer();
+    yield once(mgr, "on");
+
+    container = gBrowser.getBrowserContainer();
     is(container.getAttribute("responsivemode"), "true", "In responsive mode.");
 
     // Menus are correctly updated?
+
     is(document.getElementById("Tools:ResponsiveUI").getAttribute("checked"), "true", "menus checked");
 
     is(content.innerWidth, widthBeforeClose, "width restored.");
     is(content.innerHeight, heightBeforeClose, "height restored.");
 
-    mgr.once("off", function() {executeSoon(testScreenshot)});
-    mgr.toggle(window, gBrowser.selectedTab);
-  }
+    // Screenshot
+
+
+    let isWinXP = navigator.userAgent.indexOf("Windows NT 5.1") != -1;
+    if (!isWinXP) {
+      info("screenshot");
+      instance.screenshot("responsiveui");
+      let FileUtils = (Cu.import("resource://gre/modules/FileUtils.jsm", {})).FileUtils;
 
-  function testScreenshot() {
-    let isWinXP = navigator.userAgent.indexOf("Windows NT 5.1") != -1;
-    if (isWinXP) {
-      // We have issues testing this on Windows XP.
-      // See https://bugzilla.mozilla.org/show_bug.cgi?id=848760#c17
-      return finishUp();
+      while(true) {
+        // while(true) until we find the file.
+        // no need for a timeout, the test will get killed anyway.
+        let file = FileUtils.getFile("DfltDwnld", [ "responsiveui.png" ]);
+        if (file.exists()) {
+          ok(true, "Screenshot file exists");
+          file.remove(false);
+          break;
+        }
+        info("checking if file exists in 200ms");
+        yield wait(200);
+      }
     }
 
-    info("screenshot");
-    instance.screenshot("responsiveui");
-    let FileUtils = (Cu.import("resource://gre/modules/FileUtils.jsm", {})).FileUtils;
+    mgr.toggle(window, gBrowser.selectedTab);
 
-    // while(1) until we find the file.
-    // no need for a timeout, the test will get killed anyway.
-    info("checking if file exists in 200ms");
-    function checkIfFileExist() {
-      let file = FileUtils.getFile("DfltDwnld", [ "responsiveui.png" ]);
-      if (file.exists()) {
-        ok(true, "Screenshot file exists");
-        file.remove(false);
-        finishUp();
-      } else {
-        setTimeout(checkIfFileExist, 200);
-      }
-    }
-    checkIfFileExist();
-  }
-
-  function finishUp() {
+    yield once(mgr, "off");
 
     // Menus are correctly updated?
     is(document.getElementById("Tools:ResponsiveUI").getAttribute("checked"), "false", "menu unchecked");
 
     delete instance;
     gBrowser.removeCurrentTab();
     finish();
-  }
 
-  function synthesizeKeyFromKeyTag(aKeyId) {
-    let key = document.getElementById(aKeyId);
-    isnot(key, null, "Successfully retrieved the <key> node");
-
-    let modifiersAttr = key.getAttribute("modifiers");
-
-    let name = null;
-
-    if (key.getAttribute("keycode"))
-      name = key.getAttribute("keycode");
-    else if (key.getAttribute("key"))
-      name = key.getAttribute("key");
-
-    isnot(name, null, "Successfully retrieved keycode/key");
-
-    let modifiers = {
-      shiftKey: modifiersAttr.match("shift"),
-      ctrlKey: modifiersAttr.match("ctrl"),
-      altKey: modifiersAttr.match("alt"),
-      metaKey: modifiersAttr.match("meta"),
-      accelKey: modifiersAttr.match("accel")
-    }
-
-    info("XXX BUG 851296: key name: " + name);
-    info("XXX BUG 851296: key modifiers: " + JSON.stringify(modifiers));
-    EventUtils.synthesizeKey(name, modifiers);
-  }
-
-  function processStringAsKey(str) {
-    for (let i = 0, l = str.length; i < l; i++) {
-      EventUtils.synthesizeKey(str.charAt(i), {});
-    }
-  }
+  });
 }
--- a/browser/devtools/responsivedesign/test/browser_responsiveui_touch.js
+++ b/browser/devtools/responsivedesign/test/browser_responsiveui_touch.js
@@ -29,30 +29,30 @@ function test() {
     x += 20; y += 10;
     EventUtils.synthesizeMouse(div, x, y, {type: "mousemove", isSynthesized: false}, content);
     is(div.style.transform, "", "touch didn't work");
     EventUtils.synthesizeMouse(div, x, y, {type: "mouseup", isSynthesized: false}, content);
     testWithTouch();
   }
 
   function testWithTouch() {
-    gBrowser.selectedTab.__responsiveUI.enableTouch();
+    mgr.getResponsiveUIForTab(gBrowser.selectedTab).enableTouch();
     let div = content.document.querySelector("div");
     let x = 2, y = 2;
     EventUtils.synthesizeMouse(div, x, y, {type: "mousedown", isSynthesized: false}, content);
     x += 20; y += 10;
     EventUtils.synthesizeMouse(div, x, y, {type: "mousemove", isSynthesized: false}, content);
     is(div.style.transform, "translate(20px, 10px)", "touch worked");
     EventUtils.synthesizeMouse(div, x, y, {type: "mouseup", isSynthesized: false}, content);
     is(div.style.transform, "none", "end event worked");
     mgr.toggle(window, gBrowser.selectedTab);
   }
 
   function testWithTouchAgain() {
-    gBrowser.selectedTab.__responsiveUI.disableTouch();
+    mgr.getResponsiveUIForTab(gBrowser.selectedTab).disableTouch();
     let div = content.document.querySelector("div");
     let x = 2, y = 2;
     EventUtils.synthesizeMouse(div, x, y, {type: "mousedown", isSynthesized: false}, content);
     x += 20; y += 10;
     EventUtils.synthesizeMouse(div, x, y, {type: "mousemove", isSynthesized: false}, content);
     is(div.style.transform, "", "touch didn't work");
     EventUtils.synthesizeMouse(div, x, y, {type: "mouseup", isSynthesized: false}, content);
     finishUp();
--- a/browser/devtools/responsivedesign/test/browser_responsiveuiaddcustompreset.js
+++ b/browser/devtools/responsivedesign/test/browser_responsiveuiaddcustompreset.js
@@ -1,176 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function test() {
-  let instance, deletedPresetA, deletedPresetB, oldPrompt;
-  let mgr = ResponsiveUI.ResponsiveUIManager;
-
   waitForExplicitFinish();
-
-  gBrowser.selectedTab = gBrowser.addTab();
-  gBrowser.selectedBrowser.addEventListener("load", function onload() {
-    gBrowser.selectedBrowser.removeEventListener("load", onload, true);
-    waitForFocus(startTest, content);
-  }, true);
-
-  content.location = "data:text/html;charset=utf8,test custom presets in responsive mode";
-
-  // This test uses executeSoon() when responsive mode is initialized and when
-  // it is destroyed such that we get out of the init/destroy loops. If we try
-  // to init/destroy immediately, without waiting for the next loop, we get
-  // intermittent test failures.
-
-  function startTest() {
-    // Mocking prompt
-    oldPrompt = Services.prompt;
-    Services.prompt = {
-      value: "",
-      returnBool: true,
-      prompt: function(aParent, aDialogTitle, aText, aValue, aCheckMsg, aCheckState) {
-        aValue.value = this.value;
-        return this.returnBool;
-      }
-    };
-
-    registerCleanupFunction(() => Services.prompt = oldPrompt);
-
-    info("test started, waiting for responsive mode to activate");
-
-    document.getElementById("Tools:ResponsiveUI").removeAttribute("disabled");
-    mgr.once("on", onUIOpen);
-    synthesizeKeyFromKeyTag("key_responsiveUI");
-  }
-
-  function onUIOpen() {
-    // Is it open?
-    let container = gBrowser.getBrowserContainer();
-    is(container.getAttribute("responsivemode"), "true", "In responsive mode.");
-
-    instance = gBrowser.selectedTab.__responsiveUI;
-    ok(instance, "instance of the module is attached to the tab.");
-
-    instance.transitionsEnabled = false;
-
-    testAddCustomPreset();
-  }
-
-  function testAddCustomPreset() {
-    // Tries to add a custom preset and cancel the prompt
-    let idx = instance.menulist.selectedIndex;
-    let presetCount = instance.presets.length;
-
-    Services.prompt.value = "";
-    Services.prompt.returnBool = false;
-    instance.addbutton.doCommand();
-
-    is(idx, instance.menulist.selectedIndex, "selected item didn't change after add preset and cancel");
-    is(presetCount, instance.presets.length, "number of presets didn't change after add preset and cancel");
-
-    let customHeight = 123, customWidth = 456;
-    instance.setSize(customWidth, customHeight);
-
-    // Adds the custom preset with "Testing preset"
-    Services.prompt.value = "Testing preset";
-    Services.prompt.returnBool = true;
-    instance.addbutton.doCommand();
-
-    instance.menulist.selectedIndex = 1;
-
-    info("waiting for responsive mode to turn off");
-    mgr.once("off", restart);
-
-    // Force document reflow to avoid intermittent failures.
-    info("document height " + document.height);
+  SimpleTest.requestCompleteLog();
 
-    // We're still in the loop of initializing the responsive mode.
-    // Let's wait next loop to stop it.
-    executeSoon(function() {
-      instance.close();
-    });
-  }
-
-  function restart() {
-    info("Restarting Responsive Mode");
-    mgr.once("on", function() {
-      let container = gBrowser.getBrowserContainer();
-      is(container.getAttribute("responsivemode"), "true", "In responsive mode.");
-
-      instance = gBrowser.selectedTab.__responsiveUI;
-
-      testCustomPresetInList();
-    });
-
-    // We're still in the loop of destroying the responsive mode.
-    // Let's wait next loop to start it.
-    executeSoon(function() {
-      synthesizeKeyFromKeyTag("key_responsiveUI");
-    });
-  }
-
-  function testCustomPresetInList() {
-    let customPresetIndex = getPresetIndex("456x123 (Testing preset)");
-    ok(customPresetIndex >= 0, "is the previously added preset (idx = " + customPresetIndex + ") in the list of items");
-
-    instance.menulist.selectedIndex = customPresetIndex;
-
-    is(content.innerWidth, 456, "add preset, and selected in the list, dimension valid (width)");
-    is(content.innerHeight, 123, "add preset, and selected in the list, dimension valid (height)");
-
-    testDeleteCustomPresets();
-  }
-
-  function testDeleteCustomPresets() {
-    instance.removebutton.doCommand();
-
-    instance.menulist.selectedIndex = 2;
-    deletedPresetA = instance.menulist.selectedItem.getAttribute("label");
-    instance.removebutton.doCommand();
-
-    instance.menulist.selectedIndex = 2;
-    deletedPresetB = instance.menulist.selectedItem.getAttribute("label");
-    instance.removebutton.doCommand();
-
-    info("waiting for responsive mode to turn off");
-    mgr.once("off", restartAgain);
-
-    // We're still in the loop of initializing the responsive mode.
-    // Let's wait next loop to stop it.
-    executeSoon(() => instance.close());
-  }
-
-  function restartAgain() {
-    info("waiting for responsive mode to turn on");
-    mgr.once("on", () => {
-      instance = gBrowser.selectedTab.__responsiveUI;
-      testCustomPresetsNotInListAnymore();
-    });
-
-    // We're still in the loop of destroying the responsive mode.
-    // Let's wait next loop to start it.
-    executeSoon(() => synthesizeKeyFromKeyTag("key_responsiveUI"));
-  }
-
-  function testCustomPresetsNotInListAnymore() {
-    let customPresetIndex = getPresetIndex(deletedPresetA);
-    is(customPresetIndex, -1, "deleted preset " + deletedPresetA + " is not in the list anymore");
-
-    customPresetIndex = getPresetIndex(deletedPresetB);
-    is(customPresetIndex, -1, "deleted preset " + deletedPresetB + " is not in the list anymore");
-
-    executeSoon(finishUp);
-  }
-
-  function finishUp() {
-    delete instance;
-    gBrowser.removeCurrentTab();
-
-    finish();
-  }
+  let instance, deletedPresetA, deletedPresetB, oldPrompt;
 
   function getPresetIndex(presetLabel) {
     function testOnePreset(c) {
       if (c == 0) {
         return -1;
       }
       instance.menulist.selectedIndex = c;
 
@@ -194,9 +34,126 @@ function test() {
       name = key.getAttribute("keycode");
     else if (key.getAttribute("key"))
       name = key.getAttribute("key");
 
     isnot(name, null, "Successfully retrieved keycode/key");
 
     key.doCommand();
   }
+
+  Task.spawn(function() {
+
+    yield addTab("data:text/html;charset=utf8,test custom presets in responsive mode");
+
+    let mgr = ResponsiveUI.ResponsiveUIManager;
+
+    synthesizeKeyFromKeyTag("key_responsiveUI");
+
+    yield once(mgr, "on");
+
+    oldPrompt = Services.prompt;
+    Services.prompt = {
+      value: "",
+      returnBool: true,
+      prompt: function(aParent, aDialogTitle, aText, aValue, aCheckMsg, aCheckState) {
+        aValue.value = this.value;
+        return this.returnBool;
+      }
+    };
+
+    registerCleanupFunction(() => Services.prompt = oldPrompt);
+
+    // Is it open?
+    let container = gBrowser.getBrowserContainer();
+    is(container.getAttribute("responsivemode"), "true", "In responsive mode.");
+
+    instance = mgr.getResponsiveUIForTab(gBrowser.selectedTab);
+    ok(instance, "instance of the module is attached to the tab.");
+
+    instance.transitionsEnabled = false;
+
+    yield instance._test_notifyOnResize();
+
+    // Tries to add a custom preset and cancel the prompt
+    let idx = instance.menulist.selectedIndex;
+    let presetCount = instance.presets.length;
+
+    Services.prompt.value = "";
+    Services.prompt.returnBool = false;
+    instance.addbutton.doCommand();
+
+    is(idx, instance.menulist.selectedIndex, "selected item didn't change after add preset and cancel");
+    is(presetCount, instance.presets.length, "number of presets didn't change after add preset and cancel");
+
+    // Adds the custom preset with "Testing preset"
+    Services.prompt.value = "Testing preset";
+    Services.prompt.returnBool = true;
+
+    let customHeight = 123, customWidth = 456;
+    instance.startResizing({});
+    instance.setSize(customWidth, customHeight);
+    instance.stopResizing({});
+
+    instance.addbutton.doCommand();
+
+    // Force document reflow to avoid intermittent failures.
+    info("document height " + document.height);
+
+    instance.close();
+
+    info("waiting for responsive mode to turn off");
+    yield once(mgr, "off");
+
+    // We're still in the loop of initializing the responsive mode.
+    // Let's wait next loop to stop it.
+    yield nextTick();
+
+    synthesizeKeyFromKeyTag("key_responsiveUI");
+
+    yield once(mgr, "on");
+
+    is(container.getAttribute("responsivemode"), "true", "In responsive mode.");
+
+    instance = mgr.getResponsiveUIForTab(gBrowser.selectedTab);
+
+    let customPresetIndex = getPresetIndex("456x123 (Testing preset)");
+    info(customPresetIndex);
+    ok(customPresetIndex >= 0, "is the previously added preset (idx = " + customPresetIndex + ") in the list of items");
+
+    instance.menulist.selectedIndex = customPresetIndex;
+
+    is(content.innerWidth, 456, "add preset, and selected in the list, dimension valid (width)");
+    is(content.innerHeight, 123, "add preset, and selected in the list, dimension valid (height)");
+
+    instance.removebutton.doCommand();
+
+    instance.menulist.selectedIndex = 2;
+    deletedPresetA = instance.menulist.selectedItem.getAttribute("label");
+    instance.removebutton.doCommand();
+
+    instance.menulist.selectedIndex = 2;
+    deletedPresetB = instance.menulist.selectedItem.getAttribute("label");
+    instance.removebutton.doCommand();
+
+    yield nextTick();
+    instance.close();
+    yield once(mgr, "off");
+
+    synthesizeKeyFromKeyTag("key_responsiveUI");
+
+    info("waiting for responsive mode to turn on");
+    yield once(mgr, "on");
+
+    instance = mgr.getResponsiveUIForTab(gBrowser.selectedTab);
+
+    customPresetIndex = getPresetIndex(deletedPresetA);
+    is(customPresetIndex, -1, "deleted preset " + deletedPresetA + " is not in the list anymore");
+
+    customPresetIndex = getPresetIndex(deletedPresetB);
+    is(customPresetIndex, -1, "deleted preset " + deletedPresetB + " is not in the list anymore");
+
+    yield nextTick();
+
+    gBrowser.removeCurrentTab();
+    finish();
+  });
 }
--- a/browser/devtools/responsivedesign/test/head.js
+++ b/browser/devtools/responsivedesign/test/head.js
@@ -118,8 +118,97 @@ function openComputedView() {
  * Open the toolbox, with the inspector tool visible, and the rule-view
  * sidebar tab selected.
  * @return a promise that resolves when the inspector is ready and the rule
  * view is visible and ready
  */
 function openRuleView() {
   return openInspectorSideBar("ruleview");
 }
+
+
+/**
+ * Add a new test tab in the browser and load the given url.
+ * @param {String} url The url to be loaded in the new tab
+ * @return a promise that resolves to the tab object when the url is loaded
+ */
+let addTab = Task.async(function* (url) {
+  info("Adding a new tab with URL: '" + url + "'");
+
+  window.focus();
+
+  let tab = gBrowser.selectedTab = gBrowser.addTab(url);
+  let browser = tab.linkedBrowser;
+
+  yield once(browser, "load", true);
+  info("URL '" + url + "' loading complete");
+
+  return tab;
+});
+
+/**
+ * Wait for eventName on target.
+ * @param {Object} target An observable object that either supports on/off or
+ * addEventListener/removeEventListener
+ * @param {String} eventName
+ * @param {Boolean} useCapture Optional, for addEventListener/removeEventListener
+ * @return A promise that resolves when the event has been handled
+ */
+function once(target, eventName, useCapture=false) {
+  info("Waiting for event: '" + eventName + "' on " + target + ".");
+
+  let deferred = promise.defer();
+
+  for (let [add, remove] of [
+    ["addEventListener", "removeEventListener"],
+    ["addListener", "removeListener"],
+    ["on", "off"]
+  ]) {
+    if ((add in target) && (remove in target)) {
+      target[add](eventName, function onEvent(...aArgs) {
+        info("Got event: '" + eventName + "' on " + target + ".");
+        target[remove](eventName, onEvent, useCapture);
+        deferred.resolve.apply(deferred, aArgs);
+      }, useCapture);
+      break;
+    }
+  }
+
+  return deferred.promise;
+}
+
+function wait(ms) {
+  let def = promise.defer();
+  setTimeout(def.resolve, ms);
+  return def.promise;
+}
+
+function synthesizeKeyFromKeyTag(aKeyId) {
+  let key = document.getElementById(aKeyId);
+  isnot(key, null, "Successfully retrieved the <key> node");
+
+  let modifiersAttr = key.getAttribute("modifiers");
+
+  let name = null;
+
+  if (key.getAttribute("keycode"))
+    name = key.getAttribute("keycode");
+  else if (key.getAttribute("key"))
+    name = key.getAttribute("key");
+
+  isnot(name, null, "Successfully retrieved keycode/key");
+
+  let modifiers = {
+    shiftKey: modifiersAttr.match("shift"),
+    ctrlKey: modifiersAttr.match("ctrl"),
+    altKey: modifiersAttr.match("alt"),
+    metaKey: modifiersAttr.match("meta"),
+    accelKey: modifiersAttr.match("accel")
+  }
+
+  EventUtils.synthesizeKey(name, modifiers);
+}
+
+function nextTick() {
+  let def = promise.defer();
+  executeSoon(() => def.resolve())
+  return def.promise;
+}
deleted file mode 100644
--- a/browser/devtools/shared/FloatingScrollbars.jsm
+++ /dev/null
@@ -1,126 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
-
-this.EXPORTED_SYMBOLS = [ "switchToFloatingScrollbars", "switchToNativeScrollbars" ];
-
-Cu.import("resource://gre/modules/Services.jsm");
-
-let URL = Services.io.newURI("chrome://browser/skin/devtools/floating-scrollbars.css", null, null);
-
-let trackedTabs = new WeakMap();
-
-/**
- * Switch to floating scrollbars, à la mobile.
- *
- * @param aTab the targeted tab.
- *
- */
-this.switchToFloatingScrollbars = function switchToFloatingScrollbars(aTab) {
-  let mgr = trackedTabs.get(aTab);
-  if (!mgr) {
-    mgr = new ScrollbarManager(aTab);
-  }
-  mgr.switchToFloating();
-}
-
-/**
- * Switch to original native scrollbars.
- *
- * @param aTab the targeted tab.
- *
- */
-this.switchToNativeScrollbars = function switchToNativeScrollbars(aTab) {
-  let mgr = trackedTabs.get(aTab);
-  if (mgr) {
-    mgr.reset();
-  }
-}
-
-function ScrollbarManager(aTab) {
-  trackedTabs.set(aTab, this);
-
-  this.attachedTab = aTab;
-  this.attachedBrowser = aTab.linkedBrowser;
-
-  this.reset = this.reset.bind(this);
-  this.switchToFloating = this.switchToFloating.bind(this);
-
-  this.attachedTab.addEventListener("TabClose", this.reset, true);
-  this.attachedBrowser.addEventListener("DOMContentLoaded", this.switchToFloating, true);
-}
-
-ScrollbarManager.prototype = {
-  get win() {
-    return this.attachedBrowser.contentWindow;
-  },
-
-  /*
-   * Change the look of the scrollbars.
-   */
-  switchToFloating: function() {
-    let windows = this.getInnerWindows(this.win);
-    windows.forEach(this.injectStyleSheet);
-    this.forceStyle();
-  },
-
-
-  /*
-   * Reset the look of the scrollbars.
-   */
-  reset: function() {
-    let windows = this.getInnerWindows(this.win);
-    windows.forEach(this.removeStyleSheet);
-    this.forceStyle(this.attachedBrowser);
-    this.attachedBrowser.removeEventListener("DOMContentLoaded", this.switchToFloating, true);
-    this.attachedTab.removeEventListener("TabClose", this.reset, true);
-    trackedTabs.delete(this.attachedTab);
-  },
-
-  /*
-   * Toggle the display property of the window to force the style to be applied.
-   */
-  forceStyle: function() {
-    let parentWindow = this.attachedBrowser.ownerDocument.defaultView;
-    let display = parentWindow.getComputedStyle(this.attachedBrowser).display; // Save display value
-    this.attachedBrowser.style.display = "none";
-    parentWindow.getComputedStyle(this.attachedBrowser).display; // Flush
-    this.attachedBrowser.style.display = display; // Restore
-  },
-
-  /*
-   * return all the window objects present in the hiearchy of a window.
-   */
-  getInnerWindows: function(win) {
-    let iframes = win.document.querySelectorAll("iframe");
-    let innerWindows = [];
-    for (let iframe of iframes) {
-      innerWindows = innerWindows.concat(this.getInnerWindows(iframe.contentWindow));
-    }
-    return [win].concat(innerWindows);
-  },
-
-  /*
-   * Append the new scrollbar style.
-   */
-  injectStyleSheet: function(win) {
-    let winUtils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
-    try {
-      winUtils.loadSheet(URL, win.AGENT_SHEET);
-    }catch(e) {}
-  },
-
-  /*
-   * Remove the injected stylesheet.
-   */
-  removeStyleSheet: function(win) {
-    let winUtils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
-    try {
-      winUtils.removeSheet(URL, win.AGENT_SHEET);
-    }catch(e) {}
-  },
-}
--- a/browser/devtools/shared/moz.build
+++ b/browser/devtools/shared/moz.build
@@ -7,17 +7,16 @@
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
 XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
 
 EXTRA_JS_MODULES.devtools += [
     'AppCacheUtils.jsm',
     'Curl.jsm',
     'DeveloperToolbar.jsm',
     'DOMHelpers.jsm',
-    'FloatingScrollbars.jsm',
     'Jsbeautify.jsm',
     'Parser.jsm',
     'SplitView.jsm',
 ]
 
 EXTRA_JS_MODULES.devtools += [
     'widgets/AbstractTreeItem.jsm',
     'widgets/BreadcrumbsWidget.jsm',