merge m-c to cedar
authorDan Minor <dminor@mozilla.com>
Fri, 04 Dec 2015 07:28:50 -0500
changeset 326868 cd4e45cecf39c5371bd5cf24bf473997b7a558aa
parent 326867 3150c768471cf5800888c009a0f3ed8d752e7038 (current diff)
parent 314177 e02b17a2b5b8df7bb84f325fc08eedd2f3cab755 (diff)
child 326869 32135099de83319360173a8978349337219074fb
push id10169
push userdminor@mozilla.com
push dateThu, 28 Jan 2016 13:10:48 +0000
milestone45.0a1
merge m-c to cedar
dom/bluetooth/common/BluetoothCommon.h
dom/media/test/test_eme_key_ids_initdata.html
mfbt/Constants.h
mobile/android/app/mobile.js
mobile/android/base/resources/drawable-hdpi/menu_panel_bg.9.png
mobile/android/base/resources/drawable-xhdpi/menu_panel_bg.9.png
testing/mozharness/configs/mediatests/buildbot_posix_config.py
testing/mozharness/configs/mediatests/buildbot_windows_config.py
testing/mozharness/mozharness/mozilla/testing/testbase.py
testing/web-platform/meta/media-source/mediasource-is-type-supported.html.ini
--- a/.eslintignore
+++ b/.eslintignore
@@ -4,17 +4,16 @@
 # Exclude expected objdirs.
 obj*/**
 
 # We ignore all these directories by default, until we get them enabled.
 # If you are enabling a directory, please add directory specific exclusions
 # below.
 accessible/**
 addon-sdk/**
-b2g/**
 build/**
 caps/**
 chrome/**
 config/**
 db/**
 docshell/**
 dom/**
 editor/**
@@ -40,26 +39,53 @@ parser/**
 probes/**
 python/**
 rdf/**
 security/**
 services/**
 startupcache/**
 storage/**
 testing/**
-toolkit/**
+toolkit/components/**
+toolkit/content/**
+toolkit/crashreporter/**
+toolkit/forgetaboutsite/**
+toolkit/identity/**
+toolkit/library/**
+toolkit/locales/**
+toolkit/modules/**
+# This file contains preprocessor statements.
+toolkit/mozapps/extensions/internal/AddonConstants.jsm
+toolkit/mozapps/downloads/**
+toolkit/mozapps/handling/**
+toolkit/mozapps/installer/**
+toolkit/mozapps/preferences/**
+toolkit/mozapps/update/**
+toolkit/obsolete/**
+toolkit/pluginproblem/**
+toolkit/profile/**
+toolkit/system/**
+toolkit/themes/**
+toolkit/toolkit.mozbuild/**
+toolkit/webapps/**
+toolkit/xre/**
 tools/**
 uriloader/**
 view/**
 webapprt/**
 widget/**
 xpcom/**
 xpfe/**
 xulrunner/**
 
+# b2g exclusions (pref files).
+b2g/app/b2g.js
+b2g/graphene/graphene.js
+b2g/locales/en-US/b2g-l10n.js
+
 # browser/ exclusions
 browser/app/**
 browser/base/content/browser-social.js
 browser/base/content/nsContextMenu.js
 browser/base/content/sanitizeDialog.js
 browser/base/content/test/**
 browser/base/content/newtab/**
 browser/components/customizableui/**
--- a/accessible/generic/DocAccessible.cpp
+++ b/accessible/generic/DocAccessible.cpp
@@ -2017,16 +2017,19 @@ DocAccessible::ValidateARIAOwned()
   }
 }
 
 void
 DocAccessible::DoARIAOwnsRelocation(Accessible* aOwner)
 {
   nsTArray<RefPtr<Accessible> >* children = mARIAOwnsHash.LookupOrAdd(aOwner);
 
+  MOZ_ASSERT(aOwner, "aOwner must be a valid pointer");
+  MOZ_ASSERT(aOwner->Elm(), "aOwner->Elm() must be a valid pointer");
+
   IDRefsIterator iter(this, aOwner->Elm(), nsGkAtoms::aria_owns);
   Accessible* child = nullptr;
 
   uint32_t arrayIdx = 0, insertIdx = aOwner->ChildCount() - children->Length();
   while ((child = iter.Next())) {
     // Same child on same position, no change.
     if (child->Parent() == aOwner &&
         child->IndexInParent() == static_cast<int32_t>(insertIdx)) {
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -1082,16 +1082,19 @@ pref("layout.accessiblecaret.use_long_ta
 
 // Enable sync and mozId with Firefox Accounts.
 pref("services.sync.fxaccounts.enabled", true);
 pref("identity.fxaccounts.enabled", true);
 
 // Mobile Identity API.
 pref("services.mobileid.server.uri", "https://msisdn.services.mozilla.com");
 
+pref("identity.fxaccounts.remote.oauth.uri", "https://oauth.accounts.firefox.com/v1");
+pref("identity.fxaccounts.remote.profile.uri", "https://profile.accounts.firefox.com/v1");
+
 // Enable mapped array buffer.
 #ifndef XP_WIN
 pref("dom.mapped_arraybuffer.enabled", true);
 #endif
 
 // SystemUpdate API
 pref("dom.system_update.enabled", true);
 
--- a/b2g/chrome/content/devtools/debugger.js
+++ b/b2g/chrome/content/devtools/debugger.js
@@ -214,21 +214,21 @@ var RemoteDebugger = {
         } : DebuggerServer.globalActorFactories
       };
       let { RootActor } = devtools.require("devtools/server/actors/root");
       let root = new RootActor(connection, parameters);
       root.applicationType = "operating-system";
       return root;
     };
 
-#ifdef MOZ_WIDGET_GONK
-    DebuggerServer.on("connectionchange", function() {
-      AdbController.updateState();
-    });
-#endif
+    if (isGonk) {
+      DebuggerServer.on("connectionchange", function() {
+        AdbController.updateState();
+      });
+    }
   }
 };
 
 RemoteDebugger.allowConnection =
   RemoteDebugger.allowConnection.bind(RemoteDebugger);
 RemoteDebugger.receiveOOB =
   RemoteDebugger.receiveOOB.bind(RemoteDebugger);
 
@@ -368,19 +368,17 @@ var WiFiRemoteDebugger = {
     try {
       (value == "adb-devtools") ? USBRemoteDebugger.start()
                                 : USBRemoteDebugger.stop();
     } catch(e) {
       dump("Error while initializing USB devtools: " +
            e + "\n" + e.stack + "\n");
     }
 
-#ifdef MOZ_WIDGET_GONK
-    AdbController.setRemoteDebuggerState(value != "disabled");
-#endif
+    isGonk && AdbController.setRemoteDebuggerState(value != "disabled");
   });
 
   SettingsListener.observe("devtools.remote.wifi.enabled", false,
                            function(value) {
     devtoolsWiFi = value;
     Services.prefs.setBoolPref("devtools.debugger.remote-enabled",
                                devtoolsUSB || devtoolsWiFi);
     // Allow remote debugging on non-local interfaces when WiFi debug is enabled
--- a/b2g/chrome/content/settings.js
+++ b/b2g/chrome/content/settings.js
@@ -17,22 +17,24 @@ var Cr = Components.results;
 // prior to using SettingsListener otherwise there is a race in acquiring the
 // lock and fulfilling it. If we ever move SettingsListener or this file down in
 // the load order of shell.html things will likely break.
 Cu.import('resource://gre/modules/SettingsRequestManager.jsm');
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 Cu.import('resource://gre/modules/Services.jsm');
 Cu.import('resource://gre/modules/AppConstants.jsm');
 
-#ifdef MOZ_WIDGET_GONK
-XPCOMUtils.defineLazyGetter(this, "libcutils", function () {
-  Cu.import("resource://gre/modules/systemlibs.js");
-  return libcutils;
-});
-#endif
+const isGonk = AppConstants.platform === 'gonk';
+
+if (isGonk) {
+  XPCOMUtils.defineLazyGetter(this, "libcutils", function () {
+    Cu.import("resource://gre/modules/systemlibs.js");
+    return libcutils;
+  });
+}
 
 XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
                                    "@mozilla.org/uuid-generator;1",
                                    "nsIUUIDGenerator");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gPACGenerator",
                                    "@mozilla.org/pac-generator;1",
                                    "nsIPACGenerator");
@@ -147,39 +149,37 @@ SettingsListener.observe('language.curre
 })();
 
 //=================== DeviceInfo ====================
 Components.utils.import('resource://gre/modules/XPCOMUtils.jsm');
 Components.utils.import('resource://gre/modules/ctypes.jsm');
 (function DeviceInfoToSettings() {
   // MOZ_B2G_VERSION is set in b2g/confvars.sh, and is output as a #define value
   // from configure.in, defaults to 1.0.0 if this value is not exist.
-#filter attemptSubstitution
-  let os_version = '@MOZ_B2G_VERSION@';
-  let os_name = '@MOZ_B2G_OS_NAME@';
-#unfilter attemptSubstitution
+  let os_version = AppConstants.MOZ_B2G_VERSION;
+  let os_name = AppConstants.MOZ_B2G_OS_NAME;
 
   let appInfo = Cc["@mozilla.org/xre/app-info;1"]
                   .getService(Ci.nsIXULAppInfo);
 
   // Get the hardware info and firmware revision from device properties.
   let hardware_info = null;
   let firmware_revision = null;
   let product_manufacturer = null;
   let product_model = null;
   let product_device = null;
   let build_number = null;
-#ifdef MOZ_WIDGET_GONK
+  if (isGonk) {
     hardware_info = libcutils.property_get('ro.hardware');
     firmware_revision = libcutils.property_get('ro.firmware_revision');
     product_manufacturer = libcutils.property_get('ro.product.manufacturer');
     product_model = libcutils.property_get('ro.product.model');
     product_device = libcutils.property_get('ro.product.device');
     build_number = libcutils.property_get('ro.build.version.incremental');
-#endif
+  }
 
   // Populate deviceinfo settings,
   // copying any existing deviceinfo.os into deviceinfo.previous_os
   let lock = window.navigator.mozSettings.createLock();
   let req = lock.get('deviceinfo.os');
   req.onsuccess = req.onerror = () => {
     let previous_os = req.result && req.result['deviceinfo.os'] || '';
     let software = os_name + ' ' + os_version;
@@ -213,42 +213,41 @@ SettingsListener.observe('devtools.overl
     developerHUD.init();
   } else {
     if (developerHUD) {
       developerHUD.uninit();
     }
   }
 });
 
-#ifdef MOZ_WIDGET_GONK
-
-var LogShake;
-(function() {
-  let scope = {};
-  Cu.import('resource://gre/modules/LogShake.jsm', scope);
-  LogShake = scope.LogShake;
-  LogShake.init();
-})();
+if (isGonk) {
+  var LogShake;
+  (function() {
+    let scope = {};
+    Cu.import('resource://gre/modules/LogShake.jsm', scope);
+    LogShake = scope.LogShake;
+    LogShake.init();
+  })();
 
-SettingsListener.observe('devtools.logshake.enabled', false, value => {
-  if (value) {
-    LogShake.enableDeviceMotionListener();
-  } else {
-    LogShake.disableDeviceMotionListener();
-  }
-});
+  SettingsListener.observe('devtools.logshake.enabled', false, value => {
+    if (value) {
+      LogShake.enableDeviceMotionListener();
+    } else {
+      LogShake.disableDeviceMotionListener();
+    }
+  });
 
-SettingsListener.observe('devtools.logshake.qa_enabled', false, value => {
-  if (value) {
-    LogShake.enableQAMode();
-  } else {
-    LogShake.disableQAMode();
-  }
-});
-#endif
+  SettingsListener.observe('devtools.logshake.qa_enabled', false, value => {
+    if (value) {
+      LogShake.enableQAMode();
+    } else {
+      LogShake.disableQAMode();
+    }
+  });
+}
 
 // =================== Device Storage ====================
 SettingsListener.observe('device.storage.writable.name', 'sdcard', function(value) {
   if (Services.prefs.getPrefType('device.storage.writable.name') != Ci.nsIPrefBranch.PREF_STRING) {
     // We clear the pref because it used to be erroneously written as a bool
     // and we need to clear it before we can change it to have the correct type.
     Services.prefs.clearUserPref('device.storage.writable.name');
   }
@@ -375,25 +374,23 @@ setUpdateTrackingId();
   //3. presence of ro.display.colorfill at the Gonk level
 
   var req = navigator.mozSettings.createLock().get('layers.composer2d.enabled');
   req.onsuccess = function() {
     if (typeof(req.result['layers.composer2d.enabled']) === 'undefined') {
       var enabled = false;
       if (Services.prefs.getPrefType('layers.composer2d.enabled') == Ci.nsIPrefBranch.PREF_BOOL) {
         enabled = Services.prefs.getBoolPref('layers.composer2d.enabled');
-      } else {
-#ifdef MOZ_WIDGET_GONK
+      } else if (isGonk) {
         let androidVersion = libcutils.property_get("ro.build.version.sdk");
         if (androidVersion >= 17 ) {
           enabled = true;
         } else {
           enabled = (libcutils.property_get('ro.display.colorfill') === '1');
         }
-#endif
       }
       navigator.mozSettings.createLock().set({'layers.composer2d.enabled': enabled });
     }
 
     SettingsListener.observe("layers.composer2d.enabled", true, function(value) {
       Services.prefs.setBoolPref("layers.composer2d.enabled", value);
     });
   };
@@ -438,21 +435,17 @@ setUpdateTrackingId();
   let geckoPrefName = 'toolkit.telemetry.enabled';
   SettingsListener.observe(gaiaSettingName, null, function(value) {
     if (value !== null) {
       // Gaia setting has been set; update Gecko pref to that.
       Services.prefs.setBoolPref(geckoPrefName, value);
       return;
     }
     // Gaia setting has not been set; set the gaia setting to default.
-#ifdef MOZ_TELEMETRY_ON_BY_DEFAULT
-    let prefValue = true;
-#else
-    let prefValue = false;
-#endif
+    let prefValue = AppConstants.MOZ_TELEMETRY_ON_BY_DEFAULT;
     try {
       prefValue = Services.prefs.getBoolPref(geckoPrefName);
     } catch (e) {
       // Pref not set; use default value.
     }
     let setting = {};
     setting[gaiaSettingName] = prefValue;
     window.navigator.mozSettings.createLock().set(setting);
@@ -566,57 +559,57 @@ SettingsListener.observe("theme.selected
   SettingsListener.observe('browser.proxy.port', 0, function(value) {
     Services.prefs.setIntPref('network.proxy.browsing.port', value);
     setPAC();
   });
 
   setPAC();
 })();
 
-#ifdef MOZ_B2G_RIL
-XPCOMUtils.defineLazyModuleGetter(this, "AppsUtils",
-                                  "resource://gre/modules/AppsUtils.jsm");
-
 // ======================= Dogfooders FOTA ==========================
-SettingsListener.observe('debug.performance_data.dogfooding', false,
-  isDogfooder => {
-    if (!isDogfooder) {
-      dump('AUS:Settings: Not a dogfooder!\n');
-      return;
-    }
+if (AppConstants.MOZ_B2G_RIL) {
+  XPCOMUtils.defineLazyModuleGetter(this, "AppsUtils",
+                                    "resource://gre/modules/AppsUtils.jsm");
 
-    if (!('mozTelephony' in navigator)) {
-      dump('AUS:Settings: There is no mozTelephony!\n');
-      return;
-    }
+  SettingsListener.observe('debug.performance_data.dogfooding', false,
+    isDogfooder => {
+      if (!isDogfooder) {
+        dump('AUS:Settings: Not a dogfooder!\n');
+        return;
+      }
 
-    if (!('mozMobileConnections' in navigator)) {
-      dump('AUS:Settings: There is no mozMobileConnections!\n');
-      return;
-    }
-
-    let conn = navigator.mozMobileConnections[0];
-    conn.addEventListener('radiostatechange', function onradiostatechange() {
-      if (conn.radioState !== 'enabled') {
+      if (!('mozTelephony' in navigator)) {
+        dump('AUS:Settings: There is no mozTelephony!\n');
         return;
       }
 
-      conn.removeEventListener('radiostatechange', onradiostatechange);
-      navigator.mozTelephony.dial('*#06#').then(call => {
-        return call.result.then(res => {
-          if (res.success && res.statusMessage
-              && (res.serviceCode === 'scImei')) {
-            Services.prefs.setCharPref("app.update.imei_hash",
-                                       AppsUtils.computeHash(res.statusMessage, "SHA512"));
-          }
+      if (!('mozMobileConnections' in navigator)) {
+        dump('AUS:Settings: There is no mozMobileConnections!\n');
+        return;
+      }
+
+      let conn = navigator.mozMobileConnections[0];
+      conn.addEventListener('radiostatechange', function onradiostatechange() {
+        if (conn.radioState !== 'enabled') {
+          return;
+        }
+
+        conn.removeEventListener('radiostatechange', onradiostatechange);
+        navigator.mozTelephony.dial('*#06#').then(call => {
+          return call.result.then(res => {
+            if (res.success && res.statusMessage
+                && (res.serviceCode === 'scImei')) {
+              Services.prefs.setCharPref("app.update.imei_hash",
+                                         AppsUtils.computeHash(res.statusMessage, "SHA512"));
+            }
+          });
         });
       });
     });
-  });
-#endif
+}
 
 // =================== Various simple mapping  ======================
 var settingsToObserve = {
   'accessibility.screenreader_quicknav_modes': {
     prefName: 'accessibility.accessfu.quicknav_modes',
     resetToPref: true,
     defaultValue: ''
   },
@@ -658,28 +651,21 @@ var settingsToObserve = {
   'dom.presentation.discovery.enabled': false,
   'dom.presentation.discoverable': false,
   'dom.serviceWorkers.interception.enabled': true,
   'dom.serviceWorkers.testing.enabled': false,
   'gfx.layerscope.enabled': false,
   'layers.draw-borders': false,
   'layers.draw-tile-borders': false,
   'layers.dump': false,
-#ifdef XP_WIN
-  'layers.enable-tiles': false,
-#else
+  'layers.enable-tiles': AppConstants.platform !== "win",
   'layers.enable-tiles': true,
-#endif
   'layers.effect.invert': false,
   'layers.effect.grayscale': false,
   'layers.effect.contrast': '0.0',
-#ifdef MOZ_GRAPHENE
-  // Restart required
-  'layers.async-pan-zoom.enabled': false,
-#endif
   'layout.display-list.dump': false,
   'mms.debugging.enabled': false,
   'network.debugging.enabled': false,
   'privacy.donottrackheader.enabled': false,
   'privacy.trackingprotection.enabled': false,
   'ril.debugging.enabled': false,
   'ril.radio.disabled': false,
   'ril.mms.requestReadReport.enabled': {
@@ -724,16 +710,21 @@ var settingsToObserve = {
   },
   'ui.click_hold_context_menus.delay': {
     resetToPref: true
   },
   'wap.UAProf.tagname': 'x-wap-profile',
   'wap.UAProf.url': ''
 };
 
+if (AppConstants.MOZ_GRAPHENE) {
+  // Restart required
+  settingsToObserve['layers.async-pan-zoom.enabled'] = false;
+}
+
 function settingObserver(setPref, prefName, setting) {
   return value => {
     setPref(prefName, value);
     if (setting.notifyChange) {
       SystemAppProxy._sendCustomEvent('mozPrefChromeEvent', {
         prefName: prefName,
         value: value
       });
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -14,21 +14,23 @@ Cu.import('resource://gre/modules/Notifi
 Cu.import('resource://gre/modules/Payment.jsm');
 Cu.import("resource://gre/modules/AppsUtils.jsm");
 Cu.import('resource://gre/modules/UserAgentOverrides.jsm');
 Cu.import('resource://gre/modules/Keyboard.jsm');
 Cu.import('resource://gre/modules/ErrorPage.jsm');
 Cu.import('resource://gre/modules/AlertsHelper.jsm');
 Cu.import('resource://gre/modules/RequestSyncService.jsm');
 Cu.import('resource://gre/modules/SystemUpdateService.jsm');
-#ifdef MOZ_WIDGET_GONK
-Cu.import('resource://gre/modules/MultiscreenHandler.jsm');
-Cu.import('resource://gre/modules/NetworkStatsService.jsm');
-Cu.import('resource://gre/modules/ResourceStatsService.jsm');
-#endif
+
+if (isGonk) {
+  Cu.import('resource://gre/modules/MultiscreenHandler.jsm');
+  Cu.import('resource://gre/modules/NetworkStatsService.jsm');
+  Cu.import('resource://gre/modules/ResourceStatsService.jsm');
+}
+
 Cu.import('resource://gre/modules/KillSwitchMain.jsm');
 
 // Identity
 Cu.import('resource://gre/modules/SignInToWebsite.jsm');
 SignInToWebsiteController.init();
 
 Cu.import('resource://gre/modules/FxAccountsMgmtService.jsm');
 Cu.import('resource://gre/modules/DownloadsAPI.jsm');
@@ -57,31 +59,31 @@ XPCOMUtils.defineLazyServiceGetter(this,
                                    '@mozilla.org/system-message-internal;1',
                                    'nsISystemMessagesInternal');
 
 XPCOMUtils.defineLazyGetter(this, "ppmm", function() {
   return Cc["@mozilla.org/parentprocessmessagemanager;1"]
          .getService(Ci.nsIMessageListenerManager);
 });
 
-#ifdef MOZ_WIDGET_GONK
-XPCOMUtils.defineLazyGetter(this, "libcutils", function () {
-  Cu.import("resource://gre/modules/systemlibs.js");
-  return libcutils;
-});
-#endif
+if (isGonk) {
+  XPCOMUtils.defineLazyGetter(this, "libcutils", function () {
+    Cu.import("resource://gre/modules/systemlibs.js");
+    return libcutils;
+  });
+}
 
 XPCOMUtils.defineLazyServiceGetter(Services, 'captivePortalDetector',
                                   '@mozilla.org/toolkit/captive-detector;1',
                                   'nsICaptivePortalDetector');
 
-#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, "SafeMode",
                                   "resource://gre/modules/SafeMode.jsm");
 
 window.performance.measure('gecko-shell-jsm-loaded', 'gecko-shell-loadstart');
 
 function debug(str) {
   dump(' -*- Shell.js: ' + str + '\n');
@@ -115,35 +117,31 @@ function clearCacheAndReload() {
 }
 
 function restart() {
   let appStartup = Cc['@mozilla.org/toolkit/app-startup;1']
                      .getService(Ci.nsIAppStartup);
   appStartup.quit(Ci.nsIAppStartup.eForceQuit | Ci.nsIAppStartup.eRestart);
 }
 
-#ifdef MOZ_CRASHREPORTER
 function debugCrashReport(aStr) {
-  dump('Crash reporter : ' + aStr);
+  AppConstants.MOZ_CRASHREPORTER && dump('Crash reporter : ' + aStr);
 }
-#else
-function debugCrashReport(aStr) {}
-#endif
 
 var shell = {
 
   get CrashSubmit() {
     delete this.CrashSubmit;
-#ifdef MOZ_CRASHREPORTER
-    Cu.import("resource://gre/modules/CrashSubmit.jsm", this);
-    return this.CrashSubmit;
-#else
-    dump('Crash reporter : disabled at build time.');
-    return this.CrashSubmit = null;
-#endif
+    if (AppConstants.MOZ_CRASHREPORTER) {
+      Cu.import("resource://gre/modules/CrashSubmit.jsm", this);
+      return this.CrashSubmit;
+    } else {
+      dump('Crash reporter : disabled at build time.');
+      return this.CrashSubmit = null;
+    }
   },
 
   onlineForCrashReport: function shell_onlineForCrashReport() {
     let wifiManager = navigator.mozWifiManager;
     let onWifi = (wifiManager &&
                   (wifiManager.connection.status == 'connected'));
     return !Services.io.offline && onWifi;
   },
@@ -260,54 +258,52 @@ var shell = {
   },
 
   _started: false,
   hasStarted: function shell_hasStarted() {
     return this._started;
   },
 
   bootstrap: function() {
-#ifdef MOZ_B2GDROID
-    Cc["@mozilla.org/b2g/b2gdroid-setup;1"]
-      .getService(Ci.nsIObserver).observe(window, "shell-startup", null);
-#endif
+    if (AppConstants.MOZ_B2GDROID) {
+      Cc["@mozilla.org/b2g/b2gdroid-setup;1"]
+        .getService(Ci.nsIObserver).observe(window, "shell-startup", null);
+    }
 
     window.performance.mark('gecko-shell-bootstrap');
 
     // Before anything, check if we want to start in safe mode.
     SafeMode.check(window).then(() => {
       let startManifestURL =
         Cc['@mozilla.org/commandlinehandler/general-startup;1?type=b2gbootstrap']
           .getService(Ci.nsISupports).wrappedJSObject.startManifestURL;
 
-#ifdef MOZ_GRAPHENE
       // If --start-manifest hasn't been specified, we re-use the latest specified manifest.
       // If it's the first launch, we will fallback to b2g.default.start_manifest_url
-      if (!startManifestURL) {
+      if (AppConstants.MOZ_GRAPHENE && !startManifestURL) {
         try {
           startManifestURL = Services.prefs.getCharPref("b2g.system_manifest_url");
         } catch(e) {}
       }
-#endif
 
       if (!startManifestURL) {
         try {
           startManifestURL = Services.prefs.getCharPref("b2g.default.start_manifest_url");
         } catch(e) {}
       }
 
       if (startManifestURL) {
         Cu.import('resource://gre/modules/Bootstraper.jsm');
-#ifdef MOZ_GRAPHENE
-        if (Bootstraper.isInstallRequired(startManifestURL)) {
+
+        if (AppConstants.MOZ_GRAPHENE && Bootstraper.isInstallRequired(startManifestURL)) {
           // Installing the app my take some time. We don't want to keep the
           // native window hidden.
           showInstallScreen();
         }
-#endif
+
         Bootstraper.ensureSystemAppInstall(startManifestURL)
                    .then(this.start.bind(this))
                    .catch(Bootstraper.bailout);
       } else {
         this.start();
       }
     });
   },
@@ -329,44 +325,44 @@ var shell = {
       try {
         let dogfoodId = Services.prefs.getCharPref('prerelease.dogfood.id');
         if (dogfoodId != "") {
           cr.annotateCrashReport("Email", dogfoodId);
         }
       }
       catch (e) { }
 
-#ifdef MOZ_WIDGET_GONK
-      // Annotate crash report
-      let annotations = [ [ "Android_Hardware",     "ro.hardware" ],
-                          [ "Android_Device",       "ro.product.device" ],
-                          [ "Android_CPU_ABI2",     "ro.product.cpu.abi2" ],
-                          [ "Android_CPU_ABI",      "ro.product.cpu.abi" ],
-                          [ "Android_Manufacturer", "ro.product.manufacturer" ],
-                          [ "Android_Brand",        "ro.product.brand" ],
-                          [ "Android_Model",        "ro.product.model" ],
-                          [ "Android_Board",        "ro.product.board" ],
-        ];
+      if (isGonk) {
+        // Annotate crash report
+        let annotations = [ [ "Android_Hardware",     "ro.hardware" ],
+                            [ "Android_Device",       "ro.product.device" ],
+                            [ "Android_CPU_ABI2",     "ro.product.cpu.abi2" ],
+                            [ "Android_CPU_ABI",      "ro.product.cpu.abi" ],
+                            [ "Android_Manufacturer", "ro.product.manufacturer" ],
+                            [ "Android_Brand",        "ro.product.brand" ],
+                            [ "Android_Model",        "ro.product.model" ],
+                            [ "Android_Board",        "ro.product.board" ],
+          ];
 
-      annotations.forEach(function (element) {
-          cr.annotateCrashReport(element[0], libcutils.property_get(element[1]));
-        });
+        annotations.forEach(function (element) {
+            cr.annotateCrashReport(element[0], libcutils.property_get(element[1]));
+          });
 
-      let androidVersion = libcutils.property_get("ro.build.version.sdk") +
-                           "(" + libcutils.property_get("ro.build.version.codename") + ")";
-      cr.annotateCrashReport("Android_Version", androidVersion);
+        let androidVersion = libcutils.property_get("ro.build.version.sdk") +
+                             "(" + libcutils.property_get("ro.build.version.codename") + ")";
+        cr.annotateCrashReport("Android_Version", androidVersion);
 
-      SettingsListener.observe("deviceinfo.os", "", function(value) {
-        try {
-          let cr = Cc["@mozilla.org/xre/app-info;1"]
-                     .getService(Ci.nsICrashReporter);
-          cr.annotateCrashReport("B2G_OS_Version", value);
-        } catch(e) { }
-      });
-#endif
+        SettingsListener.observe("deviceinfo.os", "", function(value) {
+          try {
+            let cr = Cc["@mozilla.org/xre/app-info;1"]
+                       .getService(Ci.nsICrashReporter);
+            cr.annotateCrashReport("B2G_OS_Version", value);
+          } catch(e) { }
+        });
+      }
     } catch(e) {
       debugCrashReport('exception: ' + e);
     }
 
     let homeURL = this.homeURL;
     if (!homeURL) {
       let msg = 'Fatal error during startup: No homescreen found: try setting B2G_HOMESCREEN';
       alert(msg);
@@ -380,33 +376,35 @@ var shell = {
     let systemAppFrame =
       document.createElementNS('http://www.w3.org/1999/xhtml', 'html:iframe');
     systemAppFrame.setAttribute('id', 'systemapp');
     systemAppFrame.setAttribute('mozbrowser', 'true');
     systemAppFrame.setAttribute('mozapp', manifestURL);
     systemAppFrame.setAttribute('allowfullscreen', 'true');
     systemAppFrame.setAttribute('src', 'blank.html');
     let container = document.getElementById('container');
-#ifdef MOZ_WIDGET_COCOA
-    // See shell.html
-    let hotfix = document.getElementById('placeholder');
-    if (hotfix) {
-      container.removeChild(hotfix);
+
+    if (AppConstants.platform == 'macosx') {
+      // See shell.html
+      let hotfix = document.getElementById('placeholder');
+      if (hotfix) {
+        container.removeChild(hotfix);
+      }
     }
-#endif
+
     this.contentBrowser = container.appendChild(systemAppFrame);
 
     let webNav = systemAppFrame.contentWindow
                                .QueryInterface(Ci.nsIInterfaceRequestor)
                                .getInterface(Ci.nsIWebNavigation);
     webNav.sessionHistory = Cc["@mozilla.org/browser/shistory;1"].createInstance(Ci.nsISHistory);
 
-#ifdef MOZ_GRAPHENE
-    webNav.QueryInterface(Ci.nsIDocShell).windowDraggingAllowed = true;
-#endif
+    if (AppConstants.MOZ_GRAPHENE) {
+      webNav.QueryInterface(Ci.nsIDocShell).windowDraggingAllowed = true;
+    }
 
     let audioChannels = systemAppFrame.allowedAudioChannels;
     audioChannels && audioChannels.forEach(function(audioChannel) {
       // Set all audio channels as unmuted by default
       // because some audio in System app will be played
       // before AudioChannelService[1] is Gaia is loaded.
       // [1]: https://github.com/mozilla-b2g/gaia/blob/master/apps/system/js/audio_channel_service.js
       audioChannel.setMuted(false);
@@ -446,21 +444,22 @@ var shell = {
 
     window.performance.mark('gecko-shell-system-frame-set');
 
     ppmm.addMessageListener("content-handler", this);
     ppmm.addMessageListener("dial-handler", this);
     ppmm.addMessageListener("sms-handler", this);
     ppmm.addMessageListener("mail-handler", this);
     ppmm.addMessageListener("file-picker", this);
-#ifdef MOZ_SAFE_BROWSING
-    setTimeout(function() {
-      SafeBrowsing.init();
-    }, 5000);
-#endif
+
+    if (AppConstants.MOZ_SAFE_BROWSING) {
+      setTimeout(function() {
+        SafeBrowsing.init();
+      }, 5000);
+    }
   },
 
   stop: function shell_stop() {
     window.removeEventListener('unload', this);
     window.removeEventListener('keydown', this, true);
     window.removeEventListener('keyup', this, true);
     window.removeEventListener('MozApplicationManifest', this);
     window.removeEventListener('sizemodechange', this);
@@ -749,55 +748,50 @@ var shell = {
     this.reportCrash(true);
 
     SystemAppProxy.registerFrame(shell.contentBrowser);
 
     this.sendEvent(window, 'ContentStart');
 
     Services.obs.notifyObservers(null, 'content-start', null);
 
-#ifdef MOZ_WIDGET_GONK
-    Cu.import('resource://gre/modules/OperatorApps.jsm');
-#endif
+    isGonk && Cu.import('resource://gre/modules/OperatorApps.jsm');
 
-#ifdef MOZ_GRAPHENE
-    if (Services.prefs.getBoolPref("b2g.nativeWindowGeometry.fullscreen")) {
+    if (AppConstants.MOZ_GRAPHENE &&
+        Services.prefs.getBoolPref("b2g.nativeWindowGeometry.fullscreen")) {
       window.fullScreen = true;
     }
-#endif
 
     shell.handleCmdLine();
   },
 
   handleCmdLine: function() {
-  // This isn't supported on devices.
-#ifndef ANDROID
-    let b2gcmds = Cc["@mozilla.org/commandlinehandler/general-startup;1?type=b2gcmds"]
-                    .getService(Ci.nsISupports);
-    let args = b2gcmds.wrappedJSObject.cmdLine;
-    try {
-      // Returns null if -url is not present.
-      let url = args.handleFlagWithParam("url", false);
-      if (url) {
-        this.sendChromeEvent({type: "mozbrowseropenwindow", url});
-        args.preventDefault = true;
+    // This isn't supported on devices.
+    if (!isGonk && !AppConstants.MOZ_B2GDROID) {
+      let b2gcmds = Cc["@mozilla.org/commandlinehandler/general-startup;1?type=b2gcmds"]
+                      .getService(Ci.nsISupports);
+      let args = b2gcmds.wrappedJSObject.cmdLine;
+      try {
+        // Returns null if -url is not present.
+        let url = args.handleFlagWithParam("url", false);
+        if (url) {
+          this.sendChromeEvent({type: "mozbrowseropenwindow", url});
+          args.preventDefault = true;
+        }
+      } catch(e) {
+        // Throws if -url is present with no params.
       }
-    } catch(e) {
-      // Throws if -url is present with no params.
     }
-#endif
   },
 
   // This gets called when window.onload fires on the System app content window,
   // which means things in <html> are parsed and statically referenced <script>s
   // and <script defer>s are loaded and run.
   notifyContentWindowLoaded: function shell_notifyContentWindowLoaded() {
-#ifdef MOZ_WIDGET_GONK
-    libcutils.property_set('sys.boot_completed', '1');
-#endif
+    isGonk && libcutils.property_set('sys.boot_completed', '1');
 
     // This will cause Gonk Widget to remove boot animation from the screen
     // and reveals the page.
     Services.obs.notifyObservers(null, "browser-ui-startup-complete", "");
 
     SystemAppProxy.setIsLoaded();
   },
 
@@ -1119,29 +1113,31 @@ window.addEventListener('ContentStart', 
     }
   });
 });
 
 window.addEventListener('ContentStart', function update_onContentStart() {
   Cu.import('resource://gre/modules/WebappsUpdater.jsm');
   WebappsUpdater.handleContentStart(shell);
 
-#ifdef MOZ_UPDATER
+  if (!AppConstants.MOZ_UPDATER) {
+    return;
+  }
+
   let promptCc = Cc["@mozilla.org/updates/update-prompt;1"];
   if (!promptCc) {
     return;
   }
 
   let updatePrompt = promptCc.createInstance(Ci.nsIUpdatePrompt);
   if (!updatePrompt) {
     return;
   }
 
   updatePrompt.wrappedJSObject.handleContentStart(shell);
-#endif
 });
 
 (function geolocationStatusTracker() {
   let gGeolocationActive = false;
 
   Services.obs.addObserver(function(aSubject, aTopic, aData) {
     let oldState = gGeolocationActive;
     if (aData == "starting") {
@@ -1308,152 +1304,148 @@ window.addEventListener('ContentStart', 
   Services.obs.addObserver(function(aSubject, aTopic, aData) {
     shell.sendChromeEvent({
       type: 'volume-state-changed',
       active: (aData == 'Shared')
     });
 }, 'volume-state-changed', false);
 })();
 
-#ifdef MOZ_WIDGET_GONK
-// Devices don't have all the same partition size for /cache where we
-// store the http cache.
-(function setHTTPCacheSize() {
-  let path = Services.prefs.getCharPref("browser.cache.disk.parent_directory");
-  let volumeService = Cc["@mozilla.org/telephony/volume-service;1"]
-                        .getService(Ci.nsIVolumeService);
+if (isGonk) {
+  // Devices don't have all the same partition size for /cache where we
+  // store the http cache.
+  (function setHTTPCacheSize() {
+    let path = Services.prefs.getCharPref("browser.cache.disk.parent_directory");
+    let volumeService = Cc["@mozilla.org/telephony/volume-service;1"]
+                          .getService(Ci.nsIVolumeService);
 
-  let stats = volumeService.createOrGetVolumeByPath(path).getStats();
+    let stats = volumeService.createOrGetVolumeByPath(path).getStats();
 
-  // We must set the size in KB, and keep a bit of free space.
-  let size = Math.floor(stats.totalBytes / 1024) - 1024;
+    // We must set the size in KB, and keep a bit of free space.
+    let size = Math.floor(stats.totalBytes / 1024) - 1024;
 
-  // keep the default value if it is smaller than the physical partition size.
-  let oldSize = Services.prefs.getIntPref("browser.cache.disk.capacity");
-  if (size < oldSize) {
-    Services.prefs.setIntPref("browser.cache.disk.capacity", size);
-  }
-})();
-#endif
+    // keep the default value if it is smaller than the physical partition size.
+    let oldSize = Services.prefs.getIntPref("browser.cache.disk.capacity");
+    if (size < oldSize) {
+      Services.prefs.setIntPref("browser.cache.disk.capacity", size);
+    }
+  })();
 
-#ifdef MOZ_WIDGET_GONK
-try {
-  let gmpService = Cc["@mozilla.org/gecko-media-plugin-service;1"]
-                     .getService(Ci.mozIGeckoMediaPluginChromeService);
-  gmpService.addPluginDirectory("/system/b2g/gmp-clearkey/0.1");
-} catch(e) {
-  dump("Failed to add clearkey path! " + e + "\n");
+  try {
+    let gmpService = Cc["@mozilla.org/gecko-media-plugin-service;1"]
+                       .getService(Ci.mozIGeckoMediaPluginChromeService);
+    gmpService.addPluginDirectory("/system/b2g/gmp-clearkey/0.1");
+  } catch(e) {
+    dump("Failed to add clearkey path! " + e + "\n");
+  }
 }
-#endif
 
 // Calling this observer will cause a shutdown an a profile reset.
 // Use eg. : Services.obs.notifyObservers(null, 'b2g-reset-profile', null);
 Services.obs.addObserver(function resetProfile(subject, topic, data) {
   Services.obs.removeObserver(resetProfile, topic);
 
   // Listening for 'profile-before-change2' which is late in the shutdown
   // sequence, but still has xpcom access.
   Services.obs.addObserver(function clearProfile(subject, topic, data) {
     Services.obs.removeObserver(clearProfile, topic);
-#ifdef MOZ_WIDGET_GONK
-    let json = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsIFile);
-    json.initWithPath('/system/b2g/webapps/webapps.json');
-    let toRemove = json.exists()
-      // This is a user build, just rm -r /data/local /data/b2g/mozilla
-      ? ['/data/local', '/data/b2g/mozilla']
-      // This is an eng build. We clear the profile and a set of files
-      // under /data/local.
-      : ['/data/b2g/mozilla',
-         '/data/local/permissions.sqlite',
-         '/data/local/storage',
-         '/data/local/OfflineCache'];
+    if (isGonk) {
+      let json = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsIFile);
+      json.initWithPath('/system/b2g/webapps/webapps.json');
+      let toRemove = json.exists()
+        // This is a user build, just rm -r /data/local /data/b2g/mozilla
+        ? ['/data/local', '/data/b2g/mozilla']
+        // This is an eng build. We clear the profile and a set of files
+        // under /data/local.
+        : ['/data/b2g/mozilla',
+           '/data/local/permissions.sqlite',
+           '/data/local/storage',
+           '/data/local/OfflineCache'];
 
-    toRemove.forEach(function(dir) {
-      try {
-        let file = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsIFile);
-        file.initWithPath(dir);
-        file.remove(true);
-      } catch(e) { dump(e); }
-    });
-#else
-    // Desktop builds.
-    let profile = Services.dirsvc.get('ProfD', Ci.nsIFile);
+      toRemove.forEach(function(dir) {
+        try {
+          let file = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsIFile);
+          file.initWithPath(dir);
+          file.remove(true);
+        } catch(e) { dump(e); }
+      });
+    } else {
+      // Desktop builds.
+      let profile = Services.dirsvc.get('ProfD', Ci.nsIFile);
 
-    // We don't want to remove everything from the profile, since this
-    // would prevent us from starting up.
-    let whitelist = ['defaults', 'extensions', 'settings.json',
-                     'user.js', 'webapps'];
-    let enumerator = profile.directoryEntries;
-    while (enumerator.hasMoreElements()) {
-      let file = enumerator.getNext().QueryInterface(Ci.nsIFile);
-      if (whitelist.indexOf(file.leafName) == -1) {
-        file.remove(true);
+      // We don't want to remove everything from the profile, since this
+      // would prevent us from starting up.
+      let whitelist = ['defaults', 'extensions', 'settings.json',
+                       'user.js', 'webapps'];
+      let enumerator = profile.directoryEntries;
+      while (enumerator.hasMoreElements()) {
+        let file = enumerator.getNext().QueryInterface(Ci.nsIFile);
+        if (whitelist.indexOf(file.leafName) == -1) {
+          file.remove(true);
+        }
       }
     }
-#endif
   },
   'profile-before-change2', false);
 
   let appStartup = Cc['@mozilla.org/toolkit/app-startup;1']
                      .getService(Ci.nsIAppStartup);
   appStartup.quit(Ci.nsIAppStartup.eForceQuit);
 }, 'b2g-reset-profile', false);
 
-#ifdef MOZ_GRAPHENE
+if (AppConstants.MOZ_GRAPHENE) {
+  const restoreWindowGeometry = () => {
+    let screenX = Services.prefs.getIntPref("b2g.nativeWindowGeometry.screenX");
+    let screenY = Services.prefs.getIntPref("b2g.nativeWindowGeometry.screenY");
+    let width = Services.prefs.getIntPref("b2g.nativeWindowGeometry.width");
+    let height = Services.prefs.getIntPref("b2g.nativeWindowGeometry.height");
+
+    if (screenX == -1) {
+      // Center
+      screenX = (screen.width - width) / 2;
+      screenY = (screen.height - height) / 2;
+    }
+
+    moveTo(screenX, screenY);
+    resizeTo(width, height);
+  }
+  restoreWindowGeometry();
 
-const restoreWindowGeometry = () => {
-  let screenX = Services.prefs.getIntPref("b2g.nativeWindowGeometry.screenX");
-  let screenY = Services.prefs.getIntPref("b2g.nativeWindowGeometry.screenY");
-  let width = Services.prefs.getIntPref("b2g.nativeWindowGeometry.width");
-  let height = Services.prefs.getIntPref("b2g.nativeWindowGeometry.height");
+  const saveWindowGeometry = () => {
+    window.removeEventListener("unload", saveWindowGeometry);
+    Services.prefs.setIntPref("b2g.nativeWindowGeometry.screenX", screenX);
+    Services.prefs.setIntPref("b2g.nativeWindowGeometry.screenY", screenY);
+    Services.prefs.setIntPref("b2g.nativeWindowGeometry.width", outerWidth);
+    Services.prefs.setIntPref("b2g.nativeWindowGeometry.height", outerHeight);
+  }
+  window.addEventListener("unload", saveWindowGeometry);
 
-  if (screenX == -1) {
-    // Center
-    screenX = (screen.width - width) / 2;
-    screenY = (screen.height - height) / 2;
+  var baseWindow = window.QueryInterface(Ci.nsIInterfaceRequestor)
+                         .getInterface(Ci.nsIWebNavigation)
+                         .QueryInterface(Ci.nsIDocShellTreeItem)
+                         .treeOwner
+                         .QueryInterface(Ci.nsIInterfaceRequestor)
+                         .getInterface(Ci.nsIBaseWindow);
+
+  const showNativeWindow = () => baseWindow.visibility = true;
+  const hideNativeWindow = () => baseWindow.visibility = false;
+
+  const showInstallScreen = () => {
+    const grapheneStrings =
+      Services.strings.createBundle('chrome://b2g-l10n/locale/graphene.properties');
+    document.querySelector('#installing > .message').textContent =
+      grapheneStrings.GetStringFromName('installing');
+    showNativeWindow();
   }
 
-  moveTo(screenX, screenY);
-  resizeTo(width, height);
-}
-restoreWindowGeometry();
-
-const saveWindowGeometry = () => {
-  window.removeEventListener("unload", saveWindowGeometry);
-  Services.prefs.setIntPref("b2g.nativeWindowGeometry.screenX", screenX);
-  Services.prefs.setIntPref("b2g.nativeWindowGeometry.screenY", screenY);
-  Services.prefs.setIntPref("b2g.nativeWindowGeometry.width", outerWidth);
-  Services.prefs.setIntPref("b2g.nativeWindowGeometry.height", outerHeight);
-}
-window.addEventListener("unload", saveWindowGeometry);
-
-var baseWindow = window.QueryInterface(Ci.nsIInterfaceRequestor)
-                       .getInterface(Ci.nsIWebNavigation)
-                       .QueryInterface(Ci.nsIDocShellTreeItem)
-                       .treeOwner
-                       .QueryInterface(Ci.nsIInterfaceRequestor)
-                       .getInterface(Ci.nsIBaseWindow);
+  const hideInstallScreen = () => {
+    document.body.classList.add('content-loaded');
+  }
 
-const showNativeWindow = () => baseWindow.visibility = true;
-const hideNativeWindow = () => baseWindow.visibility = false;
-
-const showInstallScreen = () => {
-  const grapheneStrings =
-    Services.strings.createBundle('chrome://b2g-l10n/locale/graphene.properties');
-  document.querySelector('#installing > .message').textContent =
-    grapheneStrings.GetStringFromName('installing');
-  showNativeWindow();
-}
+  window.addEventListener('ContentStart', () => {
+    shell.contentBrowser.contentWindow.addEventListener('load', () => {
+      hideInstallScreen();
+      showNativeWindow();
+    });
+  });
 
-const hideInstallScreen = () => {
-  document.body.classList.add('content-loaded');
+  hideNativeWindow();
 }
-
-window.addEventListener('ContentStart', () => {
-  shell.contentBrowser.contentWindow.addEventListener('load', () => {
-    hideInstallScreen();
-    showNativeWindow();
-  });
-});
-
-hideNativeWindow();
-
-#endif
--- a/b2g/chrome/jar.mn
+++ b/b2g/chrome/jar.mn
@@ -4,28 +4,28 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 
 chrome.jar:
 % content branding %content/branding/ contentaccessible=yes
 % content b2g %content/
 
   content/arrow.svg                     (content/arrow.svg)
-* content/settings.js                   (content/settings.js)
+  content/settings.js                   (content/settings.js)
 * content/shell.html                    (content/shell.html)
-* content/shell.js                      (content/shell.js)
+  content/shell.js                      (content/shell.js)
   content/shell_remote.html             (content/shell_remote.html)
   content/shell_remote.js               (content/shell_remote.js)
 * content/shell.css                     (content/shell.css)
   content/blank.html                    (content/blank.html)
   content/blank.css                     (content/blank.css)
 #ifdef MOZ_WIDGET_GONK
   content/devtools/adb.js               (content/devtools/adb.js)
 #endif
-* content/devtools/debugger.js          (content/devtools/debugger.js)
+  content/devtools/debugger.js          (content/devtools/debugger.js)
   content/devtools/hud.js               (content/devtools/hud.js)
 #ifndef MOZ_WIDGET_GONK
   content/desktop.css                   (content/desktop.css)
   content/images/desktop/home-black.png (content/images/desktop/home-black.png)
   content/images/desktop/home-white.png (content/images/desktop/home-white.png)
   content/images/desktop/rotate.png     (content/images/desktop/rotate.png)
   content/desktop.js                    (content/desktop.js)
   content/screen.js                     (content/screen.js)
--- a/b2g/components/DirectoryProvider.js
+++ b/b2g/components/DirectoryProvider.js
@@ -4,16 +4,17 @@
 
 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");
+Cu.import("resource://gre/modules/AppConstants.jsm");
 
 const XRE_OS_UPDATE_APPLY_TO_DIR = "OSUpdApplyToD"
 const UPDATE_ARCHIVE_DIR = "UpdArchD"
 const LOCAL_DIR = "/data/local";
 const UPDATES_DIR = "updates/0";
 const FOTA_DIR = "updates/fota";
 const COREAPPSDIR_PREF = "b2g.coreappsdir"
 
@@ -52,18 +53,24 @@ function DirectoryProvider() {
 DirectoryProvider.prototype = {
   classID: Components.ID("{9181eb7c-6f87-11e1-90b1-4f59d80dd2e5}"),
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIDirectoryServiceProvider]),
   _xpcom_factory: XPCOMUtils.generateSingletonFactory(DirectoryProvider),
 
   _profD: null,
 
-  getFile: function dp_getFile(prop, persistent) {
-#ifdef MOZ_WIDGET_GONK
+  getFile: function(prop, persistent) {
+    if (AppConstants.platform === "gonk") {
+      return this.getFileOnGonk(prop, persistent);
+    }
+    return this.getFileNotGonk(prop, persistent);
+  },
+
+  getFileOnGonk: function(prop, persistent) {
     let localProps = ["cachePDir", "webappsDir", "PrefD", "indexedDBPDir",
                       "permissionDBPDir", "UpdRootD"];
     if (localProps.indexOf(prop) != -1) {
       let file = Cc["@mozilla.org/file/local;1"]
                    .createInstance(Ci.nsILocalFile)
       file.initWithPath(LOCAL_DIR);
       persistent.value = true;
       return file;
@@ -92,17 +99,20 @@ DirectoryProvider.prototype = {
     }
     if (prop == XRE_OS_UPDATE_APPLY_TO_DIR) {
       // getUpdateDir will set persistent to false since it may toggle between
       // /data/local/ and /mnt/sdcard based on free space and/or availability
       // of the sdcard.
       // before apply, check if free space is 1.1 times of update.mar
       return this.getUpdateDir(persistent, FOTA_DIR, 1.1);
     }
-#else
+    return null;
+  },
+
+  getFileNotGonk: function(prop, persistent) {
     // In desktop builds, coreAppsDir is the same as the profile
     // directory unless otherwise specified. We just need to get the
     // path from the parent, and it is then used to build
     // jar:remoteopenfile:// uris.
     if (prop == "coreAppsDir") {
       let coreAppsDirPref;
       try {
         coreAppsDirPref = Services.prefs.getCharPref(COREAPPSDIR_PREF);
@@ -132,17 +142,16 @@ DirectoryProvider.prototype = {
       if (!this._profD) {
         this._profD = cpmm.sendSyncMessage("getProfD", {})[0];
       }
       let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
       file.initWithPath(this._profD);
       persistent.value = true;
       return file;
     }
-#endif
     return null;
   },
 
   // The VolumeService only exists on the device, and not on desktop
   volumeHasFreeSpace: function dp_volumeHasFreeSpace(volumePath, requiredSpace) {
     if (!volumePath) {
       return false;
     }
--- a/b2g/components/HelperAppDialog.js
+++ b/b2g/components/HelperAppDialog.js
@@ -34,17 +34,17 @@ HelperAppLauncherDialog.prototype = {
   },
 
   promptForSaveToFileAsync: function(aLauncher,
                                      aContext,
                                      aDefaultFile,
                                      aSuggestedFileExt,
                                      aForcePrompt) {
     // Retrieve the user's default download directory.
-    Task.spawn(function() {
+    Task.spawn(function*() {
       let file = null;
       try {
         let defaultFolder = yield Downloads.getPreferredDownloadsDirectory();
         let dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
         dir.initWithPath(defaultFolder);
         file = this.validateLeafName(dir, aDefaultFile, aSuggestedFileExt);
       } catch(e) { }
       aLauncher.saveDestinationAvailable(file);
--- a/b2g/components/KillSwitchMain.jsm
+++ b/b2g/components/KillSwitchMain.jsm
@@ -5,16 +5,17 @@
 "use strict";
 
 this.EXPORTED_SYMBOLS = [ "KillSwitchMain" ];
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/AppConstants.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Promise", "resource://gre/modules/Promise.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "settings",
                    "@mozilla.org/settingsService;1",
                    "nsISettingsService");
 
@@ -22,24 +23,24 @@ XPCOMUtils.defineLazyServiceGetter(this,
                    "@mozilla.org/parentprocessmessagemanager;1",
                    "nsIMessageBroadcaster");
 
 XPCOMUtils.defineLazyGetter(this, "permMgr", function() {
   return Cc["@mozilla.org/permissionmanager;1"]
            .getService(Ci.nsIPermissionManager);
 });
 
-#ifdef MOZ_WIDGET_GONK
-XPCOMUtils.defineLazyGetter(this, "libcutils", function () {
-  Cu.import("resource://gre/modules/systemlibs.js");
-  return libcutils;
-});
-#else
-this.libcutils = null;
-#endif
+if (AppConstants.platform === "gonk") {
+  XPCOMUtils.defineLazyGetter(this, "libcutils", function () {
+    Cu.import("resource://gre/modules/systemlibs.js");
+    return libcutils;
+  });
+} else {
+  this.libcutils = null;
+}
 
 const DEBUG = false;
 
 const kEnableKillSwitch   = "KillSwitch:Enable";
 const kEnableKillSwitchOK = "KillSwitch:Enable:OK";
 const kEnableKillSwitchKO = "KillSwitch:Enable:KO";
 
 const kDisableKillSwitch   = "KillSwitch:Disable";
--- a/b2g/components/RecoveryService.js
+++ b/b2g/components/RecoveryService.js
@@ -4,76 +4,82 @@
  * 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, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/ctypes.jsm");
+Cu.import("resource://gre/modules/AppConstants.jsm");
 
 const RECOVERYSERVICE_CID = Components.ID("{b3caca5d-0bb0-48c6-912b-6be6cbf08832}");
 const RECOVERYSERVICE_CONTRACTID = "@mozilla.org/recovery-service;1";
 
 function log(msg) {
   dump("-*- RecoveryService: " + msg + "\n");
 }
 
-#ifdef MOZ_WIDGET_GONK
-var librecovery = (function() {
-  let library;
-  try {
-    library = ctypes.open("librecovery.so");
-  } catch (e) {
-    log("Unable to open librecovery.so");
-    throw Cr.NS_ERROR_FAILURE;
-  }
-  // Bug 1163956, modify updatePath from ctyps.char.ptr to ctype.char.array(4096)
-  // align with librecovery.h. 4096 comes from PATH_MAX
-  let FotaUpdateStatus = new ctypes.StructType("FotaUpdateStatus", [
-                                                { result: ctypes.int },
-                                                { updatePath: ctypes.char.array(4096) }
-                                              ]);
+const isGonk = AppConstants.platform === 'gonk';
+
+if (isGonk) {
+  var librecovery = (function() {
+    let library;
+    try {
+      library = ctypes.open("librecovery.so");
+    } catch (e) {
+      log("Unable to open librecovery.so");
+      throw Cr.NS_ERROR_FAILURE;
+    }
+    // Bug 1163956, modify updatePath from ctyps.char.ptr to ctype.char.array(4096)
+    // align with librecovery.h. 4096 comes from PATH_MAX
+    let FotaUpdateStatus = new ctypes.StructType("FotaUpdateStatus", [
+                                                  { result: ctypes.int },
+                                                  { updatePath: ctypes.char.array(4096) }
+                                                ]);
 
-  return {
-    factoryReset:        library.declare("factoryReset",
-                                         ctypes.default_abi,
-                                         ctypes.int),
-    installFotaUpdate:   library.declare("installFotaUpdate",
-                                         ctypes.default_abi,
-                                         ctypes.int,
-                                         ctypes.char.ptr,
-                                         ctypes.int),
+    return {
+      factoryReset:        library.declare("factoryReset",
+                                           ctypes.default_abi,
+                                           ctypes.int),
+      installFotaUpdate:   library.declare("installFotaUpdate",
+                                           ctypes.default_abi,
+                                           ctypes.int,
+                                           ctypes.char.ptr,
+                                           ctypes.int),
 
-    FotaUpdateStatus:    FotaUpdateStatus,
-    getFotaUpdateStatus: library.declare("getFotaUpdateStatus",
-                                         ctypes.default_abi,
-                                         ctypes.int,
-                                         FotaUpdateStatus.ptr)
-  };
-})();
+      FotaUpdateStatus:    FotaUpdateStatus,
+      getFotaUpdateStatus: library.declare("getFotaUpdateStatus",
+                                           ctypes.default_abi,
+                                           ctypes.int,
+                                           FotaUpdateStatus.ptr)
+    };
+  })();
 
-const gFactoryResetFile = "__post_reset_cmd__";
+  const gFactoryResetFile = "__post_reset_cmd__";
 
-#endif
+}
 
 function RecoveryService() {}
 
 RecoveryService.prototype = {
   classID: RECOVERYSERVICE_CID,
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIRecoveryService]),
   classInfo: XPCOMUtils.generateCI({
     classID: RECOVERYSERVICE_CID,
     contractID: RECOVERYSERVICE_CONTRACTID,
     interfaces: [Ci.nsIRecoveryService],
     classDescription: "B2G Recovery Service"
   }),
 
   factoryReset: function RS_factoryReset(reason) {
-#ifdef MOZ_WIDGET_GONK
+    if (!isGonk) {
+      Cr.NS_ERROR_FAILURE;
+    }
+
     function doReset() {
       // If this succeeds, then the device reboots and this never returns
       if (librecovery.factoryReset() != 0) {
         log("Error: Factory reset failed. Trying again after clearing cache.");
       }
       let cache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
                     .getService(Ci.nsICacheStorageService);
       cache.clear();
@@ -114,42 +120,41 @@ RecoveryService.prototype = {
                                         { tmpPath: postResetFile + ".tmp" });
 
       promise.then(doReset, function onError(error) {
         log("Error: " + error);
       });
     } else {
       doReset();
     }
-#endif
-    throw Cr.NS_ERROR_FAILURE;
   },
 
   installFotaUpdate: function RS_installFotaUpdate(updatePath) {
-#ifdef MOZ_WIDGET_GONK
+    if (!isGonk) {
+      throw Cr.NS_ERROR_FAILURE;
+    }
+
     // If this succeeds, then the device reboots and this never returns
     if (librecovery.installFotaUpdate(updatePath, updatePath.length) != 0) {
       log("Error: FOTA install failed. Trying again after clearing cache.");
     }
     var cache = Cc["@mozilla.org/netwerk/cache-storage-service;1"].getService(Ci.nsICacheStorageService);
     cache.clear();
     if (librecovery.installFotaUpdate(updatePath, updatePath.length) != 0) {
       log("Error: FOTA install failed again");
     }
-#endif
-    throw Cr.NS_ERROR_FAILURE;
   },
 
   getFotaUpdateStatus: function RS_getFotaUpdateStatus() {
     let status =  Ci.nsIRecoveryService.FOTA_UPDATE_UNKNOWN;
-#ifdef MOZ_WIDGET_GONK
-    let cStatus = librecovery.FotaUpdateStatus();
+
+    if (isGonk) {
+      let cStatus = librecovery.FotaUpdateStatus();
 
-    if (librecovery.getFotaUpdateStatus(cStatus.address()) == 0) {
-      status = cStatus.result;
+      if (librecovery.getFotaUpdateStatus(cStatus.address()) == 0) {
+        status = cStatus.result;
+      }
     }
-
-#endif
     return status;
   }
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([RecoveryService]);
--- a/b2g/components/UpdatePrompt.js
+++ b/b2g/components/UpdatePrompt.js
@@ -8,16 +8,17 @@
 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");
 Cu.import("resource://gre/modules/WebappsUpdater.jsm");
+Cu.import("resource://gre/modules/AppConstants.jsm");
 
 const VERBOSE = 1;
 var log =
   VERBOSE ?
   function log_dump(msg) { dump("UpdatePrompt: "+ msg +"\n"); } :
   function log_noop(msg) { };
 
 const PREF_APPLY_PROMPT_TIMEOUT          = "b2g.update.apply-prompt-timeout";
@@ -394,40 +395,40 @@ UpdatePrompt.prototype = {
     }
 
     if (!this.sendUpdateEvent("update-prompt-apply", aUpdate)) {
       log("Unable to prompt, forcing restart");
       this.restartProcess();
       return;
     }
 
-#ifdef MOZ_B2G_RIL
-    let window = Services.wm.getMostRecentWindow("navigator:browser");
-    let pinReq = window.navigator.mozIccManager.getCardLock("pin");
-    pinReq.onsuccess = function(e) {
-      if (e.target.result.enabled) {
-        // The SIM is pin locked. Don't use a fallback timer. This means that
-        // the user has to press Install to apply the update. If we use the
-        // timer, and the timer reboots the phone, then the phone will be
-        // unusable until the SIM is unlocked.
-        log("SIM is pin locked. Not starting fallback timer.");
-      } else {
-        // This means that no pin lock is enabled, so we go ahead and start
-        // the fallback timer.
+    if (AppConstants.MOZ_B2G_RIL) {
+      let window = Services.wm.getMostRecentWindow("navigator:browser");
+      let pinReq = window.navigator.mozIccManager.getCardLock("pin");
+      pinReq.onsuccess = function(e) {
+        if (e.target.result.enabled) {
+          // The SIM is pin locked. Don't use a fallback timer. This means that
+          // the user has to press Install to apply the update. If we use the
+          // timer, and the timer reboots the phone, then the phone will be
+          // unusable until the SIM is unlocked.
+          log("SIM is pin locked. Not starting fallback timer.");
+        } else {
+          // This means that no pin lock is enabled, so we go ahead and start
+          // the fallback timer.
+          this._applyPromptTimer = this.createTimer(this.applyPromptTimeout);
+        }
+      }.bind(this);
+      pinReq.onerror = function(e) {
         this._applyPromptTimer = this.createTimer(this.applyPromptTimeout);
-      }
-    }.bind(this);
-    pinReq.onerror = function(e) {
+      }.bind(this);
+    } else {
+      // Schedule a fallback timeout in case the UI is unable to respond or show
+      // a prompt for some reason.
       this._applyPromptTimer = this.createTimer(this.applyPromptTimeout);
-    }.bind(this);
-#else
-    // Schedule a fallback timeout in case the UI is unable to respond or show
-    // a prompt for some reason.
-    this._applyPromptTimer = this.createTimer(this.applyPromptTimeout);
-#endif
+    }
   },
 
   _copyProperties: ["appVersion", "buildID", "detailsURL", "displayVersion",
                     "errorCode", "isOSUpdate", "platformVersion",
                     "previousAppVersion", "state", "statusText"],
 
   sendUpdateEvent: function UP_sendUpdateEvent(aType, aUpdate) {
     let detail = {};
@@ -548,26 +549,26 @@ UpdatePrompt.prototype = {
       this.showUpdateError(this._update);
     }
   },
 
   restartProcess: function UP_restartProcess() {
     log("Update downloaded, restarting to apply it");
 
     let callbackAfterSet = function() {
-#ifndef MOZ_WIDGET_GONK
-      let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
-                       .getService(Ci.nsIAppStartup);
-      appStartup.quit(appStartup.eForceQuit | appStartup.eRestart);
-#else
-      // NB: on Gonk, we rely on the system process manager to restart us.
-      let pmService = Cc["@mozilla.org/power/powermanagerservice;1"]
-                      .getService(Ci.nsIPowerManagerService);
-      pmService.restart();
-#endif
+      if (AppConstants.platform !== "gonk") {
+        let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
+                         .getService(Ci.nsIAppStartup);
+        appStartup.quit(appStartup.eForceQuit | appStartup.eRestart);
+      } else {
+        // NB: on Gonk, we rely on the system process manager to restart us.
+        let pmService = Cc["@mozilla.org/power/powermanagerservice;1"]
+                        .getService(Ci.nsIPowerManagerService);
+        pmService.restart();
+      }
     }
 
     if (useSettings()) {
       // Save current os version in deviceinfo.previous_os
       let lock = Services.settings.createLock({
         handle: callbackAfterSet,
         handleAbort: function(error) {
           log("Abort callback when trying to set previous_os: " + error);
--- a/b2g/components/moz.build
+++ b/b2g/components/moz.build
@@ -39,49 +39,46 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'gonk
       'SimulatorScreen.js'
     ]
 
 EXTRA_PP_COMPONENTS += [
     'B2GComponents.manifest',
 ]
 
 if CONFIG['MOZ_B2G'] and not CONFIG['MOZ_B2GDROID']:
-    EXTRA_PP_COMPONENTS += [
+    EXTRA_COMPONENTS += [
         'DirectoryProvider.js',
         'RecoveryService.js',
     ]
 
 if CONFIG['MOZ_UPDATER']:
-    EXTRA_PP_COMPONENTS += [
+    EXTRA_COMPONENTS += [
         'UpdatePrompt.js',
     ]
 
 EXTRA_JS_MODULES += [
     'AboutServiceWorkers.jsm',
     'ActivityChannel.jsm',
     'AlertsHelper.jsm',
     'Bootstraper.jsm',
     'ContentRequestHelper.jsm',
     'DebuggerActors.js',
     'ErrorPage.jsm',
     'Frames.jsm',
     'FxAccountsMgmtService.jsm',
+    'KillSwitchMain.jsm',
     'LogCapture.jsm',
     'LogParser.jsm',
     'LogShake.jsm',
     'MultiscreenHandler.jsm',
     'OrientationChangeHandler.jsm',
     'SafeMode.jsm',
     'Screenshot.jsm',
     'SignInToWebsite.jsm',
     'SystemAppProxy.jsm',
     'TelURIParser.jsm',
     'WebappsUpdater.jsm',
 ]
 
-EXTRA_PP_JS_MODULES += [
-    'KillSwitchMain.jsm'
-]
-
 if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'gonk':
     EXTRA_JS_MODULES += [
       'GlobalSimulatorScreen.jsm'
     ]
--- a/b2g/config/aries/sources.xml
+++ b/b2g/config/aries/sources.xml
@@ -10,26 +10,26 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="97266c579c544f5ba57a701f39893cc86d46774a"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="e9419046f360dd05b2717c4994990608519b93e4"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ac7e9ae8a24ab4a3f3da801ca53f95f39a32b89f"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="5de6856fad82857028f9f059f50680a9bea5b75c"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="2d70fdfc0244a7217df1cfa7df9f4798cbfa3af6"/>
   <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="8b880805d454664b3eed11d0f053cdeafa1ff06e"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" revision="a1e239a0bb5cd1d69680bf1075883aa9a7bf2429"/>
   <project groups="linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" path="prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" revision="c7931763d41be602407ed9d71e2c0292c6597e00"/>
   <project groups="linux,x86" name="platform/prebuilts/python/linux-x86/2.7.5" path="prebuilts/python/linux-x86/2.7.5" revision="a32003194f707f66a2d8cdb913ed1869f1926c5d"/>
--- a/b2g/config/dolphin/sources.xml
+++ b/b2g/config/dolphin/sources.xml
@@ -10,26 +10,26 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="97266c579c544f5ba57a701f39893cc86d46774a"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="e9419046f360dd05b2717c4994990608519b93e4"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ac7e9ae8a24ab4a3f3da801ca53f95f39a32b89f"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="5de6856fad82857028f9f059f50680a9bea5b75c"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="2d70fdfc0244a7217df1cfa7df9f4798cbfa3af6"/>
   <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="8b880805d454664b3eed11d0f053cdeafa1ff06e"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" revision="a1e239a0bb5cd1d69680bf1075883aa9a7bf2429"/>
   <project groups="linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" path="prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" revision="c7931763d41be602407ed9d71e2c0292c6597e00"/>
   <project groups="linux,x86" name="platform/prebuilts/python/linux-x86/2.7.5" path="prebuilts/python/linux-x86/2.7.5" revision="83760d213fb3bec7b4117d266fcfbf6fe2ba14ab"/>
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was git://github.com/mozilla/-->
   <remote fetch="https://git.mozilla.org/b2g" name="mozilla"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gecko and Gaia -->
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="97266c579c544f5ba57a701f39893cc86d46774a"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="e9419046f360dd05b2717c4994990608519b93e4"/>
   <!-- Gonk-specific things and forks -->
   <project name="platform_bionic" path="bionic" remote="b2g" revision="e2b3733ba3fa5e3f404e983d2e4142b1f6b1b846"/>
   <project name="platform_build" path="build" remote="b2g" revision="1b0db93fb6b870b03467aff50d6419771ba0d88c">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
   <project name="android-development" path="development" remote="b2g" revision="2bdf22305b523af644e1891b4ddfd9229336d0ce"/>
   <project name="platform_external_apriori" path="external/apriori" remote="b2g" revision="11816ad0406744f963537b23d68ed9c2afb412bd"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,20 +12,20 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="660169a3d7e034a892359e39135e8c2785a6ad6f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="97266c579c544f5ba57a701f39893cc86d46774a"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="e9419046f360dd05b2717c4994990608519b93e4"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ac7e9ae8a24ab4a3f3da801ca53f95f39a32b89f"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="5de6856fad82857028f9f059f50680a9bea5b75c"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="2d70fdfc0244a7217df1cfa7df9f4798cbfa3af6"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
   <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="9025e50b9d29b3cabbbb21e1dd94d0d13121a17e"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="b89fda71fcd0fa0cf969310e75be3ea33e048b44"/>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,25 +10,25 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="97266c579c544f5ba57a701f39893cc86d46774a"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="e9419046f360dd05b2717c4994990608519b93e4"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ac7e9ae8a24ab4a3f3da801ca53f95f39a32b89f"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="5de6856fad82857028f9f059f50680a9bea5b75c"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="2d70fdfc0244a7217df1cfa7df9f4798cbfa3af6"/>
   <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="f92a936f2aa97526d4593386754bdbf02db07a12"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="6e47ff2790f5656b5b074407829ceecf3e6188c4"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="1950e4760fa14688b83cdbb5acaa1af9f82ef434"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" revision="ac6eb97a37035c09fb5ede0852f0881e9aadf9ad"/>
   <project groups="linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" path="prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" revision="737f591c5f95477148d26602c7be56cbea0cdeb9"/>
   <project groups="linux,x86" name="platform/prebuilts/python/linux-x86/2.7.5" path="prebuilts/python/linux-x86/2.7.5" revision="51da9b1981be481b92a59a826d4d78dc73d0989a"/>
--- a/b2g/config/emulator-l/sources.xml
+++ b/b2g/config/emulator-l/sources.xml
@@ -10,25 +10,25 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="c9d4fe680662ee44a4bdea42ae00366f5df399cf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="97266c579c544f5ba57a701f39893cc86d46774a"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="e9419046f360dd05b2717c4994990608519b93e4"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ac7e9ae8a24ab4a3f3da801ca53f95f39a32b89f"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="5de6856fad82857028f9f059f50680a9bea5b75c"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="2d70fdfc0244a7217df1cfa7df9f4798cbfa3af6"/>
   <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
   <!-- Stock Android things -->
   <project groups="pdk,linux" name="platform/prebuilts/clang/linux-x86/host/3.5" path="prebuilts/clang/linux-x86/host/3.5" revision="ffc05a232799fe8fcb3e47b7440b52b1fb4244c0"/>
   <project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" path="prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" revision="337e0ef5e40f02a1ae59b90db0548976c70a7226"/>
   <project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.8" revision="8af5ff6f5dced9eb5a8127459df6c75d24342204"/>
   <project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.8" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.8" revision="30915518fa7ea07166efedc191a4f40aef516fe7"/>
   <project groups="pdk,linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.6" revision="96eee58e3389fb05a835310d6a06a6ba4486097a"/>
   <project groups="pdk,linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.8" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.8" revision="7c8a46698171aa2e0be09edb43d15a6acf832770"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was git://github.com/mozilla/-->
   <remote fetch="https://git.mozilla.org/b2g" name="mozilla"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gecko and Gaia -->
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="97266c579c544f5ba57a701f39893cc86d46774a"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="e9419046f360dd05b2717c4994990608519b93e4"/>
   <!-- Gonk-specific things and forks -->
   <project name="platform_bionic" path="bionic" remote="b2g" revision="e2b3733ba3fa5e3f404e983d2e4142b1f6b1b846"/>
   <project name="platform_build" path="build" remote="b2g" revision="1b0db93fb6b870b03467aff50d6419771ba0d88c">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
   <project name="android-development" path="development" remote="b2g" revision="2bdf22305b523af644e1891b4ddfd9229336d0ce"/>
   <project name="platform_external_apriori" path="external/apriori" remote="b2g" revision="11816ad0406744f963537b23d68ed9c2afb412bd"/>
--- a/b2g/config/flame-kk/sources.xml
+++ b/b2g/config/flame-kk/sources.xml
@@ -10,26 +10,26 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="97266c579c544f5ba57a701f39893cc86d46774a"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="e9419046f360dd05b2717c4994990608519b93e4"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ac7e9ae8a24ab4a3f3da801ca53f95f39a32b89f"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="5de6856fad82857028f9f059f50680a9bea5b75c"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="2d70fdfc0244a7217df1cfa7df9f4798cbfa3af6"/>
   <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="8b880805d454664b3eed11d0f053cdeafa1ff06e"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" revision="a1e239a0bb5cd1d69680bf1075883aa9a7bf2429"/>
   <project groups="linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" path="prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" revision="c7931763d41be602407ed9d71e2c0292c6597e00"/>
   <project groups="linux,x86" name="platform/prebuilts/python/linux-x86/2.7.5" path="prebuilts/python/linux-x86/2.7.5" revision="a32003194f707f66a2d8cdb913ed1869f1926c5d"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
-        "git_revision": "97266c579c544f5ba57a701f39893cc86d46774a", 
+        "git_revision": "e9419046f360dd05b2717c4994990608519b93e4", 
         "remote": "https://git.mozilla.org/releases/gaia.git", 
         "branch": ""
     }, 
-    "revision": "91dde717827095968985b59e5a645b6ac774d015", 
+    "revision": "f8b0a6383f642ae3160fad856fc7c93496a9a1ae", 
     "repo_path": "integration/gaia-central"
 }
--- a/b2g/config/nexus-4-kk/sources.xml
+++ b/b2g/config/nexus-4-kk/sources.xml
@@ -10,26 +10,26 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="97266c579c544f5ba57a701f39893cc86d46774a"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="e9419046f360dd05b2717c4994990608519b93e4"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ac7e9ae8a24ab4a3f3da801ca53f95f39a32b89f"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="5de6856fad82857028f9f059f50680a9bea5b75c"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="2d70fdfc0244a7217df1cfa7df9f4798cbfa3af6"/>
   <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="f92a936f2aa97526d4593386754bdbf02db07a12"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="6e47ff2790f5656b5b074407829ceecf3e6188c4"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="1950e4760fa14688b83cdbb5acaa1af9f82ef434"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" revision="ac6eb97a37035c09fb5ede0852f0881e9aadf9ad"/>
   <project groups="linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" path="prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" revision="737f591c5f95477148d26602c7be56cbea0cdeb9"/>
   <project groups="linux,x86" name="platform/prebuilts/python/linux-x86/2.7.5" path="prebuilts/python/linux-x86/2.7.5" revision="51da9b1981be481b92a59a826d4d78dc73d0989a"/>
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -13,20 +13,20 @@
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="660169a3d7e034a892359e39135e8c2785a6ad6f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="97266c579c544f5ba57a701f39893cc86d46774a"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="e9419046f360dd05b2717c4994990608519b93e4"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ac7e9ae8a24ab4a3f3da801ca53f95f39a32b89f"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="5de6856fad82857028f9f059f50680a9bea5b75c"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="2d70fdfc0244a7217df1cfa7df9f4798cbfa3af6"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
   <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="9025e50b9d29b3cabbbb21e1dd94d0d13121a17e"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="b89fda71fcd0fa0cf969310e75be3ea33e048b44"/>
--- a/b2g/config/nexus-5-l/sources.xml
+++ b/b2g/config/nexus-5-l/sources.xml
@@ -10,26 +10,26 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="c9d4fe680662ee44a4bdea42ae00366f5df399cf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="97266c579c544f5ba57a701f39893cc86d46774a"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="e9419046f360dd05b2717c4994990608519b93e4"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ac7e9ae8a24ab4a3f3da801ca53f95f39a32b89f"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="5de6856fad82857028f9f059f50680a9bea5b75c"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="2d70fdfc0244a7217df1cfa7df9f4798cbfa3af6"/>
   <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
   <!-- Stock Android things -->
   <project groups="pdk,linux" name="platform/prebuilts/clang/linux-x86/host/3.5" path="prebuilts/clang/linux-x86/host/3.5" revision="ffc05a232799fe8fcb3e47b7440b52b1fb4244c0"/>
   <project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" path="prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" revision="337e0ef5e40f02a1ae59b90db0548976c70a7226"/>
   <project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.8" revision="8af5ff6f5dced9eb5a8127459df6c75d24342204"/>
   <project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.8" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.8" revision="30915518fa7ea07166efedc191a4f40aef516fe7"/>
   <project groups="pdk,linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.6" revision="96eee58e3389fb05a835310d6a06a6ba4486097a"/>
   <project groups="pdk,linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.8" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.8" revision="7c8a46698171aa2e0be09edb43d15a6acf832770"/>
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1038,16 +1038,20 @@ pref("browser.sessionstore.restore_hidde
 // be restored when they are focused.
 pref("browser.sessionstore.restore_pinned_tabs_on_demand", false);
 // The version at which we performed the latest upgrade backup
 pref("browser.sessionstore.upgradeBackup.latestBuildID", "");
 // How many upgrade backups should be kept
 pref("browser.sessionstore.upgradeBackup.maxUpgradeBackups", 3);
 // End-users should not run sessionstore in debug mode
 pref("browser.sessionstore.debug", false);
+// Causes SessionStore to ignore non-final update messages from
+// browser tabs that were not caused by a flush from the parent.
+// This is a testing flag and should not be used by end-users.
+pref("browser.sessionstore.debug.no_auto_updates", false);
 // Forget closed windows/tabs after two weeks
 pref("browser.sessionstore.cleanup.forget_closed_after", 1209600000);
 
 // allow META refresh by default
 pref("accessibility.blockautorefresh", false);
 
 // Whether history is enabled or not.
 pref("places.history.enabled", true);
--- a/browser/components/extensions/ext-contextMenus.js
+++ b/browser/components/extensions/ext-contextMenus.js
@@ -28,17 +28,17 @@ function contextMenuObserver(subject, to
 
 // When a new contextMenu is opened, this function is called and
 // we populate the |xulMenu| with all the items from extensions
 // to be displayed. We always clear all the items again when
 // popuphidden fires. Since most of the info we need is already
 // calculated in nsContextMenu.jsm we simple reuse its flags here.
 // For remote processes there is a gContextMenuContentData where all
 // the important info is stored from the child process. We get
-// this data in |contentData|.
+// this data in |contextData|.
 var menuBuilder = {
   build: function(contextData) {
     // TODO: icons should be set for items
     let xulMenu = contextData.menu;
     xulMenu.addEventListener("popuphidden", this);
     let doc = xulMenu.ownerDocument;
     for (let [ext, menuItemMap] of contextMenuMap) {
       let parentMap = new Map();
@@ -93,17 +93,17 @@ var menuBuilder = {
       }
       if (topLevelItems.size > 1) {
         // If more than one top level items are visible, callopse them.
         let top = doc.createElement("menu");
         top.setAttribute("label", ext.name);
         top.setAttribute("ext-type", "top-level-menu");
         let menupopup = doc.createElement("menupopup");
         top.appendChild(menupopup);
-        for (i of topLevelItems) {
+        for (let i of topLevelItems) {
           menupopup.appendChild(i);
         }
         xulMenu.appendChild(top);
         this._itemsToCleanUp.add(top);
       } else if (topLevelItems.size == 1) {
         // If there is only one visible item, we can just append it.
         let singleItem = topLevelItems.values().next().value;
         xulMenu.appendChild(singleItem);
@@ -321,23 +321,23 @@ MenuItem.prototype = {
         break
       }
     }
     if (!enabled) {
       return false;
     }
 
     if (this.documentUrlMatchPattern &&
-        !this.documentUrlMatchPattern.matches(contentData.documentURIObject)) {
+        !this.documentUrlMatchPattern.matches(contextData.documentURIObject)) {
       return false;
     }
 
     if (this.targetUrlPatterns &&
         (contextData.onImage || contextData.onAudio || contextData.onVideo) &&
-        !this.targetUrlPatterns.matches(contentData.mediaURL)) {
+        !this.targetUrlPatterns.matches(contextData.mediaURL)) {
       // TODO: double check if mediaURL is always set when we need it
       return false;
     }
 
     return true;
   },
 };
 
--- a/browser/components/extensions/ext-utils.js
+++ b/browser/components/extensions/ext-utils.js
@@ -468,17 +468,17 @@ global.WindowManager = {
       incognito: PrivateBrowsingUtils.isWindowPrivate(window),
 
       // We fudge on these next two.
       type: this.windowType(window),
       state: window.fullScreen ? "fullscreen" : "normal",
     };
 
     if (getInfo && getInfo.populate) {
-      results.tabs = TabManager.for(extension).getTabs(window);
+      result.tabs = TabManager.for(extension).getTabs(window);
     }
 
     return result;
   },
 };
 
 // Manages listeners for window opening and closing. A window is
 // considered open when the "load" event fires on it. A window is
--- a/browser/components/sessionstore/RunState.jsm
+++ b/browser/components/sessionstore/RunState.jsm
@@ -14,24 +14,16 @@ const STATE_STOPPED = 0;
 const STATE_RUNNING = 1;
 const STATE_QUITTING = 2;
 const STATE_CLOSING = 3;
 const STATE_CLOSED = 4;
 
 // We're initially stopped.
 var state = STATE_STOPPED;
 
-function observer(subj, topic) {
-  Services.obs.removeObserver(observer, topic);
-  state = STATE_QUITTING;
-}
-
-// Listen for when the application is quitting.
-Services.obs.addObserver(observer, "quit-application-granted", false);
-
 /**
  * This module keeps track of SessionStore's current run state. We will
  * always start out at STATE_STOPPED. After the sessionw as read from disk and
  * the initial browser window has loaded we switch to STATE_RUNNING. On the
  * first notice that a browser shutdown was granted we switch to STATE_QUITTING.
  */
 this.RunState = Object.freeze({
   // If we're stopped then SessionStore hasn't been initialized yet. As soon
@@ -90,10 +82,19 @@ this.RunState = Object.freeze({
 
   // Switch the run state to STATE_CLOSED. This must be called by SessionFile
   // after the last write to disk was accepted and no further writes will be
   // allowed. Any writes after this stage will cause exceptions.
   setClosed() {
     if (this.isClosing) {
       state = STATE_CLOSED;
     }
-  }
+  },
+
+  // Switch the run state to STATE_QUITTING. This should be called once we're
+  // certain that the browser is going away and before we start collecting the
+  // final window states to save in the session file.
+  setQuitting() {
+    if (this.isRunning) {
+      state = STATE_QUITTING;
+    }
+  },
 });
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -26,17 +26,17 @@ const NOTIFY_TAB_RESTORED = "sessionstor
 
 // Maximum number of tabs to restore simultaneously. Previously controlled by
 // the browser.sessionstore.max_concurrent_tabs pref.
 const MAX_CONCURRENT_TAB_RESTORES = 3;
 
 // global notifications observed
 const OBSERVING = [
   "browser-window-before-show", "domwindowclosed",
-  "quit-application-requested", "browser-lastwindow-close-granted",
+  "quit-application-granted", "browser-lastwindow-close-granted",
   "quit-application", "browser:purge-session-history",
   "browser:purge-domain-data",
   "idle-daily",
 ];
 
 // XUL Window properties to (re)store
 // Restored in restoreDimensions()
 const WINDOW_ATTRIBUTES = ["width", "height", "screenX", "screenY", "sizemode"];
@@ -175,16 +175,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "TabStateCache",
   "resource:///modules/sessionstore/TabStateCache.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TabStateFlusher",
   "resource:///modules/sessionstore/TabStateFlusher.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Utils",
   "resource:///modules/sessionstore/Utils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ViewSourceBrowser",
   "resource://gre/modules/ViewSourceBrowser.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
+  "resource://gre/modules/AsyncShutdown.jsm");
 
 /**
  * |true| if we are in debug mode, |false| otherwise.
  * Debug mode is controlled by preference browser.sessionstore.debug
  */
 var gDebuggingEnabled = false;
 function debug(aMsg) {
   if (gDebuggingEnabled) {
@@ -622,18 +624,18 @@ var SessionStoreInternal = {
   observe: function ssi_observe(aSubject, aTopic, aData) {
     switch (aTopic) {
       case "browser-window-before-show": // catch new windows
         this.onBeforeBrowserWindowShown(aSubject);
         break;
       case "domwindowclosed": // catch closed windows
         this.onClose(aSubject);
         break;
-      case "quit-application-requested":
-        this.onQuitApplicationRequested();
+      case "quit-application-granted":
+        this.onQuitApplicationGranted();
         break;
       case "browser-lastwindow-close-granted":
         this.onLastWindowCloseGranted();
         break;
       case "quit-application":
         this.onQuitApplication(aData);
         break;
       case "browser:purge-session-history": // catch sanitization
@@ -832,16 +834,17 @@ var SessionStoreInternal = {
         break;
       case "SessionStore:crashedTabRevived":
         // The browser was revived by navigating to a different page
         // manually, so we remove it from the ignored browser set.
         this._crashedBrowsers.delete(browser.permanentKey);
         break;
       case "SessionStore:error":
         this.reportInternalError(data);
+        TabStateFlusher.resolveAll(browser, false, "Received error from the content process");
         break;
       default:
         throw new Error(`received unknown message '${aMessage.name}'`);
         break;
     }
   },
 
   /**
@@ -1205,16 +1208,20 @@ var SessionStoreInternal = {
     aWindow.dispatchEvent(event);
 
     if (this.windowToFocus && this.windowToFocus == aWindow) {
       delete this.windowToFocus;
     }
 
     var tabbrowser = aWindow.gBrowser;
 
+    // The tabbrowser binding will go away once the window is closed,
+    // so we'll hold a reference to the browsers in the closure here.
+    let browsers = tabbrowser.browsers;
+
     TAB_EVENTS.forEach(function(aEvent) {
       tabbrowser.tabContainer.removeEventListener(aEvent, this, true);
     }, this);
 
     aWindow.gBrowser.removeEventListener("XULFrameLoaderCreated", this);
 
     let winData = this._windows[aWindow.__SSi];
 
@@ -1276,20 +1283,16 @@ var SessionStoreInternal = {
       // 3) When the flush is complete, revisit our decision to store the window
       //    in _closedWindows, and add/remove as necessary.
       if (!winData.isPrivate) {
         // Remove any open private tabs the window may contain.
         PrivacyFilter.filterPrivateTabs(winData);
         this.maybeSaveClosedWindow(winData, isLastWindow);
       }
 
-      // The tabbrowser binding will go away once the window is closed,
-      // so we'll hold a reference to the browsers in the closure here.
-      let browsers = tabbrowser.browsers;
-
       TabStateFlusher.flushWindow(aWindow).then(() => {
         // At this point, aWindow is closed! You should probably not try to
         // access any DOM elements from aWindow within this callback unless
         // you're holding on to them in the closure.
 
         // We can still access tabbrowser.browsers, thankfully.
         for (let browser of browsers) {
           if (this._closedWindowTabs.has(browser.permanentKey)) {
@@ -1305,23 +1308,23 @@ var SessionStoreInternal = {
           // It's possible that a tab switched its privacy state at some point
           // before our flush, so we need to filter again.
           PrivacyFilter.filterPrivateTabs(winData);
           this.maybeSaveClosedWindow(winData, isLastWindow);
         }
 
         // Update the tabs data now that we've got the most
         // recent information.
-        this.cleanUpWindow(aWindow, winData);
+        this.cleanUpWindow(aWindow, winData, browsers);
 
         // save the state without this window to disk
         this.saveStateDelayed();
       });
     } else {
-      this.cleanUpWindow(aWindow, winData);
+      this.cleanUpWindow(aWindow, winData, browsers);
     }
 
     for (let i = 0; i < tabbrowser.tabs.length; i++) {
       this.onTabRemove(aWindow, tabbrowser.tabs[i], true);
     }
   },
 
   /**
@@ -1331,17 +1334,23 @@ var SessionStoreInternal = {
    *
    * @param aWindow
    *        The browser window we're cleaning up.
    * @param winData
    *        The data for the window that we should hold in the
    *        DyingWindowCache in case anybody is still holding a
    *        reference to it.
    */
-  cleanUpWindow(aWindow, winData) {
+  cleanUpWindow(aWindow, winData, browsers) {
+    // Any leftover TabStateFlusher Promises need to be resolved now,
+    // since we're about to remove the message listeners.
+    for (let browser of browsers) {
+      TabStateFlusher.resolveAll(browser);
+    }
+
     // Cache the window state until it is completely gone.
     DyingWindowCache.set(aWindow, winData);
 
     let mm = aWindow.getGroupMessageManager("browsers");
     MESSAGES.forEach(msg => mm.removeMessageListener(msg, this));
 
     delete aWindow.__SSi;
   },
@@ -1390,33 +1399,86 @@ var SessionStoreInternal = {
         this._capClosedWindows();
       } else if (!shouldStore && alreadyStored) {
         this._closedWindows.splice(winIndex, 1);
       }
     }
   },
 
   /**
-   * On quit application requested
+   * On quit application granted
    */
-  onQuitApplicationRequested: function ssi_onQuitApplicationRequested() {
-    // get a current snapshot of all windows
-    this._forEachBrowserWindow(function(aWindow) {
-      // Flush all data queued in the content script to not lose it when
-      // shutting down.
-      TabState.flushWindow(aWindow);
-      this._collectWindowData(aWindow);
+  onQuitApplicationGranted: function ssi_onQuitApplicationGranted() {
+    // Collect an initial snapshot of window data before we do the flush
+    this._forEachBrowserWindow((win) => {
+      this._collectWindowData(win);
     });
-    // we must cache this because _getMostRecentBrowserWindow will always
-    // return null by the time quit-application occurs
+
+    // Now add an AsyncShutdown blocker that'll spin the event loop
+    // until the windows have all been flushed.
+
+    // This progress object will track the state of async window flushing
+    // and will help us debug things that go wrong with our AsyncShutdown
+    // blocker.
+    let progress = { total: -1, current: -1 };
+
+    // We're going down! Switch state so that we treat closing windows and
+    // tabs correctly.
+    RunState.setQuitting();
+
+    AsyncShutdown.quitApplicationGranted.addBlocker(
+      "SessionStore: flushing all windows",
+      this.flushAllWindowsAsync(progress),
+      () => progress);
+  },
+
+  /**
+   * An async Task that iterates all open browser windows and flushes
+   * any outstanding messages from their tabs. This will also close
+   * all of the currently open windows while we wait for the flushes
+   * to complete.
+   *
+   * @param progress (Object)
+   *        Optional progress object that will be updated as async
+   *        window flushing progresses. flushAllWindowsSync will
+   *        write to the following properties:
+   *
+   *        total (int):
+   *          The total number of windows to be flushed.
+   *        current (int):
+   *          The current window that we're waiting for a flush on.
+   *
+   * @return Promise
+   */
+  flushAllWindowsAsync: Task.async(function*(progress={}) {
+    let windowPromises = [];
+    // We collect flush promises and close each window immediately so that
+    // the user can't start changing any window state while we're waiting
+    // for the flushes to finish.
+    this._forEachBrowserWindow((win) => {
+      windowPromises.push(TabStateFlusher.flushWindow(win));
+      win.close();
+    });
+
+    progress.total = windowPromises.length;
+
+    // We'll iterate through the Promise array, yielding each one, so as to
+    // provide useful progress information to AsyncShutdown.
+    for (let i = 0; i < windowPromises.length; ++i) {
+      progress.current = i;
+      yield windowPromises[i];
+    };
+
+    // We must cache this because _getMostRecentBrowserWindow will always
+    // return null by the time quit-application occurs.
     var activeWindow = this._getMostRecentBrowserWindow();
     if (activeWindow)
       this.activeWindowSSiCache = activeWindow.__SSi || "";
     DirtyWindows.clear();
-  },
+  }),
 
   /**
    * On last browser window close
    */
   onLastWindowCloseGranted: function ssi_onLastWindowCloseGranted() {
     // last browser window is quitting.
     // remember to restore the last window when another browser window is opened
     // do not account for pref(resume_session_once) at this point, as it might be
--- a/browser/components/sessionstore/TabStateFlusher.jsm
+++ b/browser/components/sessionstore/TabStateFlusher.jsm
@@ -32,29 +32,47 @@ this.TabStateFlusher = Object.freeze({
    * that will resolve when we've heard back from all browsers.
    */
   flushWindow(window) {
     return TabStateFlusherInternal.flushWindow(window);
   },
 
   /**
    * Resolves the flush request with the given flush ID.
+   *
+   * @param browser (<xul:browser>)
+   *        The browser for which the flush is being resolved.
+   * @param flushID (int)
+   *        The ID of the flush that was sent to the browser.
+   * @param success (bool, optional)
+   *        Whether or not the flush succeeded.
+   * @param message (string, optional)
+   *        An error message that will be sent to the Console in the
+   *        event that a flush failed.
    */
-  resolve(browser, flushID) {
-    TabStateFlusherInternal.resolve(browser, flushID);
+  resolve(browser, flushID, success=true, message="") {
+    TabStateFlusherInternal.resolve(browser, flushID, success, message);
   },
 
   /**
    * Resolves all active flush requests for a given browser. This should be
    * used when the content process crashed or the final update message was
    * seen. In those cases we can't guarantee to ever hear back from the frame
    * script so we just resolve all requests instead of discarding them.
+   *
+   * @param browser (<xul:browser>)
+   *        The browser for which all flushes are being resolved.
+   * @param success (bool, optional)
+   *        Whether or not the flushes succeeded.
+   * @param message (string, optional)
+   *        An error message that will be sent to the Console in the
+   *        event that the flushes failed.
    */
-  resolveAll(browser) {
-    TabStateFlusherInternal.resolveAll(browser);
+  resolveAll(browser, success=true, message="") {
+    TabStateFlusherInternal.resolveAll(browser, success, message);
   }
 });
 
 var TabStateFlusherInternal = {
   // Stores the last request ID.
   _lastRequestID: 0,
 
   // A map storing all active requests per browser.
@@ -90,51 +108,77 @@ var TabStateFlusherInternal = {
   flushWindow(window) {
     let browsers = window.gBrowser.browsers;
     let promises = browsers.map((browser) => this.flush(browser));
     return Promise.all(promises);
   },
 
   /**
    * Resolves the flush request with the given flush ID.
+   *
+   * @param browser (<xul:browser>)
+   *        The browser for which the flush is being resolved.
+   * @param flushID (int)
+   *        The ID of the flush that was sent to the browser.
+   * @param success (bool, optional)
+   *        Whether or not the flush succeeded.
+   * @param message (string, optional)
+   *        An error message that will be sent to the Console in the
+   *        event that a flush failed.
    */
-  resolve(browser, flushID) {
+  resolve(browser, flushID, success=true, message="") {
     // Nothing to do if there are no pending flushes for the given browser.
     if (!this._requests.has(browser.permanentKey)) {
       return;
     }
 
     // Retrieve active requests for given browser.
     let perBrowserRequests = this._requests.get(browser.permanentKey);
     if (!perBrowserRequests.has(flushID)) {
       return;
     }
 
+    if (!success) {
+      Cu.reportError("Failed to flush browser: " + message);
+    }
+
     // Resolve the request with the given id.
     let resolve = perBrowserRequests.get(flushID);
     perBrowserRequests.delete(flushID);
-    resolve();
+    resolve(success);
   },
 
   /**
    * Resolves all active flush requests for a given browser. This should be
    * used when the content process crashed or the final update message was
    * seen. In those cases we can't guarantee to ever hear back from the frame
    * script so we just resolve all requests instead of discarding them.
+   *
+   * @param browser (<xul:browser>)
+   *        The browser for which all flushes are being resolved.
+   * @param success (bool, optional)
+   *        Whether or not the flushes succeeded.
+   * @param message (string, optional)
+   *        An error message that will be sent to the Console in the
+   *        event that the flushes failed.
    */
-  resolveAll(browser) {
+  resolveAll(browser, success=true, message="") {
     // Nothing to do if there are no pending flushes for the given browser.
     if (!this._requests.has(browser.permanentKey)) {
       return;
     }
 
     // Retrieve active requests for given browser.
     let perBrowserRequests = this._requests.get(browser.permanentKey);
 
+    if (!success) {
+      Cu.reportError("Failed to flush browser: " + message);
+    }
+
     // Resolve all requests.
     for (let resolve of perBrowserRequests.values()) {
-      resolve();
+      resolve(success);
     }
 
     // Clear active requests.
     perBrowserRequests.clear();
   }
 };
--- a/browser/components/sessionstore/content/content-sessionStore.js
+++ b/browser/components/sessionstore/content/content-sessionStore.js
@@ -40,16 +40,20 @@ XPCOMUtils.defineLazyGetter(this, 'gCont
                             () => { return new ContentRestore(this) });
 
 // The current epoch.
 var gCurrentEpoch = 0;
 
 // A bound to the size of data to store for DOM Storage.
 const DOM_STORAGE_MAX_CHARS = 10000000; // 10M characters
 
+// This pref controls whether or not we send updates to the parent on a timeout
+// or not, and should only be used for tests or debugging.
+const TIMEOUT_DISABLED_PREF = "browser.sessionstore.debug.no_auto_updates";
+
 /**
  * Returns a lazy function that will evaluate the given
  * function |fn| only once and cache its return value.
  */
 function createLazy(fn) {
   let cached = false;
   let cachedValue = null;
 
@@ -660,31 +664,79 @@ var MessageQueue = {
 
   /**
    * The current timeout ID, null if there is no queue data. We use timeouts
    * to damp a flood of data changes and send lots of changes as one batch.
    */
   _timeout: null,
 
   /**
+   * Whether or not sending batched messages on a timer is disabled. This should
+   * only be used for debugging or testing. If you need to access this value,
+   * you should probably use the timeoutDisabled getter.
+   */
+  _timeoutDisabled: false,
+
+  /**
+   * True if batched messages are not being fired on a timer. This should only
+   * ever be true when debugging or during tests.
+   */
+  get timeoutDisabled() {
+    return this._timeoutDisabled;
+  },
+
+  /**
+   * Disables sending batched messages on a timer. Also cancels any pending
+   * timers.
+   */
+  set timeoutDisabled(val) {
+    this._timeoutDisabled = val;
+
+    if (!val && this._timeout) {
+      clearTimeout(this._timeout);
+      this._timeout = null;
+    }
+
+    return val;
+  },
+
+  init() {
+    this.timeoutDisabled =
+      Services.prefs.getBoolPref(TIMEOUT_DISABLED_PREF);
+
+    Services.prefs.addObserver(TIMEOUT_DISABLED_PREF, this, false);
+  },
+
+  uninit() {
+    Services.prefs.removeObserver(TIMEOUT_DISABLED_PREF, this);
+  },
+
+  observe(subject, topic, data) {
+    if (topic == TIMEOUT_DISABLED_PREF) {
+      this.timeoutDisabled =
+        Services.prefs.getBoolPref(TIMEOUT_DISABLED_PREF);
+    }
+  },
+
+  /**
    * Pushes a given |value| onto the queue. The given |key| represents the type
    * of data that is stored and can override data that has been queued before
    * but has not been sent to the parent process, yet.
    *
    * @param key (string)
    *        A unique identifier specific to the type of data this is passed.
    * @param fn (function)
    *        A function that returns the value that will be sent to the parent
    *        process.
    */
   push: function (key, fn) {
     this._data.set(key, createLazy(fn));
     this._lastUpdated.set(key, this._id);
 
-    if (!this._timeout) {
+    if (!this._timeout && !this._timeoutDisabled) {
       // Wait a little before sending the message to batch multiple changes.
       this._timeout = setTimeout(() => this.send(), this.BATCH_DELAY_MS);
     }
   },
 
   /**
    * Sends queued data to the chrome process.
    *
@@ -794,16 +846,17 @@ MessageListener.init();
 FormDataListener.init();
 SyncHandler.init();
 PageStyleListener.init();
 SessionHistoryListener.init();
 SessionStorageListener.init();
 ScrollPositionListener.init();
 DocShellCapabilitiesListener.init();
 PrivacyListener.init();
+MessageQueue.init();
 
 function handleRevivedTab() {
   if (!content) {
     removeEventListener("pagehide", handleRevivedTab);
     return;
   }
 
   if (content.document.documentURI.startsWith("about:tabcrashed")) {
@@ -835,16 +888,17 @@ addEventListener("unload", () => {
   // tabbrowser.xml's updateBrowserRemoteness doesn't cause the pagehide
   // event to be fired.
   handleRevivedTab();
 
   // Remove all registered nsIObservers.
   PageStyleListener.uninit();
   SessionStorageListener.uninit();
   SessionHistoryListener.uninit();
+  MessageQueue.uninit();
 
   // Remove progress listeners.
   gContentRestore.resetRestore();
 
   // We don't need to take care of any gFrameTree observers as the gFrameTree
   // will die with the content script. The same goes for the privacy transition
   // observer that will die with the docShell when the tab is closed.
 });
--- a/browser/components/sessionstore/test/browser_async_window_flushing.js
+++ b/browser/components/sessionstore/test/browser_async_window_flushing.js
@@ -1,26 +1,175 @@
+"use strict";
+
+const PAGE = "http://example.com/";
+
+/**
+ * Tests that if we initially discard a window as not interesting
+ * to save in the closed windows array, that we revisit that decision
+ * after a window flush has completed.
+ */
+add_task(function* test_add_interesting_window() {
+  // We want to suppress all non-final updates from the browser tabs
+  // so as to eliminate any racy-ness with this test.
+  yield pushPrefs(["browser.sessionstore.debug.no_auto_updates", true]);
+
+  // Depending on previous tests, we might already have some closed
+  // windows stored. We'll use its length to determine whether or not
+  // the window was added or not.
+  let initialClosedWindows = ss.getClosedWindowCount();
+
+  // Make sure we can actually store another closed window
+  yield pushPrefs(["browser.sessionstore.max_windows_undo",
+                   initialClosedWindows + 1]);
+
+  // Create a new browser window. Since the default window will start
+  // at about:blank, SessionStore should find this tab (and therefore the
+  // whole window) uninteresting, and should not initially put it into
+  // the closed windows array.
+  let newWin = yield BrowserTestUtils.openNewBrowserWindow();
+
+  let browser = newWin.gBrowser.selectedBrowser;
+
+  // Send a message that will cause the content to change its location
+  // to someplace more interesting. We've disabled auto updates from
+  // the browser, so the parent won't know about this
+  yield ContentTask.spawn(browser, PAGE, function*(PAGE) {
+    content.location = PAGE;
+  });
+
+  // for e10s, this will cause a remoteness switch, since the
+  // initial browser in a newly opened window will not be remote.
+  // We need to wait for that remoteness change before we attach
+  // our OnHistoryReplaceEntry listener.
+  if (gMultiProcessBrowser) {
+    yield BrowserTestUtils.waitForEvent(newWin.gBrowser.selectedTab,
+                                        "TabRemotenessChange");
+  }
+
+  yield promiseContentMessage(browser, "ss-test:OnHistoryReplaceEntry");
+
+  // Clear out the userTypedValue so that the new window looks like
+  // it's really not worth restoring.
+  browser.userTypedValue = null;
+
+  // Once the domWindowClosed Promise resolves, the window should
+  // have closed, and SessionStore's onClose handler should have just
+  // run.
+  let domWindowClosed = BrowserTestUtils.domWindowClosed(newWin);
+
+  // Once this windowClosed Promise resolves, we should have finished
+  // the flush and revisited our decision to put this window into
+  // the closed windows array.
+  let windowClosed = BrowserTestUtils.windowClosed(newWin);
+
+  // Ok, let's close the window.
+  newWin.close();
+
+  yield domWindowClosed;
+  // OnClose has just finished running.
+  let currentClosedWindows = ss.getClosedWindowCount();
+  is(currentClosedWindows, initialClosedWindows,
+     "We should not have added the window to the closed windows array");
+
+  yield windowClosed;
+  // The window flush has finished
+  currentClosedWindows = ss.getClosedWindowCount();
+  is(currentClosedWindows,
+     initialClosedWindows + 1,
+     "We should have added the window to the closed windows array");
+});
+
+/**
+ * Tests that if we initially store a closed window as interesting
+ * to save in the closed windows array, that we revisit that decision
+ * after a window flush has completed, and stop storing a window that
+ * we've deemed no longer interesting.
+ */
+add_task(function* test_remove_uninteresting_window() {
+  // We want to suppress all non-final updates from the browser tabs
+  // so as to eliminate any racy-ness with this test.
+  yield pushPrefs(["browser.sessionstore.debug.no_auto_updates", true]);
+
+  // Depending on previous tests, we might already have some closed
+  // windows stored. We'll use its length to determine whether or not
+  // the window was added or not.
+  let initialClosedWindows = ss.getClosedWindowCount();
+
+  // Make sure we can actually store another closed window
+  yield pushPrefs(["browser.sessionstore.max_windows_undo",
+                   initialClosedWindows + 1]);
+
+  let newWin = yield BrowserTestUtils.openNewBrowserWindow();
+
+  // Now browse the initial tab of that window to an interesting
+  // site.
+  let tab = newWin.gBrowser.selectedTab;
+  let browser = tab.linkedBrowser;
+  browser.loadURI(PAGE);
+
+  yield BrowserTestUtils.browserLoaded(browser, false, PAGE);
+  yield TabStateFlusher.flush(browser);
+
+  // Send a message that will cause the content to purge its
+  // history entries and make itself seem uninteresting.
+  yield ContentTask.spawn(browser, null, function*() {
+    // Epic hackery to make this browser seem suddenly boring.
+    Components.utils.import("resource://gre/modules/BrowserUtils.jsm");
+    docShell.setCurrentURI(BrowserUtils.makeURI("about:blank"));
+
+    let {sessionHistory} = docShell.QueryInterface(Ci.nsIWebNavigation);
+    sessionHistory.PurgeHistory(sessionHistory.count);
+  });
+
+  // Once the domWindowClosed Promise resolves, the window should
+  // have closed, and SessionStore's onClose handler should have just
+  // run.
+  let domWindowClosed = BrowserTestUtils.domWindowClosed(newWin);
+
+  // Once this windowClosed Promise resolves, we should have finished
+  // the flush and revisited our decision to put this window into
+  // the closed windows array.
+  let windowClosed = BrowserTestUtils.windowClosed(newWin);
+
+  // Ok, let's close the window.
+  newWin.close();
+
+  yield domWindowClosed;
+  // OnClose has just finished running.
+  let currentClosedWindows = ss.getClosedWindowCount();
+  is(currentClosedWindows, initialClosedWindows + 1,
+     "We should have added the window to the closed windows array");
+
+  yield windowClosed;
+  // The window flush has finished
+  currentClosedWindows = ss.getClosedWindowCount();
+  is(currentClosedWindows,
+     initialClosedWindows,
+     "We should have removed the window from the closed windows array");
+});
+
 /**
  * Tests that when we close a window, it is immediately removed from the
  * _windows array.
  */
 add_task(function* test_synchronously_remove_window_state() {
   // Depending on previous tests, we might already have some closed
   // windows stored. We'll use its length to determine whether or not
   // the window was added or not.
   let state = JSON.parse(ss.getBrowserState());
   ok(state, "Make sure we can get the state");
   let initialWindows = state.windows.length;
 
   // Open a new window and send the first tab somewhere
   // interesting.
   let newWin = yield BrowserTestUtils.openNewBrowserWindow();
   let browser = newWin.gBrowser.selectedBrowser;
-  browser.loadURI("http://example.com");
-  yield BrowserTestUtils.browserLoaded(browser);
+  browser.loadURI(PAGE);
+  yield BrowserTestUtils.browserLoaded(browser, false, PAGE);
   yield TabStateFlusher.flush(browser);
 
   state = JSON.parse(ss.getBrowserState());
   is(state.windows.length, initialWindows + 1,
      "The new window to be in the state");
 
   // Now close the window, and make sure that the window was removed
   // from the windows list from the SessionState. We're specifically
--- a/browser/components/sessionstore/test/browser_broadcast.js
+++ b/browser/components/sessionstore/test/browser_broadcast.js
@@ -18,39 +18,16 @@ add_task(function flush_on_tabclose() {
 
   let [{state: {storage}}] = JSON.parse(ss.getClosedTabData(window));
   is(storage["http://example.com"].test, "on-tab-close",
     "sessionStorage data has been flushed on TabClose");
 });
 
 /**
  * This test ensures we won't lose tab data queued in the content script when
- * the application tries to quit.
- */
-add_task(function flush_on_quit_requested() {
-  let tab = yield createTabWithStorageData(["http://example.com"]);
-  let browser = tab.linkedBrowser;
-
-  yield modifySessionStorage(browser, {test: "on-quit-requested"});
-
-  // Note that sending quit-application-requested should not interfere with
-  // other tests and code. We're just notifying about a shutdown request but
-  // we will not send quit-application-granted. Observers will thus assume
-  // that some other observer has canceled the request.
-  sendQuitApplicationRequested();
-
-  let {storage} = JSON.parse(ss.getTabState(tab));
-  is(storage["http://example.com"].test, "on-quit-requested",
-    "sessionStorage data has been flushed when a quit is requested");
-
-  gBrowser.removeTab(tab);
-});
-
-/**
- * This test ensures we won't lose tab data queued in the content script when
  * duplicating a tab.
  */
 add_task(function flush_on_duplicate() {
   let tab = yield createTabWithStorageData(["http://example.com"]);
   let browser = tab.linkedBrowser;
 
   yield modifySessionStorage(browser, {test: "on-duplicate"});
   let tab2 = ss.duplicateTab(window, tab);
@@ -147,14 +124,8 @@ function createTabWithStorageData(urls, 
       browser.loadURI(url);
       yield promiseBrowserLoaded(browser);
       yield modifySessionStorage(browser, {test: INITIAL_VALUE});
     }
 
     throw new Task.Result(tab);
   });
 }
-
-function sendQuitApplicationRequested() {
-  let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
-                     .createInstance(Ci.nsISupportsPRBool);
-  Services.obs.notifyObservers(cancelQuit, "quit-application-requested", null);
-}
--- a/browser/components/sessionstore/test/browser_send_async_message_oom.js
+++ b/browser/components/sessionstore/test/browser_send_async_message_oom.js
@@ -46,17 +46,21 @@ add_task(function*() {
 
 
   let promiseReported = new Promise(resolve => {
     browser.messageManager.addMessageListener("SessionStore:error", resolve);
   });
 
   // Attempt to flush. This should fail.
   let promiseFlushed = TabStateFlusher.flush(browser);
-  promiseFlushed.then(() => {throw new Error("Flush should have failed")});
+  promiseFlushed.then((success) => {
+    if (success) {
+      throw new Error("Flush should have failed")
+    }
+  });
 
   // The frame script should report an error.
   yield promiseReported;
 
   // Give us some time to handle that error.
   yield new Promise(resolve => setTimeout(resolve, 10));
 
   // By now, Telemetry should have been updated.
--- a/browser/extensions/loop/.eslintrc
+++ b/browser/extensions/loop/.eslintrc
@@ -54,28 +54,31 @@
     "comma-spacing": 2,
     "comma-style": 2,
     "computed-property-spacing": [2, "never"],
     "consistent-return": 2,
     "curly": [2, "all"],
     "dot-location": [2, "property"],
     "eol-last": 2,
     "eqeqeq": [2, "smart"],
+    "generator-star-spacing": [2, {"before": false, "after": true}],
     "jsx-quotes": [2, "prefer-double"],
     "key-spacing": [2, {"beforeColon": false, "afterColon": true }],
     "linebreak-style": [2, "unix"],
     "new-cap": 0,                 // TODO: set to 2
     "new-parens": 2,
     "no-alert": 2,
     "no-array-constructor": 2,
     "no-caller": 2,
     "no-catch-shadow": 2,
     "no-class-assign": 2,
     "no-const-assign": 2,
     "no-console": 0,              // Leave as 0. We use console logging in content code.
+    "no-duplicate-case": 2,
+    "no-else-return": 2,
     "no-empty": 2,
     "no-empty-label": 2,
     "no-eval": 2,
     "no-extend-native": 2, // XXX
     "no-extra-bind": 0,           // Leave as 0
     "no-extra-parens": 0,         // TODO: (bug?) [2, "functions"],
     "no-extra-semi": 2,
     "no-implied-eval": 2,
@@ -94,16 +97,17 @@
     "no-new-func": 2,
     "no-new-object": 2,
     "no-new-wrappers": 2,
     "no-octal-escape": 2,
     "no-process-exit": 2,
     "no-proto": 2,
     "no-return-assign": 2,
     "no-script-url": 2,
+    "no-self-compare": 2,
     "no-sequences": 2,
     "no-shadow": 2,
     "no-shadow-restricted-names": 2,
     "no-spaced-func": 2,
     "no-trailing-spaces": 2,
     "no-undef-init": 2,
     "no-underscore-dangle": 0,    // Leave as 0. Commonly used for private variables.
     "no-unexpected-multiline": 2,
@@ -126,28 +130,29 @@
     "space-in-parens": [2, "never"],
     "space-return-throw-case": 2,
     "space-unary-ops": [2, {"words": true, "nonwords": false}],
     "spaced-comment": [2, "always"],
     "strict": [2, "function"],
     "yoda": [2, "never"],
     // eslint-plugin-react rules. These are documented at
     // <https://github.com/yannickcr/eslint-plugin-react#list-of-supported-rules>
+    "react/jsx-curly-spacing": [2, "never"],
+    "react/jsx-no-bind": 2,
+    "react/jsx-no-duplicate-props": 2,
     "react/jsx-no-undef": 2,
     "react/jsx-sort-props": 2,
     "react/jsx-sort-prop-types": 2,
     "react/jsx-uses-vars": 2,
-    "react/jsx-no-duplicate-props": 2,
     "react/no-did-mount-set-state": 2,
     "react/no-did-update-set-state": 2,
     "react/no-unknown-property": 2,
     "react/prop-types": 2,
     "react/self-closing-comp": 2,
     "react/wrap-multilines": 2,
-    "react/jsx-curly-spacing": [2, "never"],
     // Not worth it: React is defined globally
     "react/jsx-uses-react": 0,
     "react/react-in-jsx-scope": 0,
     // These ones we don't want to ever enable
     "react/display-name": 0,
     "react/jsx-boolean-value": 0,
     "react/no-danger": 0,
     "react/no-multi-comp": 0
--- a/browser/extensions/loop/bootstrap.js
+++ b/browser/extensions/loop/bootstrap.js
@@ -794,22 +794,18 @@ function startup() {
   let sheets = ["chrome://loop-shared/skin/loop.css"];
 
   if (AppConstants.platform != "linux") {
     sheets.push("chrome://loop/skin/platform.css");
   }
 
   for (let sheet of sheets) {
     let styleSheetURI = Services.io.newURI(sheet, null, null);
-    // XXX We would love to specify AUTHOR_SHEET here and in shutdown, however
-    // bug 1228542 prevents us from doing that as we'd cause a lot of assertions
-    // in debug mode for tests. Once that is fixed, we should be able to change
-    // this, and remove the !important attributes from our syle sheets.
     styleSheetService.loadAndRegisterSheet(styleSheetURI,
-                                           styleSheetService.USER_SHEET);
+                                           styleSheetService.AUTHOR_SHEET);
   }
 }
 
 /**
  * Called when the add-on is shutting down, could be for re-installation
  * or just uninstall.
  */
 function shutdown() {
@@ -841,19 +837,19 @@ function shutdown() {
   // Unload stylesheets.
   let styleSheetService = Cc["@mozilla.org/content/style-sheet-service;1"]
     .getService(Components.interfaces.nsIStyleSheetService);
   let sheets = ["chrome://loop/content/addon/css/loop.css",
                 "chrome://loop/skin/platform.css"];
   for (let sheet of sheets) {
     let styleSheetURI = Services.io.newURI(sheet, null, null);
     if (styleSheetService.sheetRegistered(styleSheetURI,
-                                          styleSheetService.USER_SHEET)) {
+                                          styleSheetService.AUTHOR_SHEET)) {
       styleSheetService.unregisterSheet(styleSheetURI,
-                                        styleSheetService.USER_SHEET);
+                                        styleSheetService.AUTHOR_SHEET);
     }
   }
 
   // Unload modules.
   Cu.unload("chrome://loop/content/modules/MozLoopAPI.jsm");
   Cu.unload("chrome://loop/content/modules/LoopRooms.jsm");
   Cu.unload("chrome://loop/content/modules/MozLoopService.jsm");
 }
--- a/browser/extensions/loop/content/panels/js/otconfig.js
+++ b/browser/extensions/loop/content/panels/js/otconfig.js
@@ -1,12 +1,12 @@
 /* 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/. */
 
 window.OTProperties = {
-  cdnURL: "loop/"
+  cdnURL: ""
 };
 window.OTProperties.assetURL = window.OTProperties.cdnURL + "sdk-content/";
 window.OTProperties.configURL = window.OTProperties.assetURL + "js/dynamic_config.min.js";
 
 // We don't use the SDK's CSS. This will prevent spurious 404 errors.
 window.OTProperties.cssURL = "about:blank";
--- a/browser/extensions/loop/content/panels/js/panel.js
+++ b/browser/extensions/loop/content/panels/js/panel.js
@@ -375,19 +375,19 @@ loop.panel = (function(_, mozL10n) {
         )
       );
     },
 
     render: function() {
       var roomUrl = this.props.roomUrls && this.props.roomUrls[0];
       if (roomUrl && roomUrl.location) {
         return this._renderIcon(roomUrl);
-      } else {
-        return this._renderDefaultIcon();
       }
+
+      return this._renderDefaultIcon();
     }
   });
 
   /**
    * Room list entry.
    *
    * Active Room means there are participants in the room.
    * Opened Room means the user is in the room.
--- a/browser/extensions/loop/content/panels/js/panel.jsx
+++ b/browser/extensions/loop/content/panels/js/panel.jsx
@@ -375,19 +375,19 @@ loop.panel = (function(_, mozL10n) {
         </div>
       );
     },
 
     render: function() {
       var roomUrl = this.props.roomUrls && this.props.roomUrls[0];
       if (roomUrl && roomUrl.location) {
         return this._renderIcon(roomUrl);
-      } else {
-        return this._renderDefaultIcon();
       }
+
+      return this._renderDefaultIcon();
     }
   });
 
   /**
    * Room list entry.
    *
    * Active Room means there are participants in the room.
    * Opened Room means the user is in the room.
--- a/browser/extensions/loop/content/panels/js/roomViews.js
+++ b/browser/extensions/loop/content/panels/js/roomViews.js
@@ -2,17 +2,16 @@
  * 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/. */
 
 var loop = loop || {};
 loop.roomViews = (function(mozL10n) {
   "use strict";
 
   var ROOM_STATES = loop.store.ROOM_STATES;
-  var SCREEN_SHARE_STATES = loop.shared.utils.SCREEN_SHARE_STATES;
   var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
   var sharedActions = loop.shared.actions;
   var sharedMixins = loop.shared.mixins;
   var sharedUtils = loop.shared.utils;
   var sharedViews = loop.shared.views;
 
   /**
    * ActiveRoomStore mixin.
@@ -617,16 +616,22 @@ loop.roomViews = (function(mozL10n) {
       if (this.state.roomState !== ROOM_STATES.MEDIA_WAIT &&
           nextState.roomState === ROOM_STATES.MEDIA_WAIT) {
         this.props.dispatcher.dispatch(new sharedActions.SetupStreamElements({
           publisherConfig: this.getDefaultPublisherConfig({
             publishVideo: !this.state.videoMuted
           })
         }));
       }
+
+      // Automatically start sharing a tab now we're ready to share.
+      if (this.state.roomState !== ROOM_STATES.SESSION_CONNECTED &&
+          nextState.roomState === ROOM_STATES.SESSION_CONNECTED) {
+        this.props.dispatcher.dispatch(new sharedActions.StartBrowserShare());
+      }
     },
 
     /**
      * User clicked on the "Leave" button.
      */
     leaveRoom: function() {
       if (this.state.used) {
         this.props.dispatcher.dispatch(new sharedActions.LeaveRoom());
@@ -762,21 +767,16 @@ loop.roomViews = (function(mozL10n) {
     render: function() {
       if (this.state.roomName || this.state.roomContextUrls) {
         var roomTitle = this.state.roomName ||
                         this.state.roomContextUrls[0].description ||
                         this.state.roomContextUrls[0].location;
         this.setTitle(roomTitle);
       }
 
-      var screenShareData = {
-        state: this.state.screenSharingState || SCREEN_SHARE_STATES.INACTIVE,
-        visible: true
-      };
-
       var shouldRenderInvitationOverlay = this._shouldRenderInvitationOverlay();
       var shouldRenderEditContextView = this.state.showEditContext;
       var roomData = this.props.roomStore.getStoreState("activeRoom");
 
       switch (this.state.roomState) {
         case ROOM_STATES.FAILED:
         case ROOM_STATES.FULL: {
           // Note: While rooms are set to hold a maximum of 2 participants, the
@@ -822,17 +822,16 @@ loop.roomViews = (function(mozL10n) {
                 screenSharePosterUrl: null, 
                 showInitialContext: false, 
                 useDesktopPaths: true}, 
                 React.createElement(sharedViews.ConversationToolbar, {
                   audio: { enabled: !this.state.audioMuted, visible: true}, 
                   dispatcher: this.props.dispatcher, 
                   hangup: this.leaveRoom, 
                   publishStream: this.publishStream, 
-                  screenShare: screenShareData, 
                   settingsMenuItems: settingsMenuItems, 
                   show: !shouldRenderEditContextView, 
                   showHangup: this.props.chatWindowDetached, 
                   video: { enabled: !this.state.videoMuted, visible: true}}), 
                 React.createElement(DesktopRoomInvitationView, {
                   dispatcher: this.props.dispatcher, 
                   error: this.state.error, 
                   onAddContextClick: this.handleAddContextClick, 
--- a/browser/extensions/loop/content/panels/js/roomViews.jsx
+++ b/browser/extensions/loop/content/panels/js/roomViews.jsx
@@ -2,17 +2,16 @@
  * 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/. */
 
 var loop = loop || {};
 loop.roomViews = (function(mozL10n) {
   "use strict";
 
   var ROOM_STATES = loop.store.ROOM_STATES;
-  var SCREEN_SHARE_STATES = loop.shared.utils.SCREEN_SHARE_STATES;
   var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
   var sharedActions = loop.shared.actions;
   var sharedMixins = loop.shared.mixins;
   var sharedUtils = loop.shared.utils;
   var sharedViews = loop.shared.views;
 
   /**
    * ActiveRoomStore mixin.
@@ -617,16 +616,22 @@ loop.roomViews = (function(mozL10n) {
       if (this.state.roomState !== ROOM_STATES.MEDIA_WAIT &&
           nextState.roomState === ROOM_STATES.MEDIA_WAIT) {
         this.props.dispatcher.dispatch(new sharedActions.SetupStreamElements({
           publisherConfig: this.getDefaultPublisherConfig({
             publishVideo: !this.state.videoMuted
           })
         }));
       }
+
+      // Automatically start sharing a tab now we're ready to share.
+      if (this.state.roomState !== ROOM_STATES.SESSION_CONNECTED &&
+          nextState.roomState === ROOM_STATES.SESSION_CONNECTED) {
+        this.props.dispatcher.dispatch(new sharedActions.StartBrowserShare());
+      }
     },
 
     /**
      * User clicked on the "Leave" button.
      */
     leaveRoom: function() {
       if (this.state.used) {
         this.props.dispatcher.dispatch(new sharedActions.LeaveRoom());
@@ -762,21 +767,16 @@ loop.roomViews = (function(mozL10n) {
     render: function() {
       if (this.state.roomName || this.state.roomContextUrls) {
         var roomTitle = this.state.roomName ||
                         this.state.roomContextUrls[0].description ||
                         this.state.roomContextUrls[0].location;
         this.setTitle(roomTitle);
       }
 
-      var screenShareData = {
-        state: this.state.screenSharingState || SCREEN_SHARE_STATES.INACTIVE,
-        visible: true
-      };
-
       var shouldRenderInvitationOverlay = this._shouldRenderInvitationOverlay();
       var shouldRenderEditContextView = this.state.showEditContext;
       var roomData = this.props.roomStore.getStoreState("activeRoom");
 
       switch (this.state.roomState) {
         case ROOM_STATES.FAILED:
         case ROOM_STATES.FULL: {
           // Note: While rooms are set to hold a maximum of 2 participants, the
@@ -822,17 +822,16 @@ loop.roomViews = (function(mozL10n) {
                 screenSharePosterUrl={null}
                 showInitialContext={false}
                 useDesktopPaths={true}>
                 <sharedViews.ConversationToolbar
                   audio={{ enabled: !this.state.audioMuted, visible: true }}
                   dispatcher={this.props.dispatcher}
                   hangup={this.leaveRoom}
                   publishStream={this.publishStream}
-                  screenShare={screenShareData}
                   settingsMenuItems={settingsMenuItems}
                   show={!shouldRenderEditContextView}
                   showHangup={this.props.chatWindowDetached}
                   video={{ enabled: !this.state.videoMuted, visible: true }} />
                 <DesktopRoomInvitationView
                   dispatcher={this.props.dispatcher}
                   error={this.state.error}
                   onAddContextClick={this.handleAddContextClick}
--- a/browser/extensions/loop/content/shared/css/conversation.css
+++ b/browser/extensions/loop/content/shared/css/conversation.css
@@ -155,34 +155,16 @@ html[dir="rtl"] .conversation-toolbar-bt
   background-color: transparent;
 }
 
 .btn-settings:hover,
 .btn-settings:active {
   background-image: url("../img/settings-hover.svg");
 }
 
-.btn-screen-share {
-  background-image: url("../img/sharing.svg");
-}
-
-.btn-screen-share:hover,
-.btn-screen-share:active {
-  background-image: url("../img/sharing-hover.svg");
-}
-
-.btn-screen-share.active {
-  background-image: url("../img/sharing-active.svg");
-}
-
-.btn-screen-share.disabled {
-  /* The screen share button is in its pending state when its disabled. */
-  background-image: url("../img/sharing-pending.svg");
-}
-
 /* General Call (incoming or outgoing). */
 
 .call-action-group {
   display: flex;
   padding: 0 4px;
   width: 100%;
 }
 
@@ -314,37 +296,30 @@ html[dir="rtl"] .room-failure > .setting
   margin: 0.25rem 0;
   flex: none;
 }
 
 .failure-info-extra-failure {
   color: #f00;
 }
 
-.screen-share-menu.dropdown-menu,
 .settings-menu.dropdown-menu {
   bottom: 3.1rem;
 }
 
 .settings-menu.dropdown-menu {
   left: auto;
   /*offset dropdown menu to be above menu button*/
   right: 14px;
 }
 
-html[dir="rtl"] .screen-share-menu.dropdown-menu,
 html[dir="rtl"] .settings-menu.dropdown-menu {
   right: auto;
 }
 
-html[dir="rtl"] .screen-share-menu.dropdown-menu {
-  /*offset dropdown menu to be above menu button*/
-  left: 40px;
-}
-
 html[dir="rtl"] .settings-menu.dropdown-menu {
   /*offset dropdown menu to be above menu button*/
   left: 14px;
 }
 
 .settings-menu.dropdown-menu.menu-below {
   top: 11.5rem;
   bottom: auto;
--- a/browser/extensions/loop/content/shared/img/sharing-active.svg
+++ b/browser/extensions/loop/content/shared/img/sharing-active.svg
@@ -1,1 +0,0 @@
-<svg width="28" height="28" viewBox="0 0 28 28" xmlns="http://www.w3.org/2000/svg"><g fill="none"><rect opacity=".95" fill="#fff" x="2" y="2" width="24" height="24" rx="40"/><path d="M2 14c0 6.629 5.373 12 12 12 6.629 0 12-5.373 12-12 0-6.629-5.373-12-12-12-6.629 0-12 5.373-12 12zm-2 0c0-7.732 6.267-14 14-14 7.732 0 14 6.267 14 14 0 7.732-6.267 14-14 14-7.732 0-14-6.267-14-14z" fill-opacity=".2" fill="#000"/><rect fill="#00A9DC" x="11" y="12" width="9" height="7" rx="1"/><path d="M17 11v-.997c0-.565-.447-1.003-.998-1.003h-7.005c-.551 0-.998.449-.998 1.003v4.994c0 .565.447 1.003.998 1.003h1.002v-3.997c0-.554.446-1.003.998-1.003h6.002z" fill="#00A9DC"/></g></svg>
--- a/browser/extensions/loop/content/shared/img/sharing-hover.svg
+++ b/browser/extensions/loop/content/shared/img/sharing-hover.svg
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg width="28px" height="28px" viewBox="0 0 28 28" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
-        <rect id="Rectangle-1264-Copy" opacity="0.95" fill="#5CCCEE" x="2" y="2" width="24" height="24" rx="40"></rect>
-        <path d="M2,14 L2,14 C2,20.6288742 7.372583,26 14,26 L14,26 C20.6288742,26 26,20.627417 26,14 L26,14 C26,7.37112582 20.627417,2 14,2 L14,2 C7.37112582,2 2,7.372583 2,14 L2,14 Z M0,14 L0,14 C0,6.26754774 6.26702203,0 14,0 C21.7324523,0 28,6.26702203 28,14 C28,21.7324523 21.732978,28 14,28 C6.26754774,28 0,21.732978 0,14 L0,14 Z" id="Shape" fill-opacity="0.2" fill="#000000"></path>
-        <rect id="Rectangle-170-Copy-4" fill="#FFFFFF" x="11" y="12" width="9" height="7" rx="1"></rect>
-        <path d="M17,11 L17,10.0029293 C17,9.43788135 16.553384,9 16.0024554,9 L8.99754465,9 C8.4463114,9 8,9.44902676 8,10.0029293 L8,14.9970707 C8,15.5621186 8.44661595,16 8.99754465,16 L10,16 L10,12.0029293 C10,11.4490268 10.4463114,11 10.9975446,11 L17,11 Z" id="Rectangle-170-Copy" fill="#FFFFFF"></path>
-    </g>
-</svg>
\ No newline at end of file
--- a/browser/extensions/loop/content/shared/img/sharing-pending.svg
+++ b/browser/extensions/loop/content/shared/img/sharing-pending.svg
@@ -1,1 +0,0 @@
-<svg width="28" height="28" viewBox="0 0 28 28" xmlns="http://www.w3.org/2000/svg"><g fill="none"><rect opacity=".95" fill="#999999" x="2" y="2" width="24" height="24" rx="40"/><path d="M2 14c0 6.629 5.373 12 12 12 6.629 0 12-5.373 12-12 0-6.629-5.373-12-12-12-6.629 0-12 5.373-12 12zm-2 0c0-7.732 6.267-14 14-14 7.732 0 14 6.267 14 14 0 7.732-6.267 14-14 14-7.732 0-14-6.267-14-14z" fill-opacity=".2" fill="#000"/><rect fill="#4A4A4A" x="11" y="12" width="9" height="7" rx="1"/><path d="M17 11v-.997c0-.565-.447-1.003-.998-1.003h-7.005c-.551 0-.998.449-.998 1.003v4.994c0 .565.447 1.003.998 1.003h1.002v-3.997c0-.554.446-1.003.998-1.003h6.002z" fill="#4A4A4A"/></g></svg>
--- a/browser/extensions/loop/content/shared/img/sharing.svg
+++ b/browser/extensions/loop/content/shared/img/sharing.svg
@@ -1,1 +0,0 @@
-<svg width="28" height="28" viewBox="0 0 28 28" xmlns="http://www.w3.org/2000/svg"><g fill="none"><rect opacity=".95" fill="#fff" x="2" y="2" width="24" height="24" rx="40"/><path d="M2 14c0 6.629 5.373 12 12 12 6.629 0 12-5.373 12-12 0-6.629-5.373-12-12-12-6.629 0-12 5.373-12 12zm-2 0c0-7.732 6.267-14 14-14 7.732 0 14 6.267 14 14 0 7.732-6.267 14-14 14-7.732 0-14-6.267-14-14z" fill-opacity=".2" fill="#000"/><rect fill="#4A4A4A" x="11" y="12" width="9" height="7" rx="1"/><path d="M17 11v-.997c0-.565-.447-1.003-.998-1.003h-7.005c-.551 0-.998.449-.998 1.003v4.994c0 .565.447 1.003.998 1.003h1.002v-3.997c0-.554.446-1.003.998-1.003h6.002z" fill="#4A4A4A"/></g></svg>
\ No newline at end of file
--- a/browser/extensions/loop/content/shared/js/actions.js
+++ b/browser/extensions/loop/content/shared/js/actions.js
@@ -12,18 +12,19 @@ loop.shared.actions = (function() {
    * or by an async event, e.g. status received.
    *
    * They should be dispatched to stores via the dispatcher.
    */
 
   function Action(name, schema, values) {
     var validatedData = new loop.validate.Validator(schema || {})
                                          .validate(values || {});
-    for (var prop in validatedData)
+    for (var prop in validatedData) {
       this[prop] = validatedData[prop];
+    }
 
     this.name = name;
   }
 
   Action.define = function(name, schema) {
     return Action.bind(null, name, schema);
   };
 
@@ -200,21 +201,19 @@ loop.shared.actions = (function() {
     SetMute: Action.define("setMute", {
       // The part of the stream to enable, e.g. "audio" or "video"
       type: String,
       // Whether or not to enable the stream.
       enabled: Boolean
     }),
 
     /**
-     * Used to start a screen share.
+     * Used to start a browser tab share.
      */
-    StartScreenShare: Action.define("startScreenShare", {
-      // The part of the screen to share, e.g. "window" or "browser".
-      type: String
+    StartBrowserShare: Action.define("startBrowserShare", {
     }),
 
     /**
      * Used to end a screen share.
      */
     EndScreenShare: Action.define("endScreenShare", {
     }),
 
--- a/browser/extensions/loop/content/shared/js/activeRoomStore.js
+++ b/browser/extensions/loop/content/shared/js/activeRoomStore.js
@@ -252,17 +252,17 @@ loop.store.ActiveRoomStore = (function()
         "remotePeerConnected",
         "windowUnload",
         "leaveRoom",
         "feedbackComplete",
         "mediaStreamCreated",
         "mediaStreamDestroyed",
         "remoteVideoStatus",
         "videoDimensionsChanged",
-        "startScreenShare",
+        "startBrowserShare",
         "endScreenShare",
         "updateSocialShareInfo",
         "connectionStatus",
         "mediaConnected"
       ];
       // Register actions that are only used on Desktop.
       if (this._isDesktop) {
         // 'receivedTextChatMessage' and  'sendTextChatMessage' actions are only
@@ -915,43 +915,40 @@ loop.store.ActiveRoomStore = (function()
         // Just update the current share.
         this._sdkDriver.switchAcquiredWindow(windowId);
       } else {
         console.error("Unexpectedly received windowId for browser sharing when pending");
       }
     },
 
     /**
-     * Initiates a screen sharing publisher.
+     * Initiates a browser tab sharing publisher.
      *
-     * @param {sharedActions.StartScreenShare} actionData
+     * @param {sharedActions.StartBrowserShare} actionData
      */
-    startScreenShare: function(actionData) {
+    startBrowserShare: function(actionData) {
       // For the unit test we already set the state here, instead of indirectly
       // via an action, because actions are queued thus depending on the
       // asynchronous nature of `loop.request`.
       this.setStoreState({ screenSharingState: SCREEN_SHARE_STATES.PENDING });
       this.dispatchAction(new sharedActions.ScreenSharingState({
         state: SCREEN_SHARE_STATES.PENDING
       }));
 
       var options = {
-        videoSource: actionData.type
+        videoSource: "browser"
       };
-      if (options.videoSource === "browser") {
-        this._browserSharingListener = this._handleSwitchBrowserShare.bind(this);
+      this._browserSharingListener = this._handleSwitchBrowserShare.bind(this);
 
-        // Set up a listener for watching screen shares. This will get notified
-        // with the first windowId when it is added, so we start off the sharing
-        // from within the listener.
-        loop.request("AddBrowserSharingListener").then(this._browserSharingListener);
-        loop.subscribe("BrowserSwitch", this._browserSharingListener);
-      } else {
-        this._sdkDriver.startScreenShare(options);
-      }
+      // Set up a listener for watching screen shares. This will get notified
+      // with the first windowId when it is added, so we start off the sharing
+      // from within the listener.
+      loop.request("AddBrowserSharingListener", this.getStoreState().windowId)
+        .then(this._browserSharingListener);
+      loop.subscribe("BrowserSwitch", this._browserSharingListener);
     },
 
     /**
      * Ends an active screenshare session.
      */
     endScreenShare: function() {
       if (this._browserSharingListener) {
         // Remove the browser sharing listener as we don't need it now.
--- a/browser/extensions/loop/content/shared/js/views.js
+++ b/browser/extensions/loop/content/shared/js/views.js
@@ -5,17 +5,16 @@
 var loop = loop || {};
 loop.shared = loop.shared || {};
 loop.shared.views = (function(_, mozL10n) {
   "use strict";
 
   var sharedActions = loop.shared.actions;
   var sharedModels = loop.shared.models;
   var sharedMixins = loop.shared.mixins;
-  var SCREEN_SHARE_STATES = loop.shared.utils.SCREEN_SHARE_STATES;
 
   /**
    * Hang-up control button.
    *
    * Required props:
    * - {Function} action  Function to be executed on click.
    * - {String}   title   Tooltip functionality.
    */
@@ -100,124 +99,16 @@ loop.shared.views = (function(_, mozL10n
         React.createElement("button", {className: this._getClasses(), 
                 onClick: this.handleClick, 
                 title: this._getTitle()})
       );
     }
   });
 
   /**
-   * Screen sharing control button.
-   *
-   * Required props:
-   * - {loop.Dispatcher} dispatcher  The dispatcher instance
-   * - {Boolean}         visible     Set to true to display the button
-   * - {String}          state       One of the screen sharing states, see
-   *                                 loop.shared.utils.SCREEN_SHARE_STATES
-   */
-  var ScreenShareControlButton = React.createClass({displayName: "ScreenShareControlButton",
-    mixins: [sharedMixins.DropdownMenuMixin()],
-
-    propTypes: {
-      dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
-      state: React.PropTypes.string.isRequired,
-      visible: React.PropTypes.bool.isRequired
-    },
-
-    getInitialState: function() {
-      var os = loop.shared.utils.getOS();
-      var osVersion = loop.shared.utils.getOSVersion();
-      // Disable screensharing on older OSX and Windows versions.
-      if ((os.indexOf("mac") > -1 && osVersion.major <= 10 && osVersion.minor <= 6) ||
-          (os.indexOf("win") > -1 && osVersion.major <= 5 && osVersion.minor <= 2)) {
-        return { windowSharingDisabled: true };
-      }
-      return { windowSharingDisabled: false };
-    },
-
-    handleClick: function() {
-      if (this.props.state === SCREEN_SHARE_STATES.ACTIVE) {
-        this.props.dispatcher.dispatch(
-          new sharedActions.EndScreenShare({}));
-      } else {
-        this.toggleDropdownMenu();
-      }
-    },
-
-    _startScreenShare: function(type) {
-      this.props.dispatcher.dispatch(new sharedActions.StartScreenShare({
-        type: type
-      }));
-    },
-
-    _handleShareTabs: function() {
-      this._startScreenShare("browser");
-      this.hideDropdownMenu();
-    },
-
-    _handleShareWindows: function() {
-      this._startScreenShare("window");
-      this.hideDropdownMenu();
-    },
-
-    _getTitle: function() {
-      var prefix = this.props.state === SCREEN_SHARE_STATES.ACTIVE ?
-        "active" : "inactive";
-
-      return mozL10n.get(prefix + "_screenshare_button_title");
-    },
-
-    render: function() {
-      if (!this.props.visible) {
-        return null;
-      }
-
-      var cx = classNames;
-
-      var isActive = this.props.state === SCREEN_SHARE_STATES.ACTIVE;
-      var screenShareClasses = cx({
-        "btn": true,
-        "btn-screen-share": true,
-        "transparent-button": true,
-        "menu-showing": this.state.showMenu,
-        "active": isActive,
-        "disabled": this.props.state === SCREEN_SHARE_STATES.PENDING
-      });
-      var dropdownMenuClasses = cx({
-        "screen-share-menu": true,
-        "dropdown-menu": true,
-        "hide": !this.state.showMenu
-      });
-      var windowSharingClasses = cx({
-        "dropdown-menu-item": true,
-        "disabled": this.state.windowSharingDisabled
-      });
-
-      return (
-        React.createElement("div", null, 
-          React.createElement("button", {className: screenShareClasses, 
-                  onClick: this.handleClick, 
-                  ref: "anchor", 
-                  title: this._getTitle()}, 
-            isActive ? null : React.createElement("span", {className: "chevron"})
-          ), 
-          React.createElement("ul", {className: dropdownMenuClasses, ref: "menu"}, 
-            React.createElement("li", {className: "dropdown-menu-item", onClick: this._handleShareTabs}, 
-              mozL10n.get("share_tabs_button_title2")
-            ), 
-            React.createElement("li", {className: windowSharingClasses, onClick: this._handleShareWindows}, 
-              mozL10n.get("share_windows_button_title")
-            )
-          )
-        )
-      );
-    }
-  });
-
-  /**
    * Settings control button.
    */
   var SettingsControlButton = React.createClass({displayName: "SettingsControlButton",
     propTypes: {
       // Set to true if the menu should be below the button rather than above.
       menuBelow: React.PropTypes.bool,
       menuItems: React.PropTypes.array
     },
@@ -386,34 +277,32 @@ loop.shared.views = (function(_, mozL10n
   /**
    * Conversation controls.
    */
   var ConversationToolbar = React.createClass({displayName: "ConversationToolbar",
     getDefaultProps: function() {
       return {
         video: { enabled: true, visible: true },
         audio: { enabled: true, visible: true },
-        screenShare: { state: SCREEN_SHARE_STATES.INACTIVE, visible: false },
         settingsMenuItems: null,
         showHangup: true
       };
     },
 
     getInitialState: function() {
       return {
         idle: false
       };
     },
 
     propTypes: {
       audio: React.PropTypes.object.isRequired,
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       hangup: React.PropTypes.func.isRequired,
       publishStream: React.PropTypes.func.isRequired,
-      screenShare: React.PropTypes.object,
       settingsMenuItems: React.PropTypes.array,
       show: React.PropTypes.bool.isRequired,
       showHangup: React.PropTypes.bool,
       video: React.PropTypes.object.isRequired
     },
 
     handleClickHangup: function() {
       this.props.hangup();
@@ -520,21 +409,16 @@ loop.shared.views = (function(_, mozL10n
                                     scope: "local", type: "video", 
                                     visible: this.props.video.visible}), 
                 React.createElement(MediaControlButton, {action: this.handleToggleAudio, 
                                     enabled: this.props.audio.enabled, 
                                     scope: "local", type: "audio", 
                                     visible: this.props.audio.visible})
             )
           ), 
-          React.createElement("li", {className: "conversation-toolbar-btn-box"}, 
-            React.createElement(ScreenShareControlButton, {dispatcher: this.props.dispatcher, 
-                                      state: this.props.screenShare.state, 
-                                      visible: this.props.screenShare.visible})
-          ), 
           React.createElement("li", {className: "conversation-toolbar-btn-box btn-edit-entry"}, 
             React.createElement(SettingsControlButton, {menuItems: this.props.settingsMenuItems})
           )
         )
       );
     }
   });
 
@@ -1136,12 +1020,11 @@ loop.shared.views = (function(_, mozL10n
     Checkbox: Checkbox,
     ContextUrlView: ContextUrlView,
     ConversationToolbar: ConversationToolbar,
     MediaControlButton: MediaControlButton,
     MediaLayoutView: MediaLayoutView,
     MediaView: MediaView,
     LoadingView: LoadingView,
     SettingsControlButton: SettingsControlButton,
-    ScreenShareControlButton: ScreenShareControlButton,
     NotificationListView: NotificationListView
   };
 })(_, navigator.mozL10n || document.mozL10n);
--- a/browser/extensions/loop/content/shared/js/views.jsx
+++ b/browser/extensions/loop/content/shared/js/views.jsx
@@ -5,17 +5,16 @@
 var loop = loop || {};
 loop.shared = loop.shared || {};
 loop.shared.views = (function(_, mozL10n) {
   "use strict";
 
   var sharedActions = loop.shared.actions;
   var sharedModels = loop.shared.models;
   var sharedMixins = loop.shared.mixins;
-  var SCREEN_SHARE_STATES = loop.shared.utils.SCREEN_SHARE_STATES;
 
   /**
    * Hang-up control button.
    *
    * Required props:
    * - {Function} action  Function to be executed on click.
    * - {String}   title   Tooltip functionality.
    */
@@ -100,124 +99,16 @@ loop.shared.views = (function(_, mozL10n
         <button className={this._getClasses()}
                 onClick={this.handleClick}
                 title={this._getTitle()}></button>
       );
     }
   });
 
   /**
-   * Screen sharing control button.
-   *
-   * Required props:
-   * - {loop.Dispatcher} dispatcher  The dispatcher instance
-   * - {Boolean}         visible     Set to true to display the button
-   * - {String}          state       One of the screen sharing states, see
-   *                                 loop.shared.utils.SCREEN_SHARE_STATES
-   */
-  var ScreenShareControlButton = React.createClass({
-    mixins: [sharedMixins.DropdownMenuMixin()],
-
-    propTypes: {
-      dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
-      state: React.PropTypes.string.isRequired,
-      visible: React.PropTypes.bool.isRequired
-    },
-
-    getInitialState: function() {
-      var os = loop.shared.utils.getOS();
-      var osVersion = loop.shared.utils.getOSVersion();
-      // Disable screensharing on older OSX and Windows versions.
-      if ((os.indexOf("mac") > -1 && osVersion.major <= 10 && osVersion.minor <= 6) ||
-          (os.indexOf("win") > -1 && osVersion.major <= 5 && osVersion.minor <= 2)) {
-        return { windowSharingDisabled: true };
-      }
-      return { windowSharingDisabled: false };
-    },
-
-    handleClick: function() {
-      if (this.props.state === SCREEN_SHARE_STATES.ACTIVE) {
-        this.props.dispatcher.dispatch(
-          new sharedActions.EndScreenShare({}));
-      } else {
-        this.toggleDropdownMenu();
-      }
-    },
-
-    _startScreenShare: function(type) {
-      this.props.dispatcher.dispatch(new sharedActions.StartScreenShare({
-        type: type
-      }));
-    },
-
-    _handleShareTabs: function() {
-      this._startScreenShare("browser");
-      this.hideDropdownMenu();
-    },
-
-    _handleShareWindows: function() {
-      this._startScreenShare("window");
-      this.hideDropdownMenu();
-    },
-
-    _getTitle: function() {
-      var prefix = this.props.state === SCREEN_SHARE_STATES.ACTIVE ?
-        "active" : "inactive";
-
-      return mozL10n.get(prefix + "_screenshare_button_title");
-    },
-
-    render: function() {
-      if (!this.props.visible) {
-        return null;
-      }
-
-      var cx = classNames;
-
-      var isActive = this.props.state === SCREEN_SHARE_STATES.ACTIVE;
-      var screenShareClasses = cx({
-        "btn": true,
-        "btn-screen-share": true,
-        "transparent-button": true,
-        "menu-showing": this.state.showMenu,
-        "active": isActive,
-        "disabled": this.props.state === SCREEN_SHARE_STATES.PENDING
-      });
-      var dropdownMenuClasses = cx({
-        "screen-share-menu": true,
-        "dropdown-menu": true,
-        "hide": !this.state.showMenu
-      });
-      var windowSharingClasses = cx({
-        "dropdown-menu-item": true,
-        "disabled": this.state.windowSharingDisabled
-      });
-
-      return (
-        <div>
-          <button className={screenShareClasses}
-                  onClick={this.handleClick}
-                  ref="anchor"
-                  title={this._getTitle()}>
-            {isActive ? null : <span className="chevron"/>}
-          </button>
-          <ul className={dropdownMenuClasses} ref="menu">
-            <li className="dropdown-menu-item" onClick={this._handleShareTabs}>
-              {mozL10n.get("share_tabs_button_title2")}
-            </li>
-            <li className={windowSharingClasses} onClick={this._handleShareWindows}>
-              {mozL10n.get("share_windows_button_title")}
-            </li>
-          </ul>
-        </div>
-      );
-    }
-  });
-
-  /**
    * Settings control button.
    */
   var SettingsControlButton = React.createClass({
     propTypes: {
       // Set to true if the menu should be below the button rather than above.
       menuBelow: React.PropTypes.bool,
       menuItems: React.PropTypes.array
     },
@@ -386,34 +277,32 @@ loop.shared.views = (function(_, mozL10n
   /**
    * Conversation controls.
    */
   var ConversationToolbar = React.createClass({
     getDefaultProps: function() {
       return {
         video: { enabled: true, visible: true },
         audio: { enabled: true, visible: true },
-        screenShare: { state: SCREEN_SHARE_STATES.INACTIVE, visible: false },
         settingsMenuItems: null,
         showHangup: true
       };
     },
 
     getInitialState: function() {
       return {
         idle: false
       };
     },
 
     propTypes: {
       audio: React.PropTypes.object.isRequired,
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       hangup: React.PropTypes.func.isRequired,
       publishStream: React.PropTypes.func.isRequired,
-      screenShare: React.PropTypes.object,
       settingsMenuItems: React.PropTypes.array,
       show: React.PropTypes.bool.isRequired,
       showHangup: React.PropTypes.bool,
       video: React.PropTypes.object.isRequired
     },
 
     handleClickHangup: function() {
       this.props.hangup();
@@ -520,21 +409,16 @@ loop.shared.views = (function(_, mozL10n
                                     scope="local" type="video"
                                     visible={this.props.video.visible}/>
                 <MediaControlButton action={this.handleToggleAudio}
                                     enabled={this.props.audio.enabled}
                                     scope="local" type="audio"
                                     visible={this.props.audio.visible}/>
             </div>
           </li>
-          <li className="conversation-toolbar-btn-box">
-            <ScreenShareControlButton dispatcher={this.props.dispatcher}
-                                      state={this.props.screenShare.state}
-                                      visible={this.props.screenShare.visible} />
-          </li>
           <li className="conversation-toolbar-btn-box btn-edit-entry">
             <SettingsControlButton menuItems={this.props.settingsMenuItems} />
           </li>
         </ul>
       );
     }
   });
 
@@ -1136,12 +1020,11 @@ loop.shared.views = (function(_, mozL10n
     Checkbox: Checkbox,
     ContextUrlView: ContextUrlView,
     ConversationToolbar: ConversationToolbar,
     MediaControlButton: MediaControlButton,
     MediaLayoutView: MediaLayoutView,
     MediaView: MediaView,
     LoadingView: LoadingView,
     SettingsControlButton: SettingsControlButton,
-    ScreenShareControlButton: ScreenShareControlButton,
     NotificationListView: NotificationListView
   };
 })(_, navigator.mozL10n || document.mozL10n);
--- a/browser/extensions/loop/skin/osx/platform.css
+++ b/browser/extensions/loop/skin/osx/platform.css
@@ -1,47 +1,41 @@
 /* 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/. */
 
 @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
 
 /* Only apply to browser.xul documents */
 @-moz-document url("chrome://browser/content/browser.xul") {
-  /**
-   * XXX Due to bug 1228542, anything in this file that overrides a browser style
-   * must specify !important. Otherwise the style won't get applied correctly
-   * due to the limitations caused by the bug.
-   */
-
   notification[value="loop-sharing-notification"] {
-    background: #00a9dc !important;
-    padding: 0 !important;
-    border: 0 !important;
+    background: #00a9dc;
+    padding: 0;
+    border: 0;
   }
 
   notification[value="loop-sharing-notification"].paused {
-    background: #ebebeb !important;
+    background: #ebebeb;
   }
 
   notification[value="loop-sharing-notification"] .notification-button {
-    background: #fff !important;
-    border-radius: 0 !important;
+    background: #fff;
+    border-radius: 0;
   }
 
   notification[value="loop-sharing-notification"].paused .notification-button {
-    background: #57bd35 !important;
+    background: #57bd35;
   }
 
   notification[value="loop-sharing-notification"].paused .notification-button:hover {
-    background: #39a017 !important;
+    background: #39a017;
   }
 
   notification[value="loop-sharing-notification"] .notification-button:hover,
   notification[value="loop-sharing-notification"].paused .notification-button-default:hover {
-    background: #ebebeb !important;
+    background: #ebebeb;
   }
 
   notification[value="loop-sharing-notification"] .notification-button-default,
   notification[value="loop-sharing-notification"].paused .notification-button-default {
-    background: #fff !important;
+    background: #fff;
   }
 }
--- a/browser/extensions/loop/skin/shared/loop.css
+++ b/browser/extensions/loop/skin/shared/loop.css
@@ -1,30 +1,24 @@
 /* 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/. */
 
 @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
 
 /* Only apply to browser.xul documents */
 @-moz-document url("chrome://browser/content/browser.xul") {
-  /**
-   * XXX Due to bug 1228542, anything in this file that overrides a browser style
-   * must specify !important. Otherwise the style won't get applied correctly
-   * due to the limitations caused by the bug.
-   */
-
   /*
      XXX Copied from browser/themes/<platform>/browser.css. Should really be
      changing the sizes of icons in files to 16px x 16px and no borders.
    */
   :-moz-any(toolbar, .widget-overflow-list) #loop-button > .toolbarbutton-icon,
   :-moz-any(toolbar, .widget-overflow-list) #loop-button > :-moz-any(.toolbarbutton-menubutton-button, .toolbarbutton-badge-stack) > .toolbarbutton-icon {
-    max-width: 18px !important;
-    margin: 0 !important;
+    max-width: 18px;
+    margin: 0;
   }
 
   #loop-button {
     list-style-image: url(chrome://loop/skin/toolbar.png);
     -moz-image-region: rect(0, 18px, 18px, 0);
   }
 
   toolbar[brighttext] #loop-button {
@@ -94,17 +88,17 @@
     #loop-button[cui-areatype="menu-panel"],
     toolbarpaletteitem[place="palette"] > #loop-button {
       list-style-image: url(chrome://loop/skin/menuPanel@2x.png);
       -moz-image-region: rect(0, 64px, 64px, 0);
     }
 
     /* Make sure that the state icons are not shown in the customization palette. */
     toolbarpaletteitem[place="palette"] > #loop-button {
-      -moz-image-region: rect(0, 64px, 64px, 0) !important;
+      -moz-image-region: rect(0, 64px, 64px, 0);
     }
 
     #loop-button[cui-areatype="menu-panel"][state="disabled"],
     #loop-button[cui-areatype="menu-panel"][disabled="true"] {
       -moz-image-region: rect(0, 128px, 64px, 64px);
     }
 
     #loop-button[cui-areatype="menu-panel"]:not([disabled="true"])[state="error"] {
@@ -132,17 +126,17 @@
     #loop-button[cui-areatype="menu-panel"],
     toolbarpaletteitem[place="palette"] > #loop-button {
       list-style-image: url(chrome://loop/skin/menuPanel.png);
       -moz-image-region: rect(0, 32px, 32px, 0);
     }
 
     /* Make sure that the state icons are not shown in the customization palette. */
     toolbarpaletteitem[place="palette"] > #loop-button {
-      -moz-image-region: rect(0, 32px, 32px, 0) !important;
+      -moz-image-region: rect(0, 32px, 32px, 0);
     }
 
     #loop-button[cui-areatype="menu-panel"][state="disabled"],
     #loop-button[cui-areatype="menu-panel"][disabled="true"] {
       -moz-image-region: rect(0, 64px, 32px, 32px);
     }
 
     #loop-button[cui-areatype="menu-panel"]:not([disabled="true"])[state="error"] {
@@ -162,125 +156,125 @@
     }
 
     #loop-button[cui-areatype="menu-panel"]:not([disabled="true"])[state="active"]:-moz-any(:hover,:hover:active,[open]) {
       -moz-image-region: rect(0, 224px, 32px, 192px);
     }
   }
 
   notification[value="loop-sharing-notification"] {
-    -moz-appearance: none !important;
-    height: 40px !important;
-    background-color: #00a9dc !important;
-    box-shadow: 0 40px 1px rgba(0,0,0,.5) inset !important;
+    -moz-appearance: none;
+    height: 40px;
+    background-color: #00a9dc;
+    box-shadow: 0 40px 1px rgba(0,0,0,.5) inset;
   }
 
   notification[value="loop-sharing-notification"].paused {
-    background-color: #ebebeb !important;
+    background-color: #ebebeb;
   }
 
   notification[value="loop-sharing-notification"] .notification-inner {
-    color: #fff !important;
-    padding: 0 !important;
+    color: #fff;
+    padding: 0;
   }
 
   notification[value="loop-sharing-notification"].paused .notification-inner {
-    color: #00a9dc !important;
+    color: #00a9dc;
   }
 
   notification[value="loop-sharing-notification"] .notification-button {
-    -moz-appearance: none !important;
-    background-color: #fff !important;
-    border: 0 !important;
-    border-right: solid 1px #ebebeb !important;
-    width: 100px !important;
-    height: 40px !important;
-    margin: 0 !important;
-    list-style-image: url(chrome://loop/content/shared/img/pause-12x12.svg) !important;
-    box-shadow: 0 40px 1px rgba(0,0,0,.5) inset !important;
-    text-shadow: none !important;
+    -moz-appearance: none;
+    background-color: #fff;
+    border: 0;
+    border-right: solid 1px #ebebeb;
+    width: 100px;
+    height: 40px;
+    margin: 0;
+    list-style-image: url(chrome://loop/content/shared/img/pause-12x12.svg);
+    box-shadow: 0 40px 1px rgba(0,0,0,.5) inset;
+    text-shadow: none;
   }
 
   notification[value="loop-sharing-notification"] .notification-button:-moz-locale-dir(rtl) {
-    border-right: 0 !important;
-    border-left: solid 1px #ebebeb !important;
+    border-right: 0;
+    border-left: solid 1px #ebebeb;
   }
 
   notification[value="loop-sharing-notification"].paused .notification-button {
-    background-color: #57bd35 !important;
-    color: #fff !important;
-    list-style-image: url(chrome://loop/content/shared/img/play-12x12.svg) !important;
+    background-color: #57bd35;
+    color: #fff;
+    list-style-image: url(chrome://loop/content/shared/img/play-12x12.svg);
   }
 
   notification[value="loop-sharing-notification"].paused .notification-button:hover {
-    background-color: #39a017 !important;
+    background-color: #39a017;
   }
 
   notification[value="loop-sharing-notification"] .notification-button:hover,
   notification[value="loop-sharing-notification"].paused .notification-button-default:hover {
-    background-color: #ebebeb !important;
+    background-color: #ebebeb;
   }
 
   notification[value="loop-sharing-notification"] .notification-button-default,
   notification[value="loop-sharing-notification"].paused .notification-button-default {
-    color: #d92215 !important;
-    background-color: #fff !important;
-    border-right: 0 !important;
-    list-style-image: url(chrome://loop/content/shared/img/stop-12x12.svg) !important;
+    color: #d92215;
+    background-color: #fff;
+    border-right: 0;
+    list-style-image: url(chrome://loop/content/shared/img/stop-12x12.svg);
   }
 
   notification[value="loop-sharing-notification"] .notification-button .button-icon {
-    display: block !important;
-    -moz-margin-end: 6px !important;
+    display: block;
+    -moz-margin-end: 6px;
   }
 
   notification[value="loop-sharing-notification"] .button-menubutton-button {
-    min-width: 0 !important;
+    min-width: 0;
   }
 
   notification[value="loop-sharing-notification"] .messageImage {
-    list-style-image: url(chrome://loop/content/shared/img/icons-16x16.svg#loop-icon-white) !important;
-    margin-inline-start: 14px !important;
+    list-style-image: url(chrome://loop/content/shared/img/icons-16x16.svg#loop-icon-white);
+    margin-inline-start: 14px;
   }
 
   notification[value="loop-sharing-notification"].paused .messageImage {
-    list-style-image: url(chrome://loop/content/shared/img/icons-16x16.svg#loop-icon-still) !important;
+    list-style-image: url(chrome://loop/content/shared/img/icons-16x16.svg#loop-icon-still);
   }
 
   notification[value="loop-sharing-notification"] .close-icon {
-    display: none !important;
+    display: none;
   }
 
   chatbox[src^="about:loopconversation#"] > .chat-titlebar {
-    background-color: #00a9dc !important;
-    border-color: #00a9dc !important;
+    background-color: #00a9dc;
+    border-color: #00a9dc;
   }
 
   chatbox[src^="about:loopconversation#"] .chat-title {
-    color: white !important;
+    color: white;
   }
 
   chatbox[src^="about:loopconversation#"] .chat-minimize-button {
-    list-style-image: url("chrome://browser/skin/social/chat-icons.svg#minimize-white") !important;
+    list-style-image: url("chrome://browser/skin/social/chat-icons.svg#minimize-white");
   }
 
   chatbox[src^="about:loopconversation#"] .chat-swap-button {
-    list-style-image: url("chrome://browser/skin/social/chat-icons.svg#expand-white") !important;
+    list-style-image: url("chrome://browser/skin/social/chat-icons.svg#expand-white");
   }
 
   .chat-loop-hangup {
-    list-style-image: url("chrome://browser/skin/social/chat-icons.svg#exit-white") !important;
-    background-color: #d13f1a !important;
-    border: 1px solid #d13f1a !important;
-    border-top-right-radius: 4px !important;
-    width: 32px !important;
-    height: 26px !important;
-    margin-top: -6px !important;
-    margin-bottom: -5px !important;
-    -moz-margin-start: 6px !important;
-    -moz-margin-end: -5px !important;
+    list-style-image: url("chrome://browser/skin/social/chat-icons.svg#exit-white");
+    background-color: #d13f1a;
+    border: 1px solid #d13f1a;
+    border-top-right-radius: 4px;
+    width: 32px;
+    height: 26px;
+    margin-top: -6px;
+    margin-bottom: -5px;
+    -moz-margin-start: 6px;
+    -moz-margin-end: -5px;
   }
 
   .chat-toolbarbutton.chat-loop-hangup:-moz-any(:hover,:hover:active) {
-    background-color: #ef6745 !important;
-    border-color: #ef6745 !important;
+    background-color: #ef6745;
+    border-color: #ef6745;
   }
 }
--- a/browser/extensions/loop/standalone/content/index.html
+++ b/browser/extensions/loop/standalone/content/index.html
@@ -161,17 +161,17 @@
           disconnect: function() {},
           observe: function () {},
           takeRecords: function() {}
         };
         window.MutationObserver = myMutationObserver;
       }
 
       window.OTProperties = {
-        cdnURL: "shared/libs/"
+        cdnURL: "shared/vendor/"
       };
       window.OTProperties.assetURL = window.OTProperties.cdnURL + "sdk-content/";
       window.OTProperties.configURL = window.OTProperties.assetURL + "js/dynamic_config.min.js";
 
       // We don't use the SDK's CSS. This will prevent spurious 404 errors.
       window.OTProperties.cssURL = "about:blank";
     </script>
 
--- a/browser/extensions/loop/standalone/content/l10n/en-US/loop.properties
+++ b/browser/extensions/loop/standalone/content/l10n/en-US/loop.properties
@@ -7,18 +7,16 @@ generic_failure_no_reason2=Would you lik
 tos_failure_message={{clientShortname}} is not available in your country.
 
 retry_call_button=Retry
 unable_retrieve_call_info=Unable to retrieve conversation information.
 mute_local_audio_button_title=Mute your audio
 unmute_local_audio_button_title=Unmute your audio
 mute_local_video_button_title2=Disable video
 unmute_local_video_button_title2=Enable video
-active_screenshare_button_title=Stop sharing
-inactive_screenshare_button_title=Share your screen
 
 welcome=Welcome to the {{clientShortname}} web client.
 incompatible_browser_heading=Oops!
 incompatible_browser_message=Firefox Hello only works in browsers that support WebRTC
 unsupported_platform_heading=Sorry!
 unsupported_platform_message={{platform}} does not currently support {{clientShortname}}
 unsupported_platform_ios=iOS
 unsupported_platform_windows_phone=Windows Phone
--- a/browser/extensions/loop/standalone/package.json
+++ b/browser/extensions/loop/standalone/package.json
@@ -9,19 +9,19 @@
   "engines": {
     "node": "0.10.x",
     "npm": "2.14.x"
   },
   "dependencies": {},
   "devDependencies": {
     "classnames": "2.2.x",
     "compression": "1.5.x",
-    "eslint": "1.6.x",
+    "eslint": "1.10.x",
     "eslint-plugin-mozilla": "../../../../testing/eslint-plugin-mozilla",
-    "eslint-plugin-react": "3.5.x",
+    "eslint-plugin-react": "3.10.x",
     "exports-loader": "0.6.x",
     "expose-loader": "0.7.x",
     "express": "4.x",
     "imports-loader": "0.6.x",
     "react": "0.13.3",
     "script-loader": "0.6.x",
     "webpack": "1.12.x"
   },
--- a/browser/extensions/loop/test/desktop-local/roomViews_test.js
+++ b/browser/extensions/loop/test/desktop-local/roomViews_test.js
@@ -395,21 +395,19 @@ describe("loop.roomViews", function() {
 
       view.setState({ audioMuted: true });
 
       var muteBtn = view.getDOMNode().querySelector(".btn-mute-audio");
 
       expect(muteBtn.classList.contains("muted")).eql(true);
     });
 
-    it("should dispatch a `StartScreenShare` action when sharing is not active and the screen share button is pressed", function() {
+    it("should dispatch a `SetMute` action when the mute button is pressed", function() {
       view = mountTestComponent();
 
-      view.setState({ screenSharingState: SCREEN_SHARE_STATES.INACTIVE });
-
       var muteBtn = view.getDOMNode().querySelector(".btn-mute-video");
 
       React.addons.TestUtils.Simulate.click(muteBtn);
 
       sinon.assert.calledWithMatch(dispatcher.dispatch,
         sinon.match.hasOwn("name", "setMute"));
     });
 
@@ -432,16 +430,25 @@ describe("loop.roomViews", function() {
       it("should dispatch a `SetupStreamElements` action on MEDIA_WAIT state is re-entered", function() {
           activeRoomStore.setStoreState({ roomState: ROOM_STATES.ENDED });
           var component = mountTestComponent();
 
           activeRoomStore.setStoreState({ roomState: ROOM_STATES.MEDIA_WAIT });
 
           expectActionDispatched(component);
         });
+
+      it("should dispatch a `StartBrowserShare` action when the SESSION_CONNECTED state is entered", function() {
+        activeRoomStore.setStoreState({ roomState: ROOM_STATES.READY });
+        var component = mountTestComponent();
+
+        activeRoomStore.setStoreState({ roomState: ROOM_STATES.SESSION_CONNECTED });
+
+        expectActionDispatched("startBrowserShare");
+      });
     });
 
     describe("#render", function() {
       it("should set document.title to store.serverData.roomName", function() {
         mountTestComponent();
 
         activeRoomStore.setStoreState({ roomName: "fakeName" });
 
--- a/browser/extensions/loop/test/functional/test_1_browser_call.py
+++ b/browser/extensions/loop/test/functional/test_1_browser_call.py
@@ -181,22 +181,16 @@ class Test1BrowserCall(MarionetteTestCas
 
         # Then send a message using the standalone.
         self.send_chat_message("test2")
 
         # Finally check the link generator got it.
         self.switch_to_chatbox()
         self.check_received_message("test2")
 
-    def local_enable_screenshare(self):
-        self.switch_to_chatbox()
-        button = self.marionette.find_element(By.CLASS_NAME, "btn-screen-share")
-
-        button.click()
-
     def standalone_check_remote_screenshare(self):
         self.switch_to_standalone()
         self.check_video(".screen-share-video")
 
     def remote_leave_room(self):
         self.switch_to_standalone()
         button = self.marionette.find_element(By.CLASS_NAME, "btn-hangup")
 
@@ -264,49 +258,47 @@ class Test1BrowserCall(MarionetteTestCas
 
         self.assertGreater(noted_calls, 0,
                            "OTSdkDriver._connectionLengthNotedCalls should be "
                            "> 0, noted_calls = " + str(noted_calls))
 
     def test_1_browser_call(self):
         self.switch_to_panel()
 
-        self.local_start_a_conversation()
+        # self.local_start_a_conversation()
 
-        # Check the self video in the conversation window
-        self.local_check_room_self_video()
+        # # Check the self video in the conversation window
+        # self.local_check_room_self_video()
 
-        # make sure that the media start time is not initialized
-        self.local_check_media_start_time_uninitialized()
+        # # make sure that the media start time is not initialized
+        # self.local_check_media_start_time_uninitialized()
 
-        room_url = self.local_get_and_verify_room_url()
+        # room_url = self.local_get_and_verify_room_url()
 
-        # load the link clicker interface into the current content browser
-        self.standalone_load_and_join_room(room_url)
+        # # load the link clicker interface into the current content browser
+        # self.standalone_load_and_join_room(room_url)
 
-        # Check we get the video streams
-        self.standalone_check_remote_video()
-        self.local_check_remote_video()
-
-        # Check text messaging
-        self.check_text_messaging()
+        # # Check we get the video streams
+        # self.standalone_check_remote_video()
+        # self.local_check_remote_video()
 
-        # since bi-directional media is connected, make sure we've set
-        # the start time
-        self.local_check_media_start_time_initialized()
+        # # Check text messaging
+        # self.check_text_messaging()
 
-        # XXX To enable this, we either need to navigate the permissions prompt
-        # or have a route where we don't need the permissions prompt.
-        # self.local_enable_screenshare()
+        # # since bi-directional media is connected, make sure we've set
+        # # the start time
+        # self.local_check_media_start_time_initialized()
+
+        # # Check that screenshare was automatically started
         # self.standalone_check_remote_screenshare()
 
-        # We hangup on the remote (standalone) side, because this also leaves
-        # the local chatbox with the local publishing media still connected,
-        # which means that the local_check_connection_length below
-        # verifies that the connection is noted at the time the remote media
-        # drops, rather than waiting until the window closes.
-        self.remote_leave_room()
+        # # We hangup on the remote (standalone) side, because this also leaves
+        # # the local chatbox with the local publishing media still connected,
+        # # which means that the local_check_connection_length below
+        # # verifies that the connection is noted at the time the remote media
+        # # drops, rather than waiting until the window closes.
+        # self.remote_leave_room()
 
-        self.local_check_connection_length_noted()
+        # self.local_check_connection_length_noted()
 
     def tearDown(self):
         self.loop_test_servers.shutdown()
         MarionetteTestCase.tearDown(self)
--- a/browser/extensions/loop/test/shared/activeRoomStore_test.js
+++ b/browser/extensions/loop/test/shared/activeRoomStore_test.js
@@ -194,19 +194,17 @@ describe("loop.store.ActiveRoomStore", f
         failedJoinRequest: false
       }));
 
       sinon.assert.calledOnce(clearTimeout);
     });
 
     it("should remove the sharing listener", function() {
       // Setup the listener.
-      store.startScreenShare(new sharedActions.StartScreenShare({
-        type: "browser"
-      }));
+      store.startBrowserShare(new sharedActions.StartBrowserShare());
 
       // Now simulate room failure.
       store.roomFailure(new sharedActions.RoomFailure({
         error: fakeError,
         failedJoinRequest: false
       }));
 
       sinon.assert.calledOnce(requestStubs.RemoveBrowserSharingListener);
@@ -1252,19 +1250,17 @@ describe("loop.store.ActiveRoomStore", f
 
       sinon.assert.calledOnce(requestStubs["Rooms:Leave"]);
       sinon.assert.calledWithExactly(requestStubs["Rooms:Leave"],
         "fakeToken", "1627384950");
     });
 
     it("should remove the sharing listener", function() {
       // Setup the listener.
-      store.startScreenShare(new sharedActions.StartScreenShare({
-        type: "browser"
-      }));
+      store.startBrowserShare(new sharedActions.StartBrowserShare());
 
       // Now simulate connection failure.
       store.connectionFailure(connectionFailureAction);
 
       sinon.assert.calledOnce(requestStubs.RemoveBrowserSharingListener);
     });
 
     it("should set the state to `FAILED`", function() {
@@ -1534,73 +1530,54 @@ describe("loop.store.ActiveRoomStore", f
       }));
 
       expect(store.getStoreState().remoteVideoDimensions).eql({
         camera: { fake: 20 }
       });
     });
   });
 
-  describe("#startScreenShare", function() {
+  describe("#startBrowserShare", function() {
     afterEach(function() {
       store.endScreenShare();
     });
 
     it("should set the state to 'pending'", function() {
-      store.startScreenShare(new sharedActions.StartScreenShare({
-        type: "window"
-      }));
+      store.startBrowserShare(new sharedActions.StartBrowserShare());
 
       sinon.assert.calledOnce(dispatcher.dispatch);
       sinon.assert.calledWith(dispatcher.dispatch,
         new sharedActions.ScreenSharingState({
           state: SCREEN_SHARE_STATES.PENDING
         }));
     });
 
-    it("should invoke the SDK driver with the correct options for window sharing", function() {
-      store.startScreenShare(new sharedActions.StartScreenShare({
-        type: "window"
-      }));
-
-      sinon.assert.calledOnce(fakeSdkDriver.startScreenShare);
-      sinon.assert.calledWith(fakeSdkDriver.startScreenShare, {
-        videoSource: "window"
-      });
-    });
-
     it("should add a browser sharing listener for tab sharing", function() {
-      store.startScreenShare(new sharedActions.StartScreenShare({
-        type: "browser"
-      }));
+      store.startBrowserShare(new sharedActions.StartBrowserShare());
 
       sinon.assert.calledOnce(requestStubs.AddBrowserSharingListener);
     });
 
     it("should invoke the SDK driver with the correct options for tab sharing", function() {
-      store.startScreenShare(new sharedActions.StartScreenShare({
-        type: "browser"
-      }));
+      store.startBrowserShare(new sharedActions.StartBrowserShare());
 
       sinon.assert.calledOnce(fakeSdkDriver.startScreenShare);
       sinon.assert.calledWith(fakeSdkDriver.startScreenShare, {
         videoSource: "browser",
         constraints: {
           browserWindow: 42,
           scrollWithPage: true
         }
       });
     });
   });
 
   describe("Screen share Events", function() {
     beforeEach(function() {
-      store.startScreenShare(new sharedActions.StartScreenShare({
-        type: "browser"
-      }));
+      store.startBrowserShare(new sharedActions.StartBrowserShare());
 
       store.setStoreState({
         screenSharingState: SCREEN_SHARE_STATES.ACTIVE
       });
 
       // Stub to prevent errors surfacing in the console.
       sandbox.stub(window.console, "error");
     });
@@ -1647,19 +1624,17 @@ describe("loop.store.ActiveRoomStore", f
       sinon.assert.calledWith(dispatcher.dispatch,
         new sharedActions.ScreenSharingState({
           state: SCREEN_SHARE_STATES.INACTIVE
         }));
     });
 
     it("should remove the sharing listener", function() {
       // Setup the listener.
-      store.startScreenShare(new sharedActions.StartScreenShare({
-        type: "browser"
-      }));
+      store.startBrowserShare(new sharedActions.StartBrowserShare());
 
       // Now stop the screen share.
       store.endScreenShare();
 
       sinon.assert.calledOnce(requestStubs.RemoveBrowserSharingListener);
     });
   });
 
@@ -1802,19 +1777,17 @@ describe("loop.store.ActiveRoomStore", f
 
         sinon.assert.calledOnce(requestStubs["Rooms:Leave"]);
         sinon.assert.calledWithExactly(requestStubs["Rooms:Leave"],
           "fakeToken", "1627384950");
       });
 
     it("should remove the sharing listener", function() {
       // Setup the listener.
-      store.startScreenShare(new sharedActions.StartScreenShare({
-        type: "browser"
-      }));
+      store.startBrowserShare(new sharedActions.StartBrowserShare());
 
       // Now unload the window.
       store.windowUnload();
 
       sinon.assert.calledOnce(requestStubs.RemoveBrowserSharingListener);
     });
 
     it("should set the state to CLOSING", function() {
@@ -1859,19 +1832,17 @@ describe("loop.store.ActiveRoomStore", f
 
       sinon.assert.calledOnce(requestStubs["Rooms:Leave"]);
       sinon.assert.calledWithExactly(requestStubs["Rooms:Leave"],
         "fakeToken", "1627384950");
     });
 
     it("should remove the sharing listener", function() {
       // Setup the listener.
-      store.startScreenShare(new sharedActions.StartScreenShare({
-        type: "browser"
-      }));
+      store.startBrowserShare(new sharedActions.StartBrowserShare());
 
       // Now leave the room.
       store.leaveRoom();
 
       sinon.assert.calledOnce(requestStubs.RemoveBrowserSharingListener);
     });
 
     it("should set the state to ENDED", function() {
--- a/browser/extensions/loop/test/shared/views_test.js
+++ b/browser/extensions/loop/test/shared/views_test.js
@@ -6,17 +6,16 @@ describe("loop.shared.views", function()
   "use strict";
 
   var expect = chai.expect;
   var l10n = navigator.mozL10n || document.mozL10n;
   var TestUtils = React.addons.TestUtils;
   var sharedActions = loop.shared.actions;
   var sharedModels = loop.shared.models;
   var sharedViews = loop.shared.views;
-  var SCREEN_SHARE_STATES = loop.shared.utils.SCREEN_SHARE_STATES;
   var getReactElementByClass = TestUtils.findRenderedDOMComponentWithClass;
   var sandbox, fakeAudioXHR, dispatcher, OS, OSVersion;
 
   beforeEach(function() {
     sandbox = LoopMochaUtils.createSandbox();
     sandbox.useFakeTimers(); // exposes sandbox.clock as a fake timer
     sandbox.stub(l10n, "get", function(x) {
       return "translated:" + x;
@@ -104,196 +103,16 @@ describe("loop.shared.views", function()
           action: function() {},
           enabled: false
         }));
 
       expect(comp.getDOMNode().classList.contains("muted")).eql(true);
     });
   });
 
-  describe("ScreenShareControlButton", function() {
-    it("should render a visible share button", function() {
-      var comp = TestUtils.renderIntoDocument(
-        React.createElement(sharedViews.ScreenShareControlButton, {
-          dispatcher: dispatcher,
-          visible: true,
-          state: SCREEN_SHARE_STATES.INACTIVE
-        }));
-
-      expect(comp.getDOMNode().classList.contains("active")).eql(false);
-      expect(comp.getDOMNode().classList.contains("disabled")).eql(false);
-    });
-
-    it("should render a disabled share button when share is pending", function() {
-      var comp = TestUtils.renderIntoDocument(
-        React.createElement(sharedViews.ScreenShareControlButton, {
-          dispatcher: dispatcher,
-          visible: true,
-          state: SCREEN_SHARE_STATES.PENDING
-        }));
-
-      var node = comp.getDOMNode().querySelector(".btn-screen-share");
-      expect(node.classList.contains("active")).eql(false);
-      expect(node.classList.contains("disabled")).eql(true);
-    });
-
-    it("should render an active share button", function() {
-      var comp = TestUtils.renderIntoDocument(
-        React.createElement(sharedViews.ScreenShareControlButton, {
-          dispatcher: dispatcher,
-          visible: true,
-          state: SCREEN_SHARE_STATES.ACTIVE
-        }));
-
-      var node = comp.getDOMNode().querySelector(".btn-screen-share");
-      expect(node.classList.contains("active")).eql(true);
-      expect(node.classList.contains("disabled")).eql(false);
-    });
-
-    it("should show the screenshare dropdown on click when the state is not active",
-       function() {
-        var comp = TestUtils.renderIntoDocument(
-          React.createElement(sharedViews.ScreenShareControlButton, {
-            dispatcher: dispatcher,
-            visible: true,
-            state: SCREEN_SHARE_STATES.INACTIVE
-          }));
-
-        expect(comp.state.showMenu).eql(false);
-
-        TestUtils.Simulate.click(comp.getDOMNode().querySelector(".btn-screen-share"));
-
-        expect(comp.state.showMenu).eql(true);
-      });
-
-    it("should dispatch a 'browser' StartScreenShare action on option click",
-      function() {
-        var comp = TestUtils.renderIntoDocument(
-          React.createElement(sharedViews.ScreenShareControlButton, {
-            dispatcher: dispatcher,
-            visible: true,
-            state: SCREEN_SHARE_STATES.INACTIVE
-          }));
-
-        TestUtils.Simulate.click(comp.getDOMNode().querySelector(
-          ".screen-share-menu > li"));
-
-        sinon.assert.calledOnce(dispatcher.dispatch);
-        sinon.assert.calledWithExactly(dispatcher.dispatch,
-          new sharedActions.StartScreenShare({ type: "browser" }));
-      });
-
-    it("should close the dropdown on 'browser' option click", function() {
-      var comp = TestUtils.renderIntoDocument(
-        React.createElement(sharedViews.ScreenShareControlButton, {
-          dispatcher: dispatcher,
-          visible: true,
-          state: SCREEN_SHARE_STATES.INACTIVE
-        }));
-
-      sandbox.stub(comp, "hideDropdownMenu");
-
-      TestUtils.Simulate.click(comp.getDOMNode().querySelector(
-        ".screen-share-menu > li"));
-
-      sinon.assert.calledOnce(comp.hideDropdownMenu);
-    });
-
-    it("should dispatch a 'window' StartScreenShare action on option click",
-      function() {
-        var comp = TestUtils.renderIntoDocument(
-          React.createElement(sharedViews.ScreenShareControlButton, {
-            dispatcher: dispatcher,
-            visible: true,
-            state: SCREEN_SHARE_STATES.INACTIVE
-          }));
-
-        TestUtils.Simulate.click(comp.getDOMNode().querySelector(
-          ".screen-share-menu > li:last-child"));
-
-        sinon.assert.calledOnce(dispatcher.dispatch);
-        sinon.assert.calledWithExactly(dispatcher.dispatch,
-          new sharedActions.StartScreenShare({ type: "window" }));
-      });
-
-    it("should close the dropdown on 'window' option click", function() {
-      var comp = TestUtils.renderIntoDocument(
-        React.createElement(sharedViews.ScreenShareControlButton, {
-          dispatcher: dispatcher,
-          visible: true,
-          state: SCREEN_SHARE_STATES.INACTIVE
-        }));
-
-      sandbox.stub(comp, "hideDropdownMenu");
-
-      TestUtils.Simulate.click(comp.getDOMNode().querySelector(
-        ".screen-share-menu > li:last-child"));
-
-      sinon.assert.calledOnce(comp.hideDropdownMenu);
-    });
-
-    it("should have the 'window' option enabled", function() {
-      var comp = TestUtils.renderIntoDocument(
-        React.createElement(sharedViews.ScreenShareControlButton, {
-          dispatcher: dispatcher,
-          visible: true,
-          state: SCREEN_SHARE_STATES.INACTIVE
-        }));
-
-      var node = comp.getDOMNode().querySelector(".screen-share-menu > li:last-child");
-      expect(node.classList.contains("disabled")).eql(false);
-    });
-
-    it("should disable the 'window' option on Windows XP", function() {
-      OS = "win";
-      OSVersion = { major: 5, minor: 1 };
-
-      var comp = TestUtils.renderIntoDocument(
-        React.createElement(sharedViews.ScreenShareControlButton, {
-          dispatcher: dispatcher,
-          visible: true,
-          state: SCREEN_SHARE_STATES.INACTIVE
-        }));
-
-      var node = comp.getDOMNode().querySelector(".screen-share-menu > li:last-child");
-      expect(node.classList.contains("disabled")).eql(true);
-    });
-
-    it("should disable the 'window' option on OSX 10.6", function() {
-      OS = "mac";
-      OSVersion = { major: 10, minor: 6 };
-
-      var comp = TestUtils.renderIntoDocument(
-        React.createElement(sharedViews.ScreenShareControlButton, {
-          dispatcher: dispatcher,
-          visible: true,
-          state: SCREEN_SHARE_STATES.INACTIVE
-        }));
-
-      var node = comp.getDOMNode().querySelector(".screen-share-menu > li:last-child");
-      expect(node.classList.contains("disabled")).eql(true);
-    });
-
-    it("should dispatch a EndScreenShare action on click when the state is active",
-      function() {
-        var comp = TestUtils.renderIntoDocument(
-          React.createElement(sharedViews.ScreenShareControlButton, {
-            dispatcher: dispatcher,
-            visible: true,
-            state: SCREEN_SHARE_STATES.ACTIVE
-          }));
-
-        TestUtils.Simulate.click(comp.getDOMNode().querySelector(".btn-screen-share"));
-
-        sinon.assert.calledOnce(dispatcher.dispatch);
-        sinon.assert.calledWithExactly(dispatcher.dispatch,
-          new sharedActions.EndScreenShare({}));
-      });
-  });
-
   describe("SettingsControlButton", function() {
     var requestStubs;
     var support_url = "https://support.com";
 
     beforeEach(function() {
       LoopMochaUtils.stubLoopRequest(requestStubs = {
         OpenURL: sandbox.stub(),
         SetLoopPref: sandbox.stub(),
--- a/browser/extensions/loop/ui/ui-showcase.js
+++ b/browser/extensions/loop/ui/ui-showcase.js
@@ -33,17 +33,16 @@
   var FeedbackView = loop.feedbackViews.FeedbackView;
   var Checkbox = loop.shared.views.Checkbox;
   var TextChatView = loop.shared.views.chat.TextChatView;
 
   // Store constants
   var ROOM_STATES = loop.store.ROOM_STATES;
   var CALL_TYPES = loop.shared.utils.CALL_TYPES;
   var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
-  var SCREEN_SHARE_STATES = loop.shared.utils.SCREEN_SHARE_STATES;
 
   // Local helpers
   function returnTrue() {
     return true;
   }
 
   function returnFalse() {
     return false;
@@ -738,47 +737,44 @@
                              height: 56, 
                              summary: "Default", 
                              width: 300}, 
                 React.createElement("div", {className: "fx-embedded"}, 
                   React.createElement(ConversationToolbar, {audio: { enabled: true, visible: true}, 
                                        dispatcher: dispatcher, 
                                        hangup: noop, 
                                        publishStream: noop, 
-                                       screenShare: { state: SCREEN_SHARE_STATES.INACTIVE, visible: true}, 
                                        settingsMenuItems: [{ id: "feedback" }], 
                                        show: true, 
                                        video: { enabled: true, visible: true}})
                 )
               ), 
               React.createElement(FramedExample, {dashed: true, 
                              height: 56, 
-                             summary: "Video muted, Screen share pending", 
+                             summary: "Video muted", 
                              width: 300}, 
                 React.createElement("div", {className: "fx-embedded"}, 
                   React.createElement(ConversationToolbar, {audio: { enabled: true, visible: true}, 
                                        dispatcher: dispatcher, 
                                        hangup: noop, 
                                        publishStream: noop, 
-                                       screenShare: { state: SCREEN_SHARE_STATES.PENDING, visible: true}, 
                                        settingsMenuItems: [{ id: "feedback" }], 
                                        show: true, 
                                        video: { enabled: false, visible: true}})
                 )
               ), 
               React.createElement(FramedExample, {dashed: true, 
                              height: 56, 
-                             summary: "Audio muted, Screen share active", 
+                             summary: "Audio muted", 
                              width: 300}, 
                 React.createElement("div", {className: "fx-embedded"}, 
                   React.createElement(ConversationToolbar, {audio: { enabled: false, visible: true}, 
                                        dispatcher: dispatcher, 
                                        hangup: noop, 
                                        publishStream: noop, 
-                                       screenShare: { state: SCREEN_SHARE_STATES.ACTIVE, visible: true}, 
                                        settingsMenuItems: [{ id: "feedback" }], 
                                        show: true, 
                                        video: { enabled: true, visible: true}})
                 )
               )
             )
           ), 
 
--- a/browser/extensions/loop/ui/ui-showcase.jsx
+++ b/browser/extensions/loop/ui/ui-showcase.jsx
@@ -33,17 +33,16 @@
   var FeedbackView = loop.feedbackViews.FeedbackView;
   var Checkbox = loop.shared.views.Checkbox;
   var TextChatView = loop.shared.views.chat.TextChatView;
 
   // Store constants
   var ROOM_STATES = loop.store.ROOM_STATES;
   var CALL_TYPES = loop.shared.utils.CALL_TYPES;
   var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
-  var SCREEN_SHARE_STATES = loop.shared.utils.SCREEN_SHARE_STATES;
 
   // Local helpers
   function returnTrue() {
     return true;
   }
 
   function returnFalse() {
     return false;
@@ -738,47 +737,44 @@
                              height={56}
                              summary="Default"
                              width={300}>
                 <div className="fx-embedded">
                   <ConversationToolbar audio={{ enabled: true, visible: true }}
                                        dispatcher={dispatcher}
                                        hangup={noop}
                                        publishStream={noop}
-                                       screenShare={{ state: SCREEN_SHARE_STATES.INACTIVE, visible: true }}
                                        settingsMenuItems={[{ id: "feedback" }]}
                                        show={true}
                                        video={{ enabled: true, visible: true }} />
                 </div>
               </FramedExample>
               <FramedExample dashed={true}
                              height={56}
-                             summary="Video muted, Screen share pending"
+                             summary="Video muted"
                              width={300}>
                 <div className="fx-embedded">
                   <ConversationToolbar audio={{ enabled: true, visible: true }}
                                        dispatcher={dispatcher}
                                        hangup={noop}
                                        publishStream={noop}
-                                       screenShare={{ state: SCREEN_SHARE_STATES.PENDING, visible: true }}
                                        settingsMenuItems={[{ id: "feedback" }]}
                                        show={true}
                                        video={{ enabled: false, visible: true }} />
                 </div>
               </FramedExample>
               <FramedExample dashed={true}
                              height={56}
-                             summary="Audio muted, Screen share active"
+                             summary="Audio muted"
                              width={300}>
                 <div className="fx-embedded">
                   <ConversationToolbar audio={{ enabled: false, visible: true }}
                                        dispatcher={dispatcher}
                                        hangup={noop}
                                        publishStream={noop}
-                                       screenShare={{ state: SCREEN_SHARE_STATES.ACTIVE, visible: true }}
                                        settingsMenuItems={[{ id: "feedback" }]}
                                        show={true}
                                        video={{ enabled: true, visible: true }} />
                 </div>
               </FramedExample>
             </div>
           </Section>
 
--- a/browser/locales/en-US/chrome/browser/loop/loop.properties
+++ b/browser/locales/en-US/chrome/browser/loop/loop.properties
@@ -99,20 +99,16 @@ initiate_call_button_label2=Ready to sta
 incoming_call_title2=Conversation Request
 incoming_call_block_button=Block
 hangup_button_title=Hang up
 hangup_button_caption2=Exit
 mute_local_audio_button_title=Mute your audio
 unmute_local_audio_button_title=Unmute your audio
 mute_local_video_button_title2=Disable video
 unmute_local_video_button_title2=Enable video
-active_screenshare_button_title=Stop sharing
-inactive_screenshare_button_title=Share your screen
-share_tabs_button_title2=Share your Tabs
-share_windows_button_title=Share other Windows
 self_view_hidden_message=Self-view hidden but still being sent; resize window to show
 
 
 ## LOCALIZATION NOTE (call_with_contact_title): The title displayed
 ## when calling a contact. Don't translate the part between {{..}} because
 ## this will be replaced by the contact's name.
 call_with_contact_title=Conversation with {{contactName}}
 
--- a/build/autoconf/icu.m4
+++ b/build/autoconf/icu.m4
@@ -15,20 +15,20 @@ MOZ_ARG_WITH_BOOL(system-icu,
 [  --with-system-icu
                           Use system ICU (located with pkgconfig)],
     MOZ_NATIVE_ICU=1)
 
 if test -n "$MOZ_NATIVE_ICU"; then
     PKG_CHECK_MODULES(MOZ_ICU, icu-i18n >= 50.1)
     MOZ_SHARED_ICU=1
 else
-    MOZ_ICU_CFLAGS="-I$_topsrcdir/intl/icu/source/common -I$_topsrcdir/intl/icu/source/i18n"
-    AC_SUBST_LIST(MOZ_ICU_CFLAGS)
+    MOZ_ICU_INCLUDES="/intl/icu/source/common /intl/icu/source/i18n"
 fi
 
+AC_SUBST_LIST(MOZ_ICU_INCLUDES)
 AC_SUBST(MOZ_NATIVE_ICU)
 
 MOZ_ARG_WITH_STRING(intl-api,
 [  --with-intl-api, --with-intl-api=build, --without-intl-api
     Determine the status of the ECMAScript Internationalization API.  The first
     (or lack of any of these) builds and exposes the API.  The second builds it
     but doesn't use ICU or expose the API to script.  The third doesn't build
     ICU at all.],
--- a/config/system-headers
+++ b/config/system-headers
@@ -178,16 +178,17 @@ Appearance.h
 AppFileInfo.h
 AppKit.h
 AppleEvents.h
 Application.h
 app/Message.h
 app/MessageRunner.h
 arpa/inet.h
 arpa/nameser.h
+array
 asm/page.h
 asm/sigcontext.h
 asm/signal.h
 ASRegistry.h
 assert.h
 atk/atk.h
 atlcom.h
 atlconv.h
--- a/configure.in
+++ b/configure.in
@@ -547,16 +547,17 @@ case "$target" in
         _CXX_MAJOR_VERSION=`echo ${CXX_VERSION} | cut -c 1-2`
 
         if test "$_CC_MAJOR_VERSION" != "$_CXX_MAJOR_VERSION"; then
             AC_MSG_ERROR([The major versions of \$CC and \$CXX do not match.])
         fi
 
         AC_DEFINE(_CRT_SECURE_NO_WARNINGS)
         AC_DEFINE(_CRT_NONSTDC_NO_WARNINGS)
+        AC_DEFINE(_USE_MATH_DEFINES) # Otherwise MSVC's math.h doesn't #define M_PI.
 
         if test "$_CC_MAJOR_VERSION" = "18" -a "$_CC_BUILD_VERSION" -ge "30723"; then
             _CC_SUITE=12
             MSVS_VERSION=2013
             MSVC_C_RUNTIME_DLL=msvcr120.dll
             MSVC_CXX_RUNTIME_DLL=msvcp120.dll
         elif test "$_CC_MAJOR_VERSION" = "19"; then
             _CC_SUITE=14
@@ -4861,16 +4862,23 @@ fi
 
 dnl =========================================================
 dnl = Whether to exclude hyphenations files in the build
 dnl =========================================================
 if test -n "$MOZ_EXCLUDE_HYPHENATION_DICTIONARIES"; then
     AC_DEFINE(MOZ_EXCLUDE_HYPHENATION_DICTIONARIES)
 fi
 
+dnl =========================================================
+dnl = Background service for downloading additional content at runtime.
+dnl =========================================================
+if test -n "$MOZ_ANDROID_DOWNLOAD_CONTENT_SERVICE"; then
+    AC_DEFINE(MOZ_ANDROID_DOWNLOAD_CONTENT_SERVICE)
+fi
+
 dnl ========================================================
 dnl = Include install tracking on Android
 dnl ========================================================
 if test -n "$MOZ_INSTALL_TRACKING"; then
     AC_DEFINE(MOZ_INSTALL_TRACKING)
 fi
 
 dnl ========================================================
@@ -8565,16 +8573,17 @@ AC_SUBST(MOZ_ANDROID_GECKOLIBS_AAR)
 AC_SUBST(MOZ_ANDROID_READING_LIST_SERVICE)
 AC_SUBST(MOZ_ANDROID_SEARCH_ACTIVITY)
 AC_SUBST(MOZ_ANDROID_TAB_QUEUE)
 AC_SUBST(MOZ_ANDROID_MLS_STUMBLER)
 AC_SUBST(MOZ_ANDROID_DOWNLOADS_INTEGRATION)
 AC_SUBST(MOZ_ANDROID_APPLICATION_CLASS)
 AC_SUBST(MOZ_ANDROID_BROWSER_INTENT_CLASS)
 AC_SUBST(MOZ_ANDROID_SEARCH_INTENT_CLASS)
+AC_SUBST(MOZ_ANDROID_DOWNLOAD_CONTENT_SERVICE)
 AC_SUBST(MOZ_EXCLUDE_HYPHENATION_DICTIONARIES)
 AC_SUBST(MOZ_INSTALL_TRACKING)
 AC_SUBST(MOZ_SWITCHBOARD)
 AC_SUBST(ENABLE_STRIP)
 AC_SUBST(PKG_SKIP_STRIP)
 AC_SUBST(STRIP_FLAGS)
 AC_SUBST(USE_ELF_HACK)
 AC_SUBST(INCREMENTAL_LINKER)
--- a/devtools/.eslintrc
+++ b/devtools/.eslintrc
@@ -249,19 +249,18 @@
     // Allow the use of ternary operators.
     "no-ternary": 0,
     // Disallow throwing literals (eg. throw "error" instead of
     // throw new Error("error")).
     "no-throw-literal": 2,
     // Disallow trailing whitespace at the end of lines.
     "no-trailing-spaces": 2,
     // Disallow use of undeclared variables unless mentioned in a /*global */
-    // block.
-    // This should really be a 2, but until we define all globals in comments
-    // and .eslintrc, keeping this as a 1.
+    // block. Note that globals from head.js are automatically imported in tests
+    // by the import-headjs-globals rule form the mozilla eslint plugin.
     "no-undef": 2,
     // Allow dangling underscores in identifiers (for privates).
     "no-underscore-dangle": 0,
     // Allow use of undefined variable.
     "no-undefined": 0,
     // Disallow the use of Boolean literals in conditional expressions.
     "no-unneeded-ternary": 2,
     // Disallow unreachable statements after a return, throw, continue, or break
--- a/devtools/client/aboutdebugging/aboutdebugging.js
+++ b/devtools/client/aboutdebugging/aboutdebugging.js
@@ -59,26 +59,30 @@ var AboutDebugging = {
     }
     // Show the corresponding tab and hide the others.
     document.querySelector(".tab.active").classList.remove("active");
     document.querySelector("#tab-" + category).classList.add("active");
     document.querySelector(".category[selected]").removeAttribute("selected");
     document.querySelector(".category[value=" + category + "]")
       .setAttribute("selected", "true");
     location.hash = "#" + category;
+
+    if (category == "addons") {
+      React.render(React.createElement(AddonsComponent, { client: this.client }),
+        document.querySelector("#addons"));
+    } else if (category == "workers") {
+      React.render(React.createElement(WorkersComponent, { client: this.client }),
+        document.querySelector("#workers"));
+    }
   },
 
   init() {
     let telemetry = this._telemetry = new Telemetry();
     telemetry.toolOpened("aboutdebugging");
 
-    // Show the first available tab.
-    this.showTab();
-    window.addEventListener("hashchange", () => this.showTab());
-
     // Link checkboxes to prefs.
     let elements = document.querySelectorAll("input[type=checkbox][data-pref]");
     Array.map(elements, element => {
       let pref = element.dataset.pref;
       let updatePref = () => {
         Services.prefs.setBoolPref(pref, element.checked);
       };
       element.addEventListener("change", updatePref, false);
@@ -94,23 +98,22 @@ var AboutDebugging = {
     let loadAddonButton = document.getElementById("load-addon-from-file");
     loadAddonButton.addEventListener("click", this.loadAddonFromFile);
 
     if (!DebuggerServer.initialized) {
       DebuggerServer.init();
       DebuggerServer.addBrowserActors();
     }
     DebuggerServer.allowChromeProcess = true;
-    let client = new DebuggerClient(DebuggerServer.connectPipe());
+    this.client = new DebuggerClient(DebuggerServer.connectPipe());
 
-    client.connect(() => {
-      React.render(React.createElement(AddonsComponent, { client }),
-        document.querySelector("#addons"));
-      React.render(React.createElement(WorkersComponent, { client }),
-        document.querySelector("#workers"));
+    this.client.connect(() => {
+      // Show the first available tab.
+      this.showTab();
+      window.addEventListener("hashchange", () => this.showTab());
     });
   },
 
   loadAddonFromFile() {
     let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
     fp.init(window,
       Strings.GetStringFromName("selectAddonFromFile"),
       Ci.nsIFilePicker.modeOpen);
@@ -139,16 +142,19 @@ var AboutDebugging = {
 
     this._prefListeners.forEach(([pref, listener]) => {
       Services.prefs.removeObserver(pref, listener);
     });
     this._prefListeners = [];
 
     React.unmountComponentAtNode(document.querySelector("#addons"));
     React.unmountComponentAtNode(document.querySelector("#workers"));
+
+    this.client.close();
+    this.client = null;
   },
 };
 
 window.addEventListener("DOMContentLoaded", function load() {
   window.removeEventListener("DOMContentLoaded", load);
   AboutDebugging.init();
 });
 
--- a/devtools/client/aboutdebugging/components/target-list.js
+++ b/devtools/client/aboutdebugging/components/target-list.js
@@ -22,16 +22,16 @@ exports.TargetListComponent = React.crea
   displayName: "TargetListComponent",
 
   render() {
     let client = this.props.client;
     let targets = this.props.targets.sort(LocaleCompare).map(target => {
       return React.createElement(TargetComponent, { client, target });
     });
     return (
-      React.createElement("div", { className: "targets" },
+      React.createElement("div", { id: this.props.id, className: "targets" },
         React.createElement("h4", null, this.props.name),
         targets.length > 0 ? targets :
           React.createElement("p", null, Strings.GetStringFromName("nothing"))
       )
     );
   },
 });
--- a/devtools/client/aboutdebugging/components/workers.js
+++ b/devtools/client/aboutdebugging/components/workers.js
@@ -9,16 +9,18 @@
 loader.lazyRequireGetter(this, "Ci",
   "chrome", true);
 loader.lazyRequireGetter(this, "React",
   "devtools/client/shared/vendor/react");
 loader.lazyRequireGetter(this, "TargetListComponent",
   "devtools/client/aboutdebugging/components/target-list", true);
 loader.lazyRequireGetter(this, "Services");
 
+loader.lazyImporter(this, "Task", "resource://gre/modules/Task.jsm");
+
 const Strings = Services.strings.createBundle(
   "chrome://devtools/locale/aboutdebugging.properties");
 const WorkerIcon = "chrome://devtools/skin/images/debugging-workers.svg";
 
 exports.WorkersComponent = React.createClass({
   displayName: "WorkersComponent",
 
   getInitialState() {
@@ -27,45 +29,50 @@ exports.WorkersComponent = React.createC
         service: [],
         shared: [],
         other: []
       }
     };
   },
 
   componentDidMount() {
-    this.props.client.addListener("workerListChanged", this.update);
+    let client = this.props.client;
+    client.addListener("workerListChanged", this.update);
+    client.addListener("processListChanged", this.update);
     this.update();
   },
 
   componentWillUnmount() {
-    this.props.client.removeListener("workerListChanged", this.update);
+    let client = this.props.client;
+    client.removeListener("processListChanged", this.update);
+    client.removeListener("workerListChanged", this.update);
   },
 
   render() {
     let client = this.props.client;
     let workers = this.state.workers;
     return React.createElement("div", { className: "inverted-icons" },
       React.createElement(TargetListComponent, {
+        id: "service-workers",
         name: Strings.GetStringFromName("serviceWorkers"),
         targets: workers.service, client }),
       React.createElement(TargetListComponent, {
+        id: "shared-workers",
         name: Strings.GetStringFromName("sharedWorkers"),
         targets: workers.shared, client }),
       React.createElement(TargetListComponent, {
+        id: "other-workers",
         name: Strings.GetStringFromName("otherWorkers"),
         targets: workers.other, client })
     );
   },
 
   update() {
-    let client = this.props.client;
     let workers = this.getInitialState().workers;
-    client.mainRoot.listWorkers(response => {
-      let forms = response.workers;
+    this.getWorkerForms().then(forms => {
       forms.forEach(form => {
         let worker = {
           name: form.url,
           icon: WorkerIcon,
           actorID: form.actor
         };
         switch (form.type) {
           case Ci.nsIWorkerDebugger.TYPE_SERVICE:
@@ -78,10 +85,34 @@ exports.WorkersComponent = React.createC
             break;
           default:
             worker.type = "worker";
             workers.other.push(worker);
         }
       });
       this.setState({ workers });
     });
-  }
+  },
+
+  getWorkerForms: Task.async(function*() {
+    let client = this.props.client;
+
+    // List workers from the Parent process
+    let result = yield client.mainRoot.listWorkers();
+    let forms = result.workers;
+
+    // And then from the Child processes
+    let { processes } = yield client.mainRoot.listProcesses();
+    for (let process of processes) {
+      // Ignore parent process
+      if (process.parent) {
+        continue;
+      }
+      let { form } = yield client.getProcess(process.id);
+      let processActor = form.actor;
+      let { workers } = yield client.request({to: processActor,
+                                              type: "listWorkers"});
+      forms = forms.concat(workers);
+    }
+
+    return forms;
+  }),
 });
--- a/devtools/client/aboutdebugging/test/browser.ini
+++ b/devtools/client/aboutdebugging/test/browser.ini
@@ -1,9 +1,12 @@
 [DEFAULT]
 tags = devtools
 subsuite = devtools
 support-files =
   head.js
   addons/unpacked/bootstrap.js
   addons/unpacked/install.rdf
+  service-workers/empty-sw.html
+  service-workers/empty-sw.js
 
 [browser_addons_install.js]
+[browser_service_workers.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/browser_service_workers.js
@@ -0,0 +1,66 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Service workers can't be loaded from chrome://,
+// but http:// is ok with dom.serviceWorkers.testing.enabled turned on.
+const HTTP_ROOT = CHROME_ROOT.replace("chrome://mochitests/content/",
+                                      "http://mochi.test:8888/");
+const SERVICE_WORKER = HTTP_ROOT + "service-workers/empty-sw.js";
+const TAB_URL = HTTP_ROOT + "service-workers/empty-sw.html";
+
+function waitForWorkersUpdate(document) {
+  return new Promise(done => {
+    var observer = new MutationObserver(function(mutations) {
+      observer.disconnect();
+      done();
+    });
+    var target = document.getElementById("service-workers");
+    observer.observe(target, { childList: true });
+  });
+}
+
+add_task(function *() {
+  yield new Promise(done => {
+    let options = {"set": [
+                    ["dom.serviceWorkers.testing.enabled", true],
+                  ]};
+    SpecialPowers.pushPrefEnv(options, done);
+  });
+
+  let { tab, document } = yield openAboutDebugging("workers");
+
+  let swTab = yield addTab(TAB_URL);
+
+  yield waitForWorkersUpdate(document);
+
+  // Check that the service worker appears in the UI
+  let names = [...document.querySelectorAll("#service-workers .target-name")];
+  names = names.map(element => element.textContent);
+  ok(names.includes(SERVICE_WORKER), "The service worker url appears in the list: " + names);
+
+  // Use message manager to work with e10s
+  let frameScript = function () {
+    // Retrieve the `sw` promise created in the html page
+    let { sw } = content.wrappedJSObject;
+    sw.then(function (registration) {
+      registration.unregister().then(function (success) {
+        dump("SW unregistered: " + success + "\n");
+      },
+      function (e) {
+        dump("SW not unregistered; " + e + "\n");
+      });
+    });
+  };
+  swTab.linkedBrowser.messageManager.loadFrameScript("data:,(" + encodeURIComponent(frameScript) + ")()", true);
+
+  yield waitForWorkersUpdate(document);
+
+  // Check that the service worker disappeared from the UI
+  names = [...document.querySelectorAll("#service-workers .target-name")];
+  names = names.map(element => element.textContent);
+  ok(!names.includes(SERVICE_WORKER), "The service worker url is no longer in the list: " + names);
+
+  yield removeTab(swTab);
+  yield closeAboutDebugging(tab);
+});
--- a/devtools/client/aboutdebugging/test/head.js
+++ b/devtools/client/aboutdebugging/test/head.js
@@ -11,19 +11,23 @@ const DevToolsUtils = require("devtools/
 DevToolsUtils.testing = true;
 
 const CHROME_ROOT = gTestPath.substr(0, gTestPath.lastIndexOf("/") + 1);
 
 registerCleanupFunction(() => {
   DevToolsUtils.testing = false;
 });
 
-function openAboutDebugging() {
+function openAboutDebugging(page) {
   info("opening about:debugging");
-  return addTab("about:debugging").then(tab => {
+  let url = "about:debugging";
+  if (page) {
+    url += "#" + page;
+  }
+  return addTab(url).then(tab => {
     let browser = tab.linkedBrowser;
     return {
       tab,
       document: browser.contentDocument,
       window: browser.contentWindow
     };
   });
 }
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/service-workers/empty-sw.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="UTF-8">
+  <title>Service worker test</title>
+</head>
+<body>
+<script type="text/javascript">
+var sw = navigator.serviceWorker.register("empty-sw.js");
+sw.then(
+  function (registration) {
+    dump("SW registered\n");
+  },
+  function (e) {
+    dump("SW not registered: " + e + "\n");
+  }
+);
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/service-workers/empty-sw.js
@@ -0,0 +1,1 @@
+// Empty, just test registering.
--- a/devtools/client/commandline/test/browser_cmd_commands.js
+++ b/devtools/client/commandline/test/browser_cmd_commands.js
@@ -27,27 +27,23 @@ function* spawnTest() {
   subject.QueryInterface(Ci.nsISupportsString);
   let hud = HUDService.getHudReferenceById(subject.data);
   ok(hud, "console open");
 
   let msg = yield hud.jsterm.execute("pprint(window)");
 
   ok(msg, "output for pprint(window)");
 
-  let oncePromise = hud.jsterm.once("messages-cleared");
-
-  helpers.audit(options, [
+  yield helpers.audit(options, [
     {
       setup: "console clear",
       exec: { output: "" }
     }
   ]);
 
-  yield oncePromise;
-
   let labels = hud.outputNode.querySelectorAll(".message");
   is(labels.length, 0, "no output in console");
 
   yield helpers.audit(options, [
     {
       setup: "console close",
       exec: { output: "" }
     }
--- a/devtools/client/debugger/debugger-controller.js
+++ b/devtools/client/debugger/debugger-controller.js
@@ -318,17 +318,21 @@ var DebuggerController = {
         fetchEventListeners();
       }
     });
 
     this.Workers.connect();
     this.ThreadState.connect();
     this.StackFrames.connect();
 
-    this._onNavigate();
+    // Load all of the sources. Note that the server will actually
+    // emit individual `newSource` notifications, which trigger
+    // separate actions, so this won't do anything other than force
+    // the server to traverse sources.
+    this.dispatch(actions.loadSources());
   },
 
   /**
    * Disconnects the debugger client and removes event handlers as necessary.
    */
   disconnect: function() {
     // Return early if the client didn't even have a chance to instantiate.
     if (!this.client) {
@@ -373,21 +377,22 @@ var DebuggerController = {
     clearNamedTimeout("event-breakpoints-update");
     clearNamedTimeout("event-listeners-fetch");
   },
 
   _onNavigate: function() {
     this.ThreadState.handleTabNavigation();
     this.StackFrames.handleTabNavigation();
 
-    // Load all of the sources. Note that the server will actually
-    // emit individual `newSource` notifications, which trigger
-    // separate actions, so this won't do anything other than force
-    // the server to traverse sources.
-
+    // TODO(jwl): We shouldn't need this call. We're already getting
+    // `newSource` notifications because we're already connected, but
+    // I'm not sure of the order those come in with regards to the
+    // navigation event.  Tests look for this action and it needs to
+    // indicate everything is done loading, so we should figure out
+    // another way to indicate that.
     this.dispatch(actions.loadSources());
   },
 
   /**
    * Called when the debugged tab is closed.
    */
   _onTabDetached: function() {
     this.shutdownDebugger();
--- a/devtools/client/framework/toolbox-hosts.js
+++ b/devtools/client/framework/toolbox-hosts.js
@@ -5,16 +5,18 @@
 "use strict";
 
 const {Cu} = require("chrome");
 const EventEmitter = require("devtools/shared/event-emitter");
 const promise = require("promise");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://devtools/client/shared/DOMHelpers.jsm");
 
+loader.lazyRequireGetter(this, "system", "devtools/shared/system");
+
 /* A host should always allow this much space for the page to be displayed.
  * There is also a min-height on the browser, but we still don't want to set
  * frame.height to be larger than that, since it can cause problems with
  * resizing the toolbox and panel layout. */
 const MIN_PAGE_SIZE = 25;
 
 /**
  * A toolbox host represents an object that contains a toolbox (e.g. the
@@ -278,16 +280,25 @@ WindowHost.prototype = {
 
     let flags = "chrome,centerscreen,resizable,dialog=no";
     let win = Services.ww.openWindow(null, this.WINDOW_URL, "_blank",
                                      flags, null);
 
     let frameLoad = () => {
       win.removeEventListener("load", frameLoad, true);
       win.focus();
+
+      let key;
+      if (system.constants.platform === "macosx") {
+        key = win.document.getElementById("toolbox-key-toggle-osx");
+      } else {
+        key = win.document.getElementById("toolbox-key-toggle");
+      }
+      key.removeAttribute("disabled");
+
       this.frame = win.document.getElementById("toolbox-iframe");
       this.emit("ready", this.frame);
 
       deferred.resolve(this.frame);
     };
 
     win.addEventListener("load", frameLoad, true);
     win.addEventListener("unload", this._boundUnload);
--- a/devtools/client/framework/toolbox-window.xul
+++ b/devtools/client/framework/toolbox-window.xul
@@ -24,22 +24,23 @@
   <keyset id="toolbox-keyset">
     <key id="toolbox-key-close"
          key="&closeCmd.key;"
          command="toolbox-cmd-close"
          modifiers="accel"/>
     <key id="toolbox-key-toggle"
          key="&toggleToolbox.key;"
          command="toolbox-cmd-close"
-#ifdef XP_MACOSX
+         modifiers="accel,shift"
+         disabled="true"/>
+    <key id="toolbox-key-toggle-osx"
+         key="&toggleToolbox.key;"
+         command="toolbox-cmd-close"
          modifiers="accel,alt"
-#else
-         modifiers="accel,shift"
-#endif
-        />
+         disabled="true"/>
     <key id="toolbox-key-toggle-F12"
          keycode="&toggleToolboxF12.keycode;"
          keytext="&toggleToolboxF12.keytext;"
          command="toolbox-cmd-close"/>
   </keyset>
 
   <iframe id="toolbox-iframe" flex="1" forceOwnRefreshDriver=""></iframe>
 </window>
--- a/devtools/client/inspector/inspector-search.js
+++ b/devtools/client/inspector/inspector-search.js
@@ -385,29 +385,30 @@ SelectorAutocompleter.prototype = {
    * Populates the suggestions list and show the suggestion popup.
    */
   _showPopup: function(list, firstPart, aState) {
     let total = 0;
     let query = this.searchBox.value;
     let items = [];
 
     for (let [value, /*count*/, state] of list) {
-      // for cases like 'div ' or 'div >' or 'div+'
       if (query.match(/[\s>+]$/)) {
+        // for cases like 'div ' or 'div >' or 'div+'
         value = query + value;
-      }
-      // for cases like 'div #a' or 'div .a' or 'div > d' and likewise
-      else if (query.match(/[\s>+][\.#a-zA-Z][^\s>+\.#]*$/)) {
-        let lastPart = query.match(/[\s>+][\.#a-zA-Z][^>\s+\.#]*$/)[0];
+      } else if (query.match(/[\s>+][\.#a-zA-Z][^\s>+\.#\[]*$/)) {
+        // for cases like 'div #a' or 'div .a' or 'div > d' and likewise
+        let lastPart = query.match(/[\s>+][\.#a-zA-Z][^\s>+\.#\[]*$/)[0];
         value = query.slice(0, -1 * lastPart.length + 1) + value;
-      }
-      // for cases like 'div.class' or '#foo.bar' and likewise
-      else if (query.match(/[a-zA-Z][#\.][^#\.\s+>]*$/)) {
-        let lastPart = query.match(/[a-zA-Z][#\.][^#\.\s>+]*$/)[0];
+      } else if (query.match(/[a-zA-Z][#\.][^#\.\s+>]*$/)) {
+        // for cases like 'div.class' or '#foo.bar' and likewise
+        let lastPart = query.match(/[a-zA-Z][#\.][^#\.\s+>]*$/)[0];
         value = query.slice(0, -1 * lastPart.length + 1) + value;
+      } else if (query.match(/[a-zA-Z]\[[^\]]*\]?$/)) {
+        // for cases like 'div[foo=bar]' and likewise
+        value = query;
       }
 
       let item = {
         preLabel: query,
         label: value
       };
 
       // In case of tagNames, change the case to small
@@ -452,16 +453,23 @@ SelectorAutocompleter.prototype = {
    * Suggests classes,ids and tags based on the user input as user types in the
    * searchbox.
    */
   showSuggestions: function() {
     let query = this.searchBox.value;
     let state = this.state;
     let firstPart = "";
 
+    if (query.endsWith("*")) {
+      // Hide the popup if the query ends with * because we don't want to
+      // suggest all nodes.
+      this.hidePopup();
+      return;
+    }
+
     if (state === this.States.TAG) {
       // gets the tag that is being completed. For ex. 'div.foo > s' returns 's',
       // 'di' returns 'di' and likewise.
       firstPart = (query.match(/[\s>+]?([a-zA-Z]*)$/) || ["", query])[1];
       query = query.slice(0, query.length - firstPart.length);
     }
     else if (state === this.States.CLASS) {
       // gets the class that is being completed. For ex. '.foo.b' returns 'b'
--- a/devtools/client/inspector/test/browser_inspector_search-03.js
+++ b/devtools/client/inspector/test/browser_inspector_search-03.js
@@ -155,16 +155,49 @@ var TEST_DATA = [
       {label: "#p3"},
       {label: "#s1"},
       {label: "#s2"}
     ]
   },
   {
     key: "VK_BACK_SPACE",
     suggestions: []
+  },
+  {
+    key: "p",
+    suggestions: [
+      {label: "p"},
+      {label: "#p1"},
+      {label: "#p2"},
+      {label: "#p3"}
+    ]
+  },
+  {
+    key: "[", suggestions: []
+  },
+  {
+    key: "i", suggestions: []
+  },
+  {
+    key: "d", suggestions: []
+  },
+  {
+    key: "*", suggestions: []
+  },
+  {
+    key: "=", suggestions: []
+  },
+  {
+    key: "p", suggestions: []
+  },
+  {
+    key: "]",
+    suggestions: [
+      {label: "p[id*=p]"}
+    ]
   }
 ];
 
 add_task(function* () {
   let { inspector } = yield openInspectorForURL(TEST_URL);
   let searchBox = inspector.searchBox;
   let popup = inspector.searchSuggestions.searchPopup;
 
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -112,17 +112,17 @@ devtools.jar:
     content/memory/memory.xhtml (memory/memory.xhtml)
     content/memory/initializer.js (memory/initializer.js)
     content/promisedebugger/promise-controller.js (promisedebugger/promise-controller.js)
     content/promisedebugger/promise-panel.js (promisedebugger/promise-panel.js)
     content/promisedebugger/promise-debugger.xhtml (promisedebugger/promise-debugger.xhtml)
     content/commandline/commandline.css (commandline/commandline.css)
     content/commandline/commandlineoutput.xhtml (commandline/commandlineoutput.xhtml)
     content/commandline/commandlinetooltip.xhtml (commandline/commandlinetooltip.xhtml)
-*   content/framework/toolbox-window.xul (framework/toolbox-window.xul)
+    content/framework/toolbox-window.xul (framework/toolbox-window.xul)
     content/framework/toolbox-options.xul (framework/toolbox-options.xul)
     content/framework/toolbox-options.js (framework/toolbox-options.js)
     content/framework/toolbox.xul (framework/toolbox.xul)
     content/framework/options-panel.css (framework/options-panel.css)
     content/framework/toolbox-process-window.xul (framework/toolbox-process-window.xul)
 *   content/framework/toolbox-process-window.js (framework/toolbox-process-window.js)
     content/framework/dev-edition-promo/dev-edition-promo.xul (framework/dev-edition-promo/dev-edition-promo.xul)
 *   content/framework/dev-edition-promo/dev-edition-promo.css (framework/dev-edition-promo/dev-edition-promo.css)
--- a/devtools/client/shared/widgets/VariablesView.jsm
+++ b/devtools/client/shared/widgets/VariablesView.jsm
@@ -3466,21 +3466,21 @@ VariablesView.stringifiers.byType = {
       return result;
     }
     return result.substr(0, result.length - 1) + ellipsis + '"';
   },
 
   object: function(aGrip, aOptions) {
     let {preview} = aGrip;
     let stringifier;
-    if (preview && preview.kind) {
-      stringifier = VariablesView.stringifiers.byObjectKind[preview.kind];
+    if (aGrip.class) {
+      stringifier = VariablesView.stringifiers.byObjectClass[aGrip.class];
     }
-    if (!stringifier && aGrip.class) {
-      stringifier = VariablesView.stringifiers.byObjectClass[aGrip.class];
+    if (!stringifier && preview && preview.kind) {
+      stringifier = VariablesView.stringifiers.byObjectKind[preview.kind];
     }
     if (stringifier) {
       return stringifier(aGrip, aOptions);
     }
     return null;
   },
 
   symbol: function(aGrip, aOptions) {
@@ -3516,28 +3516,23 @@ VariablesView.stringifiers.byObjectClass
 
     if (typeof preview.timestamp != "number") {
       return new Date(preview.timestamp).toString(); // invalid date
     }
 
     return "Date " + new Date(preview.timestamp).toISOString();
   },
 
-  String: function({displayString}) {
-    if (displayString === undefined) {
-      return null;
-    }
-    return VariablesView.getString(displayString);
-  },
-
-  Number: function({preview}) {
+  Number: function(aGrip) {
+    let {preview} = aGrip;
     if (preview === undefined) {
       return null;
     }
-    return VariablesView.getString(preview.value);
+    return aGrip.class + " { " + VariablesView.getString(preview.wrappedValue) +
+      " }";
   },
 }; // VariablesView.stringifiers.byObjectClass
 
 VariablesView.stringifiers.byObjectClass.Boolean =
   VariablesView.stringifiers.byObjectClass.Number;
 
 VariablesView.stringifiers.byObjectKind = {
   ArrayLike: function(aGrip, {concise}) {
--- a/devtools/client/styleinspector/rule-view.js
+++ b/devtools/client/styleinspector/rule-view.js
@@ -564,21 +564,22 @@ Rule.prototype = {
    *
    * @return {Promise}
    *         Promise which resolves with location as an object containing
    *         both the full and short version of the source string.
    */
   getOriginalSourceStrings: function() {
     return this.domRule.getOriginalLocation().then(({href, line, mediaText}) => {
       let mediaString = mediaText ? " @" + mediaText : "";
+      let linePart = line > 0 ? (":" + line) : "";
 
       let sourceStrings = {
-        full: (href || CssLogic.l10n("rule.sourceInline")) + ":" +
-          line + mediaString,
-        short: CssLogic.shortSource({href: href}) + ":" + line + mediaString
+        full: (href || CssLogic.l10n("rule.sourceInline")) + linePart +
+          mediaString,
+        short: CssLogic.shortSource({href: href}) + linePart + mediaString
       };
 
       return sourceStrings;
     });
   },
 
   /**
    * Returns true if the rule matches the creation options
--- a/devtools/client/styleinspector/test/browser.ini
+++ b/devtools/client/styleinspector/test/browser.ini
@@ -6,16 +6,17 @@ support-files =
   doc_content_stylesheet.xul
   doc_content_stylesheet_imported.css
   doc_content_stylesheet_imported2.css
   doc_content_stylesheet_linked.css
   doc_content_stylesheet_script.css
   doc_content_stylesheet_xul.css
   doc_copystyles.css
   doc_copystyles.html
+  doc_cssom.html
   doc_custom.html
   doc_filter.html
   doc_frame_script.js
   doc_keyframeanimation.html
   doc_keyframeanimation.css
   doc_keyframeLineNumbers.html
   doc_matched_selectors.html
   doc_media_queries.html
@@ -84,16 +85,17 @@ support-files =
 [browser_ruleview_completion-popup-hidden-after-navigation.js]
 [browser_ruleview_content_01.js]
 [browser_ruleview_content_02.js]
 skip-if = e10s # Bug 1039528: "inspect element" contextual-menu doesn't work with e10s
 [browser_ruleview_context-menu-show-mdn-docs-01.js]
 [browser_ruleview_context-menu-show-mdn-docs-02.js]
 [browser_ruleview_context-menu-show-mdn-docs-03.js]
 [browser_ruleview_copy_styles.js]
+[browser_ruleview_cssom.js]
 [browser_ruleview_cubicbezier-appears-on-swatch-click.js]
 [browser_ruleview_cubicbezier-commit-on-ENTER.js]
 [browser_ruleview_cubicbezier-revert-on-ESC.js]
 [browser_ruleview_custom.js]
 [browser_ruleview_cycle-color.js]
 [browser_ruleview_edit-property-cancel.js]
 [browser_ruleview_edit-property-commit.js]
 [browser_ruleview_edit-property-computed.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/styleinspector/test/browser_ruleview_cssom.js
@@ -0,0 +1,22 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test to ensure that CSSOM doesn't make the rule view blow up.
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1224121
+
+const TEST_URI = TEST_URL_ROOT + "doc_cssom.html";
+
+add_task(function*() {
+  yield addTab(TEST_URI);
+  let {inspector, view} = yield openRuleView();
+  yield selectNode("#target", inspector);
+
+  let elementStyle = view._elementStyle;
+  let rule = elementStyle.rules[1];
+
+  is(rule.textProps.length, 1, "rule should have one property");
+  is(rule.textProps[0].name, "color", "the property should be 'color'");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/styleinspector/test/doc_cssom.html
@@ -0,0 +1,21 @@
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<head>
+  <title>CSSOM test</title>
+
+  <script>
+    window.onload = function() {
+      let x = document.styleSheets[0];
+      x.insertRule("div { color: seagreen; }", 1);
+    };
+  </script>
+
+  <style>
+    span { }
+  </style>
+</head>
+<body>
+  <div id="target"> the ocean </div>
+</body>
+</html>
--- a/devtools/client/webconsole/console-commands.js
+++ b/devtools/client/webconsole/console-commands.js
@@ -67,17 +67,19 @@ exports.items = [
         return;
       }
 
       let panel = toolbox.getPanel("webconsole");
       if (panel == null) {
         return;
       }
 
+      let onceMessagesCleared = panel.hud.jsterm.once("messages-cleared");
       panel.hud.jsterm.clearOutput();
+      return onceMessagesCleared;
     }
   },
   {
     item: "command",
     runAt: "client",
     name: "console close",
     description: l10n.lookup("consolecloseDesc"),
     exec: function(args, context) {
--- a/devtools/client/webconsole/console-output.js
+++ b/devtools/client/webconsole/console-output.js
@@ -3372,16 +3372,64 @@ Widgets.ObjectRenderers.add({
       }
     }
 
     this._renderObjectProperties(container, addedPromiseInternalProps);
     this._renderObjectSuffix();
   }
 }); // Widgets.ObjectRenderers.byClass.Promise
 
+/*
+ * A renderer used for wrapped primitive objects.
+ */
+
+function WrappedPrimitiveRenderer() {
+  let { ownProperties, safeGetterValues } = this.objectActor.preview || {};
+  if ((!ownProperties && !safeGetterValues) || this.options.concise) {
+    this._renderConciseObject();
+    return;
+  }
+
+  this._renderObjectPrefix();
+
+  let elem =
+      this.message._renderValueGrip(this.objectActor.preview.wrappedValue);
+  this.element.appendChild(elem);
+
+  this._renderObjectProperties(this.element, true);
+  this._renderObjectSuffix();
+}
+
+/**
+ * The widget used for displaying Boolean previews.
+ */
+Widgets.ObjectRenderers.add({
+  byClass: "Boolean",
+
+  render: WrappedPrimitiveRenderer,
+});
+
+/**
+ * The widget used for displaying Number previews.
+ */
+Widgets.ObjectRenderers.add({
+  byClass: "Number",
+
+  render: WrappedPrimitiveRenderer,
+});
+
+/**
+ * The widget used for displaying String previews.
+ */
+Widgets.ObjectRenderers.add({
+  byClass: "String",
+
+  render: WrappedPrimitiveRenderer,
+});
+
 /**
  * The widget used for displaying generic JS object previews.
  */
 Widgets.ObjectRenderers.add({
   byKind: "Object",
 
   render: function()
   {
--- a/devtools/client/webconsole/test/browser_webconsole_autocomplete-properties-with-non-alphanumeric-names.js
+++ b/devtools/client/webconsole/test/browser_webconsole_autocomplete-properties-with-non-alphanumeric-names.js
@@ -17,28 +17,32 @@ var test = asyncTest(function*() {
   function* autocomplete(term) {
     let deferred = promise.defer();
 
     jsterm.setInputValue(term);
     jsterm.complete(jsterm.COMPLETE_HINT_ONLY, deferred.resolve);
 
     yield deferred.promise;
 
-    ok(popup.itemCount > 0, "There's suggestions for '" + term + "'");
+    ok(popup.itemCount > 0,
+       "There's " + popup.itemCount + " suggestions for '" + term + "'");
   }
 
   let { jsterm } = yield openConsole();
   let popup = jsterm.autocompletePopup;
 
   yield jsterm.execute("var testObject = {$$aaab: '', $$aaac: ''}");
 
-  // FIXMEshu: global lexicals can't be autocompleted without extra platform
-  // support. See bug 1207868.
-  //yield jsterm.execute("let testObject = {$$aaab: '', $$aaac: ''}");
-
   // Should work with bug 967468.
   yield autocomplete("Object.__d");
   yield autocomplete("testObject.$$a");
 
   // Here's when things go wrong in bug 967468.
   yield autocomplete("Object.__de");
   yield autocomplete("testObject.$$aa");
+
+  // Should work with bug 1207868.
+  yield jsterm.execute("let foobar = {a: ''}; const blargh = {a: 1};");
+  yield autocomplete("foobar");
+  yield autocomplete("blargh");
+  yield autocomplete("foobar.a");
+  yield autocomplete("blargh.a");
 });
--- a/devtools/client/webconsole/test/browser_webconsole_bug_632347_iterators_generators.js
+++ b/devtools/client/webconsole/test/browser_webconsole_bug_632347_iterators_generators.js
@@ -20,17 +20,17 @@ function consoleOpened(HUD) {
   let {JSPropertyProvider} = require("devtools/shared/webconsole/js-property-provider");
 
   let tmp = Cu.import("resource://gre/modules/jsdebugger.jsm", {});
   tmp.addDebuggerToGlobal(tmp);
   let dbg = new tmp.Debugger();
 
   let jsterm = HUD.jsterm;
   let win = content.wrappedJSObject;
-  let dbgWindow = dbg.makeGlobalObjectReference(win);
+  let dbgWindow = dbg.addDebuggee(content);
   let container = win._container;
 
   // Make sure autocomplete does not walk through iterators and generators.
   let result = container.gen1.next();
   let completion = JSPropertyProvider(dbgWindow, null, "_container.gen1.");
   isnot(completion.matches.length, 0, "Got matches for gen1");
 
   is(result + 1, container.gen1.next(), "gen1.next() did not execute");
@@ -57,16 +57,17 @@ function consoleOpened(HUD) {
   let dbgContent = dbg.makeGlobalObjectReference(content);
   completion = JSPropertyProvider(dbgContent, null, "_container.iter2.");
   isnot(completion.matches.length, 0, "Got matches for iter2");
 
   completion = JSPropertyProvider(dbgWindow, null, "window._container.");
   ok(completion, "matches available for window._container");
   ok(completion.matches.length, "matches available for window (length)");
 
+  dbg.removeDebuggee(content);
   jsterm.clearOutput();
 
   jsterm.execute("window._container", (msg) => {
     jsterm.once("variablesview-fetched", testVariablesView.bind(null, HUD));
     let anchor = msg.querySelector(".message-body a");
     EventUtils.synthesizeMouse(anchor, 2, 2, {}, HUD.iframeWindow);
   });
 }
--- a/devtools/client/webconsole/test/browser_webconsole_output_02.js
+++ b/devtools/client/webconsole/test/browser_webconsole_output_02.js
@@ -153,27 +153,27 @@ var inputTests = [
     inspectable: true,
     variablesViewLabel: "Map[3]",
   },
 
   // 15 - WeakSet
   {
     input: "window.weakset",
     // Need a regexp because the order may vary.
-    output: new RegExp("WeakSet \\[ (String\\[7\\], <head>|<head>, String\\[7\\]) \\]"),
+    output: new RegExp("WeakSet \\[ (String, <head>|<head>, String) \\]"),
     printOutput: "[object WeakSet]",
     inspectable: true,
     variablesViewLabel: "WeakSet[2]",
   },
 
   // 16 - WeakMap
   {
     input: "window.weakmap",
     // Need a regexp because the order may vary.
-    output: new RegExp("WeakMap { (String\\[7\\]: 23, HTMLCollection\\[2\\]: Object|HTMLCollection\\[2\\]: Object, String\\[7\\]: 23) }"),
+    output: new RegExp("WeakMap { (String: 23, HTMLCollection\\[2\\]: Object|HTMLCollection\\[2\\]: Object, String: 23) }"),
     printOutput: "[object WeakMap]",
     inspectable: true,
     variablesViewLabel: "WeakMap[2]",
   },
 ];
 
 function test() {
   requestLongerTimeout(2);
--- a/devtools/client/webconsole/test/browser_webconsole_output_05.js
+++ b/devtools/client/webconsole/test/browser_webconsole_output_05.js
@@ -45,18 +45,20 @@ var inputTests = [
   {
     input: "true",
     output: "true",
   },
 
   // 4
   {
     input: "new Boolean(false)",
-    output: "false",
+    output: "Boolean { false }",
+    printOutput: "false",
     inspectable: true,
+    variablesViewLabel: "Boolean { false }"
   },
 
   // 5
   {
     input: "new Date(" + testDate + ")",
     output: "Date " + (new Date(testDate)).toISOString(),
     printOutput: (new Date(testDate)).toString(),
     inspectable: true,
@@ -78,51 +80,73 @@ var inputTests = [
     printOutput: "Invalid Date",
     inspectable: true,
     variablesViewLabel: "Object",
   },
 
   // 8
   {
     input: "new Number(43)",
-    output: "43",
+    output: "Number { 43 }",
+    printOutput: "43",
     inspectable: true,
+    variablesViewLabel: "Number { 43 }"
   },
 
   // 9
   {
     input: "new String('hello')",
-    output: 'String [ "h", "e", "l", "l", "o" ]',
+    output: /String { "hello", 6 more.* }/,
+    printOutput: "hello",
+    inspectable: true,
+    variablesViewLabel: "String"
+  },
+
+  // 10
+  {
+    input: "(function () { var s = new String('hello'); s.whatever = 23; " +
+           " return s;})()",
+    output: /String { "hello", whatever: 23, 6 more.* }/,
     printOutput: "hello",
     inspectable: true,
-    variablesViewLabel: "String[5]"
+    variablesViewLabel: "String"
   },
 
-  // 9
+  // 11
+  {
+    input: "(function () { var s = new String('hello'); s[8] = 'x'; " +
+           " return s;})()",
+    output: /String { "hello", 8: "x", 6 more.* }/,
+    printOutput: "hello",
+    inspectable: true,
+    variablesViewLabel: "String"
+  },
+
+  // 12
   {
     // XXX: Can't test fulfilled and rejected promises, because promises get
     // settled on the next tick of the event loop.
     input: "new Promise(function () {})",
     output: 'Promise { <state>: "pending" }',
     printOutput: "[object Promise]",
     inspectable: true,
     variablesViewLabel: "Promise"
   },
 
-  // 10
+  // 13
   {
     input: "(function () { var p = new Promise(function () {}); " +
            "p.foo = 1; return p; }())",
     output: 'Promise { <state>: "pending", foo: 1 }',
     printOutput: "[object Promise]",
     inspectable: true,
     variablesViewLabel: "Promise"
   },
 
-  // 11
+  // 14
   {
     input: "new Object({1: 'this\\nis\\nsupposed\\nto\\nbe\\na\\nvery" +
            "\\nlong\\nstring\\n,shown\\non\\na\\nsingle\\nline', " +
            "2: 'a shorter string', 3: 100})",
     output: 'Object { 1: "this is supposed to be a very long ' + ELLIPSIS +
             '", 2: "a shorter string", 3: 100 }',
     printOutput: "[object Object]",
     inspectable: false,
--- a/devtools/client/webconsole/test/browser_webconsole_output_table.js
+++ b/devtools/client/webconsole/test/browser_webconsole_output_table.js
@@ -119,27 +119,27 @@ const TEST_DATA = [
         _value: "\"value associated with 'a string'\"" },
       { _index: 1, _key: "5", _value: "\"value associated with 5\"" },
     ],
     columns: { _index: "(iteration index)", _key: "Key", _value: "Values" }
   },
   {
     command: "console.table(weakset)",
     data: [
-      { _value: "String[7]" },
-      { _value: "String[7]" },
+      { _value: "String" },
+      { _value: "String" },
     ],
     columns: { _index: "(iteration index)", _value: "Values" },
     couldBeOutOfOrder: true,
   },
   {
     command: "console.table(weakmap)",
     data: [
-      { _key: "String[7]", _value: "\"oh no\"" },
-      { _key: "String[7]", _value: "23" },
+      { _key: "String", _value: "\"oh no\"" },
+      { _key: "String", _value: "23" },
     ],
     columns: { _index: "(iteration index)", _key: "Key", _value: "Values" },
     couldBeOutOfOrder: true,
   },
 ];
 
 add_task(function*() {
   const {tab} = yield loadTab(TEST_URI);
--- a/devtools/client/webconsole/test/browser_webconsole_property_provider.js
+++ b/devtools/client/webconsole/test/browser_webconsole_property_provider.js
@@ -16,17 +16,17 @@ function test() {
 
 function testPropertyProvider({browser}) {
   browser.removeEventListener("load", testPropertyProvider, true);
   let {JSPropertyProvider} = require("devtools/shared/webconsole/js-property-provider");
 
   let tmp = Cu.import("resource://gre/modules/jsdebugger.jsm", {});
   tmp.addDebuggerToGlobal(tmp);
   let dbg = new tmp.Debugger();
-  let dbgWindow = dbg.makeGlobalObjectReference(content);
+  let dbgWindow = dbg.addDebuggee(content);
 
   let completion = JSPropertyProvider(dbgWindow, null, "thisIsNotDefined");
   is(completion.matches.length, 0, "no match for 'thisIsNotDefined");
 
   // This is a case the PropertyProvider can't handle. Should return null.
   completion = JSPropertyProvider(dbgWindow, null, "window[1].acb");
   is(completion, null, "no match for 'window[1].acb");
 
@@ -36,10 +36,11 @@ function testPropertyProvider({browser})
   completion = JSPropertyProvider(dbgWindow, null, strComplete);
   ok(completion.matches.length == 2, "two matches found");
   ok(completion.matchProp == "locatio", "matching part is 'test'");
   let matches = completion.matches;
   matches.sort();
   ok(matches[0] == "location", "the first match is 'location'");
   ok(matches[1] == "locationbar", "the second match is 'locationbar'");
 
+  dbg.removeDebuggee(content);
   finishTest();
 }
--- a/devtools/server/actors/child-process.js
+++ b/devtools/server/actors/child-process.js
@@ -9,16 +9,18 @@ const { Cc, Ci, Cu } = require("chrome")
 const { ChromeDebuggerActor } = require("devtools/server/actors/script");
 const { WebConsoleActor } = require("devtools/server/actors/webconsole");
 const makeDebugger = require("devtools/server/actors/utils/make-debugger");
 const { ActorPool } = require("devtools/server/main");
 const Services = require("Services");
 const { assert } = require("devtools/shared/DevToolsUtils");
 const { TabSources } = require("./utils/TabSources");
 
+loader.lazyRequireGetter(this, "WorkerActorList", "devtools/server/actors/worker", true);
+
 function ChildProcessActor(aConnection) {
   this.conn = aConnection;
   this._contextPool = new ActorPool(this.conn);
   this.conn.addActorPool(this._contextPool);
   this.threadActor = null;
 
   // Use a see-everything debugger
   this.makeDebugger = makeDebugger.bind(null, {
@@ -27,16 +29,20 @@ function ChildProcessActor(aConnection) 
   });
 
   // Scope into which the webconsole executes:
   // An empty sandbox with chrome privileges
   let systemPrincipal = Cc["@mozilla.org/systemprincipal;1"]
     .createInstance(Ci.nsIPrincipal);
   let sandbox = Cu.Sandbox(systemPrincipal);
   this._consoleScope = sandbox;
+
+  this._workerList = null;
+  this._workerActorPool = null;
+  this._onWorkerListChanged = this._onWorkerListChanged.bind(this);
 }
 exports.ChildProcessActor = ChildProcessActor;
 
 ChildProcessActor.prototype = {
   actorPrefix: "process",
 
   get isRootActor() {
     return true;
@@ -82,25 +88,59 @@ ChildProcessActor.prototype = {
 
       traits: {
         highlightable: false,
         networkMonitor: false,
       },
     };
   },
 
+  onListWorkers: function () {
+    if (!this._workerList) {
+      this._workerList = new WorkerActorList({});
+    }
+    return this._workerList.getList().then(actors => {
+      let pool = new ActorPool(this.conn);
+      for (let actor of actors) {
+        pool.addActor(actor);
+      }
+
+      this.conn.removeActorPool(this._workerActorPool);
+      this._workerActorPool = pool;
+      this.conn.addActorPool(this._workerActorPool);
+
+      this._workerList.onListChanged = this._onWorkerListChanged;
+
+      return {
+        "from": this.actorID,
+        "workers": actors.map(actor => actor.form())
+      };
+    });
+  },
+
+  _onWorkerListChanged: function () {
+    this.conn.send({ from: this.actorID, type: "workerListChanged" });
+    this._workerList.onListChanged = null;
+  },
+
   disconnect: function() {
     this.conn.removeActorPool(this._contextPool);
     this._contextPool = null;
+
+    // Tell the live lists we aren't watching any more.
+    if (this._workerList) {
+      this._workerList.onListChanged = null;
+    }
   },
 
   preNest: function() {
     // TODO: freeze windows
     // window mediator doesn't work in child.
     // it doesn't throw, but doesn't return any window
   },
 
   postNest: function() {
   },
 };
 
 ChildProcessActor.prototype.requestTypes = {
+  "listWorkers": ChildProcessActor.prototype.onListWorkers,
 };
--- a/devtools/server/actors/moz.build
+++ b/devtools/server/actors/moz.build
@@ -35,16 +35,17 @@ DevToolsModules(
     'memprof.js',
     'monitor.js',
     'object.js',
     'performance-entries.js',
     'performance-recording.js',
     'performance.js',
     'preference.js',
     'pretty-print-worker.js',
+    'process.js',
     'profiler.js',
     'promises.js',
     'root.js',
     'script.js',
     'settings.js',
     'storage.js',
     'string.js',
     'styleeditor.js',
--- a/devtools/server/actors/object.js
+++ b/devtools/server/actors/object.js
@@ -871,62 +871,26 @@ PropertyIteratorActor.prototype.requestT
  *   - the raw JS object after calling Debugger.Object.unsafeDereference(). This
  *   argument is only provided if the object is safe for reading properties and
  *   executing methods. See DevToolsUtils.isSafeJSObject().
  *
  * Functions must return false if they cannot provide preview
  * information for the debugger object, or true otherwise.
  */
 DebuggerServer.ObjectActorPreviewers = {
-  String: [function({obj, hooks}, grip) {
-    let result = genericObjectPreviewer("String", String, obj, hooks);
-    let length = DevToolsUtils.getProperty(obj, "length");
-
-    if (!result || typeof length != "number") {
-      return false;
-    }
-
-    grip.preview = {
-      kind: "ArrayLike",
-      length: length
-    };
-
-    if (hooks.getGripDepth() > 1) {
-      return true;
-    }
-
-    let items = grip.preview.items = [];
-
-    const max = Math.min(result.value.length, OBJECT_PREVIEW_MAX_ITEMS);
-    for (let i = 0; i < max; i++) {
-      let value = hooks.createValueGrip(result.value[i]);
-      items.push(value);
-    }
-
-    return true;
+  String: [function(objectActor, grip) {
+    return wrappedPrimitivePreviewer("String", String, objectActor, grip);
   }],
 
-  Boolean: [function({obj, hooks}, grip) {
-    let result = genericObjectPreviewer("Boolean", Boolean, obj, hooks);
-    if (result) {
-      grip.preview = result;
-      return true;
-    }
-
-    return false;
+  Boolean: [function(objectActor, grip) {
+    return wrappedPrimitivePreviewer("Boolean", Boolean, objectActor, grip);
   }],
 
-  Number: [function({obj, hooks}, grip) {
-    let result = genericObjectPreviewer("Number", Number, obj, hooks);
-    if (result) {
-      grip.preview = result;
-      return true;
-    }
-
-    return false;
+  Number: [function(objectActor, grip) {
+    return wrappedPrimitivePreviewer("Number", Number, objectActor, grip);
   }],
 
   Function: [function({obj, hooks}, grip) {
     if (obj.name) {
       grip.name = obj.name;
     }
 
     if (obj.displayName) {
@@ -1213,52 +1177,115 @@ DebuggerServer.ObjectActorPreviewers = {
       }
     }
 
     return true;
   }],
 };
 
 /**
- * Generic previewer for "simple" classes like String, Number and Boolean.
+ * Generic previewer for classes wrapping primitives, like String,
+ * Number and Boolean.
  *
  * @param string className
  *        Class name to expect.
  * @param object classObj
  *        The class to expect, eg. String. The valueOf() method of the class is
  *        invoked on the given object.
- * @param Debugger.Object obj
- *        The debugger object we need to preview.
- * @param object hooks
- *        The thread actor to use to create a value grip.
- * @return object|null
- *         An object with one property, "value", which holds the value grip that
- *         represents the given object. Null is returned if we cant preview the
- *         object.
+ * @param ObjectActor objectActor
+ *        The object actor
+ * @param Object grip
+ *        The result grip to fill in
+ * @return Booolean true if the object was handled, false otherwise
  */
-function genericObjectPreviewer(className, classObj, obj, hooks) {
+function wrappedPrimitivePreviewer(className, classObj, objectActor, grip) {
+  let {obj, hooks} = objectActor;
+
   if (!obj.proto || obj.proto.class != className) {
-    return null;
+    return false;
   }
 
   let raw = obj.unsafeDereference();
   let v = null;
   try {
     v = classObj.prototype.valueOf.call(raw);
   } catch (ex) {
     // valueOf() can throw if the raw JS object is "misbehaved".
-    return null;
+    return false;
+  }
+
+  if (v === null) {
+    return false;
+  }
+
+  let canHandle = GenericObject(objectActor, grip, className === "String");
+  if (!canHandle) {
+    return false;
+  }
+
+  grip.preview.wrappedValue =
+    hooks.createValueGrip(makeDebuggeeValueIfNeeded(obj, v));
+  return true;
+}
+
+function GenericObject(objectActor, grip, specialStringBehavior = false) {
+  let {obj, hooks} = objectActor;
+  if (grip.preview || grip.displayString || hooks.getGripDepth() > 1) {
+    return false;
+  }
+
+  let i = 0, names = [];
+  let preview = grip.preview = {
+    kind: "Object",
+    ownProperties: Object.create(null),
+  };
+
+  try {
+    names = obj.getOwnPropertyNames();
+  } catch (ex) {
+    // Calling getOwnPropertyNames() on some wrapped native prototypes is not
+    // allowed: "cannot modify properties of a WrappedNative". See bug 952093.
   }
 
-  if (v !== null) {
-    v = hooks.createValueGrip(makeDebuggeeValueIfNeeded(obj, v));
-    return { value: v };
+  preview.ownPropertiesLength = names.length;
+
+  let length;
+  if (specialStringBehavior) {
+    length = DevToolsUtils.getProperty(obj, "length");
+    if (typeof length != "number") {
+      specialStringBehavior = false;
+    }
   }
 
-  return null;
+  for (let name of names) {
+    if (specialStringBehavior && /^[0-9]+$/.test(name)) {
+      let num = parseInt(name, 10);
+      if (num.toString() === name && num >= 0 && num < length) {
+        continue;
+      }
+    }
+
+    let desc = objectActor._propertyDescriptor(name, true);
+    if (!desc) {
+      continue;
+    }
+
+    preview.ownProperties[name] = desc;
+    if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
+      break;
+    }
+  }
+
+  if (i < OBJECT_PREVIEW_MAX_ITEMS) {
+    preview.safeGetterValues = objectActor._findSafeGetterValues(
+      Object.keys(preview.ownProperties),
+      OBJECT_PREVIEW_MAX_ITEMS - i);
+  }
+
+  return true;
 }
 
 // Preview functions that do not rely on the object class.
 DebuggerServer.ObjectActorPreviewers.Object = [
   function TypedArray({obj, hooks}, grip) {
     if (TYPED_ARRAY_CLASSES.indexOf(obj.class) == -1) {
       return false;
     }
@@ -1617,57 +1644,17 @@ DebuggerServer.ObjectActorPreviewers.Obj
         let value = makeDebuggeeValueIfNeeded(obj, rawObj[key]);
         items.push(hooks.createValueGrip(value));
       }
     }
 
     return true;
   },
 
-  function GenericObject(objectActor, grip) {
-    let {obj, hooks} = objectActor;
-    if (grip.preview || grip.displayString || hooks.getGripDepth() > 1) {
-      return false;
-    }
-
-    let i = 0, names = [];
-    let preview = grip.preview = {
-      kind: "Object",
-      ownProperties: Object.create(null),
-    };
-
-    try {
-      names = obj.getOwnPropertyNames();
-    } catch (ex) {
-      // Calling getOwnPropertyNames() on some wrapped native prototypes is not
-      // allowed: "cannot modify properties of a WrappedNative". See bug 952093.
-    }
-
-    preview.ownPropertiesLength = names.length;
-
-    for (let name of names) {
-      let desc = objectActor._propertyDescriptor(name, true);
-      if (!desc) {
-        continue;
-      }
-
-      preview.ownProperties[name] = desc;
-      if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
-        break;
-      }
-    }
-
-    if (i < OBJECT_PREVIEW_MAX_ITEMS) {
-      preview.safeGetterValues = objectActor._findSafeGetterValues(
-                                    Object.keys(preview.ownProperties),
-                                    OBJECT_PREVIEW_MAX_ITEMS - i);
-    }
-
-    return true;
-  },
+  GenericObject,
 ];
 
 /**
  * Call PromiseDebugging.getState on this Debugger.Object's referent and wrap
  * the resulting `value` or `reason` properties in a Debugger.Object instance.
  *
  * See dom/webidl/PromiseDebugging.webidl
  *
new file mode 100644
--- /dev/null
+++ b/devtools/server/actors/process.js
@@ -0,0 +1,83 @@
+/* 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";
+
+var { Cc, Ci } = require("chrome");
+
+loader.lazyGetter(this, "ppmm", () => {
+  return Cc["@mozilla.org/parentprocessmessagemanager;1"].getService(Ci.nsIMessageBroadcaster);
+});
+
+function ProcessActorList() {
+  this._actors = new Map();
+  this._onListChanged = null;
+  this._mustNotify = false;
+
+  this._onMessage = this._onMessage.bind(this);
+  this._processScript = "data:text/javascript,sendAsyncMessage('debug:new-process');";
+}
+
+ProcessActorList.prototype = {
+  getList: function () {
+    let processes = [];
+    for (let i = 0; i < ppmm.childCount; i++) {
+      processes.push({
+        id: i, // XXX: may not be a perfect id, but process message manager doesn't expose anything...
+        parent: i == 0, // XXX Weak, but appear to be stable
+        tabCount: undefined, // TODO: exposes process message manager on frameloaders in order to compute this
+      });
+    }
+    this._mustNotify = true;
+    this._checkListening();
+
+    return processes;
+  },
+
+  get onListChanged() {
+    return this._onListChanged;
+  },
+
+  set onListChanged(onListChanged) {
+    if (typeof onListChanged !== "function" && onListChanged !== null) {
+      throw new Error("onListChanged must be either a function or null.");
+    }
+    if (onListChanged === this._onListChanged) {
+      return;
+    }
+
+    this._onListChanged = onListChanged;
+    this._checkListening();
+  },
+
+  _checkListening: function () {
+    if (this._onListChanged !== null && this._mustNotify) {
+      this._knownProcesses = [];
+      for (let i = 0; i < ppmm.childCount; i++) {
+        this._knownProcesses.push(ppmm.getChildAt(i));
+      }
+      ppmm.addMessageListener('debug:new-process', this._onMessage);
+      ppmm.loadProcessScript(this._processScript, true);
+    } else {
+      ppmm.removeMessageListener('debug:new-process', this._onMessage);
+      ppmm.removeDelayedProcessScript(this._processScript);
+    }
+  },
+
+  _notifyListChanged: function () {
+    if (this._mustNotify) {
+      this._onListChanged();
+      this._mustNotify = false;
+    }
+  },
+
+  _onMessage: function ({ target }) {
+    if (this._knownProcesses.includes(target)) {
+      return;
+    }
+    this._notifyListChanged();
+  },
+};
+
+exports.ProcessActorList = ProcessActorList;
--- a/devtools/server/actors/root.js
+++ b/devtools/server/actors/root.js
@@ -91,16 +91,17 @@ loader.lazyGetter(this, "ppmm", () => {
  */
 function RootActor(aConnection, aParameters) {
   this.conn = aConnection;
   this._parameters = aParameters;
   this._onTabListChanged = this.onTabListChanged.bind(this);
   this._onAddonListChanged = this.onAddonListChanged.bind(this);
   this._onWorkerListChanged = this.onWorkerListChanged.bind(this);
   this._onServiceWorkerRegistrationListChanged = this.onServiceWorkerRegistrationListChanged.bind(this);
+  this._onProcessListChanged = this.onProcessListChanged.bind(this);
   this._extraActors = {};
 
   this._globalActorPool = new ActorPool(this.conn);
   this.conn.addActorPool(this._globalActorPool);
 
   this._chromeActor = null;
 }
 
@@ -201,16 +202,19 @@ RootActor.prototype = {
   disconnect: function() {
     /* Tell the live lists we aren't watching any more. */
     if (this._parameters.tabList) {
       this._parameters.tabList.onListChanged = null;
     }
     if (this._parameters.addonList) {
       this._parameters.addonList.onListChanged = null;
     }
+    if (this._parameters.workerList) {
+      this._parameters.workerList.onListChanged = null;
+    }
     if (typeof this._parameters.onShutdown === 'function') {
       this._parameters.onShutdown();
     }
     this._extraActors = null;
     this.conn = null;
     this._tabActorPool = null;
     this._globalActorPool = null;
     this._parameters = null;
@@ -414,25 +418,30 @@ RootActor.prototype = {
   },
 
   onServiceWorkerRegistrationListChanged: function () {
     this.conn.send({ from: this.actorID, type: "serviceWorkerRegistrationListChanged" });
     this._parameters.serviceWorkerRegistrationList.onListChanged = null;
   },
 
   onListProcesses: function () {
-    let processes = [];
-    for (let i = 0; i < ppmm.childCount; i++) {
-      processes.push({
-        id: i, // XXX: may not be a perfect id, but process message manager doesn't expose anything...
-        parent: i == 0, // XXX Weak, but appear to be stable
-        tabCount: undefined, // TODO: exposes process message manager on frameloaders in order to compute this
-      });
+    let { processList } = this._parameters;
+    if (!processList) {
+      return { from: this.actorID, error: "noProcesses",
+               message: "This root actor has no processes." };
     }
-    return { processes: processes };
+    processList.onListChanged = this._onProcessListChanged;
+    return {
+      processes: processList.getList()
+    };
+  },
+
+  onProcessListChanged: function () {
+    this.conn.send({ from: this.actorID, type: "processListChanged" });
+    this._parameters.processList.onListChanged = null;
   },
 
   onGetProcess: function (aRequest) {
     if (!DebuggerServer.allowChromeProcess) {
       return { error: "forbidden",
                message: "You are not allowed to debug chrome." };
     }
     if (("id" in aRequest) && typeof(aRequest.id) != "number") {
--- a/devtools/server/actors/styles.js
+++ b/devtools/server/actors/styles.js
@@ -1149,16 +1149,21 @@ var StyleRuleActor = protocol.ActorClass
   // True if this rule supports as-authored styles, meaning that the
   // rule text can be rewritten using setRuleText.
   get canSetRuleText() {
     // Special case about:PreferenceStyleSheet, as it is
     // generated on the fly and the URI is not registered with the
     // about: handler.
     // https://bugzilla.mozilla.org/show_bug.cgi?id=935803#c37
     return !!(this._parentSheet &&
+              // If a rule does not have source, then it has been
+              // modified via CSSOM; and we should fall back to
+              // non-authored editing.
+              // https://bugzilla.mozilla.org/show_bug.cgi?id=1224121
+              this.sheetActor.allRulesHaveSource() &&
               this._parentSheet.href !== "about:PreferenceStyleSheet");
   },
 
   getDocument: function(sheet) {
     let document;
 
     if (sheet.ownerNode instanceof Ci.nsIDOMHTMLDocument) {
       document = sheet.ownerNode;
--- a/devtools/server/actors/stylesheets.js
+++ b/devtools/server/actors/stylesheets.js
@@ -478,16 +478,40 @@ var StyleSheetActor = protocol.ActorClas
     // text and index are unknown until source load
     this.text = null;
     this._styleSheetIndex = -1;
 
     this._transitionRefCount = 0;
   },
 
   /**
+   * Test whether all the rules in this sheet have associated source.
+   * @return {Boolean} true if all the rules have source; false if
+   *         some rule was created via CSSOM.
+   */
+  allRulesHaveSource: function() {
+    let rules;
+    try {
+      rules = this.rawSheet.cssRules;
+    } catch (e) {
+      // sheet isn't loaded yet
+      return true;
+    }
+
+    for (let i = 0; i < rules.length; i++) {
+      let rule = rules[i];
+      if (DOMUtils.getRelativeRuleLine(rule) === 0) {
+        return false;
+      }
+    }
+
+    return true;
+  },
+
+  /**
    * Get the raw stylesheet's cssRules once the sheet has been loaded.
    *
    * @return {Promise}
    *         Promise that resolves with a CSSRuleList
    */
   getCSSRules: function() {
     let rules;
     try {
--- a/devtools/server/actors/webbrowser.js
+++ b/devtools/server/actors/webbrowser.js
@@ -19,16 +19,17 @@ var makeDebugger = require("./utils/make
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 loader.lazyRequireGetter(this, "RootActor", "devtools/server/actors/root", true);
 loader.lazyRequireGetter(this, "ThreadActor", "devtools/server/actors/script", true);
 loader.lazyRequireGetter(this, "unwrapDebuggerObjectGlobal", "devtools/server/actors/script", true);
 loader.lazyRequireGetter(this, "BrowserAddonActor", "devtools/server/actors/addon", true);
 loader.lazyRequireGetter(this, "WorkerActorList", "devtools/server/actors/worker", true);
 loader.lazyRequireGetter(this, "ServiceWorkerRegistrationActorList", "devtools/server/actors/worker", true);
+loader.lazyRequireGetter(this, "ProcessActorList", "devtools/server/actors/process", true);
 loader.lazyImporter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
 
 // Assumptions on events module:
 // events needs to be dispatched synchronously,
 // by calling the listeners in the order or registration.
 loader.lazyRequireGetter(this, "events", "sdk/event/core");
 
 loader.lazyRequireGetter(this, "StyleSheetActor", "devtools/server/actors/stylesheets", true);
@@ -127,16 +128,17 @@ exports.sendShutdownEvent = sendShutdown
 function createRootActor(aConnection)
 {
   return new RootActor(aConnection,
                        {
                          tabList: new BrowserTabList(aConnection),
                          addonList: new BrowserAddonList(aConnection),
                          workerList: new WorkerActorList({}),
                          serviceWorkerRegistrationList: new ServiceWorkerRegistrationActorList(),
+                         processList: new ProcessActorList(),
                          globalActorFactories: DebuggerServer.globalActorFactories,
                          onShutdown: sendShutdownEvent
                        });
 }
 
 /**
  * A live list of BrowserTabActors representing the current browser tabs,
  * to be provided to the root actor to answer 'listTabs' requests.
--- a/devtools/server/actors/webconsole.js
+++ b/devtools/server/actors/webconsole.js
@@ -900,36 +900,43 @@ WebConsoleActor.prototype =
    * @return object
    *         The response message - matched properties.
    */
   onAutocomplete: function WCA_onAutocomplete(aRequest)
   {
     let frameActorId = aRequest.frameActor;
     let dbgObject = null;
     let environment = null;
+    let hadDebuggee = false;
 
     // This is the case of the paused debugger
     if (frameActorId) {
       let frameActor = this.conn.getActor(frameActorId);
       if (frameActor) {
         let frame = frameActor.frame;
         environment = frame.environment;
       }
       else {
         DevToolsUtils.reportException("onAutocomplete",
           Error("The frame actor was not found: " + frameActorId));
       }
     }
     // This is the general case (non-paused debugger)
     else {
-      dbgObject = this.dbg.makeGlobalObjectReference(this.evalWindow);
+      hadDebuggee = this.dbg.hasDebuggee(this.evalWindow);
+      dbgObject = this.dbg.addDebuggee(this.evalWindow);
     }
 
     let result = JSPropertyProvider(dbgObject, environment, aRequest.text,
                                     aRequest.cursor, frameActorId) || {};
+
+    if (!hadDebuggee && dbgObject) {
+      this.dbg.removeDebuggee(this.evalWindow);
+    }
+
     let matches = result.matches || [];
     let reqText = aRequest.text.substr(0, aRequest.cursor);
 
     // We consider '$' as alphanumerc because it is used in the names of some
     // helper functions.
     let lastNonAlphaIsDot = /[.][a-zA-Z0-9$]*$/.test(reqText);
     if (!lastNonAlphaIsDot) {
       if (!this._webConsoleCommandsCache) {
--- a/devtools/server/actors/worker.js
+++ b/devtools/server/actors/worker.js
@@ -188,16 +188,19 @@ WorkerActorList.prototype = {
   get onListChanged() {
     return this._onListChanged;
   },
 
   set onListChanged(onListChanged) {
     if (typeof onListChanged !== "function" && onListChanged !== null) {
       throw new Error("onListChanged must be either a function or null.");
     }
+    if (onListChanged === this._onListChanged) {
+      return;
+    }
 
     if (this._mustNotify) {
       if (this._onListChanged === null && onListChanged !== null) {
         wdm.addListener(this);
       }
       if (this._onListChanged !== null && onListChanged === null) {
         wdm.removeListener(this);
       }
--- a/devtools/server/content-server.jsm
+++ b/devtools/server/content-server.jsm
@@ -8,24 +8,17 @@ const Ci = Components.interfaces;
 const Cc = Components.classes;
 const Cu = Components.utils;
 
 const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
 const { DevToolsLoader } = Cu.import("resource://devtools/shared/Loader.jsm", {});
 
 this.EXPORTED_SYMBOLS = ["init"];
 
-var started = false;
-
 function init(msg) {
-  if (started) {
-    return;
-  }
-  started = true;
-
   // Init a custom, invisible DebuggerServer, in order to not pollute
   // the debugger with all devtools modules, nor break the debugger itself with using it
   // in the same process.
   let devtools = new DevToolsLoader();
   devtools.invisibleToDebugger = true;
   devtools.main("devtools/server/main");
   let { DebuggerServer, ActorPool } = devtools;
 
@@ -56,11 +49,10 @@ function init(msg) {
 
   let response = {actor: actor.form()};
   mm.sendAsyncMessage("debug:content-process-actor", response);
 
   mm.addMessageListener("debug:content-process-destroy", function onDestroy() {
     mm.removeMessageListener("debug:content-process-destroy", onDestroy);
 
     DebuggerServer.destroy();
-    started = false;
   });
 }
--- a/devtools/server/tests/mochitest/test_getProcess.html
+++ b/devtools/server/tests/mochitest/test_getProcess.html
@@ -23,76 +23,98 @@ let {DebuggerServer} = require("devtools
 
 window.onload = function() {
   SimpleTest.waitForExplicitFinish();
 
   SpecialPowers.pushPrefEnv({
     "set": [
       // Always log packets when running tests.
       ["devtools.debugger.log", true],
-      ["dom.mozBrowserFramesEnabled", true]
+      // Enabled mozbrowser frame to support remote=true
+      ["dom.mozBrowserFramesEnabled", true],
+      // Allows creating a branch new process when creation the iframe
+      ["dom.ipc.processCount", 10],
     ]
   }, runTests);
 }
 
 function runTests() {
-  // Create a remote iframe with a message manager
-  let iframe = document.createElement("iframe");
-  iframe.mozbrowser = true;
-  iframe.setAttribute("remote", "true");
-  iframe.setAttribute("src", "data:text/html,foo");
-  document.body.appendChild(iframe);
-
-  let mm = iframe.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager;
-
   // Instantiate a minimal server
   if (!DebuggerServer.initialized) {
     DebuggerServer.init();
   }
   DebuggerServer.allowChromeProcess = true;
   if (!DebuggerServer.createRootActor) {
     DebuggerServer.addBrowserActors();
   }
 
-  function firstClient() {
+  let client, iframe, processCount;
+
+  function connect() {
     // Fake a first connection to the content process
     let transport = DebuggerServer.connectPipe();
-    let client = new DebuggerClient(transport);
-    client.connect(() => {
+    client = new DebuggerClient(transport);
+    client.connect(listProcess);
+  }
+
+  function listProcess() {
+    // Call listProcesses in order to start receiving new process notifications
+    client.addListener("processListChanged", function listener() {
+      client.removeListener("processListChanged", listener);
+      ok(true, "Received processListChanged event");
+      getProcess();
+    });
+    client.mainRoot.listProcesses(response => {
+      processCount = response.processes.length;
+      // Create a remote iframe to spawn a new process
+      createRemoteIframe();
+    });
+  }
+
+  function createRemoteIframe() {
+    iframe = document.createElement("iframe");
+    iframe.mozbrowser = true;
+    iframe.setAttribute("remote", "true");
+    iframe.setAttribute("src", "data:text/html,foo");
+    document.body.appendChild(iframe);
+  }
+
+  function getProcess() {
     client.mainRoot.listProcesses(response => {
       ok(response.processes.length >= 2, "Got at least the parent process and one child");
+      is(response.processes.length, processCount+1 , "Got one additional process on the second call to listProcesses");
 
       // Connect to the first content processe available
       let content = response.processes.filter(p => (!p.parent))[0];
 
       client.getProcess(content.id).then(response => {
         let actor = response.form;
         ok(actor.consoleActor, "Got the console actor");
         ok(actor.chromeDebugger, "Got the thread actor");
 
         // Ensure sending at least one request to an actor...
         client.request({
           to: actor.consoleActor,
           type: "evaluateJS",
           text: "var a = 42; a"
         }, function (response) {
           ok(response.result, 42, "console.eval worked");
-
-          client.close(cleanup);
+          cleanup();
         });
       });
     });
-    });
   }
 
   function cleanup() {
-    DebuggerServer.destroy();
-    iframe.remove();
-    SimpleTest.finish()
+    client.close(function () {
+      DebuggerServer.destroy();
+      iframe.remove();
+      SimpleTest.finish()
+    });
   }
 
-  firstClient();
+  connect();
 }
 
 </script>
 </pre>
 </body>
 </html>
--- a/devtools/shared/client/main.js
+++ b/devtools/shared/client/main.js
@@ -238,16 +238,19 @@ const DebuggerClient = exports.DebuggerC
  *        The function to call before sending the packet. Is passed the packet,
  *        and the return value is used as the new packet. The |this| context is
  *        the instance of the client object we are defining a method for.
  * @param after
  *        The function to call after the response is received. It is passed the
  *        response, and the return value is considered the new response that
  *        will be passed to the callback. The |this| context is the instance of
  *        the client object we are defining a method for.
+ * @return Request
+ *         The `Request` object that is a Promise object and resolves once
+ *         we receive the response. (See request method for more details)
  */
 DebuggerClient.requester = function (aPacketSkeleton,
                                      { telemetry, before, after }) {
   return DevToolsUtils.makeInfallible(function (...args) {
     let histogram, startTime;
     if (telemetry) {
       let transportType = this._transport.onOutputStreamReady === undefined
         ? "LOCAL_"
@@ -271,17 +274,17 @@ DebuggerClient.requester = function (aPa
         outgoingPacket[k] = aPacketSkeleton[k];
       }
     }
 
     if (before) {
       outgoingPacket = before.call(this, outgoingPacket);
     }
 
-    this.request(outgoingPacket, DevToolsUtils.makeInfallible((aResponse) => {
+    return this.request(outgoingPacket, DevToolsUtils.makeInfallible((aResponse) => {
       if (after) {
         let { from } = aResponse;
         aResponse = after.call(this, aResponse);
         if (!aResponse.from) {
           aResponse.from = from;
         }
       }
 
new file mode 100644
--- /dev/null
+++ b/devtools/shared/heapsnapshot/DominatorTreeNode.js
@@ -0,0 +1,100 @@
+/* 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 DEFAULT_MAX_DEPTH = 3;
+const DEFAULT_MAX_SIBLINGS = 15;
+
+/**
+ * A single node in a dominator tree.
+ *
+ * @param {NodeId} nodeId
+ * @param {NodeSize} retainedSize
+ */
+function DominatorTreeNode(nodeId, retainedSize) {
+  // The id of this node.
+  this.nodeId = nodeId;
+
+  // The retained size of this node.
+  this.retainedSize = retainedSize;
+
+  // The id of this node's parent or undefined if this node is the root.
+  this.parentId = undefined;
+
+  // An array of immediately dominated child `DominatorTreeNode`s, or undefined.
+  this.children = undefined;
+
+  // True iff the `children` property does not contain every immediately
+  // dominated node.
+  //
+  // * If children is an array and this property is true: the array does not
+  //   contain the complete set of immediately dominated children.
+  // * If children is an array and this property is false: the array contains
+  //   the complete set of immediately dominated children.
+  // * If children is undefined and this property is true: there exist
+  //   immediately dominated children for this node, but they have not been
+  //   loaded yet.
+  // * If children is undefined and this property is false: this node does not
+  //   dominate any others and therefore has no children.
+  this.moreChildrenAvailable = true;
+}
+
+DominatorTreeNode.prototype = null;
+
+module.exports = DominatorTreeNode;
+
+/**
+ * Add `child` to the `parent`'s set of children.
+ *
+ * @param {DominatorTreeNode} parent
+ * @param {DominatorTreeNode} child
+ */
+DominatorTreeNode.addChild = function (parent, child) {
+  if (parent.children === undefined) {
+    parent.children = [];
+  }
+
+  parent.children.push(child);
+  child.parentId = parent.nodeId;
+};
+
+/**
+ * Do a partial traversal of the given dominator tree and convert it into a tree
+ * of `DominatorTreeNode`s. Dominator trees have a node for every node in the
+ * snapshot's heap graph, so we must not allocate a JS object for every node. It
+ * would be way too many and the node count is effectively unbounded.
+ *
+ * Go no deeper down the tree than `maxDepth` and only consider at most
+ * `maxSiblings` within any single node's children.
+ *
+ * @param {DominatorTree} dominatorTree
+ * @param {Number} maxDepth
+ * @param {Number} maxSiblings
+ *
+ * @returns {DominatorTreeNode}
+ */
+DominatorTreeNode.partialTraversal = function (dominatorTree,
+                                               maxDepth = DEFAULT_MAX_DEPTH,
+                                               maxSiblings = DEFAULT_MAX_SIBLINGS) {
+  function dfs(nodeId, depth) {
+    const size = dominatorTree.getRetainedSize(nodeId);
+    const node = new DominatorTreeNode(nodeId, size);
+    const childNodeIds = dominatorTree.getImmediatelyDominated(nodeId);
+
+    const newDepth = depth + 1;
+    if (newDepth < maxDepth) {
+      const endIdx = Math.min(childNodeIds.length, maxSiblings);
+      for (let i = 0; i < endIdx; i++) {
+        DominatorTreeNode.addChild(node, dfs(childNodeIds[i], newDepth));
+      }
+      node.moreChildrenAvailable = childNodeIds.length < endIdx;
+    } else {
+      node.moreChildrenAvailable = childNodeIds.length > 0;
+    }
+
+    return node;
+  }
+
+  return dfs(dominatorTree.root, 0);
+};
--- a/devtools/shared/heapsnapshot/HeapAnalysesClient.js
+++ b/devtools/shared/heapsnapshot/HeapAnalysesClient.js
@@ -47,16 +47,32 @@ HeapAnalysesClient.prototype.destroy = f
  *          does not happen, eg due to a bad file path or malformed heap
  *          snapshot file.
  */
 HeapAnalysesClient.prototype.readHeapSnapshot = function (snapshotFilePath) {
   return this._worker.performTask("readHeapSnapshot", { snapshotFilePath });
 };
 
 /**
+ * Request the creation time given a snapshot file path. Returns `null`
+ * if snapshot does not exist.
+ *
+ * @param {String} snapshotFilePath
+ *        The path to the snapshot.
+ * @return {Number?}
+ *        The unix timestamp of the creation time of the snapshot, or null if
+ *        snapshot does not exist.
+ */
+HeapAnalysesClient.prototype.getCreationTime = function (snapshotFilePath) {
+  return this._worker.performTask("getCreationTime", snapshotFilePath);
+};
+
+/*** Censuses *****************************************************************/
+
+/**
  * Ask the worker to perform a census analysis on the heap snapshot with the
  * given path. The heap snapshot at the given path must have already been read
  * into memory by the worker (see `readHeapSnapshot`).
  *
  * @param {String} snapshotFilePath
  *
  * @param {Object} censusOptions
  *        A structured-cloneable object specifying the requested census's
@@ -131,21 +147,70 @@ HeapAnalysesClient.prototype.takeCensusD
   return this._worker.performTask("takeCensusDiff", {
     firstSnapshotFilePath,
     secondSnapshotFilePath,
     censusOptions,
     requestOptions
   });
 };
 
+/*** Dominator Trees **********************************************************/
+
 /**
- * Request the creation time given a snapshot file path. Returns `null`
- * if snapshot does not exist.
+ * Compute the dominator tree of the heap snapshot loaded from the given file
+ * path. Returns the id of the computed dominator tree.
  *
  * @param {String} snapshotFilePath
- *        The path to the snapshot.
- * @return {Number?}
- *        The unix timestamp of the creation time of the snapshot, or null if
- *        snapshot does not exist.
+ *
+ * @returns {Promise<DominatorTreeId>}
+ */
+HeapAnalysesClient.prototype.computeDominatorTree = function (snapshotFilePath) {
+  return this._worker.performTask("computeDominatorTree", snapshotFilePath);
+};
+
+/**
+ * Get the initial, partial view of the dominator tree with the given id.
+ *
+ * @param {Object} opts
+ *        An object specifying options for this request.
+ *        - {DominatorTreeId} dominatorTreeId
+ *          The id of the dominator tree.
+ *        - {Number} maxDepth
+ *          The maximum depth to traverse down the tree to create this initial
+ *          view.
+ *        - {Number} maxSiblings
+ *          The maximum number of siblings to visit within each traversed node's
+ *          children.
+ *
+ * @returns {Promise<DominatorTreeNode>}
  */
-HeapAnalysesClient.prototype.getCreationTime = function (snapshotFilePath) {
-  return this._worker.performTask("getCreationTime", snapshotFilePath);
+HeapAnalysesClient.prototype.getDominatorTree = function (opts) {
+  return this._worker.performTask("getDominatorTree", opts);
 };
+
+/**
+ * Get a subset of a nodes children in the dominator tree.
+ *
+ * @param {Object} opts
+ *        An object specifying options for this request.
+ *        - {DominatorTreeId} dominatorTreeId
+ *          The id of the dominator tree.
+ *        - {NodeId} nodeId
+ *          The id of the node whose children are being found.
+ *        - {Number} startIndex
+ *          The starting index within the full set of immediately dominated
+ *          children of the children being requested. Children are always sorted
+ *          by greatest to least retained size.
+ *        - {Number} maxCount
+ *          The maximum number of children to return.
+ *
+ * @returns {Promise<Object>}
+ *          A promise of an object with the following properties:
+ *          - {Array<DominatorTreeNode>} nodes
+ *            The requested nodes that are immediately dominated by the node
+ *            identified by `opts.nodeId`.
+ *          - {Boolean} moreChildrenAvailable
+ *            True iff there are more children available after the returned
+ *            nodes.
+ */
+HeapAnalysesClient.prototype.getImmediatelyDominated = function (opts) {
+  return this._worker.performTask("getImmediatelyDominated", opts);
+};
--- a/devtools/shared/heapsnapshot/HeapAnalysesWorker.js
+++ b/devtools/shared/heapsnapshot/HeapAnalysesWorker.js
@@ -8,18 +8,22 @@
 // HeapAnalysesWorker is owned and communicated with by a HeapAnalysesClient
 // instance. See HeapAnalysesClient.js.
 
 "use strict";
 
 importScripts("resource://gre/modules/workers/require.js");
 importScripts("resource://devtools/shared/worker/helper.js");
 const { censusReportToCensusTreeNode } = require("resource://devtools/shared/heapsnapshot/census-tree-node.js");
+const DominatorTreeNode = require("resource://devtools/shared/heapsnapshot/DominatorTreeNode.js");
 const CensusUtils = require("resource://devtools/shared/heapsnapshot/CensusUtils.js");
 
+const DEFAULT_START_INDEX = 0;
+const DEFAULT_MAX_COUNT = 50;
+
 // The set of HeapSnapshot instances this worker has read into memory. Keyed by
 // snapshot file path.
 const snapshots = Object.create(null);
 
 /**
  * @see HeapAnalysesClient.prototype.readHeapSnapshot
  */
 workerHelper.createTask(self, "readHeapSnapshot", ({ snapshotFilePath }) => {
@@ -81,12 +85,97 @@ workerHelper.createTask(self, "takeCensu
   }
 
   return delta;
 });
 
 /**
  * @see HeapAnalysesClient.prototype.getCreationTime
  */
-workerHelper.createTask(self, "getCreationTime", (snapshotFilePath) => {
+workerHelper.createTask(self, "getCreationTime", snapshotFilePath => {
   let snapshot = snapshots[snapshotFilePath];
   return snapshot ? snapshot.creationTime : null;
 });
+
+/**
+ * The set of `DominatorTree`s that have been computed, mapped by their id (aka
+ * the index into this array).
+ *
+ * @see /dom/webidl/DominatorTree.webidl
+ */
+const dominatorTrees = [];
+
+/**
+ * @see HeapAnalysesClient.prototype.computeDominatorTree
+ */
+workerHelper.createTask(self, "computeDominatorTree", snapshotFilePath => {
+  const snapshot = snapshots[snapshotFilePath];
+  if (!snapshot) {
+    throw new Error(`No known heap snapshot for '${snapshotFilePath}'`);
+  }
+
+  const id = dominatorTrees.length;
+  dominatorTrees.push(snapshot.computeDominatorTree());
+  return id;
+});
+
+/**
+ * @see HeapAnalysesClient.prototype.getDominatorTree
+ */
+workerHelper.createTask(self, "getDominatorTree", request => {
+  const {
+    dominatorTreeId,
+    maxDepth,
+    maxSiblings
+  } = request;
+
+  if (!(0 <= dominatorTreeId && dominatorTreeId < dominatorTrees.length)) {
+    throw new Error(
+      `There does not exist a DominatorTree with the id ${dominatorTreeId}`);
+  }
+
+  return DominatorTreeNode.partialTraversal(dominatorTrees[dominatorTreeId],
+                                            maxDepth,
+                                            maxSiblings);
+});
+
+/**
+ * @see HeapAnalysesClient.prototype.getImmediatelyDominated
+ */
+workerHelper.createTask(self, "getImmediatelyDominated", request => {
+  const {
+    dominatorTreeId,
+    nodeId,
+    startIndex,
+    maxCount
+  } = request;
+
+  if (!(0 <= dominatorTreeId && dominatorTreeId < dominatorTrees.length)) {
+    throw new Error(
+      `There does not exist a DominatorTree with the id ${dominatorTreeId}`);
+  }
+
+  const dominatorTree = dominatorTrees[dominatorTreeId];
+  const childIds = dominatorTree.getImmediatelyDominated(nodeId);
+  if (!childIds) {
+    throw new Error(`${nodeId} is not a node id in the dominator tree`);
+  }
+
+  const start = startIndex || DEFAULT_START_INDEX;
+  const count = maxCount || DEFAULT_MAX_COUNT;
+  const end = start + count;
+
+  const nodes = childIds
+    .slice(start, end)
+    .map(id => {
+      const size = dominatorTree.getRetainedSize(id);
+      const node = new DominatorTreeNode(id, size);
+      node.parentId = nodeId;
+      // DominatorTree.getImmediatelyDominated will always return non-null here
+      // because we got the id directly from the dominator tree.
+      node.moreChildrenAvailable = dominatorTree.getImmediatelyDominated(id).length > 0;
+      return node;
+    });
+
+  const moreChildrenAvailable = childIds.length > end;
+
+  return { nodes, moreChildrenAvailable };
+});
--- a/devtools/shared/heapsnapshot/moz.build
+++ b/devtools/shared/heapsnapshot/moz.build
@@ -43,12 +43,13 @@ SOURCES += [
 # Disable RTTI in google protocol buffer
 DEFINES['GOOGLE_PROTOBUF_NO_RTTI'] = True
 
 FINAL_LIBRARY = 'xul'
 
 DevToolsModules(
     'census-tree-node.js',
     'CensusUtils.js',
+    'DominatorTreeNode.js',
     'HeapAnalysesClient.js',
     'HeapAnalysesWorker.js',
     'HeapSnapshotFileUtils.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_computeDominatorTree_01.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test the HeapAnalyses{Client,Worker} "computeDominatorTree" request.
+
+function run_test() {
+  run_next_test();
+}
+
+add_task(function* () {
+  const client = new HeapAnalysesClient();
+
+  const snapshotFilePath = saveNewHeapSnapshot();
+  yield client.readHeapSnapshot(snapshotFilePath);
+  ok(true, "Should have read the heap snapshot");
+
+  const dominatorTreeId = yield client.computeDominatorTree(snapshotFilePath);
+  equal(typeof dominatorTreeId, "number",
+        "should get a dominator tree id, and it should be a number");
+
+  client.destroy();
+});
new file mode 100644
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_computeDominatorTree_02.js
@@ -0,0 +1,23 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test the HeapAnalyses{Client,Worker} "computeDominatorTree" request with bad
+// file paths.
+
+function run_test() {
+  run_next_test();
+}
+
+add_task(function* () {
+  const client = new HeapAnalysesClient();
+
+  let threw = false;
+  try {
+    yield client.computeDominatorTree("/etc/passwd");
+  } catch (_) {
+    threw = true;
+  }
+  ok(threw, "should throw when given a bad path");
+
+  client.destroy();
+});
new file mode 100644
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_getDominatorTree_01.js
@@ -0,0 +1,51 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test the HeapAnalyses{Client,Worker} "getDominatorTree" request.
+
+function run_test() {
+  run_next_test();
+}
+
+add_task(function* () {
+  const client = new HeapAnalysesClient();
+
+  const snapshotFilePath = saveNewHeapSnapshot();
+  yield client.readHeapSnapshot(snapshotFilePath);
+  ok(true, "Should have read the heap snapshot");
+
+  const dominatorTreeId = yield client.computeDominatorTree(snapshotFilePath);
+  equal(typeof dominatorTreeId, "number",
+        "should get a dominator tree id, and it should be a number");
+
+  const partialTree = yield client.getDominatorTree({ dominatorTreeId });
+  ok(partialTree, "Should get a partial tree");
+  equal(typeof partialTree, "object",
+        "partialTree should be an object");
+
+  function checkTree(node) {
+    equal(typeof node.nodeId, "number", "each node should have an id");
+
+    if (node === partialTree) {
+      equal(node.parentId, undefined, "the root has no parent");
+    } else {
+      equal(typeof node.parentId, "number", "each node should have a parent id");
+    }
+
+    equal(typeof node.retainedSize, "number",
+          "each node should have a retained size");
+
+    ok(node.children === undefined || Array.isArray(node.children),
+       "each node either has a list of children, or undefined meaning no children loaded");
+    equal(typeof node.moreChildrenAvailable, "boolean",
+          "each node should indicate if there are more children available or not");
+
+    if (node.children) {
+      node.children.forEach(checkTree);
+    }
+  }
+
+  checkTree(partialTree);
+
+  client.destroy();
+});
new file mode 100644
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_getDominatorTree_02.js
@@ -0,0 +1,23 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test the HeapAnalyses{Client,Worker} "getDominatorTree" request with bad
+// dominator tree ids.
+
+function run_test() {
+  run_next_test();
+}
+
+add_task(function* () {
+  const client = new HeapAnalysesClient();
+
+  let threw = false;
+  try {
+    yield client.getDominatorTree({ dominatorTreeId: 42 });
+  } catch (_) {
+    threw = true;
+  }
+  ok(threw, "should throw when given a bad id");
+
+  client.destroy();
+});
new file mode 100644
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_getImmediatelyDominated_01.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test the HeapAnalyses{Client,Worker} "getImmediatelyDominated" request.
+
+function run_test() {
+  run_next_test();
+}
+
+add_task(function* () {
+  const client = new HeapAnalysesClient();
+
+  const snapshotFilePath = saveNewHeapSnapshot();
+  yield client.readHeapSnapshot(snapshotFilePath);
+  const dominatorTreeId = yield client.computeDominatorTree(snapshotFilePath);
+
+  const partialTree = yield client.getDominatorTree({ dominatorTreeId });
+  ok(partialTree.children.length > 0,
+     "root should immediately dominate some nodes");
+
+  // First, test getting a subset of children available.
+  const response = yield client.getImmediatelyDominated({
+    dominatorTreeId,
+    nodeId: partialTree.nodeId,
+    startIndex: 0,
+    maxCount: partialTree.children.length - 1
+  });
+
+  ok(Array.isArray(response.nodes));
+  ok(response.nodes.every(node => node.parentId === partialTree.nodeId));
+  ok(response.moreChildrenAvailable);
+
+  // Next, test getting a subset of children available.
+  const secondResponse = yield client.getImmediatelyDominated({
+    dominatorTreeId,
+    nodeId: partialTree.nodeId,
+    startIndex: 0,
+    maxCount: Infinity
+  });
+
+  ok(Array.isArray(secondResponse.nodes));
+  ok(secondResponse.nodes.every(node => node.parentId === partialTree.nodeId));
+  ok(!secondResponse.moreChildrenAvailable);
+
+  client.destroy();
+});
--- a/devtools/shared/heapsnapshot/tests/unit/xpcshell.ini
+++ b/devtools/shared/heapsnapshot/tests/unit/xpcshell.ini
@@ -28,17 +28,22 @@ support-files =
 [test_census-tree-node-06.js]
 [test_census-tree-node-07.js]
 [test_census-tree-node-08.js]
 [test_DominatorTree_01.js]
 [test_DominatorTree_02.js]
 [test_DominatorTree_03.js]
 [test_DominatorTree_04.js]
 [test_DominatorTree_05.js]
+[test_HeapAnalyses_computeDominatorTree_01.js]
+[test_HeapAnalyses_computeDominatorTree_02.js]
 [test_HeapAnalyses_getCreationTime_01.js]
+[test_HeapAnalyses_getDominatorTree_01.js]
+[test_HeapAnalyses_getDominatorTree_02.js]
+[test_HeapAnalyses_getImmediatelyDominated_01.js]
 [test_HeapAnalyses_readHeapSnapshot_01.js]
 [test_HeapAnalyses_takeCensusDiff_01.js]
 [test_HeapAnalyses_takeCensusDiff_02.js]
 [test_HeapAnalyses_takeCensus_01.js]
 [test_HeapAnalyses_takeCensus_02.js]
 [test_HeapAnalyses_takeCensus_03.js]
 [test_HeapAnalyses_takeCensus_04.js]
 [test_HeapAnalyses_takeCensus_05.js]
--- a/devtools/shared/webconsole/js-property-provider.js
+++ b/devtools/shared/webconsole/js-property-provider.js
@@ -27,16 +27,20 @@ const STATE_DQUOTE = 3;
 const OPEN_BODY = "{[(".split("");
 const CLOSE_BODY = "}])".split("");
 const OPEN_CLOSE_BODY = {
   "{": "}",
   "[": "]",
   "(": ")",
 };
 
+function hasArrayIndex(str) {
+  return /\[\d+\]$/.test(str);
+}
+
 /**
  * Analyses a given string to find the last statement that is interesting for
  * later completion.
  *
  * @param   string aStr
  *          A string to analyse.
  *
  * @returns object
@@ -218,91 +222,98 @@ function JSPropertyProvider(aDbgObject, 
     }
   }
 
   // We are completing a variable / a property lookup.
   let properties = completionPart.split(".");
   let matchProp = properties.pop().trimLeft();
   let obj = aDbgObject;
 
-  // The first property must be found in the environment if the debugger is
-  // paused.
-  if (anEnvironment) {
-    if (properties.length == 0) {
-      return getMatchedPropsInEnvironment(anEnvironment, matchProp);
-    }
-    obj = getVariableInEnvironment(anEnvironment, properties.shift());
+  // The first property must be found in the environment of the paused debugger
+  // or of the global lexical scope.
+  let env = anEnvironment || obj.asEnvironment();
+
+  if (properties.length === 0) {
+    return getMatchedPropsInEnvironment(env, matchProp);
+  }
+
+  let firstProp = properties.shift().trim();
+  if (firstProp === "this") {
+    // Special case for 'this' - try to get the Object from the Environment.
+    // No problem if it throws, we will just not autocomplete.
+    try {
+      obj = env.object;
+    } catch(e) { }
+  }
+  else if (hasArrayIndex(firstProp)) {
+    obj = getArrayMemberProperty(null, env, firstProp);
+  } else {
+    obj = getVariableInEnvironment(env, firstProp);
   }
 
   if (!isObjectUsable(obj)) {
     return null;
   }
 
   // We get the rest of the properties recursively starting from the Debugger.Object
   // that wraps the first property
   for (let i = 0; i < properties.length; i++) {
     let prop = properties[i].trim();
     if (!prop) {
       return null;
     }
 
-    // Special case for 'this' since it's not part of the global's properties
-    // but we want autocompletion to work properly for it
-    if (prop === "this" && obj === aDbgObject && i === 0) {
-      continue;
-    }
-
-    if (/\[\d+\]$/.test(prop)) {
+    if (hasArrayIndex(prop)) {
       // The property to autocomplete is a member of array. For example
       // list[i][j]..[n]. Traverse the array to get the actual element.
-      obj = getArrayMemberProperty(obj, prop);
+      obj = getArrayMemberProperty(obj, null, prop);
     }
     else {
       obj = DevToolsUtils.getProperty(obj, prop);
     }
 
     if (!isObjectUsable(obj)) {
       return null;
     }
   }
 
   // If the final property is a primitive
   if (typeof obj != "object") {
     return getMatchedProps(obj, matchProp);
   }
 
-  let matchedProps = getMatchedPropsInDbgObject(obj, matchProp);
-  if (properties.length !== 0 || obj !== aDbgObject) {
-    let thisInd = matchedProps.matches.indexOf("this");
-    if (thisInd > -1) {
-      matchedProps.matches.splice(thisInd, 1)
-    }
-  }
-
-  return matchedProps;
+  return getMatchedPropsInDbgObject(obj, matchProp);
 }
 
 /**
  * Get the array member of aObj for the given aProp. For example, given
  * aProp='list[0][1]' the element at [0][1] of aObj.list is returned.
  *
  * @param object aObj
- *        The object to operate on.
+ *        The object to operate on. Should be null if aEnv is passed.
+ * @param object aEnv
+ *        The Environment to operate in. Should be null if aObj is passed.
  * @param string aProp
  *        The property to return.
  * @return null or Object
  *         Returns null if the property couldn't be located. Otherwise the array
  *         member identified by aProp.
  */
-function getArrayMemberProperty(aObj, aProp)
+function getArrayMemberProperty(aObj, aEnv, aProp)
 {
   // First get the array.
   let obj = aObj;
   let propWithoutIndices = aProp.substr(0, aProp.indexOf("["));
-  obj = DevToolsUtils.getProperty(obj, propWithoutIndices);
+
+  if (aEnv) {
+    obj = getVariableInEnvironment(aEnv, propWithoutIndices);
+  } else {
+    obj = DevToolsUtils.getProperty(obj, propWithoutIndices);
+  }
+
   if (!isObjectUsable(obj)) {
     return null;
   }
 
   // Then traverse the list of indices to get the actual element.
   let result;
   let arrayIndicesRegex = /\[[^\]]*\]/g;
   while ((result = arrayIndicesRegex.exec(aProp)) !== null) {
@@ -491,27 +502,17 @@ var DebuggerObjectSupport = {
     while (aObj) {
       yield aObj;
       aObj = aObj.proto;
     }
   },
 
   getProperties: function(aObj)
   {
-    let names = aObj.getOwnPropertyNames();
-    // Include 'this' in results (in sorted order).  It will be removed
-    // in all cases except for the first property request on the global, but
-    // it needs to be added now so it can be filtered based on string input.
-    for (let i = 0; i < names.length; i++) {
-      if (i === names.length - 1 || names[i+1] > "this") {
-        names.splice(i+1, 0, "this");
-        break;
-      }
-    }
-    return names;
+    return aObj.getOwnPropertyNames();
   },
 
   getProperty: function(aObj, aName, aRootObj)
   {
     // This is left unimplemented in favor to DevToolsUtils.getProperty().
     throw "Unimplemented!";
   },
 };
@@ -522,26 +523,44 @@ var DebuggerEnvironmentSupport = {
     while (aObj) {
       yield aObj;
       aObj = aObj.parent;
     }
   },
 
   getProperties: function(aObj)
   {
-    return aObj.names();
+    let names = aObj.names();
+
+    // Include 'this' in results (in sorted order)
+    for (let i = 0; i < names.length; i++) {
+      if (i === names.length - 1 || names[i+1] > "this") {
+        names.splice(i+1, 0, "this");
+        break;
+      }
+    }
+
+    return names;
   },
 
   getProperty: function(aObj, aName)
   {
-    // TODO: we should use getVariableDescriptor() here - bug 725815.
-    let result = aObj.getVariable(aName);
+    let result;
+    // Try/catch since aName can be anything, and getVariable throws if
+    // it's not a valid ECMAScript identifier name
+    try {
+      // TODO: we should use getVariableDescriptor() here - bug 725815.
+      result = aObj.getVariable(aName);
+    } catch(e) { }
+
     // FIXME: Need actual UI, bug 941287.
     if (result === undefined || result.optimizedOut || result.missingArguments) {
       return null;
     }
     return { value: result };
   },
 };
 
 
 exports.JSPropertyProvider = DevToolsUtils.makeInfallible(JSPropertyProvider);
 
+// Export a version that will throw (for tests)
+exports.FallibleJSPropertyProvider = JSPropertyProvider;
--- a/devtools/shared/webconsole/test/unit/test_js_property_provider.js
+++ b/devtools/shared/webconsole/test/unit/test_js_property_provider.js
@@ -1,122 +1,154 @@
 /* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
 // Any copyright is dedicated to the Public Domain.
 // http://creativecommons.org/publicdomain/zero/1.0/
 
 "use strict";
 const { require } = Components.utils.import("resource://devtools/shared/Loader.jsm", {});
-const { JSPropertyProvider } = require("devtools/shared/webconsole/js-property-provider");
+const { FallibleJSPropertyProvider: JSPropertyProvider } =
+  require("devtools/shared/webconsole/js-property-provider");
 
 Components.utils.import("resource://gre/modules/jsdebugger.jsm");
 addDebuggerToGlobal(this);
 
 function run_test() {
-  const testArray = 'var testArray = [\
-    {propA: "A"},\
-    {\
-      propB: "B", \
-      propC: [\
-        {propD: "D"}\
-      ]\
-    },\
-    [\
-      {propE: "E"}\
-    ]\
-  ];'
+  const testArray = `var testArray = [
+    {propA: "A"},
+    {
+      propB: "B",
+      propC: [
+        "D"
+      ]
+    },
+    [
+      {propE: "E"}
+    ]
+  ]`;
 
   const testObject = 'var testObject = {"propA": [{"propB": "B"}]}';
   const testHyphenated = 'var testHyphenated = {"prop-A": "res-A"}';
+  const testLet = "let foobar = {a: ''}; const blargh = {a: 1};";
 
   let sandbox = Components.utils.Sandbox("http://example.com");
   let dbg = new Debugger;
   let dbgObject = dbg.addDebuggee(sandbox);
+  let dbgEnv = dbgObject.asEnvironment();
   Components.utils.evalInSandbox(testArray, sandbox);
   Components.utils.evalInSandbox(testObject, sandbox);
   Components.utils.evalInSandbox(testHyphenated, sandbox);
+  Components.utils.evalInSandbox(testLet, sandbox);
+
+  do_print("Running tests with dbgObject");
+  runChecks(dbgObject, null);
+
+  do_print("Running tests with dbgEnv");
+  runChecks(null, dbgEnv);
+
+}
+
+function runChecks(dbgObject, dbgEnv) {
+  do_print("Test that suggestions are given for 'this'");
+  let results = JSPropertyProvider(dbgObject, dbgEnv, "t");
+  test_has_result(results, "this");
+
+  if (dbgObject != null) {
+    do_print("Test that suggestions are given for 'this.'");
+    results = JSPropertyProvider(dbgObject, dbgEnv, "this.");
+    test_has_result(results, "testObject");
+
+    do_print("Test that no suggestions are given for 'this.this'");
+    results = JSPropertyProvider(dbgObject, dbgEnv, "this.this");
+    test_has_no_results(results);
+  }
+
+  do_print("Testing lexical scope issues (Bug 1207868)");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "foobar");
+  test_has_result(results, "foobar");
+
+  results = JSPropertyProvider(dbgObject, dbgEnv, "foobar.");
+  test_has_result(results, "a");
+
+  results = JSPropertyProvider(dbgObject, dbgEnv, "blargh");
+  test_has_result(results, "blargh");
+
+  results = JSPropertyProvider(dbgObject, dbgEnv, "blargh.");
+  test_has_result(results, "a");
 
   do_print("Test that suggestions are given for 'foo[n]' where n is an integer.");
-  let results = JSPropertyProvider(dbgObject, null, "testArray[0].");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "testArray[0].");
   test_has_result(results, "propA");
 
   do_print("Test that suggestions are given for multidimensional arrays.");
-  results = JSPropertyProvider(dbgObject, null, "testArray[2][0].");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "testArray[2][0].");
   test_has_result(results, "propE");
 
+  do_print("Test that suggestions are given for nested arrays.");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "testArray[1].propC[0].");
+  test_has_result(results, "indexOf");
+
   do_print("Test that suggestions are given for literal arrays.");
-  results = JSPropertyProvider(dbgObject, null, "[1,2,3].");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "[1,2,3].");
   test_has_result(results, "indexOf");
 
   do_print("Test that suggestions are given for literal arrays with newlines.");
-  results = JSPropertyProvider(dbgObject, null, "[1,2,3,\n4\n].");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "[1,2,3,\n4\n].");
   test_has_result(results, "indexOf");
 
-  do_print("Test that suggestions are given for 'this'");
-  results = JSPropertyProvider(dbgObject, null, "t");
-  test_has_result(results, "this");
-
-  do_print("Test that suggestions are given for 'this.'");
-  results = JSPropertyProvider(dbgObject, null, "this.");
-  test_has_result(results, "testObject");
-
-  do_print("Test that no suggestions are given for 'this.this'");
-  results = JSPropertyProvider(dbgObject, null, "this.this");
-  test_has_no_results(results);
-
   do_print("Test that suggestions are given for literal strings.");
-  results = JSPropertyProvider(dbgObject, null, "'foo'.");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "'foo'.");
   test_has_result(results, "charAt");
-  results = JSPropertyProvider(dbgObject, null, '"foo".');
+  results = JSPropertyProvider(dbgObject, dbgEnv, '"foo".');
   test_has_result(results, "charAt");
-  results = JSPropertyProvider(dbgObject, null, "`foo`.");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "`foo`.");
   test_has_result(results, "charAt");
-  results = JSPropertyProvider(dbgObject, null, "'[1,2,3]'.");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "'[1,2,3]'.");
   test_has_result(results, "charAt");
 
   do_print("Test that suggestions are not given for syntax errors.");
-  results = JSPropertyProvider(dbgObject, null, "'foo\"");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "'foo\"");
   do_check_null(results);
-  results = JSPropertyProvider(dbgObject, null, "[1,',2]");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "[1,',2]");
   do_check_null(results);
-  results = JSPropertyProvider(dbgObject, null, "'[1,2].");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "'[1,2].");
   do_check_null(results);
-  results = JSPropertyProvider(dbgObject, null, "'foo'..");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "'foo'..");
   do_check_null(results);
 
   do_print("Test that suggestions are not given without a dot.");
-  results = JSPropertyProvider(dbgObject, null, "'foo'");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "'foo'");
   test_has_no_results(results);
-  results = JSPropertyProvider(dbgObject, null, "[1,2,3]");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "[1,2,3]");
   test_has_no_results(results);
-  results = JSPropertyProvider(dbgObject, null, "[1,2,3].\n'foo'");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "[1,2,3].\n'foo'");
   test_has_no_results(results);
 
   do_print("Test that suggestions are not given for numeric literals.");
-  results = JSPropertyProvider(dbgObject, null, "1.");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "1.");
   do_check_null(results);
 
   do_print("Test that suggestions are not given for index that's out of bounds.");
-  results = JSPropertyProvider(dbgObject, null, "testArray[10].");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "testArray[10].");
   do_check_null(results);
 
   do_print("Test that no suggestions are given if an index is not numerical somewhere in the chain.");
-  results = JSPropertyProvider(dbgObject, null, "testArray[0]['propC'][0].");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "testArray[0]['propC'][0].");
   do_check_null(results);
 
-  results = JSPropertyProvider(dbgObject, null, "testObject['propA'][0].");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "testObject['propA'][0].");
   do_check_null(results);
 
-  results = JSPropertyProvider(dbgObject, null, "testArray[0]['propC'].");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "testArray[0]['propC'].");
   do_check_null(results);
 
-  results = JSPropertyProvider(dbgObject, null, "testArray[][1].");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "testArray[][1].");
   do_check_null(results);
 
   do_print("Test that suggestions are not given if there is an hyphen in the chain.");
-  results = JSPropertyProvider(dbgObject, null, "testHyphenated['prop-A'].");
+  results = JSPropertyProvider(dbgObject, dbgEnv, "testHyphenated['prop-A'].");
   do_check_null(results);
 }
 
 /**
  * A helper that ensures an empty array of results were found.
  * @param Object aResults
  *        The results returned by JSPropertyProvider.
  */
--- a/dom/animation/Animation.cpp
+++ b/dom/animation/Animation.cpp
@@ -1199,17 +1199,19 @@ Animation::GetCollection() const
              "An animation with an animation manager must have an effect");
 
   Element* targetElement;
   nsCSSPseudoElements::Type targetPseudoType;
   mEffect->GetTarget(targetElement, targetPseudoType);
   MOZ_ASSERT(targetElement,
              "An animation with an animation manager must have a target");
 
-  return manager->GetAnimations(targetElement, targetPseudoType, false);
+  return manager->GetAnimationCollection(targetElement,
+                                         targetPseudoType,
+                                         false /* aCreateIfNeeded */);
 }
 
 void
 Animation::DoFinishNotification(SyncNotifyFlag aSyncNotifyFlag)
 {
   if (aSyncNotifyFlag == SyncNotifyFlag::Sync) {
     DoFinishNotificationImmediately();
   } else if (!mFinishNotificationTask.IsPending()) {
new file mode 100644
--- /dev/null
+++ b/dom/animation/AnimationUtils.cpp
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AnimationUtils.h"
+
+#include "nsDebug.h"
+#include "nsIAtom.h"
+#include "nsIContent.h"
+#include "nsString.h"
+
+namespace mozilla {
+
+/* static */ void
+AnimationUtils::LogAsyncAnimationFailure(nsCString& aMessage,
+                                         const nsIContent* aContent)
+{
+  if (aContent) {
+    aMessage.AppendLiteral(" [");
+    aMessage.Append(nsAtomCString(aContent->NodeInfo()->NameAtom()));
+
+    nsIAtom* id = aContent->GetID();
+    if (id) {
+      aMessage.AppendLiteral(" with id '");
+      aMessage.Append(nsAtomCString(aContent->GetID()));
+      aMessage.Append('\'');
+    }
+    aMessage.Append(']');
+  }
+  aMessage.Append('\n');
+  printf_stderr("%s", aMessage.get());
+}
+
+} // namespace mozilla
--- a/dom/animation/AnimationUtils.h
+++ b/dom/animation/AnimationUtils.h
@@ -4,44 +4,48 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_AnimationUtils_h
 #define mozilla_dom_AnimationUtils_h
 
 #include "mozilla/TimeStamp.h"
 #include "mozilla/dom/Nullable.h"
+#include "nsStringFwd.h"
+
+class nsIContent;
 
 namespace mozilla {
-namespace dom {
 
 class AnimationUtils
 {
 public:
-  static Nullable<double>
-    TimeDurationToDouble(const Nullable<TimeDuration>& aTime)
+  static dom::Nullable<double>
+    TimeDurationToDouble(const dom::Nullable<TimeDuration>& aTime)
   {
-    Nullable<double> result;
+    dom::Nullable<double> result;
 
     if (!aTime.IsNull()) {
       result.SetValue(aTime.Value().ToMilliseconds());
     }
 
     return result;
   }
 
-  static Nullable<TimeDuration>
-    DoubleToTimeDuration(const Nullable<double>& aTime)
+  static dom::Nullable<TimeDuration>
+    DoubleToTimeDuration(const dom::Nullable<double>& aTime)
   {
-    Nullable<TimeDuration> result;
+    dom::Nullable<TimeDuration> result;
 
     if (!aTime.IsNull()) {
       result.SetValue(TimeDuration::FromMilliseconds(aTime.Value()));
     }
 
     return result;
   }
+
+  static void LogAsyncAnimationFailure(nsCString& aMessage,
+                                       const nsIContent* aContent = nullptr);
 };
 
-} // namespace dom
 } // namespace mozilla
 
 #endif
new file mode 100644
--- /dev/null
+++ b/dom/animation/EffectCompositor.cpp
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "EffectCompositor.h"
+
+#include "mozilla/dom/Animation.h"
+#include "mozilla/dom/KeyframeEffect.h"
+#include "mozilla/AnimationUtils.h"
+#include "mozilla/EffectSet.h"
+#include "nsLayoutUtils.h"
+
+using mozilla::dom::Animation;
+using mozilla::dom::KeyframeEffectReadOnly;
+
+namespace mozilla {
+
+/* static */ nsTArray<RefPtr<dom::Animation>>
+EffectCompositor::GetAnimationsForCompositor(const nsIFrame* aFrame,
+                                             nsCSSProperty aProperty)
+{
+  nsTArray<RefPtr<dom::Animation>> result;
+
+  if (!nsLayoutUtils::AreAsyncAnimationsEnabled()) {
+    if (nsLayoutUtils::IsAnimationLoggingEnabled()) {
+      nsCString message;
+      message.AppendLiteral("Performance warning: Async animations are "
+                            "disabled");
+      AnimationUtils::LogAsyncAnimationFailure(message);
+    }
+    return result;
+  }
+
+  if (aFrame->RefusedAsyncAnimation()) {
+    return result;
+  }
+
+  EffectSet* effects = EffectSet::GetEffectSet(aFrame);
+  if (!effects) {
+    return result;
+  }
+
+  for (KeyframeEffectReadOnly* effect : *effects) {
+    MOZ_ASSERT(effect && effect->GetAnimation());
+    Animation* animation = effect->GetAnimation();
+
+    if (!animation->IsPlaying()) {
+      continue;
+    }
+
+    if (effect->ShouldBlockCompositorAnimations(aFrame)) {
+      result.Clear();
+      return result;
+    }
+
+    if (!effect->HasAnimationOfProperty(aProperty)) {
+      continue;
+    }
+
+    result.AppendElement(animation);
+  }
+
+  return result;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/animation/EffectCompositor.h
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_EffectCompositor_h
+#define mozilla_EffectCompositor_h
+
+#include "mozilla/RefPtr.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+
+namespace dom {
+class Animation;
+}
+
+class EffectCompositor
+{
+public:
+  static nsTArray<RefPtr<dom::Animation>>
+  GetAnimationsForCompositor(const nsIFrame* aFrame,
+                             nsCSSProperty aProperty);
+};
+
+} // namespace mozilla
+
+#endif // mozilla_EffectCompositor_h
--- a/dom/animation/EffectSet.cpp
+++ b/dom/animation/EffectSet.cpp
@@ -37,16 +37,52 @@ EffectSet::Traverse(nsCycleCollectionTra
 EffectSet::GetEffectSet(dom::Element* aElement,
                         nsCSSPseudoElements::Type aPseudoType)
 {
   nsIAtom* propName = GetEffectSetPropertyAtom(aPseudoType);
   return static_cast<EffectSet*>(aElement->GetProperty(propName));
 }
 
 /* static */ EffectSet*
+EffectSet::GetEffectSet(const nsIFrame* aFrame)
+{
+  nsIContent* content = aFrame->GetContent();
+  if (!content) {
+    return nullptr;
+  }
+
+  nsIAtom* propName;
+  if (aFrame->IsGeneratedContentFrame()) {
+    nsIFrame* parent = aFrame->GetParent();
+    if (parent->IsGeneratedContentFrame()) {
+      return nullptr;
+    }
+    nsIAtom* name = content->NodeInfo()->NameAtom();
+    if (name == nsGkAtoms::mozgeneratedcontentbefore) {
+      propName = nsGkAtoms::animationEffectsForBeforeProperty;
+    } else if (name == nsGkAtoms::mozgeneratedcontentafter) {
+      propName = nsGkAtoms::animationEffectsForAfterProperty;
+    } else {
+      return nullptr;
+    }
+    content = content->GetParent();
+    if (!content) {
+      return nullptr;
+    }
+  } else {
+    if (!content->MayHaveAnimations()) {
+      return nullptr;
+    }
+    propName = nsGkAtoms::animationEffectsProperty;
+  }
+
+  return static_cast<EffectSet*>(content->GetProperty(propName));
+}
+
+/* static */ EffectSet*
 EffectSet::GetOrCreateEffectSet(dom::Element* aElement,
                                 nsCSSPseudoElements::Type aPseudoType)
 {
   EffectSet* effectSet = GetEffectSet(aElement, aPseudoType);
   if (effectSet) {
     return effectSet;
   }
 
--- a/dom/animation/EffectSet.h
+++ b/dom/animation/EffectSet.h
@@ -40,16 +40,17 @@ public:
   static void PropertyDtor(void* aObject, nsIAtom* aPropertyName,
                            void* aPropertyValue, void* aData);
 
   // Methods for supporting cycle-collection
   void Traverse(nsCycleCollectionTraversalCallback& aCallback);
 
   static EffectSet* GetEffectSet(dom::Element* aElement,
                                  nsCSSPseudoElements::Type aPseudoType);
+  static EffectSet* GetEffectSet(const nsIFrame* aFrame);
   static EffectSet* GetOrCreateEffectSet(dom::Element* aElement,
                                          nsCSSPseudoElements::Type aPseudoType);
 
   void AddEffect(dom::KeyframeEffectReadOnly& aEffect);
   void RemoveEffect(dom::KeyframeEffectReadOnly& aEffect);
 
 private:
   typedef nsTHashtable<nsRefPtrHashKey<dom::KeyframeEffectReadOnly>>
--- a/dom/animation/KeyframeEffect.cpp
+++ b/dom/animation/KeyframeEffect.cpp
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/KeyframeEffect.h"
 
 #include "mozilla/dom/AnimationEffectReadOnlyBinding.h"
 #include "mozilla/dom/KeyframeEffectBinding.h"
 #include "mozilla/dom/PropertyIndexedKeyframesBinding.h"
+#include "mozilla/AnimationUtils.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/LookAndFeel.h" // For LookAndFeel::GetInt
 #include "mozilla/StyleAnimationValue.h"
 #include "AnimationCommon.h"
 #include "Layers.h" // For Layer
 #include "nsCSSParser.h"
 #include "nsCSSPropertySet.h"
 #include "nsCSSProps.h" // For nsCSSProps::PropHasFlags
@@ -50,17 +51,17 @@ GetComputedTimingDictionary(const Comput
   aRetVal.mIterations = aTiming.mIterationCount;
   aRetVal.mDuration.SetAsUnrestrictedDouble() = aTiming.mIterationDuration.ToMilliseconds();
   aRetVal.mDirection = aTiming.mDirection;
 
   // ComputedTimingProperties
   aRetVal.mActiveDuration = aComputedTiming.mActiveDuration.ToMilliseconds();
   aRetVal.mEndTime
     = std::max(aRetVal.mDelay + aRetVal.mActiveDuration + aRetVal.mEndDelay, 0.0);
-  aRetVal.mLocalTime = dom::AnimationUtils::TimeDurationToDouble(aLocalTime);
+  aRetVal.mLocalTime = AnimationUtils::TimeDurationToDouble(aLocalTime);
   aRetVal.mProgress = aComputedTiming.mProgress;
   if (!aRetVal.mProgress.IsNull()) {
     // Convert the returned currentIteration into Infinity if we set
     // (uint64_t) aComputedTiming.mCurrentIteration to UINT64_MAX
     double iteration = aComputedTiming.mCurrentIteration == UINT64_MAX
                      ? PositiveInfinity<double>()
                      : static_cast<double>(aComputedTiming.mCurrentIteration);
     aRetVal.mCurrentIteration.SetValue(iteration);
@@ -547,16 +548,22 @@ KeyframeEffectReadOnly::UpdateTargetRegi
     EffectSet* effectSet = EffectSet::GetOrCreateEffectSet(mTarget,
                                                            mPseudoType);
     effectSet->AddEffect(*this);
   } else {
     EffectSet* effectSet = EffectSet::GetEffectSet(mTarget, mPseudoType);
     if (effectSet) {
       effectSet->RemoveEffect(*this);
     }
+    // Any effects not in the effect set will not be included in the set of
+    // candidate effects for running on the compositor and hence they won't
+    // have their compositor status updated so we should do that now.
+    for (bool& isRunningOnCompositor : mIsPropertyRunningOnCompositor) {
+      isRunningOnCompositor = false;
+    }
   }
 }
 
 #ifdef DEBUG
 void
 DumpAnimationProperties(nsTArray<AnimationProperty>& aAnimationProperties)
 {
   for (auto& p : aAnimationProperties) {
@@ -1979,68 +1986,79 @@ KeyframeEffectReadOnly::CanAnimateTransf
   const nsIContent* aContent)
 {
   if (aFrame->Combines3DTransformWithAncestors() ||
       aFrame->Extend3DContext()) {
     if (aContent) {
       nsCString message;
       message.AppendLiteral("Gecko bug: Async animation of 'preserve-3d' "
         "transforms is not supported.  See bug 779598");
-      AnimationCollection::LogAsyncAnimationFailure(message, aContent);
+      AnimationUtils::LogAsyncAnimationFailure(message, aContent);
     }
     return false;
   }
   // Note that testing BackfaceIsHidden() is not a sufficient test for
   // what we need for animating backface-visibility correctly if we
   // remove the above test for Extend3DContext(); that would require
   // looking at backface-visibility on descendants as well.
   if (aFrame->StyleDisplay()->BackfaceIsHidden()) {
     if (aContent) {
       nsCString message;
       message.AppendLiteral("Gecko bug: Async animation of "
         "'backface-visibility: hidden' transforms is not supported."
         "  See bug 1186204.");
-      AnimationCollection::LogAsyncAnimationFailure(message, aContent);
+      AnimationUtils::LogAsyncAnimationFailure(message, aContent);
     }
     return false;
   }
   if (aFrame->IsSVGTransformed()) {
     if (aContent) {
       nsCString message;
       message.AppendLiteral("Gecko bug: Async 'transform' animations of "
         "aFrames with SVG transforms is not supported.  See bug 779599");
-      AnimationCollection::LogAsyncAnimationFailure(message, aContent);
+      AnimationUtils::LogAsyncAnimationFailure(message, aContent);
     }
     return false;
   }
 
   return true;
 }
 
-/* static */ bool
-KeyframeEffectReadOnly::CanAnimatePropertyOnCompositor(
-  const nsIFrame* aFrame,
-  nsCSSProperty aProperty)
+bool
+KeyframeEffectReadOnly::ShouldBlockCompositorAnimations(const nsIFrame*
+                                                          aFrame) const
 {
+  // We currently only expect this method to be called when this effect
+  // is attached to a playing Animation. If that ever changes we'll need
+  // to update this to only return true when that is the case since paused,
+  // filling, cancelled Animations etc. shouldn't stop other Animations from
+  // running on the compositor.
+  MOZ_ASSERT(mAnimation && mAnimation->IsPlaying());
+
   bool shouldLog = nsLayoutUtils::IsAnimationLoggingEnabled();
 
-  if (IsGeometricProperty(aProperty)) {
-    if (shouldLog) {
-      nsCString message;
-      message.AppendLiteral("Performance warning: Async animation of "
-        "'transform' or 'opacity' not possible due to animation of geometric"
-        "properties on the same element");
-      AnimationCollection::LogAsyncAnimationFailure(message,
-                                                    aFrame->GetContent());
+  for (const AnimationProperty& property : mProperties) {
+    // Check for geometric properties
+    if (IsGeometricProperty(property.mProperty)) {
+      if (shouldLog) {
+        nsCString message;
+        message.AppendLiteral("Performance warning: Async animation of "
+          "'transform' or 'opacity' not possible due to animation of geometric"
+          "properties on the same element");
+        AnimationUtils::LogAsyncAnimationFailure(message, aFrame->GetContent());
+      }
+      return true;
     }
-    return false;
-  }
-  if (aProperty == eCSSProperty_transform) {
-    if (!CanAnimateTransformOnCompositor(aFrame,
-          shouldLog ? aFrame->GetContent() : nullptr)) {
-      return false;
+
+    // Check for unsupported transform animations
+    if (property.mProperty == eCSSProperty_transform) {
+      if (!CanAnimateTransformOnCompositor(aFrame,
+            shouldLog ? aFrame->GetContent() : nullptr)) {
+        return true;
+      }
     }
   }
-  return true;
+
+  return false;
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/animation/KeyframeEffect.h
+++ b/dom/animation/KeyframeEffect.h
@@ -282,19 +282,32 @@ public:
   // Returns true if |aProperty| is currently being animated on compositor.
   bool IsPropertyRunningOnCompositor(nsCSSProperty aProperty) const;
   // Returns true if at least one property is being animated on compositor.
   bool IsRunningOnCompositor() const;
   void SetIsRunningOnCompositor(nsCSSProperty aProperty, bool aIsRunning);
 
   bool CanThrottle() const;
 
-  // Returns true |aProperty| can be run on compositor for |aFrame|.
-  static bool CanAnimatePropertyOnCompositor(const nsIFrame* aFrame,
-                                             nsCSSProperty aProperty);
+  // Returns true if this effect, applied to |aFrame|, contains
+  // properties that mean we shouldn't run *any* compositor animations on this
+  // element.
+  //
+  // For example, if we have an animation of geometric properties like 'left'
+  // and 'top' on an element, we force all 'transform' and 'opacity' animations
+  // running at the same time on the same element to run on the main thread.
+  //
+  // Similarly, some transform animations cannot be run on the compositor and
+  // when that is the case we simply disable all compositor animations
+  // on the same element.
+  //
+  // Bug 1218620 - It seems like we don't need to be this restrictive. Wouldn't
+  // it be ok to do 'opacity' animations on the compositor in either case?
+  bool ShouldBlockCompositorAnimations(const nsIFrame* aFrame) const;
+
   nsIDocument* GetRenderedDocument() const;
   nsPresContext* GetPresContext() const;
 
   inline AnimationCollection* GetCollection() const;
 
 protected:
   virtual ~KeyframeEffectReadOnly();
   void ResetIsRunningOnCompositor();
--- a/dom/animation/moz.build
+++ b/dom/animation/moz.build
@@ -14,26 +14,29 @@ EXPORTS.mozilla.dom += [
     'DocumentTimeline.h',
     'KeyframeEffect.h',
 ]
 
 EXPORTS.mozilla += [
     'AnimationComparator.h',
     'AnimationUtils.h',
     'ComputedTimingFunction.h',
+    'EffectCompositor.h',
     'EffectSet.h',
     'PendingAnimationTracker.h',
 ]
 
 UNIFIED_SOURCES += [
     'Animation.cpp',
     'AnimationEffectReadOnly.cpp',
     'AnimationTimeline.cpp',
+    'AnimationUtils.cpp',
     'ComputedTimingFunction.cpp',
     'DocumentTimeline.cpp',
+    'EffectCompositor.cpp',
     'EffectSet.cpp',
     'KeyframeEffect.cpp',
     'PendingAnimationTracker.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '/dom/base',
 ]
--- a/dom/animation/test/chrome/test_running_on_compositor.html
+++ b/dom/animation/test/chrome/test_running_on_compositor.html
@@ -102,67 +102,62 @@ promise_test(function(t) {
 }, 'isRunningOnCompositor is false when the animation.pause() is called');
 
 promise_test(function(t) {
   var div = addDiv(t, { style: 'animation: anim 100s' });
   var animation = div.getAnimations()[0];
 
   return animation.ready.then(t.step_func(function() {
     animation.finish();
-    // We need to wait for two frames here to ensure isRunningOnCompositor
-    // flag is cleared. The first frame is for waking root refresh driver up,
-    // the second one is for clearing the flag.
-    // In most cases root refresh driver has been dormant because there is
-    // nothing to do for the root refresh driver (e.g. nothing change on
-    // chrome window). In the first frame document's refresh driver wakes the
-    // root refresh driver up as a result of animation's style changes.
-    // In the second frame the root refresh driver clears the flag as a
-    // result of a paint.
-    return waitForAnimationFrames(2);
+    assert_equals(animation.isRunningOnCompositor, false,
+       'Animation reports that it is NOT running on the compositor'
+       + ' immediately after animation.finish() is called');
+    // Check that we don't set the flag back again on the next tick.
+    return waitForFrame();
   })).then(t.step_func(function() {
     assert_equals(animation.isRunningOnCompositor, false,
        'Animation reports that it is NOT running on the compositor'
-       + ' when animation.finish() is called');
+       + ' on the next tick after animation.finish() is called');
   }));
 }, 'isRunningOnCompositor is false when the animation.finish() is called');
 
 promise_test(function(t) {
   var div = addDiv(t, { style: 'animation: anim 100s' });
   var animation = div.getAnimations()[0];
 
   return animation.ready.then(t.step_func(function() {
     animation.currentTime = 100000; // 100s
-    // We need to wait for up to two frames here to ensure the "is running on
-    // compositor" flag is cleared.
-    // See the description in the 'isRunningOnCompositor is false when the
-    // animation.finish() is called' test for the rationale.
-    return waitForAnimationFrames(2);
+    assert_equals(animation.isRunningOnCompositor, false,
+       'Animation reports that it is NOT running on the compositor'
+       + ' immediately after manually seeking the animation to the end');
+    // Check that we don't set the flag back again on the next tick.
+    return waitForFrame();
   })).then(t.step_func(function() {
     assert_equals(animation.isRunningOnCompositor, false,
        'Animation reports that it is NOT running on the compositor'
-       + ' when manually seeking the animation to the end');
+       + ' on the next tick after manually seeking the animation to the end');
   }));
 }, 'isRunningOnCompositor is false when manually seeking the animation to ' +
    'the end');
 
 promise_test(function(t) {
   var div = addDiv(t, { style: 'animation: anim 100s' });
   var animation = div.getAnimations()[0];
 
   return animation.ready.then(t.step_func(function() {
     animation.cancel();
-    // We need to wait for up to two frames here to ensure the "is running on
-    // compositor" flag is cleared.
-    // See the description in the 'isRunningOnCompositor is false when the
-    // animation.finish() is called' test for the rationale.
-    return waitForAnimationFrames(2);
+    assert_equals(animation.isRunningOnCompositor, false,
+       'Animation reports that it is NOT running on the compositor'
+       + ' immediately after animation.cancel() is called');
+    // Check that we don't set the flag back again on the next tick.
+    return waitForFrame();
   })).then(t.step_func(function() {
     assert_equals(animation.isRunningOnCompositor, false,
        'Animation reports that it is NOT running on the compositor'
-       + ' when animation.cancel() is called');
+       + ' on the next tick after animation.cancel() is called');
   }));
 }, 'isRunningOnCompositor is false when animation.cancel() is called');
 
 promise_test(function(t) {
   var div = addDiv(t, { style: 'animation: anim 100s 100s' });
   var animation = div.getAnimations()[0];
 
   return animation.ready.then(t.step_func(function() {
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -21,16 +21,17 @@ XPIDL_SOURCES += [
     'nsIDOMParser.idl',
     'nsIDOMSerializer.idl',
     'nsIDroppedLinkHandler.idl',
     'nsIEntropyCollector.idl',
     'nsIFrameLoader.idl',
     'nsIImageLoadingContent.idl',
     'nsIMessageManager.idl',
     'nsIObjectLoadingContent.idl',
+    'nsIRemoteWindowContext.idl',
     'nsIScriptChannel.idl',
     'nsIScriptLoaderObserver.idl',
     'nsISelection.idl',
     'nsISelectionController.idl',
     'nsISelectionDisplay.idl',
     'nsISelectionListener.idl',
     'nsISelectionPrivate.idl',
     'nsISimpleContentPolicy.idl',
--- a/dom/base/nsContentTypeParser.h
+++ b/dom/base/nsContentTypeParser.h
@@ -12,20 +12,17 @@
 class nsIMIMEHeaderParam;
 
 class nsContentTypeParser {
 public:
   explicit nsContentTypeParser(const nsAString& aString);
   ~nsContentTypeParser();
 
   nsresult GetParameter(const char* aParameterName, nsAString& aResult);
-  nsresult GetType(nsAString& aResult)
-  {
-    return GetParameter(nullptr, aResult);
-  }
+  nsresult GetType(nsAString& aResult);
 
 private:
   NS_ConvertUTF16toUTF8 mString;
   nsIMIMEHeaderParam*   mService;
 };
 
 #endif
 
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -6052,16 +6052,25 @@ nsresult
 nsContentTypeParser::GetParameter(const char* aParameterName, nsAString& aResult)
 {
   NS_ENSURE_TRUE(mService, NS_ERROR_FAILURE);
   return mService->GetParameterHTTP(mString, aParameterName,
                                     EmptyCString(), false, nullptr,
                                     aResult);
 }
 
+nsresult
+nsContentTypeParser::GetType(nsAString& aResult)
+{
+  nsresult rv = GetParameter(nullptr, aResult);
+  NS_ENSURE_SUCCESS(rv, rv);
+  nsContentUtils::ASCIIToLower(aResult);
+  return NS_OK;
+}
+
 /* static */
 
 bool
 nsContentUtils::CanAccessNativeAnon()
 {
   return LegacyIsCallerChromeOrNativeCode() || IsCallerContentXBL();
 }
 
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -370,29 +370,21 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
     }
 
     if (callbacks->mDetachedCallback.WasPassed()) {
       NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
         "mCustomDefinitions->mCallbacks->mDetachedCallback");
       cb.NoteXPCOMChild(callbacks->mDetachedCallback.Value());
     }
   }
-  for (auto iter = tmp->mCandidatesMap.Iter(); !iter.Done(); iter.Next()) {
-    nsTArray<RefPtr<Element>>* elems = iter.UserData();
-    for (size_t i = 0; i < elems->Length(); ++i) {
-      NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mCandidatesMap->Element");
-      cb.NoteXPCOMChild(elems->ElementAt(i));
-    }
-  }
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Registry)
   tmp->mCustomDefinitions.Clear();
-  tmp->mCandidatesMap.Clear();
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Registry)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(Registry)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(Registry)
@@ -5777,26 +5769,26 @@ nsDocument::RegisterUnresolvedElement(El
     typeName = info->NameAtom();
   }
 
   CustomElementHashKey key(info->NamespaceID(), typeName);
   if (mRegistry->mCustomDefinitions.Get(&key)) {
     return NS_OK;
   }
 
-  nsTArray<RefPtr<Element>>* unresolved;
+  nsTArray<nsWeakPtr>* unresolved;
   mRegistry->mCandidatesMap.Get(&key, &unresolved);
   if (!unresolved) {
-    unresolved = new nsTArray<RefPtr<Element>>();
+    unresolved = new nsTArray<nsWeakPtr>();
     // Ownership of unresolved is taken by mCandidatesMap.
     mRegistry->mCandidatesMap.Put(&key, unresolved);
   }
 
-  RefPtr<Element>* elem = unresolved->AppendElement();
-  *elem = aElement;
+  nsWeakPtr* elem = unresolved->AppendElement();
+  *elem = do_GetWeakReference(aElement);
   aElement->AddStates(NS_EVENT_STATE_UNRESOLVED);
 
   return NS_OK;
 }
 
 void
 nsDocument::EnqueueLifecycleCallback(nsIDocument::ElementCallbackType aType,
                                      Element* aCustomElement,
@@ -6170,21 +6162,24 @@ nsDocument::RegisterElement(JSContext* a
                                 typeAtom,
                                 nameAtom,
                                 callbacks,
                                 namespaceID,
                                 0 /* TODO dependent on HTML imports. Bug 877072 */);
   definitions.Put(&key, definition);
 
   // Do element upgrade.
-  nsAutoPtr<nsTArray<RefPtr<Element>>> candidates;
+  nsAutoPtr<nsTArray<nsWeakPtr>> candidates;
   mRegistry->mCandidatesMap.RemoveAndForget(&key, candidates);
   if (candidates) {
     for (size_t i = 0; i < candidates->Length(); ++i) {
-      Element *elem = candidates->ElementAt(i);
+      nsCOMPtr<Element> elem = do_QueryReferent(candidates->ElementAt(i));
+      if (!elem) {
+        continue;
+      }
 
       elem->RemoveStates(NS_EVENT_STATE_UNRESOLVED);
 
       // Make sure that the element name matches the name in the definition.
       // (e.g. a definition for x-button extending button should match
       // <button is="x-button"> but not <x-button>.
       if (elem->NodeInfo()->NameAtom() != nameAtom) {
         //Skip over this element because definition does not apply.
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -422,17 +422,17 @@ public:
 
 protected:
   virtual ~Registry();
 
   typedef nsClassHashtable<mozilla::dom::CustomElementHashKey,
                            mozilla::dom::CustomElementDefinition>
     DefinitionMap;
   typedef nsClassHashtable<mozilla::dom::CustomElementHashKey,
-                           nsTArray<RefPtr<mozilla::dom::Element>>>
+                           nsTArray<nsWeakPtr>>
     CandidateMap;
 
   // Hashtable for custom element definitions in web components.
   // Custom prototypes are stored in the compartment where
   // registerElement was called.
   DefinitionMap mCustomDefinitions;
 
   // The "upgrade candidates map" from the web components spec. Maps from a
new file mode 100644
--- /dev/null
+++ b/dom/base/nsIRemoteWindowContext.idl
@@ -0,0 +1,15 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+
+[scriptable, builtinclass, uuid(94f4a92b-752e-4fd9-8345-11b069ca19f3)]
+interface nsIRemoteWindowContext : nsISupports
+{
+  void openURI(in nsIURI aURI, in uint32_t aFlags);
+};
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -577,17 +577,17 @@ skip-if = toolkit == 'android' #TIMED_OU
 [test_bug545644.xhtml]
 [test_bug548463.html]
 [test_bug553896.xhtml]
 [test_bug557892.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
 [test_bug558726.html]
 [test_bug559526.html]
 [test_bug560780.html]
-skip-if = android_version == '18' # Android 4.3 intermittent, Bug 1154497
+skip-if = (os == "android") # Failure with AccessibleCarets on Android, bug 1230229
 [test_bug562137.html]
 [test_bug562169-1.html]
 [test_bug562169-2.html]
 [test_bug562652.html]
 [test_bug564047.html]
 [test_bug564863.xhtml]
 [test_bug567350.html]
 [test_bug578096.html]
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -405,61 +405,27 @@ class CGDOMJSClass(CGThing):
     def __init__(self, descriptor):
         CGThing.__init__(self)
         self.descriptor = descriptor
 
     def declare(self):
         return ""
 
     def define(self):
-        traceHook = 'nullptr'
         callHook = LEGACYCALLER_HOOK_NAME if self.descriptor.operations["LegacyCaller"] else 'nullptr'
         objectMovedHook = OBJECT_MOVED_HOOK_NAME if self.descriptor.wrapperCache else 'nullptr'
         slotCount = INSTANCE_RESERVED_SLOTS + self.descriptor.interface.totalMembersInSlots
         classFlags = "JSCLASS_IS_DOMJSCLASS | "
-        classExtensionAndObjectOps = fill(
-            """
-            {
-              false,   /* isWrappedNative */
-              nullptr, /* weakmapKeyDelegateOp */
-              ${objectMoved} /* objectMovedOp */
-            },
-            JS_NULL_OBJECT_OPS
-            """,
-            objectMoved=objectMovedHook)
         if self.descriptor.isGlobal():
             classFlags += "JSCLASS_DOM_GLOBAL | JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(DOM_GLOBAL_SLOTS)"
             traceHook = "JS_GlobalObjectTraceHook"
             reservedSlots = "JSCLASS_GLOBAL_APPLICATION_SLOTS"
-            if self.descriptor.interface.identifier.name == "Window":
-                classExtensionAndObjectOps = fill(
-                    """
-                    {
-                      false,   /* isWrappedNative */
-                      nullptr, /* weakmapKeyDelegateOp */
-                      ${objectMoved} /* objectMovedOp */
-                    },
-                    {
-                      nullptr, /* lookupProperty */
-                      nullptr, /* defineProperty */
-                      nullptr, /* hasProperty */
-                      nullptr, /* getProperty */
-                      nullptr, /* setProperty */
-                      nullptr, /* getOwnPropertyDescriptor */
-                      nullptr, /* deleteProperty */
-                      nullptr, /* watch */
-                      nullptr, /* unwatch */
-                      nullptr, /* getElements */
-                      nullptr, /* enumerate */
-                      nullptr, /* funToString */
-                    }
-                    """,
-                    objectMoved=objectMovedHook)
         else:
             classFlags += "JSCLASS_HAS_RESERVED_SLOTS(%d)" % slotCount
+            traceHook = 'nullptr'
             reservedSlots = slotCount
         if self.descriptor.interface.getExtendedAttribute("NeedResolve"):
             resolveHook = RESOLVE_HOOK_NAME
             mayResolveHook = MAY_RESOLVE_HOOK_NAME
             enumerateHook = ENUMERATE_HOOK_NAME
         elif self.descriptor.isGlobal():
             resolveHook = "mozilla::dom::ResolveGlobal"
             mayResolveHook = "mozilla::dom::MayResolveGlobal"
@@ -482,17 +448,22 @@ class CGDOMJSClass(CGThing):
                 ${resolve}, /* resolve */
                 ${mayResolve}, /* mayResolve */
                 ${finalize}, /* finalize */
                 ${call}, /* call */
                 nullptr,               /* hasInstance */
                 nullptr,               /* construct */
                 ${trace}, /* trace */
                 JS_NULL_CLASS_SPEC,
-                $*{classExtensionAndObjectOps}
+                {
+                  false,   /* isWrappedNative */
+                  nullptr, /* weakmapKeyDelegateOp */
+                  ${objectMoved} /* objectMovedOp */
+                },
+                JS_NULL_OBJECT_OPS
               },
               $*{descriptor}
             };
             static_assert(${instanceReservedSlots} == DOM_INSTANCE_RESERVED_SLOTS,
                           "Must have the right minimal number of reserved slots.");
             static_assert(${reservedSlots} >= ${slotCount},
                           "Must have enough reserved slots.");
             """,
@@ -500,17 +471,17 @@ class CGDOMJSClass(CGThing):
             flags=classFlags,
             addProperty=ADDPROPERTY_HOOK_NAME if wantsAddProperty(self.descriptor) else 'nullptr',
             enumerate=enumerateHook,
             resolve=resolveHook,
             mayResolve=mayResolveHook,
             finalize=FINALIZE_HOOK_NAME,
             call=callHook,
             trace=traceHook,
-            classExtensionAndObjectOps=classExtensionAndObjectOps,
+            objectMoved=objectMovedHook,
             descriptor=DOMClass(self.descriptor),
             instanceReservedSlots=INSTANCE_RESERVED_SLOTS,
             reservedSlots=reservedSlots,
             slotCount=slotCount)
 
 
 class CGDOMProxyJSClass(CGThing):
     """
--- a/dom/bluetooth/bluedroid/BluetoothDaemonHelpers.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothDaemonHelpers.cpp
@@ -451,17 +451,18 @@ Convert(uint8_t aIn, BluetoothStatus& aO
     [0x02] = STATUS_NOT_READY,
     [0x03] = STATUS_NOMEM,
     [0x04] = STATUS_BUSY,
     [0x05] = STATUS_DONE,
     [0x06] = STATUS_UNSUPPORTED,
     [0x07] = STATUS_PARM_INVALID,
     [0x08] = STATUS_UNHANDLED,
     [0x09] = STATUS_AUTH_FAILURE,
-    [0x0a] = STATUS_RMT_DEV_DOWN
+    [0x0a] = STATUS_RMT_DEV_DOWN,
+    [0x0b] = STATUS_AUTH_REJECTED
   };
   if (MOZ_HAL_IPC_CONVERT_WARN_IF(
         aIn >= MOZ_ARRAY_LENGTH(sStatus), uint8_t, BluetoothStatus)) {
     return NS_ERROR_ILLEGAL_VALUE;
   }
   aOut = sStatus[aIn];
   return NS_OK;
 }
--- a/dom/bluetooth/common/BluetoothCommon.h
+++ b/dom/bluetooth/common/BluetoothCommon.h
@@ -315,16 +315,17 @@ enum BluetoothStatus {
   STATUS_NOMEM,
   STATUS_BUSY,
   STATUS_DONE,
   STATUS_UNSUPPORTED,
   STATUS_PARM_INVALID,
   STATUS_UNHANDLED,
   STATUS_AUTH_FAILURE,
   STATUS_RMT_DEV_DOWN,
+  STATUS_AUTH_REJECTED,
   NUM_STATUS
 };
 
 enum BluetoothAclState {
   ACL_STATE_CONNECTED,
   ACL_STATE_DISCONNECTED
 };
 
--- a/dom/browser-element/mochitest/mochitest-oop.ini
+++ b/dom/browser-element/mochitest/mochitest-oop.ini
@@ -83,17 +83,18 @@ skip-if = (toolkit == 'gonk')
 [test_browserElement_oop_PurgeHistory.html]
 [test_browserElement_oop_Reload.html]
 [test_browserElement_oop_ReloadPostRequest.html]
 [test_browserElement_oop_RemoveBrowserElement.html]
 [test_browserElement_oop_ScrollEvent.html]
 [test_browserElement_oop_SecurityChange.html]
 skip-if = toolkit == 'android' || (toolkit == 'gonk' && !debug) #TIMED_OUT, bug 766586
 [test_browserElement_oop_SelectionStateBlur.html]
-skip-if = (toolkit == 'gonk') # Disabled on b2g due to bug 1097419
+skip-if = (os == "android" || toolkit == 'gonk') # Disabled on b2g due to bug 1097419
+# Disabled on Android, see bug 1230230
 [test_browserElement_oop_SendEvent.html]
 [test_browserElement_oop_SetInputMethodActive.html]
 skip-if = (os == "android")
 [test_browserElement_oop_SetVisible.html]
 [test_browserElement_oop_SetVisibleFrames.html]
 [test_browserElement_oop_SetVisibleFrames2.html]
 [test_browserElement_oop_Stop.html]
 [test_browserElement_oop_TargetBlank.html]
--- a/dom/browser-element/mochitest/mochitest.ini
+++ b/dom/browser-element/mochitest/mochitest.ini
@@ -169,16 +169,17 @@ skip-if = buildapp == 'b2g'
 [test_browserElement_inproc_Close.html]
 [test_browserElement_inproc_CloseApp.html]
 skip-if = toolkit == 'android' || buildapp == 'b2g' # android(FAILS, bug 796982) androidx86(FAILS, bug 796982)
 [test_browserElement_inproc_CloseFromOpener.html]
 skip-if = buildapp == 'b2g'
 [test_browserElement_inproc_ContextmenuEvents.html]
 [test_browserElement_inproc_CookiesNotThirdParty.html]
 [test_browserElement_inproc_CopyPaste.html]
+skip-if = (os == "android") # Disabled on Android, see bug 1230421
 [test_browserElement_inproc_DOMRequestError.html]
 [test_browserElement_inproc_DataURI.html]
 [test_browserElement_inproc_DisallowEmbedAppsInOOP.html]
 skip-if = os == "android" || toolkit == 'gonk' # embed-apps doesn't work in the mochitest app
 [test_browserElement_inproc_DocumentFirstPaint.html]
 [test_browserElement_inproc_Download.html]
 disabled = bug 1022281
 [test_browserElement_inproc_ExecuteScript.html]
@@ -218,17 +219,18 @@ skip-if = (toolkit == 'gonk' && !debug)
 skip-if = (toolkit == 'gonk')
 [test_browserElement_inproc_PurgeHistory.html]
 [test_browserElement_inproc_ReloadPostRequest.html]
 [test_browserElement_inproc_RemoveBrowserElement.html]
 [test_browserElement_inproc_ScrollEvent.html]
 [test_browserElement_inproc_SecurityChange.html]
 skip-if = toolkit == 'android' || (toolkit == 'gonk' && !debug) # android(TIMED_OUT, bug 766586) androidx86(TIMED_OUT, bug 766586)
 [test_browserElement_inproc_SelectionStateBlur.html]
-skip-if = (toolkit == 'gonk') # Disabled on b2g due to bug 1097419
+skip-if = (os == "android" || toolkit == 'gonk') # Disabled on b2g due to bug 1097419
+# Disabled on Android, see bug 1230230
 [test_browserElement_inproc_SendEvent.html]
 # The setInputMethodActive() tests will timed out on Android
 [test_browserElement_inproc_SetInputMethodActive.html]
 skip-if = (os == "android")
 [test_browserElement_inproc_SetVisible.html]
 [test_browserElement_inproc_SetVisibleFrames.html]
 [test_browserElement_inproc_SetVisibleFrames2.html]
 [test_browserElement_inproc_Stop.html]
--- a/dom/canvas/WebGLShader.cpp
+++ b/dom/canvas/WebGLShader.cpp
@@ -88,21 +88,19 @@ TranslateWithoutValidation(const nsACStr
         glesslVersion = 100;
     }
 
     std::string reversionedSource = source;
     reversionedSource.erase(versionStrStart, versionStrLen);
 
     switch (glesslVersion) {
     case 100:
-        if (!versionStrLen) {
-            /* According to ARB_ES2_compatibility extension glsl
-             * should accept #version 100 for ES 2 shaders. */
-            reversionedSource.insert(versionStrStart, "#version 100\n");
-        }
+        /* According to ARB_ES2_compatibility extension glsl
+         * should accept #version 100 for ES 2 shaders. */
+        reversionedSource.insert(versionStrStart, "#version 100\n");
         break;
     case 300:
         reversionedSource.insert(versionStrStart, "#version 330\n");
         break;
     default:
         MOZ_CRASH("Bad `glesslVersion`.");
     }
 
--- a/dom/canvas/WebGLShaderValidator.cpp
+++ b/dom/canvas/WebGLShaderValidator.cpp
@@ -88,26 +88,52 @@ ChooseValidatorCompileOptions(const ShBu
 
     return options;
 }
 
 } // namespace webgl
 
 ////////////////////////////////////////
 
+static ShShaderOutput
+ShaderOutput(gl::GLContext* gl)
+{
+    if (gl->IsGLES()) {
+        return SH_ESSL_OUTPUT;
+    } else {
+        uint32_t version = gl->ShadingLanguageVersion();
+        switch (version) {
+        case 100: return SH_GLSL_COMPATIBILITY_OUTPUT;
+        case 120: return SH_GLSL_COMPATIBILITY_OUTPUT;
+        case 130: return SH_GLSL_130_OUTPUT;
+        case 140: return SH_GLSL_140_OUTPUT;
+        case 150: return SH_GLSL_150_CORE_OUTPUT;
+        case 330: return SH_GLSL_330_CORE_OUTPUT;
+        case 400: return SH_GLSL_400_CORE_OUTPUT;
+        case 410: return SH_GLSL_410_CORE_OUTPUT;
+        case 420: return SH_GLSL_420_CORE_OUTPUT;
+        case 430: return SH_GLSL_430_CORE_OUTPUT;
+        case 440: return SH_GLSL_440_CORE_OUTPUT;
+        case 450: return SH_GLSL_450_CORE_OUTPUT;
+        default:
+            MOZ_CRASH("Unexpected GLSL version.");
+        }
+    }
+
+    return SH_GLSL_OUTPUT;
+}
+
 webgl::ShaderValidator*
 WebGLContext::CreateShaderValidator(GLenum shaderType) const
 {
     if (mBypassShaderValidation)
         return nullptr;
 
     ShShaderSpec spec = IsWebGL2() ? SH_WEBGL2_SPEC : SH_WEBGL_SPEC;
-    ShShaderOutput outputLanguage = gl->IsGLES() ? SH_ESSL_OUTPUT
-                                                 : SH_GLSL_OUTPUT;
-
+    ShShaderOutput outputLanguage = ShaderOutput(gl);
     ShBuiltInResources resources;
     memset(&resources, 0, sizeof(resources));
     ShInitBuiltInResources(&resources);
 
     resources.HashFunction = webgl::IdentifierHashFunc;
 
     resources.MaxVertexAttribs = mGLMaxVertexAttribs;
     resources.MaxVertexUniformVectors = mGLMaxVertexUniformVectors;
--- a/dom/canvas/WebGLTextureUpload.cpp
+++ b/dom/canvas/WebGLTextureUpload.cpp
@@ -360,16 +360,23 @@ WebGLTexture::ValidateTexImageSpecificat
          *   and if either wt or ht are less than zero, then the error
          *   INVALID_VALUE is generated."
          */
         mContext->ErrorInvalidValue("%s: `width`/`height`/`depth` must be >= 0.",
                                     funcName);
         return false;
     }
 
+    if (mTarget == LOCAL_GL_TEXTURE_CUBE_MAP &&
+        width != height)
+    {
+        mContext->ErrorInvalidValue("%s: Cube map images must be square.", funcName);
+        return false;
+    }
+
     /* GLES 3.0.4, p133-134:
      * GL_MAX_TEXTURE_SIZE is *not* the max allowed texture size. Rather, it is the
      * max (width/height) size guaranteed not to generate an INVALID_VALUE for too-large
      * dimensions. Sizes larger than GL_MAX_TEXTURE_SIZE *may or may not* result in an
      * INVALID_VALUE, or possibly GL_OOM.
      *
      * However, we have needed to set our maximums lower in the past to prevent resource
      * corruption. Therefore we have mImplMaxTextureSize, which is neither necessarily
--- a/dom/canvas/test/webgl-mochitest.ini
+++ b/dom/canvas/test/webgl-mochitest.ini
@@ -6,16 +6,17 @@ support-files =
   webgl-mochitest/driver-info.js
   webgl-mochitest/webgl-util.js
 
 [webgl-mochitest/test_backbuffer_channels.html]
 fail-if = (os == 'b2g')
 [webgl-mochitest/test_depth_readpixels.html]
 [webgl-mochitest/test_capture.html]
 support-files = captureStream_common.js
+[webgl-mochitest/test_cubemap_must_be_square.html]
 [webgl-mochitest/test_draw.html]
 [webgl-mochitest/test_fb_param.html]
 [webgl-mochitest/test_fb_param_crash.html]
 [webgl-mochitest/test_hidden_alpha.html]
 skip-if = (os == 'b2g') || buildapp == 'mulet' # Mulet - bug 1093639 (crashes in libLLVM-3.0.so)
 [webgl-mochitest/test_implicit_color_buffer_float.html]
 [webgl-mochitest/test_highp_fs.html]
 [webgl-mochitest/test_no_arr_points.html]
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/webgl-mochitest/test_cubemap_must_be_square.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta http-equiv='content-type' content='text/html; charset=utf-8'/>
+
+  <script src='/tests/SimpleTest/SimpleTest.js'></script>
+  <link rel='stylesheet' href='/tests/SimpleTest/test.css'>
+  <script src='webgl-util.js'></script>
+</head>
+
+<body>
+<script>
+'use strict';
+
+(function() {
+  var c = document.createElement('canvas');
+  var gl = c.getContext('webgl');
+
+  ok(!gl.getError(), 'No error before.');
+
+  var tex = gl.createTexture();
+  gl.bindTexture(gl.TEXTURE_CUBE_MAP, tex);
+  gl.texImage2D(gl.TEXTURE_CUBE_MAP_NEGATIVE_X, 0, gl.RGBA, 4, 3, 0, gl.RGBA,
+                gl.UNSIGNED_BYTE, null);
+
+  var err = gl.getError();
+  ok(err == gl.INVALID_VALUE,
+     'Should be INVALID_VALUE (0x501) after, was 0x' + err.toString(16) + '.');
+})();
+
+ok(true, 'Test complete.');
+
+</script>
+</body>
+</html>
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -259,31 +259,33 @@ NS_INTERFACE_MAP_END
 
 /******************************************************************/
 /* mozilla::EventStateManager                                     */
 /******************************************************************/
 
 static uint32_t sESMInstanceCount = 0;
 static bool sPointerEventEnabled = false;
 
+uint64_t EventStateManager::sUserInputCounter = 0;
 int32_t EventStateManager::sUserInputEventDepth = 0;
 bool EventStateManager::sNormalLMouseEventInProcess = false;
 EventStateManager* EventStateManager::sActiveESM = nullptr;
 nsIDocument* EventStateManager::sMouseOverDocument = nullptr;
 nsWeakFrame EventStateManager::sLastDragOverFrame = nullptr;
 LayoutDeviceIntPoint EventStateManager::sLastRefPoint = kInvalidRefPoint;
 CSSIntPoint EventStateManager::sLastScreenPoint = CSSIntPoint(0, 0);
 LayoutDeviceIntPoint EventStateManager::sSynthCenteringPoint = kInvalidRefPoint;
 CSSIntPoint EventStateManager::sLastClientPoint = CSSIntPoint(0, 0);
 bool EventStateManager::sIsPointerLocked = false;
 // Reference to the pointer locked element.
 nsWeakPtr EventStateManager::sPointerLockedElement;
 // Reference to the document which requested pointer lock.
 nsWeakPtr EventStateManager::sPointerLockedDoc;
 nsCOMPtr<nsIContent> EventStateManager::sDragOverContent = nullptr;
+TimeStamp EventStateManager::sLatestUserInputStart;
 TimeStamp EventStateManager::sHandlingInputStart;
 
 EventStateManager::WheelPrefs*
   EventStateManager::WheelPrefs::sInstance = nullptr;
 EventStateManager::DeltaAccumulator*
   EventStateManager::DeltaAccumulator::sInstance = nullptr;
 
 EventStateManager::EventStateManager()
--- a/dom/events/EventStateManager.h
+++ b/dom/events/EventStateManager.h
@@ -190,41 +190,63 @@ public:
 
   nsresult SetCursor(int32_t aCursor, imgIContainer* aContainer,
                      bool aHaveHotspot, float aHotspotX, float aHotspotY,
                      nsIWidget* aWidget, bool aLockCursor); 
 
   static void StartHandlingUserInput()
   {
     ++sUserInputEventDepth;
+    ++sUserInputCounter;
     if (sUserInputEventDepth == 1) {
-      sHandlingInputStart = TimeStamp::Now();
+      sLatestUserInputStart = sHandlingInputStart = TimeStamp::Now();
     }
   }
 
   static void StopHandlingUserInput()
   {
     --sUserInputEventDepth;
     if (sUserInputEventDepth == 0) {
       sHandlingInputStart = TimeStamp();
     }
   }
 
   /**
-   * Returns true if the current code is being executed as a result of user input.
-   * This includes timers or anything else that is initiated from user input.
-   * However, mouse over events are not counted as user input, nor are
-   * page load events. If this method is called from asynchronously executed code,
-   * such as during layout reflows, it will return false. If more time has elapsed
-   * since the user input than is specified by the
-   * dom.event.handling-user-input-time-limit pref (default 1 second), this
-   * function also returns false.
+   * Returns true if the current code is being executed as a result of
+   * user input.  This includes anything that is initiated by user,
+   * with the exception of page load events or mouse over events. If
+   * this method is called from asynchronously executed code, such as
+   * during layout reflows, it will return false. If more time has
+   * elapsed since the user input than is specified by the
+   * dom.event.handling-user-input-time-limit pref (default 1 second),
+   * this function also returns false.
    */
   static bool IsHandlingUserInput();
 
+  /**
+   * Get the number of user inputs handled since process start. This
+   * includes anything that is initiated by user, with the exception
+   * of page load events or mouse over events.
+   */
+  static uint64_t UserInputCount()
+  {
+    return sUserInputCounter;
+  }
+
+  /**
+   * Get the timestamp at which the latest user input was handled.
+   *
+   * Guaranteed to be monotonic. Until the first user input, return
+   * the epoch.
+   */
+  static TimeStamp LatestUserInputStart()
+  {
+    return sLatestUserInputStart;
+  }
+
   nsPresContext* GetPresContext() { return mPresContext; }
 
   NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(EventStateManager,
                                            nsIObserver)
 
   static nsIDocument* sMouseOverDocument;
 
   static EventStateManager* GetActiveEventStateManager() { return sActiveESM; }
@@ -916,27 +938,42 @@ private:
   RefPtr<IMEContentObserver> mIMEContentObserver;
 
   uint32_t mLClickCount;
   uint32_t mMClickCount;
   uint32_t mRClickCount;
 
   bool m_haveShutdown;
 
-  // Time at which we began handling user input.
+  // Time at which we began handling user input. Reset to the epoch
+  // once we have finished handling user input.
   static TimeStamp sHandlingInputStart;
 
+  // Time at which we began handling the latest user input. Not reset
+  // at the end of the input.
+  static TimeStamp sLatestUserInputStart;
+
   RefPtr<OverOutElementsWrapper> mMouseEnterLeaveHelper;
   nsRefPtrHashtable<nsUint32HashKey, OverOutElementsWrapper> mPointersEnterLeaveHelper;
 
 public:
   static nsresult UpdateUserActivityTimer(void);
   // Array for accesskey support
   nsCOMArray<nsIContent> mAccessKeys;
 
+  // The number of user inputs handled since process start. This
+  // includes anything that is initiated by user, with the exception
+  // of page load events or mouse over events.
+  static uint64_t sUserInputCounter;
+
+  // The current depth of user inputs. This includes anything that is
+  // initiated by user, with the exception of page load events or
+  // mouse over events. Incremented whenever we start handling a user
+  // input, decremented when we have finished handling a user
+  // input. This depth is *not* reset in case of nested event loops.
   static int32_t sUserInputEventDepth;
   
   static bool sNormalLMouseEventInProcess;
 
   static EventStateManager* sActiveESM;
   
   static void ClearGlobalActiveContent(EventStateManager* aClearer);
 
--- a/dom/html/HTMLImageElement.cpp
+++ b/dom/html/HTMLImageElement.cpp
@@ -1304,16 +1304,18 @@ HTMLImageElement::SelectSourceForTagWith
   // <source> tags with no match would leave source yet-undetermined.
   return false;
 }
 
 void
 HTMLImageElement::DestroyContent()
 {
   mResponsiveSelector = nullptr;
+
+  nsGenericHTMLElement::DestroyContent();
 }
 
 void
 HTMLImageElement::MediaFeatureValuesChanged()
 {
   QueueImageLoadTask(false);
 }
 
new file mode 100644
--- /dev/null
+++ b/dom/html/crashtests/1230110.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+// This test case should not leak.
+function leak()
+{
+    var img = document.createElement("img");
+    var iframe = document.createElement("iframe");
+    img.appendChild(iframe);
+    document.body.appendChild(img);
+
+    document.addEventListener('Foo', function(){}, false);
+}
+</script>
+</head>
+<body onload="leak();"></body>
+</html>
--- a/dom/html/crashtests/crashtests.list
+++ b/dom/html/crashtests/crashtests.list
@@ -70,8 +70,10 @@ load 862084.html
 load 865147.html
 load 877910.html
 load 903106.html
 load 916322-1.html
 load 916322-2.html
 load 1032654.html
 pref(dom.image.srcset.enabled,true) load 1141260.html
 load 1228876.html
+load 1230110.html
+
--- a/dom/indexedDB/moz.build
+++ b/dom/indexedDB/moz.build
@@ -102,8 +102,9 @@ LOCAL_INCLUDES += [
     '/dom/workers',
     '/ipc/glue',
     '/xpcom/build',
     '/xpcom/threads',
 ]
 
 if CONFIG['ENABLE_INTL_API']:
     CXXFLAGS += CONFIG['MOZ_ICU_CFLAGS']
+    LOCAL_INCLUDES += CONFIG['MOZ_ICU_INCLUDES']
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -2960,16 +2960,25 @@ ContentChild::RecvShutdown()
     nsCOMPtr<nsIObserverService> os = services::GetObserverService();
     if (os) {
         os->NotifyObservers(static_cast<nsIContentChild*>(this),
                             "content-child-shutdown", nullptr);
     }
 
     GetIPCChannel()->SetAbortOnError(false);
 
+#ifdef MOZ_ENABLE_PROFILER_SPS
+    if (profiler_is_active()) {
+        // We're shutting down while we were profiling. Send the
+        // profile up to the parent so that we don't lose this
+        // information.
+        Unused << RecvGatherProfile();
+    }
+#endif
+
     // Ignore errors here. If this fails, the parent will kill us after a
     // timeout.
     Unused << SendFinishShutdown();
     return true;
 }
 
 PBrowserOrId
 ContentChild::GetBrowserOrId(TabChild* aTabChild)
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -134,16 +134,17 @@
 #include "nsIIdleService.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIMemoryInfoDumper.h"
 #include "nsIMemoryReporter.h"
 #include "nsIMozBrowserFrame.h"
 #include "nsIMutable.h"
 #include "nsIObserverService.h"
 #include "nsIPresShell.h"
+#include "nsIRemoteWindowContext.h"
 #include "nsIScriptError.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsISiteSecurityService.h"
 #include "nsISpellChecker.h"
 #include "nsISupportsPrimitives.h"
 #include "nsISystemMessagesInternal.h"
 #include "nsITimer.h"
 #include "nsIURIFixup.h"
@@ -1564,16 +1565,21 @@ ContentParent::Init()
     DebugOnly<nsresult> rv = profiler->IsActive(&profilerActive);
     MOZ_ASSERT(NS_SUCCEEDED(rv));
 
     if (profilerActive) {
         nsCOMPtr<nsIProfilerStartParams> currentProfilerParams;
         rv = profiler->GetStartParams(getter_AddRefs(currentProfilerParams));
         MOZ_ASSERT(NS_SUCCEEDED(rv));
 
+        nsCOMPtr<nsISupports> gatherer;
+        rv = profiler->GetProfileGatherer(getter_AddRefs(gatherer));
+        MOZ_ASSERT(NS_SUCCEEDED(rv));
+        mGatherer = static_cast<ProfileGatherer*>(gatherer.get());
+
         StartProfiler(currentProfilerParams);
     }
 #endif
 }
 
 void
 ContentParent::ForwardKnownInfo()
 {
@@ -1681,16 +1687,57 @@ private:
 };
 
 StaticAutoPtr<LinkedList<SystemMessageHandledListener> >
     SystemMessageHandledListener::sListeners;
 
 NS_IMPL_ISUPPORTS(SystemMessageHandledListener,
                   nsITimerCallback)
 
+
+class RemoteWindowContext final : public nsIRemoteWindowContext
+                                , public nsIInterfaceRequestor
+{
+public:
+    explicit RemoteWindowContext(TabParent* aTabParent)
+    : mTabParent(aTabParent)
+    {
+    }
+
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSIINTERFACEREQUESTOR
+    NS_DECL_NSIREMOTEWINDOWCONTEXT
+
+private:
+    ~RemoteWindowContext();
+    RefPtr<TabParent> mTabParent;
+};
+
+NS_IMPL_ISUPPORTS(RemoteWindowContext, nsIRemoteWindowContext, nsIInterfaceRequestor)
+
+RemoteWindowContext::~RemoteWindowContext()
+{
+}
+
+NS_IMETHODIMP
+RemoteWindowContext::GetInterface(const nsIID& aIID, void** aSink)
+{
+    return QueryInterface(aIID, aSink);
+}
+
+NS_IMETHODIMP
+RemoteWindowContext::OpenURI(nsIURI* aURI, uint32_t aFlags)
+{
+    URIParams uri;
+    SerializeURI(aURI, uri);
+
+    Unused << mTabParent->SendOpenURI(uri, aFlags);
+    return NS_OK;
+}
+
 } // namespace
 
 void
 ContentParent::MaybeTakeCPUWakeLock(Element* aFrameElement)
 {
     // Take the CPU wake lock on behalf of this processs if it's expecting a
     // system message.  We'll release the CPU lock once the message is
     // delivered, or after some period of time, which ever comes first.
@@ -2084,16 +2131,22 @@ ContentParent::ActorDestroy(ActorDestroy
         sNuwaPrefUpdates = nullptr;
     }
 #endif
 
     RecvRemoveGeolocationListener();
 
     mConsoleService = nullptr;
 
+#ifdef MOZ_ENABLE_PROFILER_SPS
+    if (mGatherer && !mProfile.IsEmpty()) {
+        mGatherer->OOPExitProfile(mProfile);
+    }
+#endif
+
     if (obs) {
         RefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
 
         props->SetPropertyAsUint64(NS_LITERAL_STRING("childID"), mChildID);
 
         if (AbnormalShutdown == why) {
             Telemetry::Accumulate(Telemetry::SUBPROCESS_ABNORMAL_ABORT,
                                   NS_LITERAL_CSTRING("content"), 1);
@@ -3288,28 +3341,30 @@ ContentParent::Observe(nsISupports* aSub
         Unused << SendOnAppThemeChanged();
     }
 #ifdef MOZ_ENABLE_PROFILER_SPS
     else if (!strcmp(aTopic, "profiler-started")) {
         nsCOMPtr<nsIProfilerStartParams> params(do_QueryInterface(aSubject));
         StartProfiler(params);
     }
     else if (!strcmp(aTopic, "profiler-stopped")) {
+        mGatherer = nullptr;
         Unused << SendStopProfiler();
     }
     else if (!strcmp(aTopic, "profiler-paused")) {
         Unused << SendPauseProfiler(true);
     }
     else if (!strcmp(aTopic, "profiler-resumed")) {
         Unused << SendPauseProfiler(false);
     }
     else if (!strcmp(aTopic, "profiler-subprocess-gather")) {
-        mGatherer = static_cast<ProfileGatherer*>(aSubject);
-        mGatherer->WillGatherOOPProfile();
-        Unused << SendGatherProfile();
+        if (mGatherer) {
+            mGatherer->WillGatherOOPProfile();
+            Unused << SendGatherProfile();
+        }
     }
     else if (!strcmp(aTopic, "profiler-subprocess")) {
         nsCOMPtr<nsIProfileSaveEvent> pse = do_QueryInterface(aSubject);
         if (pse) {
             if (!mProfile.IsEmpty()) {
                 pse->AddSubProfile(mProfile.get());
                 mProfile.Truncate();
             }
@@ -4303,27 +4358,31 @@ ContentParent::RecvAccumulateMixedConten
     if (!ourURI) {
         return false;
     }
     nsMixedContentBlocker::AccumulateMixedContentHSTS(ourURI, aActive);
     return true;
 }
 
 bool
-ContentParent::RecvLoadURIExternal(const URIParams& uri)
+ContentParent::RecvLoadURIExternal(const URIParams& uri,
+                                   PBrowserParent* windowContext)
 {
     nsCOMPtr<nsIExternalProtocolService> extProtService(do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID));
     if (!extProtService) {
         return true;
     }
     nsCOMPtr<nsIURI> ourURI = DeserializeURI(uri);
     if (!ourURI) {
         return false;
     }
-    extProtService->LoadURI(ourURI, nullptr);
+
+    RefPtr<RemoteWindowContext> context =
+        new RemoteWindowContext(static_cast<TabParent*>(windowContext));
+    extProtService->LoadURI(ourURI, context);
     return true;
 }
 
 bool
 ContentParent::HasNotificationPermission(const IPC::Principal& aPrincipal)
 {
 #ifdef MOZ_CHILD_PERMISSIONS
     uint32_t permission = mozilla::CheckPermission(this, aPrincipal,
@@ -5651,17 +5710,16 @@ bool
 ContentParent::RecvProfile(const nsCString& aProfile)
 {
 #ifdef MOZ_ENABLE_PROFILER_SPS
     if (NS_WARN_IF(!mGatherer)) {
         return true;
     }
     mProfile = aProfile;
     mGatherer->GatheredOOPProfile();
-    mGatherer = nullptr;
 #endif
     return true;
 }
 
 bool
 ContentParent::RecvGetGraphicsDeviceInitData(DeviceInitData* aOut)
 {
   gfxPlatform::GetPlatform()->GetDeviceInitData(aOut);
@@ -5742,16 +5800,24 @@ ContentParent::StartProfiler(nsIProfiler
 
     ipcParams.enabled() = true;
     aParams->GetEntries(&ipcParams.entries());
     aParams->GetInterval(&ipcParams.interval());
     ipcParams.features() = aParams->GetFeatures();
     ipcParams.threadFilters() = aParams->GetThreadFilterNames();
 
     Unused << SendStartProfiler(ipcParams);
+
+    nsCOMPtr<nsIProfiler> profiler(do_GetService("@mozilla.org/tools/profiler;1"));
+    if (NS_WARN_IF(!profiler)) {
+        return;
+    }
+    nsCOMPtr<nsISupports> gatherer;
+    profiler->GetProfileGatherer(getter_AddRefs(gatherer));
+    mGatherer = static_cast<ProfileGatherer*>(gatherer.get());
 #endif
 }
 
 } // namespace dom
 } // namespace mozilla
 
 NS_IMPL_ISUPPORTS(ParentIdleListener, nsIObserver)
 
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -793,17 +793,18 @@ private:
 
     virtual bool RecvCloseAlert(const nsString& aName,
                                 const IPC::Principal& aPrincipal) override;
 
     virtual bool RecvDisableNotifications(const IPC::Principal& aPrincipal) override;
 
     virtual bool RecvOpenNotificationSettings(const IPC::Principal& aPrincipal) override;
 
-    virtual bool RecvLoadURIExternal(const URIParams& uri) override;
+    virtual bool RecvLoadURIExternal(const URIParams& uri,
+                                     PBrowserParent* windowContext) override;
 
     virtual bool RecvSyncMessage(const nsString& aMsg,
                                  const ClonedMessageData& aData,
                                  InfallibleTArray<CpowEntry>&& aCpows,
                                  const IPC::Principal& aPrincipal,
                                  nsTArray<StructuredCloneData>* aRetvals) override;
     virtual bool RecvRpcMessage(const nsString& aMsg,
                                 const ClonedMessageData& aData,
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -551,16 +551,18 @@ child:
          ShowInfo info,
          TextureFactoryIdentifier textureFactoryIdentifier,
          uint64_t layersId,
          nullable PRenderFrame renderFrame,
          bool parentIsActive);
 
     LoadURL(nsCString uri, BrowserConfiguration config, ShowInfo info);
 
+    OpenURI(URIParams uri, uint32_t flags);
+
     CacheFileDescriptor(nsString path, FileDescriptor fd);
 
     UpdateDimensions(CSSRect rect, CSSSize size, nsSizeMode sizeMode,
                      ScreenOrientationInternal orientation,
                      LayoutDeviceIntPoint chromeDisp) compressall;
 
     UpdateFrame(FrameMetrics frame);
 
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -847,17 +847,17 @@ parent:
     PPresentation();
 
     // Services remoting
 
     async StartVisitedQuery(URIParams uri);
     async VisitURI(URIParams uri, OptionalURIParams referrer, uint32_t flags);
     async SetURITitle(URIParams uri, nsString title);
 
-    async LoadURIExternal(URIParams uri);
+    async LoadURIExternal(URIParams uri, PBrowser windowContext);
 
     // PrefService message
     sync ReadPrefsArray() returns (PrefSetting[] prefs);
 
     sync ReadFontList() returns (FontListEntry[] retValue);
 
     sync ReadDataStorageArray(nsString aFilename)
       returns (DataStorageItem[] retValue);
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -18,16 +18,17 @@
 #include "mozilla/Preferences.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/EventListenerManager.h"
 #include "mozilla/dom/workers/ServiceWorkerManager.h"
 #include "mozilla/dom/indexedDB/PIndexedDBPermissionRequestChild.h"
 #include "mozilla/plugins/PluginWidgetChild.h"
 #include "mozilla/IMEStateManager.h"
 #include "mozilla/ipc/DocumentRendererChild.h"
+#include "mozilla/ipc/URIUtils.h"
 #ifdef MOZ_NUWA_PROCESS
 #include "ipc/Nuwa.h"
 #endif
 #include "mozilla/ipc/FileDescriptorUtils.h"
 #include "mozilla/layers/APZCCallbackHelper.h"
 #include "mozilla/layers/APZCTreeManager.h"
 #include "mozilla/layers/APZEventState.h"
 #include "mozilla/layers/CompositorChild.h"
@@ -96,16 +97,17 @@
 #include "nsIOService.h"
 #include "nsDOMClassInfoID.h"
 #include "nsColorPickerProxy.h"
 #include "nsContentPermissionHelper.h"
 #include "nsPresShell.h"
 #include "nsIAppsService.h"
 #include "nsNetUtil.h"
 #include "nsIPermissionManager.h"
+#include "nsIURILoader.h"
 #include "nsIScriptError.h"
 #include "mozilla/EventForwards.h"
 #include "nsDeviceContext.h"
 
 #define BROWSER_ELEMENT_CHILD_SCRIPT \
     NS_LITERAL_STRING("chrome://global/content/BrowserElementChild.js")
 
 #define TABC_LOG(...)
@@ -1259,16 +1261,41 @@ TabChild::RecvLoadURL(const nsCString& a
 #ifdef MOZ_CRASHREPORTER
     CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("URL"), aURI);
 #endif
 
     return true;
 }
 
 bool
+TabChild::RecvOpenURI(const URIParams& aURI, const uint32_t& aFlags)
+{
+  nsCOMPtr<nsIURI> uri = DeserializeURI(aURI);
+  nsCOMPtr<nsIChannel> channel;
+  nsresult rv =
+    NS_NewChannel(getter_AddRefs(channel),
+                  uri,
+                  nsContentUtils::GetSystemPrincipal(),
+                  nsILoadInfo::SEC_NORMAL,
+                  nsIContentPolicy::TYPE_DOCUMENT);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return true;
+  }
+
+  nsCOMPtr<nsIURILoader> loader = do_GetService("@mozilla.org/uriloader;1");
+  if (NS_WARN_IF(!loader)) {
+    return true;
+  }
+
+  nsCOMPtr<nsIInterfaceRequestor> context(do_QueryInterface(WebNavigation()));
+  loader->OpenURI(channel, aFlags, context);
+  return true;
+}
+
+bool
 TabChild::RecvCacheFileDescriptor(const nsString& aPath,
                                   const FileDescriptor& aFileDescriptor)
 {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(!aPath.IsEmpty());
     MOZ_ASSERT(!mAppPackageFileDescriptorRecved);
 
     mAppPackageFileDescriptorRecved = true;
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -296,16 +296,18 @@ public:
                                         JS::Handle<JSObject *> aCpows,
                                         nsIPrincipal* aPrincipal) override;
     virtual bool DoUpdateZoomConstraints(const uint32_t& aPresShellId,
                                          const ViewID& aViewId,
                                          const Maybe<ZoomConstraints>& aConstraints) override;
     virtual bool RecvLoadURL(const nsCString& aURI,
                              const BrowserConfiguration& aConfiguration,
                              const ShowInfo& aInfo) override;
+    virtual bool RecvOpenURI(const URIParams& aURI,
+                             const uint32_t& aFlags) override;
     virtual bool RecvCacheFileDescriptor(const nsString& aPath,
                                          const FileDescriptor& aFileDescriptor)
                                          override;
     virtual bool RecvShow(const ScreenIntSize& aSize,
                           const ShowInfo& aInfo,
                           const TextureFactoryIdentifier& aTextureFactoryIdentifier,
                           const uint64_t& aLayersId,
                           PRenderFrameChild* aRenderFrame,
--- a/dom/media/AudioStream.h
+++ b/dom/media/AudioStream.h
@@ -83,17 +83,17 @@ public:
     : mBuffer(nullptr), mCapacity(0), mStart(0), mCount(0)
   {}
 
   // Set the capacity of the buffer in bytes.  Must be called before any
   // call to append or pop elements.
   void SetCapacity(uint32_t aCapacity) {
     MOZ_ASSERT(!mBuffer, "Buffer allocated.");
     mCapacity = aCapacity;
-    mBuffer = new uint8_t[mCapacity];
+    mBuffer = MakeUnique<uint8_t[]>(mCapacity);
   }
 
   uint32_t Length() {
     return mCount;
   }
 
   uint32_t Capacity() {
     return mCapacity;
@@ -132,22 +132,22 @@ public:
     mCount -= *aSize1 + *aSize2;
     mStart += *aSize1 + *aSize2;
     mStart %= mCapacity;
   }
 
   size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
   {
     size_t amount = 0;
-    amount += mBuffer.SizeOfExcludingThis(aMallocSizeOf);
+    amount += aMallocSizeOf(mBuffer.get());
     return amount;
   }
 
 private:
-  nsAutoArrayPtr<uint8_t> mBuffer;
+  UniquePtr<uint8_t[]> mBuffer;
   uint32_t mCapacity;
   uint32_t mStart;
   uint32_t mCount;
 };
 
 // Access to a single instance of this class must be synchronized by
 // callers, or made from a single thread.  One exception is that access to
 // GetPosition, GetPositionInFrames, SetVolume, and Get{Rate,Channels},
--- a/dom/media/DecoderTraits.cpp
+++ b/dom/media/DecoderTraits.cpp
@@ -149,42 +149,38 @@ IsWaveType(const nsACString& aType)
     return false;
   }
 
   return CodecListContains(gWaveTypes, aType);
 }
 #endif
 
 #ifdef MOZ_WEBM
-static const char* const gWebMTypes[3] = {
-  "video/webm",
-  "audio/webm",
-  nullptr
-};
-
-static char const *const gWebMCodecs[7] = {
-  "vp8",
-  "vp8.0",
-  "vp9",
-  "vp9.0",
-  "vorbis",
-  "opus",
-  nullptr
-};
+static bool
+IsWebMSupportedType(const nsACString& aType,
+                    const nsAString& aCodecs = EmptyString())
+{
+  return WebMDecoder::CanHandleMediaType(aType, aCodecs);
+}