merge fx-team to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 21 Jan 2015 14:05:24 +0100
changeset 251926 4135f5372644d89621d19079d7a393717a23f9e6
parent 251909 84439f7b7bae3f93200085a99491176f9c5375c7 (current diff)
parent 251925 68dff687420e7a439e53bcc7ebdfcb65e997e666 (diff)
child 251987 0f2b4a7a9daff18d5ae09e2e526f8f29b7b58be0
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)
reviewersmerge
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
merge fx-team to mozilla-central a=merge
browser/devtools/shared/FloatingScrollbars.jsm
--- 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/components/loop/MozLoopService.jsm
+++ b/browser/components/loop/MozLoopService.jsm
@@ -255,16 +255,17 @@ let MozLoopServiceInternal = {
    *                           error of a type will be saved at a time. This value may be used to
    *                           determine user-facing (aka. friendly) strings.
    * @param {Object} error     an object describing the error in the format from Hawk errors
    * @param {Function} [actionCallback] an object describing the label and callback function for error
    *                                    bar's button e.g. to retry.
    */
   setError: function(errorType, error, actionCallback = null) {
     log.debug("setError", errorType, error);
+    log.trace();
     let messageString, detailsString, detailsButtonLabelString, detailsButtonCallback;
     const NETWORK_ERRORS = [
       Cr.NS_ERROR_CONNECTION_REFUSED,
       Cr.NS_ERROR_NET_INTERRUPT,
       Cr.NS_ERROR_NET_RESET,
       Cr.NS_ERROR_NET_TIMEOUT,
       Cr.NS_ERROR_OFFLINE,
       Cr.NS_ERROR_PROXY_CONNECTION_REFUSED,
@@ -295,25 +296,34 @@ let MozLoopServiceInternal = {
       messageString = "service_not_available";
       detailsString = "try_again_later";
       detailsButtonLabelString = "retry_button";
     } else {
       messageString = "generic_failure_title";
     }
 
     error.friendlyMessage = this.localizedStrings.get(messageString);
-    error.friendlyDetails = detailsString ?
-                              this.localizedStrings.get(detailsString) :
-                              null;
+
+    // Default to the generic "retry_button" text even though the button won't be shown if
+    // error.friendlyDetails is null.
     error.friendlyDetailsButtonLabel = detailsButtonLabelString ?
                                          this.localizedStrings.get(detailsButtonLabelString) :
-                                         null;
+                                         this.localizedStrings.get("retry_button");
 
     error.friendlyDetailsButtonCallback = actionCallback || detailsButtonCallback || null;
 
+    if (detailsString) {
+      error.friendlyDetails = this.localizedStrings.get(detailsString);
+    } else if (error.friendlyDetailsButtonCallback) {
+      // If we have a retry callback but no details use the generic try again string.
+      error.friendlyDetails = this.localizedStrings.get("generic_failure_no_reason2");
+    } else {
+      error.friendlyDetails = null;
+    }
+
     gErrors.set(errorType, error);
     this.notifyStatusChanged();
   },
 
   clearError: function(errorType) {
     if (gErrors.has(errorType)) {
       gErrors.delete(errorType);
       this.notifyStatusChanged();
@@ -1214,17 +1224,18 @@ this.MozLoopService = {
       return;
     }
 
     log.debug("MozLoopService: Initializing with already logged-in account");
     MozLoopServiceInternal.promiseRegisteredWithServers(LOOP_SESSION_TYPE.FXA).then(() => {
       deferredInitialization.resolve("initialized to logged-in status");
     }, error => {
       log.debug("MozLoopService: error logging in using cached auth token");
-      MozLoopServiceInternal.setError("login", error);
+      let retryFunc = () => MozLoopServiceInternal.promiseRegisteredWithServers(LOOP_SESSION_TYPE.FXA);
+      MozLoopServiceInternal.setError("login", error, retryFunc);
       deferredInitialization.reject("error logging in using cached auth token");
     });
     yield completedPromise;
   }),
 
   /**
    * Opens the chat window
    *
@@ -1416,37 +1427,27 @@ this.MozLoopService = {
       MozLoopServiceInternal.fxAOAuthTokenData = tokenData;
       return tokenData;
     }).then(tokenData => {
       return MozLoopServiceInternal.promiseRegisteredWithServers(LOOP_SESSION_TYPE.FXA).then(() => {
         MozLoopServiceInternal.clearError("login");
         MozLoopServiceInternal.clearError("profile");
         return MozLoopServiceInternal.fxAOAuthTokenData;
       });
-    }).then(tokenData => {
-      let client = new FxAccountsProfileClient({
-        serverURL: gFxAOAuthClient.parameters.profile_uri,
-        token: tokenData.access_token
-      });
-      client.fetchProfile().then(result => {
-        MozLoopServiceInternal.fxAOAuthProfile = result;
-      }, error => {
-        log.error("Failed to retrieve profile", error);
-        this.setError("profile", error);
-        MozLoopServiceInternal.fxAOAuthProfile = null;
-        MozLoopServiceInternal.notifyStatusChanged();
-      });
+    }).then(Task.async(function* fetchProfile(tokenData) {
+      yield MozLoopService.fetchFxAProfile(tokenData);
       return tokenData;
-    }).catch(error => {
+    })).catch(error => {
       MozLoopServiceInternal.fxAOAuthTokenData = null;
       MozLoopServiceInternal.fxAOAuthProfile = null;
       MozLoopServiceInternal.deferredRegistrations.delete(LOOP_SESSION_TYPE.FXA);
       throw error;
     }).catch((error) => {
-      MozLoopServiceInternal.setError("login", error);
+      MozLoopServiceInternal.setError("login", error,
+                                      () => MozLoopService.logInToFxA());
       // Re-throw for testing
       throw error;
     });
   },
 
   /**
    * Logs the user out from FxA.
    *
@@ -1480,16 +1481,40 @@ this.MozLoopService = {
       // clearError calls notifyStatusChanged so should be done last when the
       // state is clean.
       MozLoopServiceInternal.clearError("registration");
       MozLoopServiceInternal.clearError("login");
       MozLoopServiceInternal.clearError("profile");
     }
   }),
 
+  /**
+   * Fetch/update the FxA Profile for the logged in user.
+   *
+   * @return {Promise} resolving if the profile information was succesfully retrieved
+   *                   rejecting if the profile information couldn't be retrieved.
+   *                   A profile error is registered.
+   **/
+  fetchFxAProfile: function() {
+    log.debug("fetchFxAProfile");
+    let client = new FxAccountsProfileClient({
+      serverURL: gFxAOAuthClient.parameters.profile_uri,
+      token: MozLoopServiceInternal.fxAOAuthTokenData.access_token
+    });
+    return client.fetchProfile().then(result => {
+      MozLoopServiceInternal.fxAOAuthProfile = result;
+      MozLoopServiceInternal.clearError("profile");
+    }, error => {
+      log.error("Failed to retrieve profile", error, this.fetchFxAProfile.bind(this));
+      MozLoopServiceInternal.setError("profile", error);
+      MozLoopServiceInternal.fxAOAuthProfile = null;
+      MozLoopServiceInternal.notifyStatusChanged();
+    });
+  },
+
   openFxASettings: Task.async(function() {
     try {
       let fxAOAuthClient = yield MozLoopServiceInternal.promiseFxAOAuthClient();
       if (!fxAOAuthClient) {
         log.error("Could not get the OAuth client");
         return;
       }
       let url = new URL("/settings", fxAOAuthClient.parameters.content_uri);
--- a/browser/components/loop/test/mochitest/browser_fxa_login.js
+++ b/browser/components/loop/test/mochitest/browser_fxa_login.js
@@ -291,18 +291,19 @@ add_task(function* basicAuthorizationAnd
   let loopDoc = document.getElementById("loop-panel-iframe").contentDocument;
   let visibleEmail = loopDoc.getElementsByClassName("user-identity")[0];
   is(visibleEmail.textContent, "Guest", "Guest should be displayed on the panel when not logged in");
   is(MozLoopService.userProfile, null, "profile should be null before log-in");
   let loopButton = document.getElementById("loop-button");
   is(loopButton.getAttribute("state"), "", "state of loop button should be empty when not logged in");
 
   info("Login");
+  statusChangedPromise = promiseObserverNotified("loop-status-changed", "login");
   let tokenData = yield MozLoopService.logInToFxA();
-  yield promiseObserverNotified("loop-status-changed", "login");
+  yield statusChangedPromise;
   ise(tokenData.access_token, "code1_access_token", "Check access_token");
   ise(tokenData.scope, "profile", "Check scope");
   ise(tokenData.token_type, "bearer", "Check token_type");
 
   is(MozLoopService.userProfile.email, "test@example.com", "email should exist in the profile data");
   is(MozLoopService.userProfile.uid, "1234abcd", "uid should exist in the profile data");
   is(visibleEmail.textContent, "test@example.com", "the email should be correct on the panel");
   is(loopButton.getAttribute("state"), "active", "state of loop button should be active when logged in");
--- a/browser/components/loop/test/xpcshell/test_loopservice_hawk_errors.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_hawk_errors.js
@@ -69,17 +69,16 @@ add_task(function* guest_401() {
                          "FxA session token should NOT have been cleared");
 
       Assert.strictEqual(MozLoopService.errors.size, 1, "Should be one error");
 
       let err = MozLoopService.errors.get("registration");
       Assert.strictEqual(err.code, 401);
       Assert.strictEqual(err.friendlyMessage, getLoopString("session_expired_error_description"));
       Assert.equal(err.friendlyDetails, null);
-      Assert.equal(err.friendlyDetailsButtonLabel, null);
   });
 });
 
 add_task(cleanup_between_tests);
 
 add_task(function* fxa_401() {
   Services.prefs.setCharPref("loop.hawk-session-token", "guest");
   Services.prefs.setCharPref("loop.hawk-session-token.fxa", "fxa");
@@ -110,17 +109,16 @@ add_task(function* error_404() {
     (error) => {
       MozLoopServiceInternal.setError("testing", error);
       Assert.strictEqual(MozLoopService.errors.size, 1, "Should be one error");
 
       let err = MozLoopService.errors.get("testing");
       Assert.strictEqual(err.code, 404);
       Assert.strictEqual(err.friendlyMessage, getLoopString("generic_failure_title"));
       Assert.equal(err.friendlyDetails, null);
-      Assert.equal(err.friendlyDetailsButtonLabel, null);
   });
 });
 
 add_task(cleanup_between_tests);
 
 add_task(function* error_500() {
   yield MozLoopServiceInternal.hawkRequestInternal(LOOP_SESSION_TYPE.GUEST, "/500", "GET").then(
     () => Assert.ok(false, "Should have rejected"),
@@ -144,17 +142,16 @@ add_task(function* profile_500() {
     (error) => {
       MozLoopServiceInternal.setError("profile", error);
       Assert.strictEqual(MozLoopService.errors.size, 1, "Should be one error");
 
       let err = MozLoopService.errors.get("profile");
       Assert.strictEqual(err.code, 500);
       Assert.strictEqual(err.friendlyMessage, getLoopString("problem_accessing_account"));
       Assert.equal(err.friendlyDetails, null);
-      Assert.equal(err.friendlyDetailsButtonLabel, null);
   });
 });
 
 add_task(cleanup_between_tests);
 
 add_task(function* error_503() {
   yield MozLoopServiceInternal.hawkRequestInternal(LOOP_SESSION_TYPE.GUEST, "/503", "GET").then(
     () => Assert.ok(false, "Should have rejected"),
--- a/browser/components/loop/test/xpcshell/test_loopservice_restart.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_restart.js
@@ -72,16 +72,19 @@ add_task(function test_initialize_with_i
   (error) => {
     Assert.equal(MozLoopServiceInternal.pushHandler.registrationPushURL, kEndPointUrl, "Push URL should match");
     Assert.equal(Services.prefs.getCharPref(LOOP_FXA_TOKEN_PREF), "",
                  "FXA pref should be cleared if token was invalid");
     Assert.equal(Services.prefs.getCharPref(LOOP_FXA_PROFILE_PREF), "",
                  "FXA profile pref should be cleared if token was invalid");
     Assert.ok(MozLoopServiceInternal.errors.has("login"),
               "Initialization error should have been reported to UI");
+    Assert.ok(MozLoopServiceInternal.errors.has("login"));
+    Assert.ok(MozLoopServiceInternal.errors.get("login").friendlyDetailsButtonCallback,
+              "Check that there is a retry callback");
   });
 });
 
 add_task(function test_initialize_with_fxa_token() {
   Services.prefs.setCharPref(LOOP_FXA_PROFILE_PREF, FAKE_FXA_PROFILE);
   Services.prefs.setCharPref(LOOP_FXA_TOKEN_PREF, FAKE_FXA_TOKEN_DATA);
 
   MozLoopService.errors.clear();
--- a/browser/components/places/content/browserPlacesViews.js
+++ b/browser/components/places/content/browserPlacesViews.js
@@ -1033,17 +1033,17 @@ PlacesToolbar.prototype = {
     let type = aChild.type;
     let button;
     if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
       button = document.createElement("toolbarseparator");
     }
     else {
       button = document.createElement("toolbarbutton");
       button.className = "bookmark-item";
-      button.setAttribute("label", aChild.title);
+      button.setAttribute("label", aChild.title || "");
       let icon = aChild.icon;
       if (icon)
         button.setAttribute("image",
                             PlacesUtils.getImageURLForResolution(window, icon));
 
       if (PlacesUtils.containerTypes.indexOf(type) != -1) {
         button.setAttribute("type", "menu");
         button.setAttribute("container", "true");
@@ -1861,17 +1861,17 @@ PlacesPanelMenuView.prototype = {
       button = document.createElement("toolbarseparator");
       button.setAttribute("class", "small-separator");
     }
     else {
       button = document.createElement("toolbarbutton");
       button.className = "bookmark-item";
       if (typeof this.options.extraClasses.entry == "string")
         button.classList.add(this.options.extraClasses.entry);
-      button.setAttribute("label", aChild.title);
+      button.setAttribute("label", aChild.title || "");
       let icon = aChild.icon;
       if (icon)
         button.setAttribute("image",
                             PlacesUtils.getImageURLForResolution(window, icon));
 
       if (PlacesUtils.containerTypes.indexOf(type) != -1) {
         button.setAttribute("container", "true");
 
--- 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',
--- a/browser/devtools/webaudioeditor/controller.js
+++ b/browser/devtools/webaudioeditor/controller.js
@@ -36,17 +36,17 @@ function shutdownWebAudioEditor() {
 
 /**
  * Functions handling target-related lifetime events.
  */
 let WebAudioEditorController = {
   /**
    * Listen for events emitted by the current tab target.
    */
-  initialize: function() {
+  initialize: Task.async(function* () {
     telemetry.toolOpened("webaudioeditor");
     this._onTabNavigated = this._onTabNavigated.bind(this);
     this._onThemeChange = this._onThemeChange.bind(this);
 
     gTarget.on("will-navigate", this._onTabNavigated);
     gTarget.on("navigate", this._onTabNavigated);
     gFront.on("start-context", this._onStartContext);
     gFront.on("create-node", this._onCreateNode);
@@ -55,17 +55,20 @@ let WebAudioEditorController = {
     gFront.on("disconnect-node", this._onDisconnectNode);
     gFront.on("change-param", this._onChangeParam);
     gFront.on("destroy-node", this._onDestroyNode);
 
     // Hook into theme change so we can change
     // the graph's marker styling, since we can't do this
     // with CSS
     gDevTools.on("pref-changed", this._onThemeChange);
-  },
+
+    // Store the AudioNode definitions from the WebAudioFront
+    AUDIO_NODE_DEFINITION = yield gFront.getDefinition();
+  }),
 
   /**
    * Remove events emitted by the current tab target.
    */
   destroy: function() {
     telemetry.toolClosed("webaudioeditor");
     gTarget.off("will-navigate", this._onTabNavigated);
     gTarget.off("navigate", this._onTabNavigated);
--- a/browser/devtools/webaudioeditor/includes.js
+++ b/browser/devtools/webaudioeditor/includes.js
@@ -10,26 +10,31 @@ Cu.import("resource://gre/modules/XPCOMU
 Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
 Cu.import("resource:///modules/devtools/gDevTools.jsm");
 
 const devtools = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
 const { require } = devtools;
 
 let { console } = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
 let { EventTarget } = require("sdk/event/target");
+
 const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
 const { Class } = require("sdk/core/heritage");
 const EventEmitter = require("devtools/toolkit/event-emitter");
 const STRINGS_URI = "chrome://browser/locale/devtools/webaudioeditor.properties"
 const L10N = new ViewHelpers.L10N(STRINGS_URI);
 const Telemetry = require("devtools/shared/telemetry");
 const telemetry = new Telemetry();
 devtools.lazyImporter(this, "LineGraphWidget",
   "resource:///modules/devtools/Graphs.jsm");
 
+// `AUDIO_NODE_DEFINITION` defined in the controller's initialization,
+// which describes all the properties of an AudioNode
+let AUDIO_NODE_DEFINITION;
+
 // Override DOM promises with Promise.jsm helpers
 const { defer, all } = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
 
 /* Events fired on `window` to indicate state or actions*/
 const EVENTS = {
   // Fired when the first AudioNode has been created, signifying
   // that the AudioContext is being used and should be tracked via the editor.
   START_CONTEXT: "WebAudioEditor:StartContext",
--- a/browser/devtools/webaudioeditor/models.js
+++ b/browser/devtools/webaudioeditor/models.js
@@ -30,16 +30,22 @@ const AudioNodeModel = Class({
   /**
    * After instantiating the AudioNodeModel, calling `setup` caches values
    * from the actor onto the model. In this case, only the type of audio node.
    *
    * @return promise
    */
   setup: Task.async(function* () {
     yield this.getType();
+
+    // Query bypass status on start up
+    this._bypassed = yield this.isBypassed();
+
+    // Store whether or not this node is bypassable in the first place
+    this.bypassable = !AUDIO_NODE_DEFINITION[this.type].unbypassable;
   }),
 
   /**
    * A proxy for the underlying AudioNodeActor to fetch its type
    * and subsequently assign the type to the instance.
    *
    * @return Promise->String
    */
@@ -71,16 +77,36 @@ const AudioNodeModel = Class({
    * Clears out all internal connection data. Emits "disconnect" event.
    */
   disconnect: function () {
     this.connections.length = 0;
     coreEmit(this, "disconnect", this);
   },
 
   /**
+   * Gets the bypass status of the audio node.
+   *
+   * @return Promise->Boolean
+   */
+  isBypassed: function () {
+    return this.actor.isBypassed();
+  },
+
+  /**
+   * Sets the bypass value of an AudioNode.
+   *
+   * @param Boolean enable
+   * @return Promise
+   */
+  bypass: function (enable) {
+    this._bypassed = enable;
+    return this.actor.bypass(enable).then(() => coreEmit(this, "bypass", this, enable));
+  },
+
+  /**
    * Returns a promise that resolves to an array of objects containing
    * both a `param` name property and a `value` property.
    *
    * @return Promise->Object
    */
   getParams: function () {
     return this.actor.getParams();
   },
@@ -101,17 +127,18 @@ const AudioNodeModel = Class({
    * the graph to be rendered.
    *
    * @param dagreD3.Digraph
    */
   addToGraph: function (graph) {
     graph.addNode(this.id, {
       type: this.type,
       label: this.type.replace(/Node$/, ""),
-      id: this.id
+      id: this.id,
+      bypassed: this._bypassed
     });
   },
 
   /**
    * Takes a `dagreD3.Digraph` object and adds edges to
    * the graph to be rendered. Separate from `addToGraph`,
    * as while we depend on D3/Dagre's constraints, we cannot
    * add edges for nodes that have not yet been added to the graph.
@@ -274,12 +301,12 @@ const AudioNodesCollection = Class({
   _onModelEvent: function (eventName, node, ...args) {
     if (eventName === "remove") {
       // If a `remove` event from the model, remove it
       // from the collection, and let the method handle the emitting on
       // the collection
       this.remove(node);
     } else {
       // Pipe the event to the collection
-      coreEmit(this, eventName, [node].concat(args));
+      coreEmit(this, eventName, node, ...args);
     }
   }
 });
--- a/browser/devtools/webaudioeditor/test/browser.ini
+++ b/browser/devtools/webaudioeditor/test/browser.ini
@@ -47,16 +47,17 @@ support-files =
 [browser_wa_graph-render-03.js]
 [browser_wa_graph-render-04.js]
 [browser_wa_graph-render-05.js]
 [browser_wa_graph-selected.js]
 [browser_wa_graph-zoom.js]
 
 [browser_wa_inspector.js]
 [browser_wa_inspector-toggle.js]
+[browser_wa_inspector-bypass-01.js]
 
 [browser_wa_properties-view.js]
 [browser_wa_properties-view-edit-01.js]
 skip-if = true # bug 1010423
 [browser_wa_properties-view-edit-02.js]
 skip-if = true # bug 1010423
 [browser_wa_properties-view-media-nodes.js]
 [browser_wa_properties-view-params.js]
--- a/browser/devtools/webaudioeditor/test/browser_audionode-actor-bypass.js
+++ b/browser/devtools/webaudioeditor/test/browser_audionode-actor-bypass.js
@@ -10,19 +10,27 @@ add_task(function*() {
   let [_, [destNode, oscNode, gainNode]] = yield Promise.all([
     front.setup({ reload: true }),
     get3(front, "create-node")
   ]);
 
   is((yield gainNode.isBypassed()), false, "Nodes start off unbypassed.");
 
   info("Calling node#bypass(true)");
-  yield gainNode.bypass(true);
+  let isBypassed = yield gainNode.bypass(true);
 
+  is(isBypassed, true, "node.bypass(true) resolves to true");
   is((yield gainNode.isBypassed()), true, "Node is now bypassed.");
 
   info("Calling node#bypass(false)");
-  yield gainNode.bypass(false);
+  isBypassed = yield gainNode.bypass(false);
+
+  is(isBypassed, false, "node.bypass(false) resolves to false");
+  is((yield gainNode.isBypassed()), false, "Node back to being unbypassed.");
 
-  is((yield gainNode.isBypassed()), false, "Node back to being unbypassed.");
+  info("Calling node#bypass(true) on unbypassable node");
+  isBypassed = yield destNode.bypass(true);
+
+  is(isBypassed, false, "node.bypass(true) resolves to false for unbypassable node");
+  is((yield gainNode.isBypassed()), false, "Unbypassable node is unaffect");
 
   yield removeTab(target.tab);
 });
--- a/browser/devtools/webaudioeditor/test/browser_wa_automation-view-01.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_automation-view-01.js
@@ -15,41 +15,42 @@ add_task(function*() {
 
   reload(target);
 
   let [actors] = yield Promise.all([
     get3(gFront, "create-node"),
     waitForGraphRendered(panelWin, 3, 2)
   ]);
   let nodeIds = actors.map(actor => actor.actorID);
+  let $tabbox = $("#web-audio-editor-tabs");
 
   // Oscillator node
   click(panelWin, findGraphNode(panelWin, nodeIds[1]));
   yield waitForInspectorRender(panelWin, EVENTS);
-  click(panelWin, $("#automation-tab"));
+  $tabbox.selectedIndex = 1;
 
   ok(isVisible($("#automation-graph-container")), "graph container should be visible");
   ok(isVisible($("#automation-content")), "automation content should be visible");
   ok(!isVisible($("#automation-no-events")), "no-events panel should not be visible");
   ok(!isVisible($("#automation-empty")), "empty panel should not be visible");
 
   // Gain node
   click(panelWin, findGraphNode(panelWin, nodeIds[2]));
   yield waitForInspectorRender(panelWin, EVENTS);
-  click(panelWin, $("#automation-tab"));
+  $tabbox.selectedIndex = 1;
 
-  ok(!isVisible($("#automation-graph-container")), "graph container should be visible");
-  ok(isVisible($("#automation-content")), "automation content should not be visible");
+  ok(!isVisible($("#automation-graph-container")), "graph container should not be visible");
+  ok(isVisible($("#automation-content")), "automation content should be visible");
   ok(isVisible($("#automation-no-events")), "no-events panel should be visible");
   ok(!isVisible($("#automation-empty")), "empty panel should not be visible");
 
   // destination node
   click(panelWin, findGraphNode(panelWin, nodeIds[0]));
   yield waitForInspectorRender(panelWin, EVENTS);
-  click(panelWin, $("#automation-tab"));
+  $tabbox.selectedIndex = 1;
 
   ok(!isVisible($("#automation-graph-container")), "graph container should not be visible");
   ok(!isVisible($("#automation-content")), "automation content should not be visible");
   ok(!isVisible($("#automation-no-events")), "no-events panel should not be visible");
   ok(isVisible($("#automation-empty")), "empty panel should be visible");
 
   yield teardown(target);
 });
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webaudioeditor/test/browser_wa_inspector-bypass-01.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that nodes are correctly bypassed when bypassing.
+ */
+
+add_task(function*() {
+  let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
+  let { panelWin } = panel;
+  let { gFront, $, $$, EVENTS, gAudioNodes } = panelWin;
+
+  reload(target);
+
+  let [actors] = yield Promise.all([
+    get3(gFront, "create-node"),
+    waitForGraphRendered(panelWin, 3, 2)
+  ]);
+  let nodeIds = actors.map(actor => actor.actorID);
+
+  click(panelWin, findGraphNode(panelWin, nodeIds[1]));
+  // Wait for the node to be set as well as the inspector to come fully into the view
+  yield Promise.all([
+    waitForInspectorRender(panelWin, EVENTS),
+    once(panelWin, EVENTS.UI_INSPECTOR_TOGGLED)
+  ]);
+
+  let $bypass = $("toolbarbutton.bypass");
+
+  is((yield actors[1].isBypassed()), false, "AudioNodeActor is not bypassed by default.")
+  is($bypass.checked, true, "Button is 'on' for normal nodes");
+  is($bypass.disabled, false, "Bypass button is not disabled for normal nodes");
+
+  command($bypass);
+  yield gAudioNodes.once("bypass");
+
+  is((yield actors[1].isBypassed()), true, "AudioNodeActor is bypassed.")
+  is($bypass.checked, false, "Button is 'off' when clicked");
+  is($bypass.disabled, false, "Bypass button is not disabled after click");
+  ok(findGraphNode(panelWin, nodeIds[1]).classList.contains("bypassed"),
+    "AudioNode has 'bypassed' class.");
+
+  command($bypass);
+  yield gAudioNodes.once("bypass");
+
+  is((yield actors[1].isBypassed()), false, "AudioNodeActor is no longer bypassed.")
+  is($bypass.checked, true, "Button is back on when clicked");
+  is($bypass.disabled, false, "Bypass button is not disabled after click");
+  ok(!findGraphNode(panelWin, nodeIds[1]).classList.contains("bypassed"),
+    "AudioNode no longer has 'bypassed' class.");
+
+  click(panelWin, findGraphNode(panelWin, nodeIds[0]));
+
+  yield once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET);
+
+  is((yield actors[0].isBypassed()), false, "Unbypassable AudioNodeActor is not bypassed.");
+  is($bypass.checked, false, "Button is 'off' for unbypassable nodes");
+  is($bypass.disabled, true, "Bypass button is disabled for unbypassable nodes");
+
+  command($bypass);
+  is((yield actors[0].isBypassed()), false,
+    "Clicking button on unbypassable node does not change bypass state on actor.");
+  is($bypass.checked, false, "Button is still 'off' for unbypassable nodes");
+  is($bypass.disabled, true, "Bypass button is still disabled for unbypassable nodes");
+
+  yield teardown(target);
+});
--- a/browser/devtools/webaudioeditor/test/browser_wa_inspector-toggle.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_inspector-toggle.js
@@ -29,18 +29,16 @@ add_task(function*() {
   yield once(panelWin, EVENTS.UI_INSPECTOR_TOGGLED);
 
   ok(InspectorView.isVisible(), "InspectorView shown after toggling.");
 
   ok(isVisible($("#web-audio-editor-details-pane-empty")),
     "InspectorView empty message should still be visible.");
   ok(!isVisible($("#web-audio-editor-tabs")),
     "InspectorView tabs view should still be hidden.");
-  is($("#web-audio-inspector-title").value, "AudioNode Inspector",
-    "Inspector should still have default title.");
 
   // Close inspector pane
   $("#inspector-pane-toggle").click();
   yield once(panelWin, EVENTS.UI_INSPECTOR_TOGGLED);
 
   ok(!InspectorView.isVisible(), "InspectorView back to being hidden.");
 
   // Open again to test node loading while open
@@ -54,13 +52,11 @@ add_task(function*() {
   let nodeSet = once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET);
   click(panelWin, findGraphNode(panelWin, nodeIds[1]));
   yield nodeSet;
 
   ok(!isVisible($("#web-audio-editor-details-pane-empty")),
     "Empty message hides even when loading node while open.");
   ok(isVisible($("#web-audio-editor-tabs")),
     "Switches to tab view when loading node while open.");
-  is($("#web-audio-inspector-title").value, "Oscillator",
-    "Inspector title updates when loading node while open.");
 
   yield teardown(target);
 });
--- a/browser/devtools/webaudioeditor/test/browser_wa_inspector.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_inspector.js
@@ -22,40 +22,32 @@ add_task(function*() {
   ]);
   let nodeIds = actors.map(actor => actor.actorID);
 
   ok(!InspectorView.isVisible(), "InspectorView hidden on start.");
   ok(isVisible($("#web-audio-editor-details-pane-empty")),
     "InspectorView empty message should show when no node's selected.");
   ok(!isVisible($("#web-audio-editor-tabs")),
     "InspectorView tabs view should be hidden when no node's selected.");
-  is($("#web-audio-inspector-title").value, "AudioNode Inspector",
-    "Inspector should have default title when empty.");
 
   // Wait for the node to be set as well as the inspector to come fully into the view
   let nodeSet = Promise.all([
     once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET),
     once(panelWin, EVENTS.UI_INSPECTOR_TOGGLED)
   ]);
   click(panelWin, findGraphNode(panelWin, nodeIds[1]));
   yield nodeSet;
 
   ok(InspectorView.isVisible(), "InspectorView shown once node selected.");
   ok(!isVisible($("#web-audio-editor-details-pane-empty")),
     "InspectorView empty message hidden when node selected.");
   ok(isVisible($("#web-audio-editor-tabs")),
     "InspectorView tabs view visible when node selected.");
 
-  is($("#web-audio-inspector-title").value, "Oscillator",
-    "Inspector should have the node title when a node is selected.");
-
   is($("#web-audio-editor-tabs").selectedIndex, 0,
     "default tab selected should be the parameters tab.");
 
   nodeSet = once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET);
   click(panelWin, findGraphNode(panelWin, nodeIds[2]));
   yield nodeSet;
 
-  is($("#web-audio-inspector-title").value, "Gain",
-    "Inspector title updates when a new node is selected.");
-
   yield teardown(target);
 });
--- a/browser/devtools/webaudioeditor/test/head.js
+++ b/browser/devtools/webaudioeditor/test/head.js
@@ -313,16 +313,22 @@ function findGraphNode (win, node) {
 function click (win, element) {
   EventUtils.sendMouseEvent({ type: "click" }, element, win);
 }
 
 function mouseOver (win, element) {
   EventUtils.sendMouseEvent({ type: "mouseover" }, element, win);
 }
 
+function command (button) {
+  let ev = button.ownerDocument.createEvent("XULCommandEvent");
+  ev.initCommandEvent("command", true, true, button.ownerDocument.defaultView, 0, false, false, false, false, null);
+  button.dispatchEvent(ev);
+}
+
 function isVisible (element) {
   return !element.getAttribute("hidden");
 }
 
 /**
  * Used in debugging, returns a promise that resolves in `n` milliseconds.
  */
 function wait (n) {
--- a/browser/devtools/webaudioeditor/views/context.js
+++ b/browser/devtools/webaudioeditor/views/context.js
@@ -31,39 +31,40 @@ const GRAPH_REDRAW_EVENTS = ["add", "con
 /**
  * Functions handling the graph UI.
  */
 let ContextView = {
   /**
    * Initialization function, called when the tool is started.
    */
   initialize: function() {
-    this._onGraphNodeClick = this._onGraphNodeClick.bind(this);
+    this._onGraphClick = this._onGraphClick.bind(this);
     this._onThemeChange = this._onThemeChange.bind(this);
     this._onStartContext = this._onStartContext.bind(this);
     this._onEvent = this._onEvent.bind(this);
 
     this.draw = debounce(this.draw.bind(this), GRAPH_DEBOUNCE_TIMER);
-    $('#graph-target').addEventListener('click', this._onGraphNodeClick, false);
+    $("#graph-target").addEventListener("click", this._onGraphClick, false);
 
     window.on(EVENTS.THEME_CHANGE, this._onThemeChange);
     window.on(EVENTS.START_CONTEXT, this._onStartContext);
     gAudioNodes.on("*", this._onEvent);
   },
 
   /**
    * Destruction function, called when the tool is closed.
    */
   destroy: function() {
     // If the graph was rendered at all, then the handler
     // for zooming in will be set. We must remove it to prevent leaks.
     if (this._zoomBinding) {
       this._zoomBinding.on("zoom", null);
     }
-    $('#graph-target').removeEventListener('click', this._onGraphNodeClick, false);
+    $("#graph-target").removeEventListener("click", this._onGraphClick, false);
+
     window.off(EVENTS.THEME_CHANGE, this._onThemeChange);
     window.off(EVENTS.START_CONTEXT, this._onStartContext);
     gAudioNodes.off("*", this._onEvent);
   },
 
   /**
    * Called when a page is reloaded and waiting for a "start-context" event
    * and clears out old content
@@ -122,68 +123,68 @@ let ContextView = {
   /**
    * Takes an actorID and returns the corresponding DOM SVG element in the graph
    */
   _getNodeByID: function (actorID) {
     return $(".nodes > g[data-id='" + actorID + "']");
   },
 
   /**
+   * Sets the appropriate class on an SVG node when its bypass
+   * status is toggled.
+   */
+  _bypassNode: function (node, enabled) {
+    let el = this._getNodeByID(node.id);
+    el.classList[enabled ? "add" : "remove"]("bypassed");
+  },
+
+  /**
    * This method renders the nodes currently available in `gAudioNodes` and is
    * throttled to be called at most every `GRAPH_DEBOUNCE_TIMER` milliseconds.
    * It's called whenever the audio context routing changes, after being debounced.
    */
   draw: function () {
     // Clear out previous SVG information
     this.clearGraph();
 
     let graph = new dagreD3.Digraph();
     let renderer = new dagreD3.Renderer();
     gAudioNodes.populateGraph(graph);
 
     // Post-render manipulation of the nodes
     let oldDrawNodes = renderer.drawNodes();
     renderer.drawNodes(function(graph, root) {
       let svgNodes = oldDrawNodes(graph, root);
-      svgNodes.attr("class", (n) => {
+      svgNodes.each(function (n) {
         let node = graph.node(n);
-        return "audionode type-" + node.type;
-      });
-      svgNodes.attr("data-id", (n) => {
-        let node = graph.node(n);
-        return node.id;
+        let classString = "audionode type-" + node.type + (node.bypassed ? " bypassed" : "");
+        this.setAttribute("class", classString);
+        this.setAttribute("data-id", node.id);
+        this.setAttribute("data-type", node.type);
       });
       return svgNodes;
     });
 
     // Post-render manipulation of edges
-    // TODO do all of this more efficiently, rather than
-    // using the direct D3 helper utilities to loop over each
-    // edge several times
     let oldDrawEdgePaths = renderer.drawEdgePaths();
+    let defaultClasses = "edgePath enter";
+
     renderer.drawEdgePaths(function(graph, root) {
       let svgEdges = oldDrawEdgePaths(graph, root);
-      svgEdges.attr("data-source", (n) => {
-        let edge = graph.edge(n);
-        return edge.source;
-      });
-      svgEdges.attr("data-target", (n) => {
-        let edge = graph.edge(n);
-        return edge.target;
-      });
-      svgEdges.attr("data-param", (n) => {
-        let edge = graph.edge(n);
-        return edge.param ? edge.param : null;
-      });
-      // We have to manually specify the default classes on the edges
-      // as to not overwrite them
-      let defaultClasses = "edgePath enter";
-      svgEdges.attr("class", (n) => {
-        let edge = graph.edge(n);
-        return defaultClasses + (edge.param ? (" param-connection " + edge.param) : "");
+      svgEdges.each(function (e) {
+        let edge = graph.edge(e);
+
+        // We have to manually specify the default classes on the edges
+        // as to not overwrite them
+        let edgeClass = defaultClasses + (edge.param ? (" param-connection " + edge.param) : "");
+
+        this.setAttribute("data-source", edge.source);
+        this.setAttribute("data-target", edge.target);
+        this.setAttribute("data-param", edge.param ? edge.param : null);
+        this.setAttribute("class", edgeClass);
       });
 
       return svgEdges;
     });
 
     // Override Dagre-d3's post render function by passing in our own.
     // This way we can leave styles out of it.
     renderer.postRender((graph, root) => {
@@ -258,16 +259,21 @@ let ContextView = {
     this.draw();
   },
 
   /**
    * Called when `gAudioNodes` fires an event -- most events (listed
    * in GRAPH_REDRAW_EVENTS) qualify as a redraw event.
    */
   _onEvent: function (eventName, ...args) {
+    // If bypassing, just toggle the class on the SVG node
+    // rather than rerendering everything
+    if (eventName === "bypass") {
+      this._bypassNode.apply(this, args);
+    }
     if (~GRAPH_REDRAW_EVENTS.indexOf(eventName)) {
       this.draw();
     }
   },
 
   /**
    * Fired when the devtools theme changes.
    */
@@ -275,22 +281,22 @@ let ContextView = {
     let markerColor = MARKER_STYLING[theme];
     let marker = $("#arrowhead");
     if (marker) {
       marker.setAttribute("style", "fill: " + markerColor);
     }
   },
 
   /**
-   * Fired when a node in the svg graph is clicked. Used to handle triggering the AudioNodePane.
+   * Fired when a click occurs in the graph.
    *
    * @param Event e
    *        Click event.
    */
-  _onGraphNodeClick: function (e) {
+  _onGraphClick: function (e) {
     let node = findGraphNodeParent(e.target);
     // If node not found (clicking outside of an audio node in the graph),
     // then ignore this event
     if (!node)
       return;
 
     let id = node.getAttribute("data-id");
 
--- a/browser/devtools/webaudioeditor/views/inspector.js
+++ b/browser/devtools/webaudioeditor/views/inspector.js
@@ -38,58 +38,67 @@ let InspectorView = {
     this.bindToggle();
 
     // Hide inspector view on startup
     this.hideImmediately();
 
     this._onNodeSelect = this._onNodeSelect.bind(this);
     this._onDestroyNode = this._onDestroyNode.bind(this);
     this._onResize = this._onResize.bind(this);
+    this._onCommandClick = this._onCommandClick.bind(this);
 
     this.splitter.addEventListener("mouseup", this._onResize);
+    for (let $el of $$("#audio-node-toolbar toolbarbutton")) {
+      $el.addEventListener("command", this._onCommandClick);
+    }
     window.on(EVENTS.UI_SELECT_NODE, this._onNodeSelect);
     gAudioNodes.on("remove", this._onDestroyNode);
   },
 
   /**
    * Destruction function called when the tool cleans up.
    */
   destroy: function () {
     this.unbindToggle();
     this.splitter.removeEventListener("mouseup", this._onResize);
+
+    $("#audio-node-toolbar toolbarbutton").removeEventListener("command", this._onCommandClick);
+    for (let $el of $$("#audio-node-toolbar toolbarbutton")) {
+      $el.removeEventListener("command", this._onCommandClick);
+    }
     window.off(EVENTS.UI_SELECT_NODE, this._onNodeSelect);
     gAudioNodes.off("remove", this._onDestroyNode);
 
     this.el = null;
     this.button = null;
     this.splitter = null;
   },
 
   /**
    * Takes a AudioNodeView `node` and sets it as the current
    * node and scaffolds the inspector view based off of the new node.
    */
-  setCurrentAudioNode: function (node) {
+  setCurrentAudioNode: Task.async(function* (node) {
     this._currentNode = node || null;
 
     // If no node selected, set the inspector back to "no AudioNode selected"
     // view.
     if (!node) {
       $("#web-audio-editor-details-pane-empty").removeAttribute("hidden");
       $("#web-audio-editor-tabs").setAttribute("hidden", "true");
       window.emit(EVENTS.UI_INSPECTOR_NODE_SET, null);
     }
     // Otherwise load up the tabs view and hide the empty placeholder
     else {
       $("#web-audio-editor-details-pane-empty").setAttribute("hidden", "true");
       $("#web-audio-editor-tabs").removeAttribute("hidden");
-      this._setTitle();
+      yield this._buildToolbar();
       window.emit(EVENTS.UI_INSPECTOR_NODE_SET, this._currentNode.id);
     }
-  },
+  }),
 
   /**
    * Returns the current AudioNodeView.
    */
   getCurrentAudioNode: function () {
     return this._currentNode;
   },
 
@@ -99,24 +108,35 @@ let InspectorView = {
   resetUI: function () {
     // Set current node to empty to load empty view
     this.setCurrentAudioNode();
 
     // Reset AudioNode inspector and hide
     this.hideImmediately();
   },
 
-  /**
-   * Sets the title of the Inspector view
-   */
-  _setTitle: function () {
-    let node = this._currentNode;
-    let title = node.type.replace(/Node$/, "");
-    $("#web-audio-inspector-title").setAttribute("value", title);
-  },
+  _buildToolbar: Task.async(function* () {
+    let node = this.getCurrentAudioNode();
+
+    let bypassable = node.bypassable;
+    let bypassed = yield node.isBypassed();
+    let button = $("#audio-node-toolbar .bypass");
+
+    if (!bypassable) {
+      button.setAttribute("disabled", true);
+    } else {
+      button.removeAttribute("disabled");
+    }
+
+    if (!bypassable || bypassed) {
+      button.removeAttribute("checked");
+    } else {
+      button.setAttribute("checked", true);
+    }
+  }),
 
   /**
    * Event handlers
    */
 
   /**
    * Called on EVENTS.UI_SELECT_NODE, and takes an actorID `id`
    * and calls `setCurrentAudioNode` to scaffold the inspector view.
@@ -135,10 +155,31 @@ let InspectorView = {
   /**
    * Called when `DESTROY_NODE` is fired to remove the node from props view if
    * it's currently selected.
    */
   _onDestroyNode: function (node) {
     if (this._currentNode && this._currentNode.id === node.id) {
       this.setCurrentAudioNode(null);
     }
+  },
+
+  _onCommandClick: function (e) {
+    let node = this.getCurrentAudioNode();
+    let button = e.target;
+    let command = button.getAttribute("data-command");
+    let checked = button.getAttribute("checked");
+
+    if (button.getAttribute("disabled")) {
+      return;
+    }
+
+    if (command === "bypass") {
+      if (checked) {
+        button.removeAttribute("checked");
+        node.bypass(true);
+      } else {
+        button.setAttribute("checked", true);
+        node.bypass(false);
+      }
+    }
   }
 };
--- a/browser/devtools/webaudioeditor/webaudioeditor.xul
+++ b/browser/devtools/webaudioeditor/webaudioeditor.xul
@@ -1,17 +1,17 @@
 <?xml version="1.0"?>
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 <?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/content/devtools/widgets.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/devtools/common.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/devtools/widgets.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/devtools/webaudioeditor.css" type="text/css"?>
-<?xml-stylesheet href="chrome://browser/content/devtools/widgets.css" type="text/css"?>
 <!DOCTYPE window [
   <!ENTITY % debuggerDTD SYSTEM "chrome://browser/locale/devtools/webaudioeditor.dtd">
   %debuggerDTD;
 ]>
 
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
   <script type="application/javascript;version=1.8"
@@ -74,26 +74,30 @@
                   xmlns:xlink="http://www.w3.org/1999/xlink">
                 <g id="graph-target" transform="translate(20,20)"/>
               </svg>
             </vbox>
           </box>
         </hbox>
         <splitter id="inspector-splitter" class="devtools-side-splitter"/>
         <vbox id="web-audio-inspector" hidden="true">
-          <hbox class="devtools-toolbar">
-            <label id="web-audio-inspector-title" value="&webAudioEditorUI.inspectorTitle;"></label>
-          </hbox>
           <deck id="web-audio-editor-details-pane" flex="1">
             <vbox id="web-audio-editor-details-pane-empty" flex="1">
               <label value="&webAudioEditorUI.inspectorEmpty;"></label>
             </vbox>
             <tabbox id="web-audio-editor-tabs"
                     class="devtools-sidebar-tabs"
                     handleCtrlTab="false">
+              <toolbar id="audio-node-toolbar" class="devtools-toolbar">
+                <hbox class="devtools-toolbarbutton-group">
+                  <toolbarbutton class="bypass devtools-toolbarbutton"
+                                 data-command="bypass"
+                                 tabindex="0"/>
+                </hbox>
+              </toolbar>
               <tabs>
                 <tab id="properties-tab"
                      label="&webAudioEditorUI.tab.properties;"/>
                 <tab id="automation-tab"
                      label="&webAudioEditorUI.tab.automation;"/>
               </tabs>
               <tabpanels flex="1">
                 <!-- Properties Panel -->
--- a/browser/locales/en-US/chrome/browser/devtools/webaudioeditor.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/webaudioeditor.properties
@@ -28,8 +28,12 @@ ToolboxWebAudioEditor1.tooltip=Web Audio
 # LOCALIZATION NOTE (collapseInspector): This is the tooltip for the button
 # that collapses the inspector in the web audio tool UI.
 collapseInspector=Collapse inspector
 
 # LOCALIZATION NOTE (expandInspector): This is the tooltip for the button
 # that expands the inspector in the web audio tool UI.
 expandInspector=Expand inspector
 
+# LOCALIZATION NOTE (webAudioEditorTooltipBypass): This is the tooltip for the
+# button that bypasses an AudioNode
+webAudioEditorTooltipBypass=Bypass AudioNode
+
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -224,16 +224,17 @@ browser.jar:
   skin/classic/browser/devtools/controls.png          (../shared/devtools/images/controls.png)
   skin/classic/browser/devtools/controls@2x.png       (../shared/devtools/images/controls@2x.png)
   skin/classic/browser/devtools/performance-icons.svg  (../shared/devtools/images/performance-icons.svg)
   skin/classic/browser/devtools/newtab.png             (../shared/devtools/images/newtab.png)
   skin/classic/browser/devtools/newtab@2x.png          (../shared/devtools/images/newtab@2x.png)
   skin/classic/browser/devtools/newtab-inverted.png    (../shared/devtools/images/newtab-inverted.png)
   skin/classic/browser/devtools/newtab-inverted@2x.png (../shared/devtools/images/newtab-inverted@2x.png)
 * skin/classic/browser/devtools/widgets.css           (devtools/widgets.css)
+  skin/classic/browser/devtools/power.svg                     (../shared/devtools/images/power.svg)
   skin/classic/browser/devtools/filetype-dir-close.svg        (../shared/devtools/images/filetypes/dir-close.svg)
   skin/classic/browser/devtools/filetype-dir-open.svg         (../shared/devtools/images/filetypes/dir-open.svg)
   skin/classic/browser/devtools/filetype-globe.svg            (../shared/devtools/images/filetypes/globe.svg)
   skin/classic/browser/devtools/filetype-store.svg            (../shared/devtools/images/filetypes/store.svg)
   skin/classic/browser/devtools/commandline-icon.png          (../shared/devtools/images/commandline-icon.png)
   skin/classic/browser/devtools/commandline-icon@2x.png       (../shared/devtools/images/commandline-icon@2x.png)
   skin/classic/browser/devtools/command-paintflashing.png     (../shared/devtools/images/command-paintflashing.png)
   skin/classic/browser/devtools/command-paintflashing@2x.png  (../shared/devtools/images/command-paintflashing@2x.png)
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -355,16 +355,17 @@ browser.jar:
   skin/classic/browser/devtools/controls.png                (../shared/devtools/images/controls.png)
   skin/classic/browser/devtools/controls@2x.png             (../shared/devtools/images/controls@2x.png)
   skin/classic/browser/devtools/performance-icons.svg       (../shared/devtools/images/performance-icons.svg)
   skin/classic/browser/devtools/newtab.png                  (../shared/devtools/images/newtab.png)
   skin/classic/browser/devtools/newtab@2x.png               (../shared/devtools/images/newtab@2x.png)
   skin/classic/browser/devtools/newtab-inverted.png         (../shared/devtools/images/newtab-inverted.png)
   skin/classic/browser/devtools/newtab-inverted@2x.png      (../shared/devtools/images/newtab-inverted@2x.png)
 * skin/classic/browser/devtools/widgets.css                 (devtools/widgets.css)
+  skin/classic/browser/devtools/power.svg                   (../shared/devtools/images/power.svg)
   skin/classic/browser/devtools/filetype-dir-close.svg      (../shared/devtools/images/filetypes/dir-close.svg)
   skin/classic/browser/devtools/filetype-dir-open.svg       (../shared/devtools/images/filetypes/dir-open.svg)
   skin/classic/browser/devtools/filetype-globe.svg          (../shared/devtools/images/filetypes/globe.svg)
   skin/classic/browser/devtools/filetype-store.svg          (../shared/devtools/images/filetypes/store.svg)
   skin/classic/browser/devtools/commandline-icon.png        (../shared/devtools/images/commandline-icon.png)
   skin/classic/browser/devtools/commandline-icon@2x.png     (../shared/devtools/images/commandline-icon@2x.png)
   skin/classic/browser/devtools/command-paintflashing.png     (../shared/devtools/images/command-paintflashing.png)
   skin/classic/browser/devtools/command-paintflashing@2x.png  (../shared/devtools/images/command-paintflashing@2x.png)
--- a/browser/themes/shared/devtools/filters.svg
+++ b/browser/themes/shared/devtools/filters.svg
@@ -8,9 +8,20 @@
 </filter>
 <filter id="invert-white" x="0%" y="0%" width="100%" height="100%" >
   <feComponentTransfer>
     <feFuncR type="table" tableValues=".6 0"/>
     <feFuncG type="table" tableValues=".6 0"/>
     <feFuncB type="table" tableValues=".6 0"/>
   </feComponentTransfer>
  </filter>
+
+  <!-- Web Audio Gradients -->
+  <linearGradient id="bypass-light" x1="6%" y1="8%" x2="12%" y2="12%" spreadMethod="repeat">
+    <stop offset="0%" stop-color="#f0f1f2"/> <!-- theme-toolbar-background -->
+    <stop offset="50%" stop-color="#fff"/>
+  </linearGradient>
+
+  <linearGradient id="bypass-dark" x1="6%" y1="8%" x2="12%" y2="12%" spreadMethod="repeat">
+    <stop offset="0%" stop-color="#343c45"/> <!-- theme-toolbar-background -->
+    <stop offset="50%" stop-color="transparent"/>
+  </linearGradient>
 </svg>
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/devtools/images/power.svg
@@ -0,0 +1,14 @@
+<!--
+Logo from raphaeljs.com, MIT License
+
+Copyright © 2008 Dmitry Baranovskiy
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+The software is provided “as is”, without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the software.
+-->
+<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg">
+  <path stroke="#edf0f1" d="m10.89891,2.50043c-0.49827,-0.24134 -1.09841,-0.03411 -1.34129,0.46514c-0.24185,0.49928 -0.03311,1.09942 0.46517,1.34128c1.56306,0.76071 2.64193,2.36094 2.64092,4.21555c-0.00501,2.58626 -2.09749,4.6787 -4.68322,4.68321c-2.58623,-0.005 -4.67869,-2.09746 -4.68371,-4.68321c-0.001,-1.85561 1.07834,-3.45731 2.64294,-4.21654c0.49928,-0.24185 0.7065,-0.84201 0.46514,-1.34129c-0.24185,-0.49825 -0.84098,-0.70697 -1.34029,-0.46513c-2.23396,1.08135 -3.77446,3.37351 -3.77545,6.02296c0.00099,3.69518 2.99518,6.68989 6.69138,6.69088c3.6957,-0.00099 6.69037,-2.9957 6.69089,-6.69088c-0.00102,-2.64846 -1.53948,-4.9391 -3.77247,-6.02197zm-2.91842,4.9346c0.55398,0 1.00309,-0.44861 1.00309,-1.00357l0,-4.68373c0,-0.55446 -0.44911,-1.00309 -1.00309,-1.00309c-0.555,0 -1.00358,0.44911 -1.00358,1.00309l0,4.68321c0,0.55499 0.44858,1.00409 1.00358,1.00409z" stroke-width="0" fill="#edf0f1"/>
+</svg>
--- a/browser/themes/shared/devtools/webaudioeditor.inc.css
+++ b/browser/themes/shared/devtools/webaudioeditor.inc.css
@@ -76,38 +76,69 @@ g.edgePath.param-connection {
 /* Audio Nodes */
 .nodes rect {
   stroke-width: 1px;
   cursor: pointer;
 }
 
 .nodes rect {
   stroke: var(--theme-tab-toolbar-background);
+}
+.theme-light rect {
+  fill: var(--theme-tab-toolbar-background);
+}
+.theme-dark rect {
   fill: var(--theme-toolbar-background);
 }
 
+/**
+ * Bypassed Nodes
+ */
+
+.theme-light .nodes g.bypassed rect {
+  fill: url(chrome://browser/skin/devtools/filters.svg#bypass-light);
+}
+.theme-dark .nodes g.bypassed rect {
+  fill: url(chrome://browser/skin/devtools/filters.svg#bypass-dark);
+}
+.nodes g.bypassed.selected rect {
+  stroke: var(--theme-selection-background);
+}
+
+/*
+.nodes g.bypassed text {
+  opacity: 0.8;
+}
+*/
+
+/**
+ * Selected Nodes
+ */
 .nodes g.selected rect {
   fill: var(--theme-selection-background);
 }
 
+/* Don't style bypassed nodes text different because it'd be illegible in light-theme */
+.theme-light g.selected:not(.bypassed) text {
+  fill: var(--theme-toolbar-background);
+}
+
+
 /* Text in nodes and edges */
 text {
   cursor: default; /* override the "text" cursor */
   font-weight: 300;
   font-family: "Helvetica Neue", Helvetica, Arial, sans-serf;
   font-size: 14px;
 }
 
 text {
   fill: var(--theme-body-color-alt);
 }
 
-.theme-light g.selected text {
-  fill: var(--theme-toolbar-background);
-}
 
 .nodes text {
   cursor: pointer;
 }
 
 /**
  * Inspector Styles
  */
@@ -174,16 +205,42 @@ text {
   }
 
   .web-audio-inspector .error {
     background-image: url(alerticon-warning@2x.png);
   }
 }
 
 /**
+ * Inspector toolbar
+ */
+
+#audio-node-toolbar .bypass {
+  list-style-image: url(power.svg);
+}
+
+#audio-node-toolbar toolbarbutton[disabled] {
+  opacity: 0.5;
+  background-color: transparent;
+}
+
+.theme-dark #audio-node-toolbar toolbarbutton[checked] {
+  background-color: #1d4f73; /* Select Highlight Blue */
+}
+.theme-light #audio-node-toolbar toolbarbutton[checked] {
+  background-color: #4c9ed9; /* Select Highlight Blue */
+}
+
+/* don't invert checked buttons so we can have white icons on light theme */
+#audio-node-toolbar toolbarbutton[checked] > .toolbarbutton-icon {
+  filter: none;
+}
+
+
+/**
  * Responsive Styles
  * `.devtools-responsive-container` takes care of most of
  * the changing of host types.
  */
 @media (max-width: 700px) {
   /**
    * Override the inspector toggle so it's always open
    * in the portrait view, with the toggle button hidden.
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -261,16 +261,17 @@ browser.jar:
         skin/classic/browser/devtools/controls.png                  (../shared/devtools/images/controls.png)
         skin/classic/browser/devtools/controls@2x.png               (../shared/devtools/images/controls@2x.png)
         skin/classic/browser/devtools/performance-icons.svg         (../shared/devtools/images/performance-icons.svg)
         skin/classic/browser/devtools/newtab.png                    (../shared/devtools/images/newtab.png)
         skin/classic/browser/devtools/newtab@2x.png                 (../shared/devtools/images/newtab@2x.png)
         skin/classic/browser/devtools/newtab-inverted.png           (../shared/devtools/images/newtab-inverted.png)
         skin/classic/browser/devtools/newtab-inverted@2x.png        (../shared/devtools/images/newtab-inverted@2x.png)
 *       skin/classic/browser/devtools/widgets.css                   (devtools/widgets.css)
+        skin/classic/browser/devtools/power.svg                     (../shared/devtools/images/power.svg)
         skin/classic/browser/devtools/filetype-dir-close.svg        (../shared/devtools/images/filetypes/dir-close.svg)
         skin/classic/browser/devtools/filetype-dir-open.svg         (../shared/devtools/images/filetypes/dir-open.svg)
         skin/classic/browser/devtools/filetype-globe.svg            (../shared/devtools/images/filetypes/globe.svg)
         skin/classic/browser/devtools/filetype-store.svg            (../shared/devtools/images/filetypes/store.svg)
         skin/classic/browser/devtools/commandline-icon.png          (../shared/devtools/images/commandline-icon.png)
         skin/classic/browser/devtools/commandline-icon@2x.png          (../shared/devtools/images/commandline-icon@2x.png)
         skin/classic/browser/devtools/alerticon-warning.png         (../shared/devtools/images/alerticon-warning.png)
         skin/classic/browser/devtools/alerticon-warning@2x.png      (../shared/devtools/images/alerticon-warning@2x.png)
@@ -724,16 +725,17 @@ browser.jar:
         skin/classic/aero/browser/devtools/controls.png              (../shared/devtools/images/controls.png)
         skin/classic/aero/browser/devtools/controls@2x.png           (../shared/devtools/images/controls@2x.png)
         skin/classic/aero/browser/devtools/performance-icons.svg     (../shared/devtools/images/performance-icons.svg)
         skin/classic/aero/browser/devtools/newtab.png                (../shared/devtools/images/newtab.png)
         skin/classic/aero/browser/devtools/newtab@2x.png             (../shared/devtools/images/newtab@2x.png)
         skin/classic/aero/browser/devtools/newtab-inverted.png       (../shared/devtools/images/newtab-inverted.png)
         skin/classic/aero/browser/devtools/newtab-inverted@2x.png    (../shared/devtools/images/newtab-inverted@2x.png)
 *       skin/classic/aero/browser/devtools/widgets.css               (devtools/widgets.css)
+        skin/classic/aero/browser/devtools/power.svg                 (../shared/devtools/images/power.svg)
         skin/classic/aero/browser/devtools/filetype-dir-close.svg    (../shared/devtools/images/filetypes/dir-close.svg)
         skin/classic/aero/browser/devtools/filetype-dir-open.svg     (../shared/devtools/images/filetypes/dir-open.svg)
         skin/classic/aero/browser/devtools/filetype-globe.svg        (../shared/devtools/images/filetypes/globe.svg)
         skin/classic/aero/browser/devtools/filetype-store.svg        (../shared/devtools/images/filetypes/store.svg)
         skin/classic/aero/browser/devtools/commandline-icon.png      (../shared/devtools/images/commandline-icon.png)
         skin/classic/aero/browser/devtools/commandline-icon@2x.png      (../shared/devtools/images/commandline-icon@2x.png)
         skin/classic/aero/browser/devtools/command-paintflashing.png    (../shared/devtools/images/command-paintflashing.png)
         skin/classic/aero/browser/devtools/command-paintflashing@2x.png (../shared/devtools/images/command-paintflashing@2x.png)
--- a/build/autoconf/android.m4
+++ b/build/autoconf/android.m4
@@ -34,17 +34,17 @@ MOZ_ARG_WITH_STRING(android-version,
                           android platform version, default] MIN_ANDROID_VERSION,
     android_version=$withval)
 
 if test $android_version -lt MIN_ANDROID_VERSION ; then
     AC_MSG_ERROR([--with-android-version must be at least MIN_ANDROID_VERSION.])
 fi
 
 case "$target" in
-arm-linux*-android*|*-linuxandroid*)
+arm-*linux*-android*|*-linuxandroid*)
     android_tool_prefix="arm-linux-androideabi"
     ;;
 i?86-*android*)
     android_tool_prefix="i686-linux-android"
     ;;
 mipsel-*android*)
     android_tool_prefix="mipsel-linux-android"
     ;;
--- a/dom/media/webrtc/MediaEngineTabVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineTabVideoSource.cpp
@@ -27,17 +27,17 @@
 namespace mozilla {
 
 using namespace mozilla::gfx;
 using dom::ConstrainLongRange;
 
 NS_IMPL_ISUPPORTS(MediaEngineTabVideoSource, nsIDOMEventListener, nsITimerCallback)
 
 MediaEngineTabVideoSource::MediaEngineTabVideoSource()
-: mMonitor("MediaEngineTabVideoSource"), mTabSource(nullptr)
+: mData(NULL), mDataSize(0), mMonitor("MediaEngineTabVideoSource"), mTabSource(nullptr)
 {
 }
 
 nsresult
 MediaEngineTabVideoSource::StartRunnable::Run()
 {
   mVideoSource->Draw();
   mVideoSource->mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
@@ -74,17 +74,16 @@ MediaEngineTabVideoSource::Notify(nsITim
   Draw();
   return NS_OK;
 }
 #define LOGTAG "TabVideo"
 
 nsresult
 MediaEngineTabVideoSource::InitRunnable::Run()
 {
-  mVideoSource->mData = (unsigned char*)malloc(mVideoSource->mBufW * mVideoSource->mBufH * 4);
   if (mVideoSource->mWindowId != -1) {
     nsCOMPtr<nsPIDOMWindow> window  = nsGlobalWindow::GetOuterWindowWithId(mVideoSource->mWindowId);
     if (window) {
       mVideoSource->mWindow = window;
     }
   }
   if (!mVideoSource->mWindow) {
     nsresult rv;
@@ -145,30 +144,20 @@ MediaEngineTabVideoSource::Allocate(cons
 
       if (!haveScrollWithPage && advanced[i].mScrollWithPage.WasPassed()) {
         mScrollWithPage = advanced[i].mScrollWithPage.Value();
         haveScrollWithPage = true;
       }
     }
   }
 
-  mBufW = aPrefs.GetWidth(false);
-  mBufH = aPrefs.GetHeight(false);
+  ConstrainLongRange defaultRange;
 
-  if (cWidth.mMin > mBufW) {
-    mBufW = cWidth.mMin;
-  } else if (cWidth.mMax < mBufW) {
-    mBufW = cWidth.mMax;
-  }
-
-  if (cHeight.mMin > mBufH) {
-    mBufH = cHeight.mMin;
-  } else if (cHeight.mMax < mBufH) {
-    mBufH = cHeight.mMax;
-  }
+  mBufWidthMax  = defaultRange.mMax > cWidth.mMax ? cWidth.mMax : aPrefs.GetWidth(false);
+  mBufHeightMax  = defaultRange.mMax > cHeight.mMax ? cHeight.mMax : aPrefs.GetHeight(false);
 
   mTimePerFrame = aPrefs.mFPS ? 1000 / aPrefs.mFPS : aPrefs.mFPS;
   return NS_OK;
 }
 
 nsresult
 MediaEngineTabVideoSource::Deallocate()
 {
@@ -208,45 +197,48 @@ MediaEngineTabVideoSource::NotifyPull(Me
     // This can fail if either a) we haven't added the track yet, or b)
     // we've removed or finished the track.
     aSource->AppendToTrack(aID, &(segment));
   }
 }
 
 void
 MediaEngineTabVideoSource::Draw() {
-
-  IntSize size(mBufW, mBufH);
-
-  nsresult rv;
-
   nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(mWindow);
 
   if (!win) {
     return;
   }
 
-  int32_t width, height;
-  win->GetInnerWidth(&width);
-  win->GetInnerHeight(&height);
+  int32_t innerWidth, innerHeight;
+  win->GetInnerWidth(&innerWidth);
+  win->GetInnerHeight(&innerHeight);
 
-  if (width == 0 || height == 0) {
+  if (innerWidth == 0 || innerHeight == 0) {
     return;
   }
 
-  int32_t srcW;
-  int32_t srcH;
+  IntSize size;
+  // maintain source aspect ratio
+  if (mBufWidthMax/innerWidth < mBufHeightMax/innerHeight) {
+    size = IntSize(mBufWidthMax, (mBufWidthMax * ((float) innerHeight/innerWidth)));
+  } else {
+    size = IntSize((mBufHeightMax * ((float) innerWidth/innerHeight)), mBufHeightMax);
+  }
 
-  float aspectRatio = ((float) size.width) / size.height;
-  if (width / aspectRatio < height) {
-    srcW = width;
-    srcH = width / aspectRatio;
-  } else {
-    srcW = height * aspectRatio;
-    srcH = height;
+  gfxImageFormat format = gfxImageFormat::RGB24;
+  uint32_t stride = gfxASurface::FormatStrideForWidth(format, size.width);
+
+  if (mDataSize < static_cast<size_t>(stride * size.height)) {
+    mDataSize = stride * size.height;
+    mData = static_cast<unsigned char*>(malloc(mDataSize));
+  }
+
+  if (!mData) {
+    return;
   }
 
   nsRefPtr<nsPresContext> presContext;
   nsIDocShell* docshell = win->GetDocShell();
   if (docshell) {
     docshell->GetPresContext(getter_AddRefs(presContext));
   }
   if (!presContext) {
@@ -254,39 +246,34 @@ MediaEngineTabVideoSource::Draw() {
   }
 
   nscolor bgColor = NS_RGB(255, 255, 255);
   nsCOMPtr<nsIPresShell> presShell = presContext->PresShell();
   uint32_t renderDocFlags = 0;
   if (!mScrollWithPage) {
     renderDocFlags |= nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING;
   }
-  nsRect r(0, 0, nsPresContext::CSSPixelsToAppUnits((float)srcW),
-           nsPresContext::CSSPixelsToAppUnits((float)srcH));
-
-  gfxImageFormat format = gfxImageFormat::RGB24;
-  uint32_t stride = gfxASurface::FormatStrideForWidth(format, size.width);
+  nsRect r(0, 0, nsPresContext::CSSPixelsToAppUnits((float)innerWidth),
+           nsPresContext::CSSPixelsToAppUnits((float)innerHeight));
 
   nsRefPtr<layers::ImageContainer> container = layers::LayerManager::CreateImageContainer();
   RefPtr<DrawTarget> dt =
     Factory::CreateDrawTargetForData(BackendType::CAIRO,
                                      mData.rwget(),
                                      size,
                                      stride,
                                      SurfaceFormat::B8G8R8X8);
   if (!dt) {
     return;
   }
   nsRefPtr<gfxContext> context = new gfxContext(dt);
-  context->SetMatrix(context->CurrentMatrix().Scale((float)size.width/srcW,
-                                                    (float)size.height/srcH));
+  context->SetMatrix(context->CurrentMatrix().Scale((((float) size.width)/innerWidth),
+                                                    (((float) size.height)/innerHeight)));
 
-  rv = presShell->RenderDocument(r, renderDocFlags, bgColor, context);
-
-  NS_ENSURE_SUCCESS_VOID(rv);
+  NS_ENSURE_SUCCESS_VOID(presShell->RenderDocument(r, renderDocFlags, bgColor, context));
 
   RefPtr<SourceSurface> surface = dt->Snapshot();
   if (!surface) {
     return;
   }
 
   layers::CairoImage::Data cairoData;
   cairoData.mSize = size;
--- a/dom/media/webrtc/MediaEngineTabVideoSource.h
+++ b/dom/media/webrtc/MediaEngineTabVideoSource.h
@@ -65,21 +65,22 @@ class MediaEngineTabVideoSource : public
       NS_IMETHOD Run();
       nsRefPtr<MediaEngineTabVideoSource> mVideoSource;
     };
 
 protected:
     ~MediaEngineTabVideoSource() {}
 
 private:
-    int mBufW;
-    int mBufH;
+    int mBufWidthMax;
+    int mBufHeightMax;
     int64_t mWindowId;
     bool mScrollWithPage;
     int mTimePerFrame;
     ScopedFreePtr<unsigned char> mData;
+    size_t mDataSize;
     nsCOMPtr<nsIDOMWindow> mWindow;
     nsRefPtr<layers::CairoImage> mImage;
     nsCOMPtr<nsITimer> mTimer;
     Monitor mMonitor;
     nsCOMPtr<nsITabSource> mTabSource;
   };
 }
--- a/extensions/spellcheck/locales/en-US/hunspell/dictionary-sources/upstream-hunspell.diff
+++ b/extensions/spellcheck/locales/en-US/hunspell/dictionary-sources/upstream-hunspell.diff
@@ -11174,254 +11174,256 @@ 46675,46676c52323
 < template/S
 ---
 > template/SM
 46752a52400
 > terabit/MS
 46753a52402,52403
 > terahertz/M
 > terapixel/MS
-46806,46807c52456
+46756a52407
+> teriyaki
+46806,46807c52457
 < test's/AFK
 < test/AKFCDGS
 ---
 > test/AKFCDGSM
-46817a52467
+46817a52468
 > testcase/MS
-46831a52482
+46831a52483
 > testsuite/MS
-46845a52497
+46845a52498
 > textbox/SM
-46925a52578
+46925a52579
 > theremin/MS
-46999c52652
+46999c52653
 < thinking's
 ---
 > thinking/M
-47095,47096c52748
+47095,47096c52749
 < throne's
 < throne/CDS
 ---
 > throne/CDSM
-47188,47189c52840
+47188,47189c52841
 < tie's
 < tie/AUSD
 ---
 > tie/AUSDM
-47213,47214c52864
+47213,47214c52865
 < till's
 < till/EDRZGS
 ---
 > till/EDRZGSM
-47303,47304c52953
+47303,47304c52954
 < tire's
 < tire/AGDS
 ---
 > tire/AGDSM
-47433,47434c53082
+47433,47434c53083
 < tone's
 < tone/IZGDRS
 ---
 > tone/IZGDRSM
-47453,47455c53101,53102
+47453,47455c53102,53103
 < tool's
 < tool/ADGS
 < toolbar
 ---
 > tool/ADGSM
 > toolbar/MS
-47540,47541c53187
+47540,47541c53188
 < tort's
 < tort/FEAS
 ---
 > tort/FEASM
-47644a53291
+47644a53292
 > traceur/SM
-47657,47658c53304
+47657,47658c53305
 < tract's
 < tract/CEKFAS
 ---
 > tract/CEKFASM
-47755a53402
+47755a53403
 > transfect/DSMG
-47774a53422,53423
+47774a53423,53424
 > transgenderism
 > transgene/MS
-47807,47808c53456
+47807,47808c53457
 < transmission's
 < transmission/AS
 ---
 > transmission/ASM
-47928,47929c53576
+47928,47929c53577
 < trench's
 < trench/AIGSD
 ---
 > trench/AIGSDM
-47951c53598
+47951c53599
 < triage/M
 ---
 > triage/MGS
-47976,47977c53623
+47976,47977c53624
 < tribute's
 < tribute/FS
 ---
 > tribute/FSM
-47997a53644
+47997a53645
 > trifecta/S
-48165,48166c53812
+48165,48166c53813
 < trust's/E
 < trust/IESGD
 ---
 > trust/IESGDM
-48180,48181c53826
+48180,48181c53827
 < try's
 < try/AGDS
 ---
 > try/AGDSM
-48271a53917
+48271a53918
 > turducken
-48334a53981
+48334a53982
 > tweep/S
-48371,48372c54018
+48371,48372c54019
 < twist's
 < twist/USDG
 ---
 > twist/USDGM
-48396,48397c54042
+48396,48397c54043
 < type's
 < type/AGDS
 ---
 > type/AGDSM
-48869a54515
+48869a54516
 > unlikeable
-49163,49164c54809
+49163,49164c54810
 < usual's
 < usual/UY
 ---
 > usual/UYM
-49211c54856
+49211c54857
 < vagina/M
 ---
 > vagina/MS
-49249,49250c54894
+49249,49250c54895
 < value's
 < value/CAGSD
 ---
 > value/CAGSDM
-49292,49293c54936
+49292,49293c54937
 < variant's
 < variant/IS
 ---
 > variant/ISM
-49356,49357c54999
+49356,49357c55000
 < veil's
 < veil/UDGS
 ---
 > veil/UDGSM
-49368,49369c55010
+49368,49369c55011
 < velour's
 < velours's
 ---
 > velour/MS
-49398,49399c55039
+49398,49399c55040
 < vent's
 < vent/DGS
 ---
 > vent/DGSM
-49435,49436c55075
+49435,49436c55076
 < verge's
 < verge/FDSG
 ---
 > verge/FDSGM
-49478a55118
+49478a55119
 > vertices
-49488,49489c55128
+49488,49489c55129
 < vest's
 < vest/ILDGS
 ---
 > vest/ILDGSM
-49681,49682c55320
+49681,49682c55321
 < visit's
 < visit/ASGD
 ---
 > visit/ASGDM
-49772a55411,55413
+49772a55412,55414
 > volcanological
 > volcanologist/MS
 > volcanology/M
-49807,49808c55448
+49807,49808c55449
 < vote's
 < vote/CGVDS
 ---
 > vote/CGVDSM
-50148a55789
+50148a55790
 > weaponize/DSG
-50215,50216c55856
+50215,50216c55857
 < weigh's
 < weigh/AGD
 ---
 > weigh/AGDM
-50260,50261d55899
+50260,50261d55900
 < werwolf/M
 < werwolves
-50555,50556c56193
+50555,50556c56194
 < wind's
 < wind/UASG
 ---
 > wind/UASGM
-50626,50627c56263
+50626,50627c56264
 < wire's
 < wire/AGDS
 ---
 > wire/AGDSM
-50728c56364
+50728c56365
 < women
 ---
 > women/M
-50794,50796c56430,56431
+50794,50796c56431,56432
 < wop/S!
 < word's
 < word/AJDSG
 ---
 > wop/MS!
 > word/AJDSGM
-50801c56436
+50801c56437
 < wording's
 ---
 > wording/M
-50808,50809c56443
+50808,50809c56444
 < work's
 < work/ADJSG
 ---
 > work/ADJSGM
-50824c56458
+50824c56459
 < working's
 ---
 > working/M
-50884,50885c56518
+50884,50885c56519
 < worthy's
 < worthy/UPRT
 ---
 > worthy/UPRTM
-50903,50904c56536
+50903,50904c56537
 < wrap's
 < wrap/US
 ---
 > wrap/USM
-50945c56577
+50945c56578
 < writing's
 ---
 > writing/M
-51118,51119c56750
+51118,51119c56751
 < yoke's
 < yoke/UGDS
 ---
 > yoke/UGDSM
-51212,51213c56843
+51212,51213c56844
 < zip's
 < zip/US
 ---
 > zip/USM
-51228,51229c56858
+51228,51229c56859
 < zone's
 < zone/AGDS
 ---
 > zone/AGDSM
--- a/extensions/spellcheck/locales/en-US/hunspell/en-US.dic
+++ b/extensions/spellcheck/locales/en-US/hunspell/en-US.dic
@@ -1,9 +1,9 @@
-57245
+57246
 0/nm
 0th/pt
 1/n1
 1st/p
 1th/tc
 2/nm
 2nd/p
 2th/tc
@@ -52752,16 +52752,17 @@ tepidness/M
 tequila/SM
 terabit/MS
 terabyte/MS
 terahertz/M
 terapixel/MS
 terbium/M
 tercentenary/SM
 tercentennial/MS
+teriyaki
 term/MDYGS
 termagant/MS
 terminable/IC
 terminal/MYS
 terminate/DSGNX
 terminated/U
 termination/CMS
 terminator/S
--- a/mobile/android/base/FilePickerResultHandler.java
+++ b/mobile/android/base/FilePickerResultHandler.java
@@ -245,17 +245,17 @@ class FilePickerResultHandler implements
         @Override
         public void onLoaderReset(Loader<Cursor> loader) { }
 
         /*Tabs.OnTabsChangedListener*/
         // This cleans up our temp file. If it doesn't run, we just hope that Android
         // will eventually does the cleanup for us.
         @Override
         public void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) {
-            if (tab.getId() != tabId) {
+            if ((tab == null) || (tab.getId() != tabId)) {
                 return;
             }
 
             if (msg == Tabs.TabEvents.LOCATION_CHANGE ||
                 msg == Tabs.TabEvents.CLOSED) {
                 ThreadUtils.postToBackgroundThread(new Runnable() {
                     @Override
                     public void run() {
--- a/mobile/android/chrome/content/aboutHealthReport.js
+++ b/mobile/android/chrome/content/aboutHealthReport.js
@@ -1,9 +1,8 @@
-#filter substitution
 // -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
 /* 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;
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -1,30 +1,30 @@
-#filter substitution
 // -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
 /* 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";
 
 let Cc = Components.classes;
 let Ci = Components.interfaces;
 let Cu = Components.utils;
 let Cr = Components.results;
 
+Cu.import("resource://gre/modules/AppConstants.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/AddonManager.jsm");
 Cu.import('resource://gre/modules/Payment.jsm');
 Cu.import("resource://gre/modules/NotificationDB.jsm");
 Cu.import("resource://gre/modules/SpatialNavigation.jsm");
 
-#ifdef ACCESSIBILITY
-Cu.import("resource://gre/modules/accessibility/AccessFu.jsm");
-#endif
+if (AppConstants.ACCESSIBILITY) {
+  Cu.import("resource://gre/modules/accessibility/AccessFu.jsm");
+}
 
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadNotifications",
                                   "resource://gre/modules/DownloadNotifications.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "JNI",
@@ -52,20 +52,20 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/LoginManagerContent.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerParent",
                                   "resource://gre/modules/LoginManagerParent.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
 
-#ifdef MOZ_SAFE_BROWSING
-XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing",
-                                  "resource://gre/modules/SafeBrowsing.jsm");
-#endif
+if (AppConstants.MOZ_SAFE_BROWSING) {
+  XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing",
+                                    "resource://gre/modules/SafeBrowsing.jsm");
+}
 
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Sanitizer",
                                   "resource://gre/modules/Sanitizer.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Prompt",
@@ -82,20 +82,20 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 
 XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
                                    "@mozilla.org/uuid-generator;1",
                                    "nsIUUIDGenerator");
 
 XPCOMUtils.defineLazyModuleGetter(this, "SimpleServiceDiscovery",
                                   "resource://gre/modules/SimpleServiceDiscovery.jsm");
 
-#ifdef NIGHTLY_BUILD
-XPCOMUtils.defineLazyModuleGetter(this, "ShumwayUtils",
-                                  "resource://shumway/ShumwayUtils.jsm");
-#endif
+if (AppConstants.NIGHTLY_BUILD) {
+  XPCOMUtils.defineLazyModuleGetter(this, "ShumwayUtils",
+                                    "resource://shumway/ShumwayUtils.jsm");
+}
 
 XPCOMUtils.defineLazyModuleGetter(this, "WebappManager",
                                   "resource://gre/modules/WebappManager.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "CharsetMenu",
                                   "resource://gre/modules/CharsetMenu.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "NetErrorHelper",
@@ -108,52 +108,57 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/SharedPreferences.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Notifications",
                                   "resource://gre/modules/Notifications.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "GMPInstallManager",
                                   "resource://gre/modules/GMPInstallManager.jsm");
 
-// Lazily-loaded browser scripts:
-[
+let lazilyLoadedBrowserScripts = [
   ["SelectHelper", "chrome://browser/content/SelectHelper.js"],
   ["InputWidgetHelper", "chrome://browser/content/InputWidgetHelper.js"],
   ["MasterPassword", "chrome://browser/content/MasterPassword.js"],
   ["PluginHelper", "chrome://browser/content/PluginHelper.js"],
   ["OfflineApps", "chrome://browser/content/OfflineApps.js"],
   ["Linkifier", "chrome://browser/content/Linkify.js"],
   ["ZoomHelper", "chrome://browser/content/ZoomHelper.js"],
   ["CastingApps", "chrome://browser/content/CastingApps.js"],
-#ifdef NIGHTLY_BUILD
-  ["WebcompatReporter", "chrome://browser/content/WebcompatReporter.js"],
-#endif
-].forEach(function (aScript) {
+];
+if (AppConstants.NIGHTLY_BUILD) {
+  lazilyLoadedBrowserScripts.push(
+    ["WebcompatReporter", "chrome://browser/content/WebcompatReporter.js"]);
+}
+
+lazilyLoadedBrowserScripts.forEach(function (aScript) {
   let [name, script] = aScript;
   XPCOMUtils.defineLazyGetter(window, name, function() {
     let sandbox = {};
     Services.scriptloader.loadSubScript(script, sandbox);
     return sandbox[name];
   });
 });
 
-[
-#ifdef MOZ_WEBRTC
-  ["WebrtcUI", ["getUserMedia:request", "recording-device-events"], "chrome://browser/content/WebrtcUI.js"],
-#endif
+let lazilyLoadedObserverScripts = [
   ["MemoryObserver", ["memory-pressure", "Memory:Dump"], "chrome://browser/content/MemoryObserver.js"],
   ["ConsoleAPI", ["console-api-log-event"], "chrome://browser/content/ConsoleAPI.js"],
   ["FindHelper", ["FindInPage:Opened", "FindInPage:Closed", "Tab:Selected"], "chrome://browser/content/FindHelper.js"],
   ["PermissionsHelper", ["Permissions:Get", "Permissions:Clear"], "chrome://browser/content/PermissionsHelper.js"],
   ["FeedHandler", ["Feeds:Subscribe"], "chrome://browser/content/FeedHandler.js"],
   ["Feedback", ["Feedback:Show"], "chrome://browser/content/Feedback.js"],
   ["SelectionHandler", ["TextSelection:Get"], "chrome://browser/content/SelectionHandler.js"],
   ["EmbedRT", ["GeckoView:ImportScript"], "chrome://browser/content/EmbedRT.js"],
   ["Reader", ["Reader:Added", "Reader:Removed", "Gesture:DoubleTap"], "chrome://browser/content/Reader.js"],
-].forEach(function (aScript) {
+];
+if (AppConstants.MOZ_WEBRTC) {
+  lazilyLoadedObserverScripts.push(
+    ["WebrtcUI", ["getUserMedia:request", "recording-device-events"], "chrome://browser/content/WebrtcUI.js"])
+}
+
+lazilyLoadedObserverScripts.forEach(function (aScript) {
   let [name, notifications, script] = aScript;
   XPCOMUtils.defineLazyGetter(window, name, function() {
     let sandbox = {};
     Services.scriptloader.loadSubScript(script, sandbox);
     return sandbox[name];
   });
   let observer = (s, t, d) => {
     Services.obs.removeObserver(observer, t);
@@ -222,20 +227,20 @@ XPCOMUtils.defineLazyServiceGetter(this,
   "@mozilla.org/parental-controls-service;1", "nsIParentalControlsService");
 
 XPCOMUtils.defineLazyServiceGetter(this, "DOMUtils",
   "@mozilla.org/inspector/dom-utils;1", "inIDOMUtils");
 
 XPCOMUtils.defineLazyServiceGetter(window, "URIFixup",
   "@mozilla.org/docshell/urifixup;1", "nsIURIFixup");
 
-#ifdef MOZ_WEBRTC
-XPCOMUtils.defineLazyServiceGetter(this, "MediaManagerService",
-  "@mozilla.org/mediaManagerService;1", "nsIMediaManagerService");
-#endif
+if (AppConstants.MOZ_WEBRTC) {
+  XPCOMUtils.defineLazyServiceGetter(this, "MediaManagerService",
+    "@mozilla.org/mediaManagerService;1", "nsIMediaManagerService");
+}
 
 const kStateActive = 0x00000001; // :active pseudoclass for elements
 
 const kXLinkNamespace = "http://www.w3.org/1999/xlink";
 
 const kDefaultCSSViewportWidth = 980;
 const kDefaultCSSViewportHeight = 480;
 
@@ -282,22 +287,16 @@ function fuzzyEquals(a, b) {
 /**
  * Convert a font size to CSS pixels (px) from twentieiths-of-a-point
  * (twips).
  */
 function convertFromTwipsToPx(aSize) {
   return aSize/240 * 16.0;
 }
 
-#ifdef MOZ_CRASHREPORTER
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-XPCOMUtils.defineLazyServiceGetter(this, "CrashReporter",
-  "@mozilla.org/xre/app-info;1", "nsICrashReporter");
-#endif
-
 XPCOMUtils.defineLazyGetter(this, "ContentAreaUtils", function() {
   let ContentAreaUtils = {};
   Services.scriptloader.loadSubScript("chrome://global/content/contentAreaUtils.js", ContentAreaUtils);
   return ContentAreaUtils;
 });
 
 XPCOMUtils.defineLazyModuleGetter(this, "Rect",
                                   "resource://gre/modules/Geometry.jsm");
@@ -377,27 +376,28 @@ var BrowserApp = {
 
           // Delay this a minute because there's no rush
           setTimeout(() => {
             BrowserApp.gmpInstallManager = new GMPInstallManager();
             BrowserApp.gmpInstallManager.simpleCheckAndInstall().then(null, () => {});
           }, 1000 * 60);
         }, Ci.nsIThread.DISPATCH_NORMAL);
 
-#ifdef MOZ_SAFE_BROWSING
-        Services.tm.mainThread.dispatch(function() {
-          // Bug 778855 - Perf regression if we do this here. To be addressed in bug 779008.
-          SafeBrowsing.init();
-        }, Ci.nsIThread.DISPATCH_NORMAL);
-#endif
-#ifdef NIGHTLY_BUILD
-        WebcompatReporter.init();
-        Telemetry.addData("TRACKING_PROTECTION_ENABLED",
-          Services.prefs.getBoolPref("privacy.trackingprotection.enabled"));
-#endif
+        if (AppConstants.MOZ_SAFE_BROWSING) {
+          Services.tm.mainThread.dispatch(function() {
+            // Bug 778855 - Perf regression if we do this here. To be addressed in bug 779008.
+            SafeBrowsing.init();
+          }, Ci.nsIThread.DISPATCH_NORMAL);
+        }
+
+        if (AppConstants.NIGHTLY_BUILD) {
+          WebcompatReporter.init();
+          Telemetry.addData("TRACKING_PROTECTION_ENABLED",
+            Services.prefs.getBoolPref("privacy.trackingprotection.enabled"));
+        }
       } catch(ex) { console.log(ex); }
     }, false);
 
     BrowserEventHandler.init();
     ViewportHandler.init();
 
     Services.androidBridge.browserApp = this;
 
@@ -478,22 +478,22 @@ var BrowserApp = {
     Cu.import("resource://gre/modules/Webapps.jsm");
     DOMApplicationRegistry.allAppsLaunchable = true;
     RemoteDebugger.init();
     UserAgentOverrides.init();
     DesktopUserAgent.init();
     Distribution.init();
     Tabs.init();
     SearchEngines.init();
-#ifdef ACCESSIBILITY
-    AccessFu.attach(window);
-#endif
-#ifdef NIGHTLY_BUILD
-    ShumwayUtils.init();
-#endif
+    if (AppConstants.ACCESSIBILITY) {
+      AccessFu.attach(window);
+    }
+    if (AppConstants.NIGHTLY_BUILD) {
+      ShumwayUtils.init();
+    }
 
     let url = null;
     if ("arguments" in window) {
       if (window.arguments[0])
         url = window.arguments[0];
       if (window.arguments[1])
         gScreenWidth = window.arguments[1];
       if (window.arguments[2])
@@ -543,17 +543,17 @@ var BrowserApp = {
   get _startupStatus() {
     delete this._startupStatus;
 
     let savedMilestone = null;
     try {
       savedMilestone = Services.prefs.getCharPref("browser.startup.homepage_override.mstone");
     } catch (e) {
     }
-#expand    let ourMilestone = "__MOZ_APP_VERSION__";
+    let ourMilestone = AppConstants.MOZ_APP_VERSION;
     this._startupStatus = "";
     if (ourMilestone != savedMilestone) {
       Services.prefs.setCharPref("browser.startup.homepage_override.mstone", ourMilestone);
       this._startupStatus = savedMilestone ? "upgrade" : "new";
     }
 
     return this._startupStatus;
   },
@@ -1330,24 +1330,25 @@ var BrowserApp = {
           prefs.push(pref);
           continue;
         // Handle master password
         case "privacy.masterpassword.enabled":
           pref.type = "bool";
           pref.value = MasterPassword.enabled;
           prefs.push(pref);
           continue;
-#ifdef MOZ_CRASHREPORTER
         // Crash reporter submit pref must be fetched from nsICrashReporter service.
         case "datareporting.crashreporter.submitEnabled":
-          pref.type = "bool";
-          pref.value = CrashReporter.submitReports;
-          prefs.push(pref);
+          let crashReporterBuilt = "nsICrashReporter" in Ci && Services.appinfo instanceof Ci.nsICrashReporter;
+          if (crashReporterBuilt) {
+            pref.type = "bool";
+            pref.value = Services.appinfo.submitReports;
+            prefs.push(pref);
+          }
           continue;
-#endif
       }
 
       try {
         switch (Services.prefs.getPrefType(prefName)) {
           case Ci.nsIPrefBranch.PREF_BOOL:
             pref.type = "bool";
             pref.value = Services.prefs.getBoolPref(prefName);
             break;
@@ -1416,22 +1417,24 @@ var BrowserApp = {
           MasterPassword.setPassword(json.value);
         return;
 
       // Enabling or disabling suggestions will prevent future prompts
       case SearchEngines.PREF_SUGGEST_ENABLED:
         Services.prefs.setBoolPref(SearchEngines.PREF_SUGGEST_PROMPTED, true);
         break;
 
-#ifdef MOZ_CRASHREPORTER
       // Crash reporter preference is in a service; set and return.
       case "datareporting.crashreporter.submitEnabled":
-        CrashReporter.submitReports = json.value;
+        let crashReporterBuilt = "nsICrashReporter" in Ci && Services.appinfo instanceof Ci.nsICrashReporter;
+        if (crashReporterBuilt) {
+          Services.appinfo.submitReports = json.value;
+        }
         return;
-#endif
+
       // When sending to Java, we normalized special preferences that use
       // integers and strings to represent booleans. Here, we convert them back
       // to their actual types so we can store them.
       case "browser.chrome.titlebarMode":
       case "network.cookie.cookieBehavior":
       case "font.size.inflation.minTwips":
       case "home.sync.updateMode":
         json.type = "int";
@@ -3061,26 +3064,24 @@ var DesktopUserAgent = {
     // See https://developer.mozilla.org/en/Gecko_user_agent_string_reference
     this.DESKTOP_UA = Cc["@mozilla.org/network/protocol;1?name=http"]
                         .getService(Ci.nsIHttpProtocolHandler).userAgent
                         .replace(/Android; [a-zA-Z]+/, "X11; Linux x86_64")
                         .replace(/Gecko\/[0-9\.]+/, "Gecko/20100101");
   },
 
   onRequest: function(channel, defaultUA) {
-#ifdef NIGHTLY_BUILD
-    if (this.TCO_DOMAIN == channel.URI.host) {
+    if (AppConstants.NIGHTLY_BUILD && this.TCO_DOMAIN == channel.URI.host) {
       // Force the referrer
       channel.referrer = channel.URI;
 
       // Send a bot-like UA to t.co to get a real redirect. We strip off the
       // "Gecko/x.y Firefox/x.y" part
       return defaultUA.replace(this.TCO_REPLACE, "");
     }
-#endif
 
     let channelWindow = this._getWindowForRequest(channel);
     let tab = BrowserApp.getTabForWindow(channelWindow);
     if (tab) {
       return this.getUserAgentForTab(tab);
     }
 
     return null;
@@ -5863,22 +5864,19 @@ var FormAssistant = {
 /**
  * An object to watch for Gecko status changes -- add-on installs, pref changes
  * -- and reflect them back to Java.
  */
 let HealthReportStatusListener = {
   PREF_ACCEPT_LANG: "intl.accept_languages",
   PREF_BLOCKLIST_ENABLED: "extensions.blocklist.enabled",
 
-  PREF_TELEMETRY_ENABLED:
-#ifdef MOZ_TELEMETRY_REPORTING
-    "toolkit.telemetry.enabled",
-#else
+  PREF_TELEMETRY_ENABLED: AppConstants.MOZ_TELEMETRY_REPORTING ?
+    "toolkit.telemetry.enabled" :
     null,
-#endif
 
   init: function () {
     try {
       AddonManager.addAddonListener(this);
     } catch (ex) {
       console.log("Failed to initialize add-on status listener. FHR cannot report add-on state. " + ex);
     }
 
--- a/mobile/android/chrome/jar.mn
+++ b/mobile/android/chrome/jar.mn
@@ -19,28 +19,28 @@ chrome.jar:
   content/aboutDownloads.js            (content/aboutDownloads.js)
   content/aboutFeedback.xhtml          (content/aboutFeedback.xhtml)
   content/aboutFeedback.js             (content/aboutFeedback.js)
   content/aboutPrivateBrowsing.xhtml   (content/aboutPrivateBrowsing.xhtml)
   content/aboutPrivateBrowsing.js      (content/aboutPrivateBrowsing.js)
   content/Reader.js                    (content/Reader.js)
   content/aboutHome.xhtml              (content/aboutHome.xhtml)
   content/aboutRights.xhtml            (content/aboutRights.xhtml)
-* content/aboutApps.xhtml              (content/aboutApps.xhtml)
-* content/aboutApps.js                 (content/aboutApps.js)
+  content/aboutApps.xhtml              (content/aboutApps.xhtml)
+  content/aboutApps.js                 (content/aboutApps.js)
   content/blockedSite.xhtml            (content/blockedSite.xhtml)
   content/languages.properties         (content/languages.properties)
   content/browser.xul                  (content/browser.xul)
-* content/browser.js                   (content/browser.js)
+  content/browser.js                   (content/browser.js)
   content/bindings/checkbox.xml        (content/bindings/checkbox.xml)
   content/bindings/settings.xml        (content/bindings/settings.xml)
   content/netError.xhtml               (content/netError.xhtml)
   content/SelectHelper.js              (content/SelectHelper.js)
   content/SelectionHandler.js          (content/SelectionHandler.js)
-* content/WebappRT.js                  (content/WebappRT.js)
+  content/WebappRT.js                  (content/WebappRT.js)
   content/EmbedRT.js                   (content/EmbedRT.js)
   content/InputWidgetHelper.js         (content/InputWidgetHelper.js)
   content/WebrtcUI.js                  (content/WebrtcUI.js)
   content/MemoryObserver.js            (content/MemoryObserver.js)
   content/ConsoleAPI.js                (content/ConsoleAPI.js)
   content/PluginHelper.js              (content/PluginHelper.js)
   content/OfflineApps.js               (content/OfflineApps.js)
   content/MasterPassword.js            (content/MasterPassword.js)
@@ -48,17 +48,17 @@ chrome.jar:
   content/PermissionsHelper.js         (content/PermissionsHelper.js)
   content/FeedHandler.js               (content/FeedHandler.js)
   content/Feedback.js                  (content/Feedback.js)
   content/Linkify.js                   (content/Linkify.js)
   content/ZoomHelper.js                (content/ZoomHelper.js)
   content/CastingApps.js               (content/CastingApps.js)
 #ifdef MOZ_SERVICES_HEALTHREPORT
   content/aboutHealthReport.xhtml      (content/aboutHealthReport.xhtml)
-* content/aboutHealthReport.js         (content/aboutHealthReport.js)
+  content/aboutHealthReport.js         (content/aboutHealthReport.js)
 #endif
 #ifdef MOZ_DEVICES
   content/aboutDevices.xhtml           (content/aboutDevices.xhtml)
   content/aboutDevices.js              (content/aboutDevices.js)
 #endif
 #ifdef NIGHTLY_BUILD
   content/aboutPasswords.xhtml         (content/aboutPasswords.xhtml)
   content/aboutPasswords.js            (content/aboutPasswords.js)
--- a/mobile/android/components/AboutRedirector.js
+++ b/mobile/android/components/AboutRedirector.js
@@ -1,15 +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/. */
 const Cc = Components.classes;
 const Ci = Components.interfaces;
+const Cu = Components.utils;
 
-Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/AppConstants.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 let modules = {
   // about:
   "": {
     uri: "chrome://browser/content/about.xhtml",
     privileged: true
   },
 
@@ -25,21 +27,19 @@ let modules = {
   // about:blank has some bad loading behavior we can avoid, if we use an alias
   empty: {
     uri: "about:blank",
     privileged: false,
     hide: true
   },
 
   rights: {
-#ifdef MOZ_OFFICIAL_BRANDING
-    uri: "chrome://browser/content/aboutRights.xhtml",
-#else
-    uri: "chrome://global/content/aboutRights-unbranded.xhtml",
-#endif
+    uri: AppConstants.MOZ_OFFICIAL_BRANDING ?
+      "chrome://browser/content/aboutRights.xhtml" :
+      "chrome://global/content/aboutRights-unbranded.xhtml",
     privileged: false
   },
   blocked: {
     uri: "chrome://browser/content/blockedSite.xhtml",
     privileged: false,
     hide: true
   },
   certerror: {
@@ -67,34 +67,35 @@ let modules = {
   feedback: {
     uri: "chrome://browser/content/aboutFeedback.xhtml",
     privileged: true
   },
   privatebrowsing: {
     uri: "chrome://browser/content/aboutPrivateBrowsing.xhtml",
     privileged: true
   },
-#ifdef MOZ_SERVICES_HEALTHREPORT
-  healthreport: {
+}
+
+if (AppConstants.MOZ_SERVICES_HEALTHREPORT) {
+  modules['healthreport'] = {
     uri: "chrome://browser/content/aboutHealthReport.xhtml",
     privileged: true
-  },
-#endif
-#ifdef MOZ_DEVICES
-  devices: {
+  };
+}
+if (AppConstants.MOZ_DEVICES) {
+  modules['devices'] = {
     uri: "chrome://browser/content/aboutDevices.xhtml",
     privileged: true
-  },
-#endif
-#ifdef NIGHTLY_BUILD
-  passwords: {
+  };
+}
+if (AppConstants.NIGHTLY_BUILD) {
+  modules['passwords'] = {
     uri: "chrome://browser/content/aboutPasswords.xhtml",
     privileged: true
-  }
-#endif
+  };
 }
 
 function AboutRedirector() {}
 AboutRedirector.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutModule]),
   classID: Components.ID("{322ba47e-7047-4f71-aebf-cb7d69325cd9}"),
 
   _getModuleInfo: function (aURI) {
--- a/mobile/android/components/DirectoryProvider.js
+++ b/mobile/android/components/DirectoryProvider.js
@@ -1,18 +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/. */
 
-#filter substitution
-
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
+Cu.import("resource://gre/modules/AppConstants.jsm");
 Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 // -----------------------------------------------------------------------
 // Directory Provider for special browser folders and files
 // -----------------------------------------------------------------------
 
@@ -22,23 +21,23 @@ const NS_APP_SEARCH_DIR_LIST  = "SrchPlu
 const NS_APP_USER_SEARCH_DIR  = "UsrSrchPlugns";
 const NS_XPCOM_CURRENT_PROCESS_DIR = "XCurProcD";
 const XRE_APP_DISTRIBUTION_DIR = "XREAppDist";
 const XRE_UPDATE_ROOT_DIR     = "UpdRootD";
 const ENVVAR_UPDATE_DIR       = "UPDATES_DIRECTORY";
 const WEBAPPS_DIR             = "webappsDir";
 const DOWNLOAD_DIR            = "DfltDwnld";
 
-const SYSTEM_DIST_PATH = "/system/@ANDROID_PACKAGE_NAME@/distribution";
+const SYSTEM_DIST_PATH = `/system/${AppConstants.ANDROID_PACKAGE_NAME}/distribution`;
 
 function DirectoryProvider() {}
 
 DirectoryProvider.prototype = {
   classID: Components.ID("{ef0f7a87-c1ee-45a8-8d67-26f586e46a4b}"),
-  
+
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIDirectoryServiceProvider,
                                          Ci.nsIDirectoryServiceProvider2]),
 
   getFile: function(prop, persistent) {
     if (prop == NS_APP_CACHE_PARENT_DIR) {
       let dirsvc = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
       let profile = dirsvc.get("ProfD", Ci.nsIFile);
       return profile;
@@ -167,9 +166,8 @@ DirectoryProvider.prototype = {
       getNext: function() {
         return result.shift();
       }
     };
   }
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DirectoryProvider]);
-
--- a/mobile/android/components/SessionStore.js
+++ b/mobile/android/components/SessionStore.js
@@ -5,21 +5,16 @@
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
-#ifdef MOZ_CRASHREPORTER
-XPCOMUtils.defineLazyServiceGetter(this, "CrashReporter",
-  "@mozilla.org/xre/app-info;1", "nsICrashReporter");
-#endif
-
 XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Messaging", "resource://gre/modules/Messaging.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 function dump(a) {
   Services.console.logStringMessage(a);
 }
@@ -560,36 +555,38 @@ SessionStore.prototype = {
                       createInstance(Ci.nsIConverterOutputStream);
       converter.init(foStream, "UTF-8", 0, 0);
       converter.writeString(aData);
       converter.close();
     }
   },
 
   _updateCrashReportURL: function ss_updateCrashReportURL(aWindow) {
-#ifdef MOZ_CRASHREPORTER
+    let crashReporterBuilt = "nsICrashReporter" in Ci && Services.appinfo instanceof Ci.nsICrashReporter;
+    if (!crashReporterBuilt)
+      return;
+
     if (!aWindow.BrowserApp.selectedBrowser)
       return;
 
     try {
       let currentURI = aWindow.BrowserApp.selectedBrowser.currentURI.clone();
       // if the current URI contains a username/password, remove it
       try {
         currentURI.userPass = "";
       }
       catch (ex) { } // ignore failures on about: URIs
 
-      CrashReporter.annotateCrashReport("URL", currentURI.spec);
+      Services.appinfo.annotateCrashReport("URL", currentURI.spec);
     }
     catch (ex) {
       // don't make noise when crashreporter is built but not enabled
-      if (ex.result != Components.results.NS_ERROR_NOT_INITIALIZED)
-        Components.utils.reportError("SessionStore:" + ex);
+      if (ex.result != Cr.NS_ERROR_NOT_INITIALIZED)
+        Cu.reportError("SessionStore:" + ex);
     }
-#endif
   },
 
   _serializeHistoryEntry: function _serializeHistoryEntry(aEntry) {
     let entry = { url: aEntry.URI.spec };
 
     if (aEntry.title && aEntry.title != entry.url)
       entry.title = aEntry.title;
 
--- a/mobile/android/components/Sidebar.js
+++ b/mobile/android/components/Sidebar.js
@@ -1,11 +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/.
+/* 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 Cc = Components.classes;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
--- a/mobile/android/components/moz.build
+++ b/mobile/android/components/moz.build
@@ -6,44 +6,44 @@
 
 XPIDL_SOURCES += [
     'SessionStore.idl',
 ]
 
 XPIDL_MODULE = 'MobileComponents'
 
 EXTRA_COMPONENTS += [
+    'AboutRedirector.js',
     'ActivitiesGlue.js',
     'AddonUpdateService.js',
     'BlocklistPrompt.js',
+    'BrowserCLH.js',
     'ColorPicker.js',
     'ContentDispatchChooser.js',
     'ContentPermissionPrompt.js',
+    'DirectoryProvider.js',
     'FilePicker.js',
+    'HelperAppDialog.js',
     'LoginManagerPrompter.js',
     'NSSDialogService.js',
     'PromptService.js',
+    'SessionStore.js',
+    'Sidebar.js',
     'SiteSpecificUserAgent.js',
     'Snippets.js',
     'TabSource.js',
     'WebappsUpdateTimer.js',
     'XPIDialogService.js',
 ]
 
 if CONFIG['MOZ_PAY']:
     EXTRA_COMPONENTS += [
         'PaymentProviderStrategy.js',
         'PaymentsUI.js'
     ]
 
+# Keep it this way if at all possible.  If you need preprocessing,
+# consider adding fields to AppConstants.jsm.
 EXTRA_PP_COMPONENTS += [
-    'AboutRedirector.js',
-    'BrowserCLH.js',
-    'DirectoryProvider.js',
-    'HelperAppDialog.js',
     'MobileComponents.manifest',
-    'SessionStore.js',
-    'Sidebar.js',
 ]
 
-DEFINES['ANDROID_PACKAGE_NAME'] = CONFIG['ANDROID_PACKAGE_NAME']
-
 DIRS += ['build']
new file mode 100644
--- /dev/null
+++ b/mobile/android/modules/AppConstants.jsm
@@ -0,0 +1,97 @@
+#filter substitution;
+/* 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";
+
+this.EXPORTED_SYMBOLS = ["AppConstants"];
+
+// Immutable for export.
+let AppConstants = Object.freeze({
+  // See this wiki page for more details about channel specific build
+  // defines: https://wiki.mozilla.org/Platform/Channel-specific_build_defines
+  NIGHTLY_BUILD:
+#ifdef NIGHTLY_BUILD
+  true,
+#else
+  false,
+#endif
+
+  RELEASE_BUILD:
+#ifdef RELEASE_BUILD
+  true,
+#else
+  false,
+#endif
+
+  ACCESSIBILITY:
+#ifdef MOZ_ACCESSIBILITY
+  true,
+#else
+  false,
+#endif
+
+  // Official corresponds, roughly, to whether this build is performed
+  // on Mozilla's continuous integration infrastructure. You should
+  // disable developer-only functionality when this flag is set.
+  MOZILLA_OFFICIAL:
+#ifdef MOZILLA_OFFICIAL
+  true,
+#else
+  false,
+#endif
+
+  MOZ_OFFICIAL_BRANDING:
+#ifdef MOZ_OFFICIAL_BRANDING
+  true,
+#else
+  false,
+#endif
+
+  MOZ_SERVICES_HEALTHREPORT:
+#ifdef MOZ_SERVICES_HEALTHREPORT
+  true,
+#else
+  false,
+#endif
+
+  MOZ_DEVICES:
+#ifdef MOZ_DEVICES
+  true,
+#else
+  false,
+#endif
+
+  MOZ_DEVICES:
+#ifdef MOZ_DEVICES
+  true,
+#else
+  false,
+#endif
+
+  MOZ_SAFE_BROWSING:
+#ifdef MOZ_SAFE_BROWSING
+  true,
+#else
+  false,
+#endif
+
+  MOZ_TELEMETRY_REPORTING:
+#ifdef MOZ_TELEMETRY_REPORTING
+  true,
+#else
+  false,
+#endif
+
+  MOZ_WEBRTC:
+#ifdef MOZ_WEBRTC
+  true,
+#else
+  false,
+#endif
+
+  MOZ_APP_VERSION: "@MOZ_APP_VERSION@",
+
+  ANDROID_PACKAGE_NAME: "@ANDROID_PACKAGE_NAME@",
+});
--- a/mobile/android/modules/TabMirror.jsm
+++ b/mobile/android/modules/TabMirror.jsm
@@ -36,40 +36,26 @@ let TabMirror = function(deviceId, windo
 TabMirror.prototype = {
   _window: null,
   _screenSize: { width: 1280, height: 720 },
   _pc: null,
   _start: function() {
     this._pc.onicecandidate = this._onIceCandidate.bind(this);
 
     let windowId = this._window.BrowserApp.selectedBrowser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
-    let viewport = this._window.BrowserApp.selectedTab.getViewport();
-    let maxWidth =  Math.max(viewport.cssWidth, viewport.width);
-    let maxHeight = Math.max(viewport.cssHeight, viewport.height);
-
-    let videoWidth = 0;
-    let videoHeight = 0;
-    if (this._screenSize.width/this._screenSize.height < maxWidth / maxHeight) {
-      videoWidth = this._screenSize.width;
-      videoHeight = Math.ceil(videoWidth * maxHeight / maxWidth);
-    } else {
-      videoHeight = this._screenSize.height;
-      videoWidth = Math.ceil(videoHeight * maxWidth / maxHeight);
-    }
-
     let constraints = {
       video: {
         mediaSource: "browser",
         browserWindow: windowId,
         scrollWithPage: true,
         advanced: [
-          { width: { min: videoWidth, max: videoWidth },
-            height: { min: videoHeight, max: videoHeight }
+          { width: { min: 0, max: this._screenSize.width },
+            height: { min: 0, max: this._screenSize.height }
           },
-          { aspectRatio: maxWidth / maxHeight }
+          { aspectRatio: this._screenSize.width / this._screenSize.height }
         ]
       }
     };
 
     this._window.navigator.mozGetUserMedia(constraints, this._onGumSuccess.bind(this), this._onGumFailure.bind(this));
   },
 
   _processMessage: function(data) {
--- a/mobile/android/modules/moz.build
+++ b/mobile/android/modules/moz.build
@@ -22,14 +22,26 @@ EXTRA_JS_MODULES += [
     'Notifications.jsm',
     'OrderedBroadcast.jsm',
     'PageActions.jsm',
     'Prompt.jsm',
     'Sanitizer.jsm',
     'SharedPreferences.jsm',
     'SSLExceptions.jsm',
     'TabMirror.jsm',
+    'WebappManager.jsm',
     'WebappManagerWorker.js',
 ]
 
+for var in ('ANDROID_PACKAGE_NAME', 'MOZ_APP_VERSION'):
+    DEFINES[var] = CONFIG[var]
+
+for var in ('NIGHTLY_BUILD', 'RELEASE_BUILD', 'MOZ_ACCESSIBILITY',
+    'MOZILLA_OFFICIAL', 'MOZ_OFFICIAL_BRANDING', 'MOZ_SERVICES_HEALTHREPORT',
+    'MOZ_DEVICES', 'MOZ_DEVICES', 'MOZ_SAFE_BROWSING',
+    'MOZ_TELEMETRY_REPORTING', 'MOZ_WEBRTC'):
+    if CONFIG[var]:
+        DEFINES[var] = 1
+
+# Keep it this way if at all possible.
 EXTRA_PP_JS_MODULES += [
-    'WebappManager.jsm',
+    'AppConstants.jsm',
 ]
--- a/mobile/android/themes/core/jar.mn
+++ b/mobile/android/themes/core/jar.mn
@@ -15,17 +15,17 @@ chrome.jar:
   skin/aboutDevices.css                     (aboutDevices.css)
 #endif
 * skin/aboutDownloads.css                   (aboutDownloads.css)
   skin/aboutFeedback.css                    (aboutFeedback.css)
 #ifdef MOZ_SERVICES_HEALTHREPORT
   skin/aboutHealthReport.css                (aboutHealthReport.css)
 #endif
   skin/aboutMemory.css                      (aboutMemory.css)
-* skin/aboutPrivateBrowsing.css             (aboutPrivateBrowsing.css)
+  skin/aboutPrivateBrowsing.css             (aboutPrivateBrowsing.css)
   skin/aboutReader.css                      (aboutReader.css)
   skin/aboutSupport.css                     (aboutSupport.css)
   skin/browser.css                          (browser.css)
 * skin/content.css                          (content.css)
   skin/config.css                           (config.css)
   skin/touchcontrols.css                    (touchcontrols.css)
   skin/netError.css                         (netError.css)
 % override chrome://global/skin/about.css chrome://browser/skin/about.css
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -1553,16 +1553,33 @@ nsHttpChannel::ProcessResponse()
         cacheDisposition = kCacheHitViaReval;
     else
         cacheDisposition = kCacheMissedViaReval;
 
     AccumulateCacheHitTelemetry(cacheDisposition);
     Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_VERSION,
                           mResponseHead->Version());
 
+#if defined(ANDROID) || defined(MOZ_B2G)
+    if (gHttpHandler->IsTelemetryEnabled()) {
+      // Gather telemetry on being sent a WAP content-type
+      // This check will catch (at least) the following content types:
+      // application/wml+xml, application/vnd.wap.xhtml+xml,
+      // text/vnd.wap.wml, application/vnd.wap.wmlc
+      bool isWap = false;
+      if (!mResponseHead->ContentType().IsEmpty() && (
+          mResponseHead->ContentType().Find(".wap") != -1 ||
+          mResponseHead->ContentType().Find("/wml") != -1)) {
+        isWap = true;
+      }
+
+      Telemetry::Accumulate(Telemetry::HTTP_WAP_CONTENT_TYPE_RECEIVED, isWap);
+    }
+#endif
+
     return rv;
 }
 
 nsresult
 nsHttpChannel::ContinueProcessResponse(nsresult rv)
 {
     bool doNotRender = DoNotRender3xxBody(rv);
 
--- a/testing/profiles/prefs_general.js
+++ b/testing/profiles/prefs_general.js
@@ -257,16 +257,17 @@ user_pref("dom.mozApps.debug", true);
 // Enable apps customizations
 user_pref("dom.apps.customization.enabled", true);
 
 // Don't fetch or send directory tiles data from real servers
 user_pref("browser.newtabpage.directory.source", 'data:application/json,{"testing":1}');
 user_pref("browser.newtabpage.directory.ping", "");
 
 // Enable Loop
+user_pref("loop.debug.loglevel", "All");
 user_pref("loop.enabled", true);
 user_pref("loop.throttled", false);
 user_pref("loop.oauth.google.URL", "http://%(server)s/browser/browser/components/loop/test/mochitest/google_service.sjs?action=");
 user_pref("loop.oauth.google.getContactsURL", "http://%(server)s/browser/browser/components/loop/test/mochitest/google_service.sjs?action=contacts");
 user_pref("loop.oauth.google.getGroupsURL", "http://%(server)s/browser/browser/components/loop/test/mochitest/google_service.sjs?action=groups");
 user_pref("loop.server", "http://%(server)s/browser/browser/components/loop/test/mochitest/loop_fxa.sjs?");
 user_pref("loop.CSP","default-src 'self' about: file: chrome: data: wss://* http://* https://*");
 
--- a/toolkit/components/places/tests/unifiedcomplete/test_searchEngine_host.js
+++ b/toolkit/components/places/tests/unifiedcomplete/test_searchEngine_host.js
@@ -47,17 +47,18 @@ add_task(function* test_searchEngine_aut
   // Add an uri that matches the search string with high frecency.
   let uri = NetUtil.newURI("http://www.example.com/my/");
   let visits = [];
   for (let i = 0; i < 100; ++i) {
     visits.push({ uri , title: "Terms - SearchEngine Search" });
   }
   yield promiseAddVisits(visits);
   addBookmark({ uri: uri, title: "Example bookmark" });
-  ok(frecencyForUrl(uri) > 10000, "Adeded URI should have expected high frecency");
+  yield promiseAsyncUpdates();
+  ok(frecencyForUrl(uri) > 10000, "Added URI should have expected high frecency");
 
   do_log_info("Check search domain is autoFilled even if there's an higher frecency match");
   yield check_autocomplete({
     search: "my",
     autofilled: "my.search.com",
     completed: "http://my.search.com"
   });
 
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -1205,16 +1205,21 @@
     "kind": "boolean",
     "description": "Whether a HTTP transaction was routed via Alt-Svc or not."
   },
   "HTTP_TRANSACTION_USE_ALTSVC_OE": {
     "expires_in_version": "never",
     "kind": "boolean",
     "description": "Whether a HTTP transaction routed via Alt-Svc was scheme=http"
   },
+  "HTTP_WAP_CONTENT_TYPE_RECEIVED": {
+    "expires_in_version": "40",
+    "kind": "boolean",
+    "description": "Whether a WAP content type response is served to the browser."
+  },
   "SSL_HANDSHAKE_VERSION": {
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 16,
     "description": "SSL Version (0=ssl3, 1=tls1, 2=tls1.1, 3=tls1.2)"
   },
   "SSL_TIME_UNTIL_READY": {
     "expires_in_version": "never",
--- a/toolkit/devtools/server/actors/webaudio.js
+++ b/toolkit/devtools/server/actors/webaudio.js
@@ -39,118 +39,123 @@ const AUTOMATION_METHODS = [
 ];
 
 const NODE_ROUTING_METHODS = [
   "connect", "disconnect"
 ];
 
 const NODE_PROPERTIES = {
   "OscillatorNode": {
-    "type": {},
-    "frequency": {
-      "param": true
-    },
-    "detune": {
-      "param": true
+    "properties": {
+      "type": {},
+      "frequency": {
+        "param": true
+      },
+      "detune": {
+        "param": true
+      }
     }
   },
   "GainNode": {
-    "gain": {
-      "param": true
-    }
+    "properties": { "gain": { "param": true }}
   },
   "DelayNode": {
-    "delayTime": {
-      "param": true
-    }
+    "properties": { "delayTime": { "param": true }}
   },
   // TODO deal with figuring out adding `detune` AudioParam
   // for AudioBufferSourceNode, which is in the spec
   // but not yet added in implementation
   // bug 1116852
   "AudioBufferSourceNode": {
-    "buffer": { "Buffer": true },
-    "playbackRate": {
-      "param": true,
-    },
-    "loop": {},
-    "loopStart": {},
-    "loopEnd": {}
+    "properties": {
+      "buffer": { "Buffer": true },
+      "playbackRate": {
+        "param": true
+      },
+      "loop": {},
+      "loopStart": {},
+      "loopEnd": {}
+    }
   },
   "ScriptProcessorNode": {
-    "bufferSize": { "readonly": true }
+    "properties": { "bufferSize": { "readonly": true }}
   },
   "PannerNode": {
-    "panningModel": {},
-    "distanceModel": {},
-    "refDistance": {},
-    "maxDistance": {},
-    "rolloffFactor": {},
-    "coneInnerAngle": {},
-    "coneOuterAngle": {},
-    "coneOuterGain": {}
+    "properties": {
+      "panningModel": {},
+      "distanceModel": {},
+      "refDistance": {},
+      "maxDistance": {},
+      "rolloffFactor": {},
+      "coneInnerAngle": {},
+      "coneOuterAngle": {},
+      "coneOuterGain": {}
+    }
   },
   "ConvolverNode": {
-    "buffer": { "Buffer": true },
-    "normalize": {},
+    "properties": {
+      "buffer": { "Buffer": true },
+      "normalize": {},
+    }
   },
   "DynamicsCompressorNode": {
-    "threshold": {
-      "param": true
-    },
-    "knee": {
-      "param": true
-    },
-    "ratio": {
-      "param": true
-    },
-    "reduction": {},
-    "attack": {
-      "param": true
-    },
-    "release": {
-      "param": true
+    "properties": {
+      "threshold": { "param": true },
+      "knee": { "param": true },
+      "ratio": { "param": true },
+      "reduction": {},
+      "attack": { "param": true },
+      "release": { "param": true }
     }
   },
   "BiquadFilterNode": {
-    "type": {},
-    "frequency": {
-      "param": true
-    },
-    "Q": {
-      "param": true
-    },
-    "detune": {
-      "param": true
-    },
-    "gain": {
-      "param": true
+    "properties": {
+      "type": {},
+      "frequency": { "param": true },
+      "Q": { "param": true },
+      "detune": { "param": true },
+      "gain": { "param": true }
     }
   },
   "WaveShaperNode": {
-    "curve": { "Float32Array": true },
-    "oversample": {}
+    "properties": {
+      "curve": { "Float32Array": true },
+      "oversample": {}
+    }
   },
   "AnalyserNode": {
-    "fftSize": {},
-    "minDecibels": {},
-    "maxDecibels": {},
-    "smoothingTimeConstant": {},
-    "frequencyBinCount": { "readonly": true },
+    "properties": {
+      "fftSize": {},
+      "minDecibels": {},
+      "maxDecibels": {},
+      "smoothingTimeConstant": {},
+      "frequencyBinCount": { "readonly": true },
+    }
   },
-  "AudioDestinationNode": {},
-  "ChannelSplitterNode": {},
-  "ChannelMergerNode": {},
+  "AudioDestinationNode": {
+    "unbypassable": true
+  },
+  "ChannelSplitterNode": {
+    "unbypassable": true
+  },
+  "ChannelMergerNode": {
+    "unbypassable": true
+  },
   "MediaElementAudioSourceNode": {},
   "MediaStreamAudioSourceNode": {},
   "MediaStreamAudioDestinationNode": {
-    "stream": { "MediaStream": true }
+    "unbypassable": true,
+    "properties": {
+      "stream": { "MediaStream": true }
+    }
   },
   "StereoPannerNode": {
-    "pan": {}
+    "properties": {
+      "pan": {}
+    }
   }
 };
 
 /**
  * An Audio Node actor allowing communication to a specific audio node in the
  * Audio Context graph.
  */
 types.addActorType("audionode");
@@ -179,17 +184,17 @@ let AudioNodeActor = exports.AudioNodeAc
 
     try {
       this.type = getConstructorName(node);
     } catch (e) {
       this.type = "";
     }
 
     // Create automation timelines for all AudioParams
-    Object.keys(NODE_PROPERTIES[this.type])
+    Object.keys(NODE_PROPERTIES[this.type].properties || {})
       .filter(isAudioParam.bind(null, node))
       .forEach(paramName => {
         this.automation[paramName] = new AutomationTimeline(node[paramName].defaultValue);
       });
   },
 
   /**
    * Returns the name of the audio type.
@@ -218,40 +223,49 @@ let AudioNodeActor = exports.AudioNodeAc
    * @return Boolean
    */
   isBypassed: method(function () {
     let node = this.node.get();
     if (node === null) {
       return false;
     }
 
-    return node.passThrough;
+    // Cast to boolean incase `passThrough` is undefined,
+    // like for AudioDestinationNode
+    return !!node.passThrough;
   }, {
     response: { bypassed: RetVal("boolean") }
   }),
 
   /**
    * Takes a boolean, either enabling or disabling the "passThrough" option
    * on an AudioNode. If a node is bypassed, an effects processing node (like gain, biquad),
-   * will allow the audio stream to pass through the node, unaffected.
+   * will allow the audio stream to pass through the node, unaffected. Returns
+   * the bypass state of the node.
    *
    * @param Boolean enable
    *        Whether the bypass value should be set on or off.
+   * @return Boolean
    */
   bypass: method(function (enable) {
     let node = this.node.get();
 
     if (node === null) {
       return;
     }
 
-    node.passThrough = enable;
+    let bypassable = !NODE_PROPERTIES[this.type].unbypassable;
+    if (bypassable) {
+      node.passThrough = enable;
+    }
+
+    return this.isBypassed();
   }, {
     request: { enable: Arg(0, "boolean") },
-    oneway: true
+    response: { bypassed: RetVal("boolean") }
   }),
 
   /**
    * Changes a param on the audio node. Responds with either `undefined`
    * on success, or a description of the error upon param set failure.
    *
    * @param String param
    *        Name of the AudioParam to change.
@@ -326,28 +340,28 @@ let AudioNodeActor = exports.AudioNodeAc
    * Get an object containing key-value pairs of additional attributes
    * to be consumed by a front end, like if a property should be read only,
    * or is a special type (Float32Array, Buffer, etc.)
    *
    * @param String param
    *        Name of the AudioParam whose flags are desired.
    */
   getParamFlags: method(function (param) {
-    return (NODE_PROPERTIES[this.type] || {})[param];
+    return ((NODE_PROPERTIES[this.type] || {}).properties || {})[param];
   }, {
     request: { param: Arg(0, "string") },
     response: { flags: RetVal("nullable:primitive") }
   }),
 
   /**
    * Get an array of objects each containing a `param` and `value` property,
    * corresponding to a property name and current value of the audio node.
    */
-  getParams: method(function () {
-    let props = Object.keys(NODE_PROPERTIES[this.type]);
+  getParams: method(function (param) {
+    let props = Object.keys(NODE_PROPERTIES[this.type].properties || {});
     return props.map(prop =>
       ({ param: prop, value: this.getParam(prop), flags: this.getParamFlags(prop) }));
   }, {
     response: { params: RetVal("json") }
   }),
 
   /**
    * Connects this audionode to an AudioParam via `node.connect(param)`.
@@ -596,16 +610,26 @@ let WebAudioActor = exports.WebAudioActo
   },
 
   destroy: function(conn) {
     protocol.Actor.prototype.destroy.call(this, conn);
     this.finalize();
   },
 
   /**
+   * Returns definition of all AudioNodes, such as AudioParams, and
+   * flags.
+   */
+  getDefinition: method(function () {
+    return NODE_PROPERTIES;
+  }, {
+    response: { definition: RetVal("json") }
+  }),
+
+  /**
    * Starts waiting for the current tab actor's document global to be
    * created, in order to instrument the Canvas context and become
    * aware of everything the content does with Web Audio.
    *
    * See ContentObserver and WebAudioInstrumenter for more details.
    */
   setup: method(function({ reload }) {
     // Used to track when something is happening with the web audio API
@@ -805,17 +829,17 @@ let WebAudioActor = exports.WebAudioActo
 
   /**
    * Takes an XrayWrapper node, and attaches the node's `nativeID`
    * to the AudioParams as `_parentID`, as well as the the type of param
    * as a string on `_paramName`.
    */
   _instrumentParams: function (node) {
     let type = getConstructorName(node);
-    Object.keys(NODE_PROPERTIES[type])
+    Object.keys(NODE_PROPERTIES[type].properties || {})
       .filter(isAudioParam.bind(null, node))
       .forEach(paramName => {
         let param = node[paramName];
         param._parentID = node.id;
         param._paramName = paramName;
       });
   },
 
--- a/toolkit/devtools/webconsole/network-helper.js
+++ b/toolkit/devtools/webconsole/network-helper.js
@@ -484,19 +484,19 @@ let NetworkHelper = {
 
   /**
    * Takes a securityInfo object of nsIRequest, the nsIRequest itself and
    * extracts security information from them.
    *
    * @param object securityInfo
    *        The securityInfo object of a request. If null channel is assumed
    *        to be insecure.
-   * @param nsIRequest request
-   *        The nsIRequest object for the request used to dig more information
-   *        about this request.
+   * @param object httpActivity
+   *        The httpActivity object for the request with at least members
+   *        { private, hostname }.
    *
    * @return object
    *         Returns an object containing following members:
    *          - state: The security of the connection used to fetch this
    *                   request. Has one of following string values:
    *                    * "insecure": the connection was not secure (only http)
    *                    * "broken": secure connection failed (e.g. expired cert)
    *                    * "secure": the connection was properly secured.
@@ -505,17 +505,17 @@ let NetworkHelper = {
    *          If state == secure:
    *            - protocolVersion: one of SSLv3, TLSv1, TLSv1.1, TLSv1.2.
    *            - cipherSuite: the cipher suite used in this connection.
    *            - cert: information about certificate used in this connection.
    *                    See parseCertificateInfo for the contents.
    *            - hsts: true if host uses Strict Transport Security, false otherwise
    *            - hpkp: true if host uses Public Key Pinning, false otherwise
    */
-  parseSecurityInfo: function NH_parseSecurityInfo(securityInfo, request) {
+  parseSecurityInfo: function NH_parseSecurityInfo(securityInfo, httpActivity) {
     const info = {
       state: "insecure",
     };
 
     // The request did not contain any security info.
     if (!securityInfo) {
       return info;
     }
@@ -569,35 +569,34 @@ let NetworkHelper = {
 
       // Protocol version.
       info.protocolVersion = this.formatSecurityProtocol(SSLStatus.protocolVersion);
 
       // Certificate.
       info.cert = this.parseCertificateInfo(SSLStatus.serverCert);
 
       // HSTS and HPKP if available.
-      if (request.URI) {
+      if (httpActivity.hostname) {
         const sss = Cc["@mozilla.org/ssservice;1"]
                       .getService(Ci.nsISiteSecurityService);
 
-        request.QueryInterface(Ci.nsIPrivateBrowsingChannel);
 
         // SiteSecurityService uses different storage if the channel is
         // private. Thus we must give isSecureHost correct flags or we
         // might get incorrect results.
-        let flags = (request.isChannelPrivate) ?
+        let flags = (httpActivity.private) ?
                       Ci.nsISocketProvider.NO_PERMANENT_STORAGE : 0;
 
-        let host = request.URI.host;
+        let host = httpActivity.hostname;
 
         info.hsts = sss.isSecureHost(sss.HEADER_HSTS, host, flags);
         info.hpkp = sss.isSecureHost(sss.HEADER_HPKP, host, flags);
       } else {
         DevToolsUtils.reportException("NetworkHelper.parseSecurityInfo",
-          "Could not get HSTS/HPKP status as request.URI not available.");
+          "Could not get HSTS/HPKP status as hostname is not available.");
         info.hsts = false;
         info.hpkp = false;
       }
 
     } else if (NSSErrorsService.isNSSErrorCode(securityInfo.errorCode)) {
       // The connection failed.
       info.state = "broken";
       info.errorMessage = securityInfo.errorMessage;
--- a/toolkit/devtools/webconsole/network-monitor.js
+++ b/toolkit/devtools/webconsole/network-monitor.js
@@ -7,17 +7,16 @@
 const {Cc, Ci, Cu} = require("chrome");
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 loader.lazyGetter(this, "NetworkHelper", () => require("devtools/toolkit/webconsole/network-helper"));
 loader.lazyImporter(this, "Services", "resource://gre/modules/Services.jsm");
 loader.lazyImporter(this, "DevToolsUtils", "resource://gre/modules/devtools/DevToolsUtils.jsm");
 loader.lazyImporter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
-loader.lazyImporter(this, "PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.jsm");
 loader.lazyServiceGetter(this, "gActivityDistributor",
                          "@mozilla.org/network/http-activity-distributor;1",
                          "nsIHttpActivityDistributor");
 
 ///////////////////////////////////////////////////////////////////////////////
 // Network logging
 ///////////////////////////////////////////////////////////////////////////////
 
@@ -170,17 +169,17 @@ NetworkResponseListener.prototype = {
    * Parse security state of this request and report it to the client.
    */
   _getSecurityInfo: DevToolsUtils.makeInfallible(function NRL_getSecurityInfo() {
     // Take the security information from the original nsIHTTPChannel instead of
     // the nsIRequest received in onStartRequest. If response to this request
     // was a redirect from http to https, the request object seems to contain
     // security info for the https request after redirect.
     let secinfo = this.httpActivity.channel.securityInfo;
-    let info = NetworkHelper.parseSecurityInfo(secinfo, this.request);
+    let info = NetworkHelper.parseSecurityInfo(secinfo, this.httpActivity);
 
     this.httpActivity.owner.addSecurityInfo(info);
   }),
 
   /**
    * Handle the onStopRequest by closing the sink output stream.
    *
    * For more documentation about nsIRequestObserver go to:
@@ -660,17 +659,19 @@ NetworkMonitor.prototype = {
       return;
     }
 
     let win = NetworkHelper.getWindowForRequest(aChannel);
     let httpActivity = this.createActivityObject(aChannel);
 
     // see NM__onRequestBodySent()
     httpActivity.charset = win ? win.document.characterSet : null;
-    httpActivity.private = win ? PrivateBrowsingUtils.isWindowPrivate(win) : false;
+
+    aChannel.QueryInterface(Ci.nsIPrivateBrowsingChannel);
+    httpActivity.private = aChannel.isChannelPrivate;
 
     httpActivity.timings.REQUEST_HEADER = {
       first: aTimestamp,
       last: aTimestamp
     };
 
     let httpVersionMaj = {};
     let httpVersionMin = {};
@@ -746,16 +747,17 @@ NetworkMonitor.prototype = {
    */
   createActivityObject: function NM_createActivityObject(aChannel)
   {
     return {
       id: gSequenceId(),
       channel: aChannel,
       charset: null, // see NM__onRequestHeader()
       url: aChannel.URI.spec,
+      hostname: aChannel.URI.host, // needed for host specific security info
       discardRequestBody: !this.saveRequestAndResponseBodies,
       discardResponseBody: !this.saveRequestAndResponseBodies,
       timings: {}, // internal timing information, see NM_observeActivity()
       responseStatus: null, // see NM__onResponseHeader()
       owner: null, // the activity owner which is notified when changes happen
     };
   },
 
--- a/toolkit/devtools/webconsole/test/unit/test_security-info-static-hpkp.js
+++ b/toolkit/devtools/webconsole/test/unit/test_security-info-static-hpkp.js
@@ -29,19 +29,17 @@ const MockSecurityInfo = {
     cipherSuite: "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256",
     protocolVersion: 3, // TLS_VERSION_1_2
     serverCert: {
       validity: {}
     },
   }
 };
 
-const MockRequest = {
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPrivateBrowsingChannel]),
-  URI: {
-    host: "include-subdomains.pinning.example.com"
-  }
+const MockHttpInfo = {
+  hostname: "include-subdomains.pinning.example.com",
+  private: false,
 };
 
 function run_test() {
-  let result = NetworkHelper.parseSecurityInfo(MockSecurityInfo, MockRequest);
+  let result = NetworkHelper.parseSecurityInfo(MockSecurityInfo, MockHttpInfo);
   equal(result.hpkp, true, "Static HPKP detected.");
 }
--- a/toolkit/modules/secondscreen/RokuApp.jsm
+++ b/toolkit/modules/secondscreen/RokuApp.jsm
@@ -283,45 +283,30 @@ function RemoteMirror(url, win, viewport
   this._serverURI = Services.io.newURI(url , null, null);
   this._window = win;
   this._iceCandidates = [];
   this.mirrorStarted = mirrorStartedCallback;
 
   // This code insures the generated tab mirror is not wider than 1280 nor taller than 720
   // Better dimensions should be chosen after the Roku Channel is working.
   let windowId = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
-  let cWidth =  Math.max(viewport.cssWidth, viewport.width);
-  let cHeight = Math.max(viewport.cssHeight, viewport.height);
-
   const MAX_WIDTH = 1280;
   const MAX_HEIGHT = 720;
 
-  let tWidth = 0;
-  let tHeight = 0;
-
-  // division and multiplication by 4 to ensure dimensions are multiples of 4
-  if ((cWidth / MAX_WIDTH) > (cHeight / MAX_HEIGHT)) {
-    tHeight = Math.ceil((MAX_WIDTH / (4* cWidth)) * cHeight) * 4;
-    tWidth = MAX_WIDTH;
-  } else {
-    tWidth = Math.ceil((MAX_HEIGHT / (4 * cHeight)) * cWidth) * 4;
-    tHeight = MAX_HEIGHT;
-  }
-
   let constraints = {
     video: {
       mediaSource: "browser",
       browserWindow: windowId,
       scrollWithPage: true,
       advanced: [
         {
-          width: { min: tWidth, max: tWidth },
-          height: { min: tHeight, max: tHeight }
+          width: { min: 0, max: MAX_WIDTH },
+          height: { min: 0, max: MAX_HEIGHT }
         },
-        { aspectRatio: cWidth / cHeight }
+        { aspectRatio: MAX_WIDTH/MAX_HEIGHT }
       ]
     }
   };
 
   this._window.navigator.mozGetUserMedia(constraints, this._onReceiveGUMStream.bind(this), function() {});
 }
 
 RemoteMirror.prototype = {
--- a/toolkit/themes/osx/global/in-content/common.css
+++ b/toolkit/themes/osx/global/in-content/common.css
@@ -21,16 +21,20 @@ xul|menulist {
 }
 
 xul|button {
   /* use the same margin of other elements for the alignment */
   margin-left: 4px;
   margin-right: 4px;
 }
 
+xul|caption {
+  -moz-padding-start: 0;
+}
+
 xul|groupbox > xul|*.groupbox-body {
   padding: 0;
 }
 
 xul|menulist:not([editable="true"]) > xul|menupopup > xul|menuitem[checked="true"]::before,
 xul|menulist:not([editable="true"]) > xul|menupopup > xul|menuitem[selected="true"]::before {
   display: none;
 }