Merge m-c to b2g-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 12 Sep 2014 15:30:24 +0200
changeset 205180 eda409b0d4188774ec87d45d4f5ac53ac397aae3
parent 205179 15b8fb767f2867cc58d4b84f56d549ee4c9c201a (current diff)
parent 205061 59d4326311e07f7118794857c77435e06b874acb (diff)
child 205181 c141502dfb265240b8a6d4f9110c702b2a5b8a8d
push id49106
push userphilringnalda@gmail.com
push dateSat, 13 Sep 2014 17:12:34 +0000
treeherdermozilla-inbound@ab04d0f2665f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone35.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to b2g-inbound
security/manager/ssl/public/nsICertificatePrincipal.idl
security/manager/ssl/src/nsCertificatePrincipal.cpp
security/manager/ssl/src/nsCertificatePrincipal.h
toolkit/modules/AsyncShutdown.jsm
toolkit/modules/tests/xpcshell/test_AsyncShutdown.js
--- a/accessible/tests/mochitest/common.js
+++ b/accessible/tests/mochitest/common.js
@@ -234,17 +234,17 @@ function getAccessible(aAccOrElmOrID, aI
   if (!acc) {
     try {
       acc = gAccRetrieval.getAccessibleFor(elm);
     } catch (e) {
     }
 
     if (!acc) {
       if (!(aDoNotFailIf & DONOTFAIL_IF_NO_ACC))
-        ok(false, "Can't get accessible for " + aAccOrElmOrID);
+        ok(false, "Can't get accessible for " + prettyName(aAccOrElmOrID));
 
       return null;
     }
   }
 
   if (!aInterfaces)
     return acc;
 
--- a/accessible/tests/mochitest/relations/test_embeds.xul
+++ b/accessible/tests/mochitest/relations/test_embeds.xul
@@ -54,17 +54,21 @@
 
     function browserReorderChecker()
     {
       this.type = EVENT_REORDER;
 
       this.match = function browserReorderChecker_match(aEvent)
       {
         // Reorder event might be duped because of temporary document creation.
-        if (aEvent.accessible == getAccessible(currentBrowser())) {
+        var browserAcc = getAccessible(currentBrowser());
+        if (!browserAcc)
+          ok(false, "opa opa sralslasya");
+
+        if (aEvent.accessible == browserAcc) {
           this.cnt++;
           return this.cnt != 2;
         }
 
         return false;
       }
 
       this.cnt = 0;
@@ -78,18 +82,21 @@
       }
 
       this.eventSeq = [
         new browserReorderChecker()
       ];
 
       this.finalCheck = function loadURI_finalCheck()
       {
-        testRelation(browserDocument(), RELATION_EMBEDS,
-                     getAccessible(currentTabDocument()));
+        var acc = getAccessible(currentTabDocument());
+        if (!acc)
+          ok(false, "ahahahaha");
+
+        testRelation(browserDocument(), RELATION_EMBEDS, acc);
       }
 
       this.getID = function loadOneTab_getID()
       {
         return "load uri '" + aURI + "' in new tab";
       }
     }
 
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -288,16 +288,17 @@
 #ifdef MOZ_CAPTIVEDETECT
 @BINPATH@/components/captivedetect.xpt
 #endif
 @BINPATH@/components/shellservice.xpt
 @BINPATH@/components/shistory.xpt
 @BINPATH@/components/spellchecker.xpt
 @BINPATH@/components/storage.xpt
 @BINPATH@/components/telemetry.xpt
+@BINPATH@/components/toolkit_asyncshutdown.xpt
 @BINPATH@/components/toolkit_filewatcher.xpt
 @BINPATH@/components/toolkit_finalizationwitness.xpt
 @BINPATH@/components/toolkit_formautofill.xpt
 @BINPATH@/components/toolkit_osfile.xpt
 @BINPATH@/components/toolkit_xulstore.xpt
 @BINPATH@/components/toolkitprofile.xpt
 #ifdef MOZ_ENABLE_XREMOTE
 @BINPATH@/components/toolkitremote.xpt
@@ -391,16 +392,18 @@
 @BINPATH@/components/jsconsole-clhandler.manifest
 @BINPATH@/components/jsconsole-clhandler.js
 @BINPATH@/components/nsDownloadManagerUI.manifest
 @BINPATH@/components/nsDownloadManagerUI.js
 @BINPATH@/components/Downloads.manifest
 @BINPATH@/components/DownloadLegacy.js
 @BINPATH@/components/nsSidebar.manifest
 @BINPATH@/components/nsSidebar.js
+@BINPATH@/components/nsAsyncShutdown.manifest
+@BINPATH@/components/nsAsyncShutdown.js
 
 ; WiFi, NetworkManager, NetworkStats
 #ifdef MOZ_WIDGET_GONK
 @BINPATH@/components/DOMWifiManager.js
 @BINPATH@/components/DOMWifiManager.manifest
 @BINPATH@/components/DOMWifiP2pManager.js
 @BINPATH@/components/DOMWifiP2pManager.manifest
 @BINPATH@/components/NetworkInterfaceListService.js
new file mode 100644
--- /dev/null
+++ b/browser/app/default_permissions
@@ -0,0 +1,9 @@
+# This file has default permissions for the permission manager.
+# The file-format is strict:
+# * matchtype \t type \t permission \t host
+# * Only "host" is supported for matchtype
+# * type is a string that identifies the type of permission (e.g. "cookie")
+# * permission is an integer between 1 and 15
+# See nsPermissionManager.cpp for more...
+
+# (This file is intentionally blank for the moment...)
new file mode 100644
--- /dev/null
+++ b/browser/app/jar.mn
@@ -0,0 +1,4 @@
+browser.jar:
+
+# The file that holds the default permissions (which is loaded by nsPermissionManager) for the browser.
+    default_permissions (default_permissions)
--- a/browser/app/moz.build
+++ b/browser/app/moz.build
@@ -67,8 +67,10 @@ else:
 
 DISABLE_STL_WRAPPING = True
 
 if CONFIG['MOZ_LINKER']:
     OS_LIBS += CONFIG['MOZ_ZLIB_LIBS']
 
 if CONFIG['HAVE_CLOCK_MONOTONIC']:
     OS_LIBS += CONFIG['REALTIME_LIBS']
+
+JAR_MANIFESTS += ['jar.mn']
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -181,17 +181,17 @@ skip-if = e10s # Bug 918663 - DOMLinkAdd
 [browser_bug533232.js]
 [browser_bug537013.js]
 skip-if = buildapp == 'mulet' || e10s # Bug 918634 - swapFrameLoaders not implemented for e10s (test calls replaceTabWithWindow)
 [browser_bug537474.js]
 skip-if = e10s # Bug ?????? - test doesn't wait for document to be created before it checks it
 [browser_bug550565.js]
 skip-if = e10s # Bug 918663 - DOMLinkAdded events don't make their way to chrome (which is how gBrowser.getIcon works)
 [browser_bug553455.js]
-skip-if = buildapp == 'mulet' || e10s # Bug ????? - I don't think either popup notifications nor addon install stuff works?
+skip-if = buildapp == 'mulet' || e10s # Bug 1066070 - I don't think either popup notifications nor addon install stuff works?
 [browser_bug555224.js]
 skip-if = e10s # Bug 691614 - no e10s zoom support yet
 [browser_bug555767.js]
 skip-if = e10s # Bug 916974 - Session history doesn't work in e10s
 [browser_bug556061.js]
 skip-if = e10s # Bug 932651 - getClipboardData in specialpowersAPI.js not e10s friendly
 [browser_bug559991.js]
 skip-if = e10s # Bug 691614 - no e10s zoom support yet
--- a/browser/base/content/test/general/mochitest.ini
+++ b/browser/base/content/test/general/mochitest.ini
@@ -30,9 +30,11 @@ support-files =
 [test_bug364677.html]
 [test_bug395533.html]
 [test_contextmenu.html]
 skip-if = toolkit == "gtk2" || toolkit == "gtk3" # disabled on Linux due to bug 513558
 [test_contextmenu_input.html]
 skip-if = toolkit == "gtk2" || toolkit == "gtk3" # disabled on Linux due to bug 513558
 [test_feed_discovery.html]
 [test_offlineNotification.html]
+skip-if = buildapp == 'mulet' # Bug 1066070 - I don't think either popup notifications nor addon install stuff works?
 [test_offline_gzip.html]
+skip-if = buildapp == 'mulet' # Bug 1066070 - I don't think either popup notifications nor addon install stuff works?
--- a/browser/components/loop/MozLoopService.jsm
+++ b/browser/components/loop/MozLoopService.jsm
@@ -556,17 +556,26 @@ let MozLoopServiceInternal = {
   },
 
   /**
    * Fetch Firefox Accounts (FxA) OAuth parameters from the Loop Server.
    *
    * @return {Promise} resolved with the body of the hawk request for OAuth parameters.
    */
   promiseFxAOAuthParameters: function() {
-    return this.hawkRequest(LOOP_SESSION_TYPE.FXA, "/fxa-oauth/params", "POST").then(response => {
+    const SESSION_TYPE = LOOP_SESSION_TYPE.FXA;
+    return this.hawkRequest(SESSION_TYPE, "/fxa-oauth/params", "POST").then(response => {
+      if (!this.storeSessionToken(SESSION_TYPE, response.headers)) {
+        throw new Error("Invalid FxA hawk token returned");
+      }
+      let prefType = Services.prefs.getPrefType(this.getSessionTokenPrefName(SESSION_TYPE));
+      if (prefType == Services.prefs.PREF_INVALID) {
+        throw new Error("No FxA hawk token returned and we don't have one saved");
+      }
+
       return JSON.parse(response.body);
     });
   },
 
   /**
    * Get the OAuth client constructed with Loop OAauth parameters.
    *
    * @return {Promise}
--- a/browser/components/loop/content/conversation.html
+++ b/browser/components/loop/content/conversation.html
@@ -30,16 +30,17 @@
     <script type="text/javascript" src="loop/shared/libs/react-0.11.1.js"></script>
     <script type="text/javascript" src="loop/shared/libs/jquery-2.1.0.js"></script>
     <script type="text/javascript" src="loop/shared/libs/lodash-2.4.1.js"></script>
     <script type="text/javascript" src="loop/shared/libs/backbone-1.1.2.js"></script>
 
     <script type="text/javascript" src="loop/shared/js/utils.js"></script>
     <script type="text/javascript" src="loop/shared/js/models.js"></script>
     <script type="text/javascript" src="loop/shared/js/router.js"></script>
+    <script type="text/javascript" src="loop/shared/js/mixins.js"></script>
     <script type="text/javascript" src="loop/shared/js/views.js"></script>
     <script type="text/javascript" src="loop/shared/js/feedbackApiClient.js"></script>
     <script type="text/javascript" src="loop/shared/js/websocket.js"></script>
     <script type="text/javascript" src="loop/js/client.js"></script>
     <script type="text/javascript" src="loop/js/desktopRouter.js"></script>
     <script type="text/javascript" src="loop/js/conversation.js"></script>
   </body>
 </html>
--- a/browser/components/loop/content/js/panel.js
+++ b/browser/components/loop/content/js/panel.js
@@ -6,20 +6,20 @@
 
 /*jshint newcap:false*/
 /*global loop:true, React */
 
 var loop = loop || {};
 loop.panel = (function(_, mozL10n) {
   "use strict";
 
-  var sharedViews = loop.shared.views,
-      sharedModels = loop.shared.models,
-      // aliasing translation function as __ for concision
-      __ = mozL10n.get;
+  var sharedViews = loop.shared.views;
+  var sharedModels = loop.shared.models;
+  var sharedMixins = loop.shared.mixins;
+  var __ = mozL10n.get; // aliasing translation function as __ for concision
 
   /**
    * Panel router.
    * @type {loop.desktopRouter.DesktopRouter}
    */
   var router;
 
   var TabView = React.createClass({displayName: 'TabView',
@@ -69,50 +69,20 @@ loop.panel = (function(_, mozL10n) {
 
   var Tab = React.createClass({displayName: 'Tab',
     render: function() {
       return null;
     }
   });
 
   /**
-   * Dropdown menu mixin.
-   * @type {Object}
-   */
-  var DropdownMenuMixin = {
-    getInitialState: function() {
-      return {showMenu: false};
-    },
-
-    _onBodyClick: function() {
-      this.setState({showMenu: false});
-    },
-
-    componentDidMount: function() {
-      document.body.addEventListener("click", this._onBodyClick);
-    },
-
-    componentWillUnmount: function() {
-      document.body.removeEventListener("click", this._onBodyClick);
-    },
-
-    showDropdownMenu: function() {
-      this.setState({showMenu: true});
-    },
-
-    hideDropdownMenu: function() {
-      this.setState({showMenu: false});
-    }
-  };
-
-  /**
    * Availability drop down menu subview.
    */
   var AvailabilityDropdown = React.createClass({displayName: 'AvailabilityDropdown',
-    mixins: [DropdownMenuMixin],
+    mixins: [sharedMixins.DropdownMenuMixin],
 
     getInitialState: function() {
       return {
         doNotDisturb: navigator.mozLoop.doNotDisturb
       };
     },
 
     // XXX target event can either be the li, the span or the i tag
@@ -234,17 +204,17 @@ loop.panel = (function(_, mozL10n) {
       );
     }
   });
 
   /**
    * Panel settings (gear) menu.
    */
   var SettingsDropdown = React.createClass({displayName: 'SettingsDropdown',
-    mixins: [DropdownMenuMixin],
+    mixins: [sharedMixins.DropdownMenuMixin],
 
     handleClickSettingsEntry: function() {
       // XXX to be implemented
     },
 
     handleClickAccountEntry: function() {
       // XXX to be implemented
     },
@@ -304,17 +274,22 @@ loop.panel = (function(_, mozL10n) {
           React.DOM.div({className: "action"}, 
             this.props.children
           )
         )
       );
     }
   });
 
+  /**
+   * Call url result view.
+   */
   var CallUrlResult = React.createClass({displayName: 'CallUrlResult',
+    mixins: [sharedMixins.DocumentVisibilityMixin],
+
     propTypes: {
       callUrl:        React.PropTypes.string,
       callUrlExpiry:  React.PropTypes.number,
       notifications:  React.PropTypes.object.isRequired,
       client:         React.PropTypes.object.isRequired
     },
 
     getInitialState: function() {
@@ -322,31 +297,46 @@ loop.panel = (function(_, mozL10n) {
         pending: false,
         copied: false,
         callUrl: this.props.callUrl || "",
         callUrlExpiry: 0
       };
     },
 
     /**
+     * Provided by DocumentVisibilityMixin. Schedules retrieval of a new call
+     * URL everytime the panel is reopened.
+     */
+    onDocumentVisible: function() {
+      this._fetchCallUrl();
+    },
+
+    /**
     * Returns a random 5 character string used to identify
     * the conversation.
     * XXX this will go away once the backend changes
     */
     conversationIdentifier: function() {
       return Math.random().toString(36).substring(5);
     },
 
     componentDidMount: function() {
       // If we've already got a callURL, don't bother requesting a new one.
       // As of this writing, only used for visual testing in the UI showcase.
       if (this.state.callUrl.length) {
         return;
       }
 
+      this._fetchCallUrl();
+    },
+
+    /**
+     * Fetches a call URL.
+     */
+    _fetchCallUrl: function() {
       this.setState({pending: true});
       this.props.client.requestCallUrl(this.conversationIdentifier(),
                                        this._onCallUrlReceived);
     },
 
     _onCallUrlReceived: function(err, callUrlData) {
       this.props.notifications.reset();
 
@@ -469,17 +459,18 @@ loop.panel = (function(_, mozL10n) {
       callUrl: React.PropTypes.string
     },
 
     render: function() {
       var NotificationListView = sharedViews.NotificationListView;
 
       return (
         React.DOM.div(null, 
-          NotificationListView({notifications: this.props.notifications}), 
+          NotificationListView({notifications: this.props.notifications, 
+                                clearOnDocumentHidden: true}), 
           TabView({onSelect: this.selectTab}, 
             Tab({name: "call"}, 
               CallUrlResult({client: this.props.client, 
                              notifications: this.props.notifications, 
                              callUrl: this.props.callUrl}), 
               ToSView(null)
             ), 
             Tab({name: "contacts"}, 
@@ -507,52 +498,22 @@ loop.panel = (function(_, mozL10n) {
       "": "home"
     },
 
     initialize: function(options) {
       options = options || {};
       if (!options.document) {
         throw new Error("missing required document");
       }
-      this.document = options.document;
-
-      this._registerVisibilityChangeEvent();
-
-      this.on("panel:open", this.reset, this);
-    },
-
-    /**
-     * Register the DOM visibility API event for the whole document, and trigger
-     * appropriate events accordingly:
-     *
-     * - `panel:opened` when the panel is open
-     * - `panel:closed` when the panel is closed
-     *
-     * @link  http://www.w3.org/TR/page-visibility/
-     */
-    _registerVisibilityChangeEvent: function() {
-      // XXX pass in the visibility status to detect when to generate a new
-      // panel view
-      this.document.addEventListener("visibilitychange", function(event) {
-        this.trigger(event.currentTarget.hidden ? "panel:closed"
-                                                : "panel:open");
-      }.bind(this));
     },
 
     /**
      * Default entry point.
      */
     home: function() {
-      this.reset();
-    },
-
-    /**
-     * Resets this router to its initial state.
-     */
-    reset: function() {
       this._notifications.reset();
       var client = new loop.Client({
         baseServerUrl: navigator.mozLoop.serverUrl
       });
       this.loadReactComponent(
           PanelView({client: client, notifications: this._notifications}));
     }
   });
--- a/browser/components/loop/content/js/panel.jsx
+++ b/browser/components/loop/content/js/panel.jsx
@@ -6,20 +6,20 @@
 
 /*jshint newcap:false*/
 /*global loop:true, React */
 
 var loop = loop || {};
 loop.panel = (function(_, mozL10n) {
   "use strict";
 
-  var sharedViews = loop.shared.views,
-      sharedModels = loop.shared.models,
-      // aliasing translation function as __ for concision
-      __ = mozL10n.get;
+  var sharedViews = loop.shared.views;
+  var sharedModels = loop.shared.models;
+  var sharedMixins = loop.shared.mixins;
+  var __ = mozL10n.get; // aliasing translation function as __ for concision
 
   /**
    * Panel router.
    * @type {loop.desktopRouter.DesktopRouter}
    */
   var router;
 
   var TabView = React.createClass({
@@ -69,50 +69,20 @@ loop.panel = (function(_, mozL10n) {
 
   var Tab = React.createClass({
     render: function() {
       return null;
     }
   });
 
   /**
-   * Dropdown menu mixin.
-   * @type {Object}
-   */
-  var DropdownMenuMixin = {
-    getInitialState: function() {
-      return {showMenu: false};
-    },
-
-    _onBodyClick: function() {
-      this.setState({showMenu: false});
-    },
-
-    componentDidMount: function() {
-      document.body.addEventListener("click", this._onBodyClick);
-    },
-
-    componentWillUnmount: function() {
-      document.body.removeEventListener("click", this._onBodyClick);
-    },
-
-    showDropdownMenu: function() {
-      this.setState({showMenu: true});
-    },
-
-    hideDropdownMenu: function() {
-      this.setState({showMenu: false});
-    }
-  };
-
-  /**
    * Availability drop down menu subview.
    */
   var AvailabilityDropdown = React.createClass({
-    mixins: [DropdownMenuMixin],
+    mixins: [sharedMixins.DropdownMenuMixin],
 
     getInitialState: function() {
       return {
         doNotDisturb: navigator.mozLoop.doNotDisturb
       };
     },
 
     // XXX target event can either be the li, the span or the i tag
@@ -234,17 +204,17 @@ loop.panel = (function(_, mozL10n) {
       );
     }
   });
 
   /**
    * Panel settings (gear) menu.
    */
   var SettingsDropdown = React.createClass({
-    mixins: [DropdownMenuMixin],
+    mixins: [sharedMixins.DropdownMenuMixin],
 
     handleClickSettingsEntry: function() {
       // XXX to be implemented
     },
 
     handleClickAccountEntry: function() {
       // XXX to be implemented
     },
@@ -304,17 +274,22 @@ loop.panel = (function(_, mozL10n) {
           <div className="action">
             {this.props.children}
           </div>
         </div>
       );
     }
   });
 
+  /**
+   * Call url result view.
+   */
   var CallUrlResult = React.createClass({
+    mixins: [sharedMixins.DocumentVisibilityMixin],
+
     propTypes: {
       callUrl:        React.PropTypes.string,
       callUrlExpiry:  React.PropTypes.number,
       notifications:  React.PropTypes.object.isRequired,
       client:         React.PropTypes.object.isRequired
     },
 
     getInitialState: function() {
@@ -322,31 +297,46 @@ loop.panel = (function(_, mozL10n) {
         pending: false,
         copied: false,
         callUrl: this.props.callUrl || "",
         callUrlExpiry: 0
       };
     },
 
     /**
+     * Provided by DocumentVisibilityMixin. Schedules retrieval of a new call
+     * URL everytime the panel is reopened.
+     */
+    onDocumentVisible: function() {
+      this._fetchCallUrl();
+    },
+
+    /**
     * Returns a random 5 character string used to identify
     * the conversation.
     * XXX this will go away once the backend changes
     */
     conversationIdentifier: function() {
       return Math.random().toString(36).substring(5);
     },
 
     componentDidMount: function() {
       // If we've already got a callURL, don't bother requesting a new one.
       // As of this writing, only used for visual testing in the UI showcase.
       if (this.state.callUrl.length) {
         return;
       }
 
+      this._fetchCallUrl();
+    },
+
+    /**
+     * Fetches a call URL.
+     */
+    _fetchCallUrl: function() {
       this.setState({pending: true});
       this.props.client.requestCallUrl(this.conversationIdentifier(),
                                        this._onCallUrlReceived);
     },
 
     _onCallUrlReceived: function(err, callUrlData) {
       this.props.notifications.reset();
 
@@ -469,17 +459,18 @@ loop.panel = (function(_, mozL10n) {
       callUrl: React.PropTypes.string
     },
 
     render: function() {
       var NotificationListView = sharedViews.NotificationListView;
 
       return (
         <div>
-          <NotificationListView notifications={this.props.notifications} />
+          <NotificationListView notifications={this.props.notifications}
+                                clearOnDocumentHidden={true} />
           <TabView onSelect={this.selectTab}>
             <Tab name="call">
               <CallUrlResult client={this.props.client}
                              notifications={this.props.notifications}
                              callUrl={this.props.callUrl} />
               <ToSView />
             </Tab>
             <Tab name="contacts">
@@ -507,52 +498,22 @@ loop.panel = (function(_, mozL10n) {
       "": "home"
     },
 
     initialize: function(options) {
       options = options || {};
       if (!options.document) {
         throw new Error("missing required document");
       }
-      this.document = options.document;
-
-      this._registerVisibilityChangeEvent();
-
-      this.on("panel:open", this.reset, this);
-    },
-
-    /**
-     * Register the DOM visibility API event for the whole document, and trigger
-     * appropriate events accordingly:
-     *
-     * - `panel:opened` when the panel is open
-     * - `panel:closed` when the panel is closed
-     *
-     * @link  http://www.w3.org/TR/page-visibility/
-     */
-    _registerVisibilityChangeEvent: function() {
-      // XXX pass in the visibility status to detect when to generate a new
-      // panel view
-      this.document.addEventListener("visibilitychange", function(event) {
-        this.trigger(event.currentTarget.hidden ? "panel:closed"
-                                                : "panel:open");
-      }.bind(this));
     },
 
     /**
      * Default entry point.
      */
     home: function() {
-      this.reset();
-    },
-
-    /**
-     * Resets this router to its initial state.
-     */
-    reset: function() {
       this._notifications.reset();
       var client = new loop.Client({
         baseServerUrl: navigator.mozLoop.serverUrl
       });
       this.loadReactComponent(
           <PanelView client={client} notifications={this._notifications}/>);
     }
   });
--- a/browser/components/loop/content/panel.html
+++ b/browser/components/loop/content/panel.html
@@ -7,27 +7,26 @@
     <meta charset="utf-8">
     <title>Loop Panel</title>
     <link rel="stylesheet" type="text/css" href="loop/shared/css/reset.css">
     <link rel="stylesheet" type="text/css" href="loop/shared/css/common.css">
     <link rel="stylesheet" type="text/css" href="loop/shared/css/panel.css">
   </head>
   <body class="panel" onload="loop.panel.init();">
 
-    <div id="messages"></div>
-
     <div id="main"></div>
 
     <script type="text/javascript" src="loop/shared/libs/react-0.11.1.js"></script>
     <script type="text/javascript" src="loop/libs/l10n.js"></script>
     <script type="text/javascript" src="loop/shared/libs/jquery-2.1.0.js"></script>
     <script type="text/javascript" src="loop/shared/libs/lodash-2.4.1.js"></script>
     <script type="text/javascript" src="loop/shared/libs/backbone-1.1.2.js"></script>
 
     <script type="text/javascript" src="loop/shared/js/utils.js"></script>
     <script type="text/javascript" src="loop/shared/js/models.js"></script>
     <script type="text/javascript" src="loop/shared/js/router.js"></script>
+    <script type="text/javascript" src="loop/shared/js/mixins.js"></script>
     <script type="text/javascript" src="loop/shared/js/views.js"></script>
     <script type="text/javascript" src="loop/js/client.js"></script>
     <script type="text/javascript" src="loop/js/desktopRouter.js"></script>
     <script type="text/javascript" src="loop/js/panel.js"></script>
  </body>
 </html>
--- a/browser/components/loop/content/shared/css/panel.css
+++ b/browser/components/loop/content/shared/css/panel.css
@@ -132,18 +132,22 @@
 }
 
 .share > .action > .invite > .url-actions > .btn:first-child {
   -moz-margin-end: 1em;
 }
 
 /* Specific cases */
 
-.panel #messages .alert {
-  margin-bottom: 0;
+.panel .messages {
+  margin: 0;
+}
+
+.panel .messages .alert {
+  margin: 0;
 }
 
 /* Dropdown menu (shared styles) */
 
 .dropdown {
   position: relative;
 }
 
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/content/shared/js/mixins.js
@@ -0,0 +1,95 @@
+/* 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/. */
+
+/* global loop:true */
+
+var loop = loop || {};
+loop.shared = loop.shared || {};
+loop.shared.mixins = (function() {
+  "use strict";
+
+  /**
+   * Root object, by default set to window.
+   * @type {DOMWindow|Object}
+   */
+  var rootObject = window;
+
+  /**
+   * Sets a new root object. This is useful for testing native DOM events so we
+   * can fake them.
+   *
+   * @param {Object}
+   */
+  function setRootObject(obj) {
+    console.info("loop.shared.mixins: rootObject set to " + obj);
+    rootObject = obj;
+  }
+
+  /**
+   * Dropdown menu mixin.
+   * @type {Object}
+   */
+  var DropdownMenuMixin = {
+    getInitialState: function() {
+      return {showMenu: false};
+    },
+
+    _onBodyClick: function() {
+      this.setState({showMenu: false});
+    },
+
+    componentDidMount: function() {
+      rootObject.document.body.addEventListener("click", this._onBodyClick);
+    },
+
+    componentWillUnmount: function() {
+      rootObject.document.body.removeEventListener("click", this._onBodyClick);
+    },
+
+    showDropdownMenu: function() {
+      this.setState({showMenu: true});
+    },
+
+    hideDropdownMenu: function() {
+      this.setState({showMenu: false});
+    }
+  };
+
+  /**
+   * Document visibility mixin. Allows defining the following hooks for when the
+   * document visibility status changes:
+   *
+   * - {Function} onDocumentVisible For when the document becomes visible.
+   * - {Function} onDocumentHidden  For when the document becomes hidden.
+   *
+   * @type {Object}
+   */
+  var DocumentVisibilityMixin = {
+    _onDocumentVisibilityChanged: function(event) {
+      var hidden = event.target.hidden;
+      if (hidden && typeof this.onDocumentHidden === "function") {
+        this.onDocumentHidden();
+      }
+      if (!hidden && typeof this.onDocumentVisible === "function") {
+        this.onDocumentVisible();
+      }
+    },
+
+    componentDidMount: function() {
+      rootObject.document.addEventListener(
+        "visibilitychange", this._onDocumentVisibilityChanged);
+    },
+
+    componentWillUnmount: function() {
+      rootObject.document.removeEventListener(
+        "visibilitychange", this._onDocumentVisibilityChanged);
+    }
+  };
+
+  return {
+    setRootObject: setRootObject,
+    DropdownMenuMixin: DropdownMenuMixin,
+    DocumentVisibilityMixin: DocumentVisibilityMixin
+  };
+})();
--- a/browser/components/loop/content/shared/js/views.js
+++ b/browser/components/loop/content/shared/js/views.js
@@ -7,16 +7,18 @@
 /* jshint newcap:false */
 /* global loop:true, React */
 var loop = loop || {};
 loop.shared = loop.shared || {};
 loop.shared.views = (function(_, OT, l10n) {
   "use strict";
 
   var sharedModels = loop.shared.models;
+  var sharedMixins = loop.shared.mixins;
+
   var WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS = 5;
 
   /**
    * Media control button.
    *
    * Required props:
    * - {String}   scope   Media scope, can be "local" or "remote".
    * - {String}   type    Media type, can be "audio" or "video".
@@ -570,18 +572,17 @@ loop.shared.views = (function(_, OT, l10
           );
       }
     }
   });
 
   /**
    * Notification view.
    */
-  var NotificationView = React.createClass({
-    displayName: 'NotificationView',
+  var NotificationView = React.createClass({displayName: 'NotificationView',
     mixins: [Backbone.Events],
 
     propTypes: {
       notification: React.PropTypes.object.isRequired,
       key: React.PropTypes.number.isRequired
     },
 
     render: function() {
@@ -594,35 +595,56 @@ loop.shared.views = (function(_, OT, l10
       );
     }
   });
 
   /**
    * Notification list view.
    */
   var NotificationListView = React.createClass({displayName: 'NotificationListView',
-    mixins: [Backbone.Events],
+    mixins: [Backbone.Events, sharedMixins.DocumentVisibilityMixin],
 
     propTypes: {
-      notifications: React.PropTypes.object.isRequired
+      notifications: React.PropTypes.object.isRequired,
+      clearOnDocumentHidden: React.PropTypes.bool
+    },
+
+    getDefaultProps: function() {
+      return {clearOnDocumentHidden: false};
     },
 
     componentDidMount: function() {
       this.listenTo(this.props.notifications, "reset add remove", function() {
         this.forceUpdate();
       }.bind(this));
     },
 
     componentWillUnmount: function() {
       this.stopListening(this.props.notifications);
     },
 
+    /**
+     * Provided by DocumentVisibilityMixin. Clears notifications stack when the
+     * current document is hidden if the clearOnDocumentHidden prop is set to
+     * true and the collection isn't empty.
+     */
+    onDocumentHidden: function() {
+      if (this.props.clearOnDocumentHidden &&
+          this.props.notifications.length > 0) {
+        // Note: The `silent` option prevents the `reset` event to be triggered
+        // here, preventing the UI to "jump" a little because of the event
+        // callback being processed in another tick (I think).
+        this.props.notifications.reset([], {silent: true});
+        this.forceUpdate();
+      }
+    },
+
     render: function() {
       return (
-        React.DOM.div({id: "messages"}, 
+        React.DOM.div({className: "messages"}, 
           this.props.notifications.map(function(notification, key) {
             return NotificationView({key: key, notification: notification});
           })
         
         )
       );
     }
   });
--- a/browser/components/loop/content/shared/js/views.jsx
+++ b/browser/components/loop/content/shared/js/views.jsx
@@ -7,16 +7,18 @@
 /* jshint newcap:false */
 /* global loop:true, React */
 var loop = loop || {};
 loop.shared = loop.shared || {};
 loop.shared.views = (function(_, OT, l10n) {
   "use strict";
 
   var sharedModels = loop.shared.models;
+  var sharedMixins = loop.shared.mixins;
+
   var WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS = 5;
 
   /**
    * Media control button.
    *
    * Required props:
    * - {String}   scope   Media scope, can be "local" or "remote".
    * - {String}   type    Media type, can be "audio" or "video".
@@ -571,17 +573,16 @@ loop.shared.views = (function(_, OT, l10
       }
     }
   });
 
   /**
    * Notification view.
    */
   var NotificationView = React.createClass({
-    displayName: 'NotificationView',
     mixins: [Backbone.Events],
 
     propTypes: {
       notification: React.PropTypes.object.isRequired,
       key: React.PropTypes.number.isRequired
     },
 
     render: function() {
@@ -594,35 +595,56 @@ loop.shared.views = (function(_, OT, l10
       );
     }
   });
 
   /**
    * Notification list view.
    */
   var NotificationListView = React.createClass({
-    mixins: [Backbone.Events],
+    mixins: [Backbone.Events, sharedMixins.DocumentVisibilityMixin],
 
     propTypes: {
-      notifications: React.PropTypes.object.isRequired
+      notifications: React.PropTypes.object.isRequired,
+      clearOnDocumentHidden: React.PropTypes.bool
+    },
+
+    getDefaultProps: function() {
+      return {clearOnDocumentHidden: false};
     },
 
     componentDidMount: function() {
       this.listenTo(this.props.notifications, "reset add remove", function() {
         this.forceUpdate();
       }.bind(this));
     },
 
     componentWillUnmount: function() {
       this.stopListening(this.props.notifications);
     },
 
+    /**
+     * Provided by DocumentVisibilityMixin. Clears notifications stack when the
+     * current document is hidden if the clearOnDocumentHidden prop is set to
+     * true and the collection isn't empty.
+     */
+    onDocumentHidden: function() {
+      if (this.props.clearOnDocumentHidden &&
+          this.props.notifications.length > 0) {
+        // Note: The `silent` option prevents the `reset` event to be triggered
+        // here, preventing the UI to "jump" a little because of the event
+        // callback being processed in another tick (I think).
+        this.props.notifications.reset([], {silent: true});
+        this.forceUpdate();
+      }
+    },
+
     render: function() {
       return (
-        <div id="messages">{
+        <div className="messages">{
           this.props.notifications.map(function(notification, key) {
             return <NotificationView key={key} notification={notification}/>;
           })
         }
         </div>
       );
     }
   });
--- a/browser/components/loop/jar.mn
+++ b/browser/components/loop/jar.mn
@@ -46,16 +46,17 @@ browser.jar:
   content/browser/loop/shared/img/svg/glyph-signout-16x16.svg   (content/shared/img/svg/glyph-signout-16x16.svg)
   content/browser/loop/shared/img/audio-call-avatar.svg         (content/shared/img/audio-call-avatar.svg)
   content/browser/loop/shared/img/icons-16x16.svg               (content/shared/img/icons-16x16.svg)
 
   # Shared scripts
   content/browser/loop/shared/js/feedbackApiClient.js (content/shared/js/feedbackApiClient.js)
   content/browser/loop/shared/js/models.js            (content/shared/js/models.js)
   content/browser/loop/shared/js/router.js            (content/shared/js/router.js)
+  content/browser/loop/shared/js/mixins.js            (content/shared/js/mixins.js)
   content/browser/loop/shared/js/views.js             (content/shared/js/views.js)
   content/browser/loop/shared/js/utils.js             (content/shared/js/utils.js)
   content/browser/loop/shared/js/websocket.js         (content/shared/js/websocket.js)
 
   # Shared libs
 #ifdef DEBUG
   content/browser/loop/shared/libs/react-0.11.1.js    (content/shared/libs/react-0.11.1.js)
 #else
--- a/browser/components/loop/standalone/content/index.html
+++ b/browser/components/loop/standalone/content/index.html
@@ -31,16 +31,17 @@
     <script type="text/javascript" src="shared/libs/jquery-2.1.0.js"></script>
     <script type="text/javascript" src="shared/libs/lodash-2.4.1.js"></script>
     <script type="text/javascript" src="shared/libs/backbone-1.1.2.js"></script>
 
     <!-- app scripts -->
     <script type="text/javascript" src="config.js"></script>
     <script type="text/javascript" src="shared/js/utils.js"></script>
     <script type="text/javascript" src="shared/js/models.js"></script>
+    <script type="text/javascript" src="shared/js/mixins.js"></script>
     <script type="text/javascript" src="shared/js/views.js"></script>
     <script type="text/javascript" src="shared/js/router.js"></script>
     <script type="text/javascript" src="shared/js/websocket.js"></script>
     <script type="text/javascript" src="js/standaloneClient.js"></script>
     <script type="text/javascript" src="js/webapp.js"></script>
 
     <script>
       // Wait for all the localization notes to load
--- a/browser/components/loop/test/desktop-local/index.html
+++ b/browser/components/loop/test/desktop-local/index.html
@@ -31,16 +31,17 @@
     mocha.setup('bdd');
   </script>
 
   <!-- App scripts -->
   <script src="../../content/shared/js/utils.js"></script>
   <script src="../../content/shared/js/feedbackApiClient.js"></script>
   <script src="../../content/shared/js/models.js"></script>
   <script src="../../content/shared/js/router.js"></script>
+  <script src="../../content/shared/js/mixins.js"></script>
   <script src="../../content/shared/js/views.js"></script>
   <script src="../../content/shared/js/websocket.js"></script>
   <script src="../../content/js/client.js"></script>
   <script src="../../content/js/desktopRouter.js"></script>
   <script src="../../content/js/conversation.js"></script>
   <script src="../../content/js/panel.js"></script>
 
   <!-- Test scripts -->
--- a/browser/components/loop/test/desktop-local/panel_test.js
+++ b/browser/components/loop/test/desktop-local/panel_test.js
@@ -1,12 +1,13 @@
 /* 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/. */
 
+/*jshint newcap:false*/
 /*global loop, sinon */
 
 var expect = chai.expect;
 var TestUtils = React.addons.TestUtils;
 
 describe("loop.panel", function() {
   "use strict";
 
@@ -77,90 +78,38 @@ describe("loop.panel", function() {
           hidden: true,
           addEventListener: sandbox.spy()
         });
 
         sandbox.stub(router, "loadReactComponent");
       });
 
       describe("#home", function() {
-        it("should reset the PanelView", function() {
-          sandbox.stub(router, "reset");
-
-          router.home();
-
-          sinon.assert.calledOnce(router.reset);
+        beforeEach(function() {
+          sandbox.stub(notifications, "reset");
         });
-      });
 
-      describe("#reset", function() {
         it("should clear all pending notifications", function() {
-          sandbox.stub(notifications, "reset");
-          router.reset();
+          router.home();
 
           sinon.assert.calledOnce(notifications.reset);
         });
 
         it("should load the home view", function() {
-          router.reset();
+          router.home();
 
           sinon.assert.calledOnce(router.loadReactComponent);
           sinon.assert.calledWithExactly(router.loadReactComponent,
             sinon.match(function(value) {
               return React.addons.TestUtils.isDescriptorOfType(
                 value, loop.panel.PanelView);
             }));
         });
       });
     });
-
-    describe("Events", function() {
-      beforeEach(function() {
-        sandbox.stub(loop.panel.PanelRouter.prototype, "trigger");
-      });
-
-      it("should listen to document visibility changes", function() {
-        var fakeDocument = {
-          hidden: true,
-          addEventListener: sandbox.spy()
-        };
-
-        var router = createTestRouter(fakeDocument);
-
-        sinon.assert.calledOnce(fakeDocument.addEventListener);
-        sinon.assert.calledWith(fakeDocument.addEventListener,
-                                "visibilitychange");
-      });
-
-      it("should trigger panel:open when the panel document is visible",
-        function() {
-          var router = createTestRouter({
-            hidden: false,
-            addEventListener: function(name, cb) {
-              cb({currentTarget: {hidden: false}});
-            }
-          });
-
-          sinon.assert.calledOnce(router.trigger);
-          sinon.assert.calledWith(router.trigger, "panel:open");
-        });
-
-      it("should trigger panel:closed when the panel document is hidden",
-        function() {
-          var router = createTestRouter({
-            hidden: true,
-            addEventListener: function(name, cb) {
-              cb({currentTarget: {hidden: true}});
-            }
-          });
-
-          sinon.assert.calledOnce(router.trigger);
-          sinon.assert.calledWith(router.trigger, "panel:closed");
-        });
-    });
   });
 
   describe("loop.panel.AvailabilityDropdown", function() {
     var view;
 
     beforeEach(function() {
       view = TestUtils.renderIntoDocument(loop.panel.AvailabilityDropdown());
     });
--- a/browser/components/loop/test/mochitest/browser_fxa_login.js
+++ b/browser/components/loop/test/mochitest/browser_fxa_login.js
@@ -3,49 +3,50 @@
 
 /**
  * Test FxA logins with Loop.
  */
 
 "use strict";
 
 const {
-  LOOP_SESSION_TYPE,
-  gFxAOAuthTokenData
+  gFxAOAuthTokenData,
 } = Cu.import("resource:///modules/loop/MozLoopService.jsm", {});
 
 const BASE_URL = "http://mochi.test:8888/browser/browser/components/loop/test/mochitest/loop_fxa.sjs?";
-const HAWK_TOKEN_LENGTH = 64;
 
 add_task(function* setup() {
   Services.prefs.setCharPref("loop.server", BASE_URL);
   Services.prefs.setCharPref("services.push.serverURL", "ws://localhost/");
   registerCleanupFunction(function* () {
     info("cleanup time");
     yield promiseDeletedOAuthParams(BASE_URL);
     Services.prefs.clearUserPref("loop.server");
     Services.prefs.clearUserPref("services.push.serverURL");
+    resetFxA();
     Services.prefs.clearUserPref(MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.GUEST));
-    Services.prefs.clearUserPref(MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.FXA));
   });
 });
 
 add_task(function* checkOAuthParams() {
   let params = {
     client_id: "client_id",
     content_uri: BASE_URL + "/content",
     oauth_uri: BASE_URL + "/oauth",
     profile_uri: BASE_URL + "/profile",
     state: "state",
   };
   yield promiseOAuthParamsSetup(BASE_URL, params);
   let client = yield MozLoopServiceInternal.promiseFxAOAuthClient();
   for (let key of Object.keys(params)) {
     ise(client.parameters[key], params[key], "Check " + key + " was passed to the OAuth client");
   }
+  let prefName = MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.FXA);
+  let padding = "X".repeat(HAWK_TOKEN_LENGTH - params.client_id.length);
+  ise(Services.prefs.getCharPref(prefName), params.client_id + padding, "Check FxA hawk token");
 });
 
 add_task(function* basicAuthorization() {
   let result = yield MozLoopServiceInternal.promiseFxAOAuthAuthorization();
   is(result.code, "code1", "Check code");
   is(result.state, "state", "Check state");
 });
 
@@ -66,25 +67,53 @@ add_task(function* paramsInvalid() {
   yield loginPromise.catch(() => {
     ok(true, "The login promise should be rejected due to invalid params");
     caught = true;
   });
   ok(caught, "Should have caught the rejection");
   is(result, null, "No token data should be returned");
 });
 
+add_task(function* params_no_hawk_session() {
+  resetFxA();
+  let params = {
+    client_id: "client_id",
+    content_uri: BASE_URL + "/content",
+    oauth_uri: BASE_URL + "/oauth",
+    profile_uri: BASE_URL + "/profile",
+    state: "state",
+    test_error: "params_no_hawk",
+  };
+  yield promiseOAuthParamsSetup(BASE_URL, params);
+
+  let loginPromise = MozLoopService.logInToFxA();
+  let caught = false;
+  yield loginPromise.catch(() => {
+    ok(true, "The login promise should be rejected due to a lack of a hawk session");
+    caught = true;
+  });
+  ok(caught, "Should have caught the rejection");
+  let prefName = MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.FXA);
+  ise(Services.prefs.getPrefType(prefName),
+      Services.prefs.PREF_INVALID,
+      "Check FxA hawk token is not set");
+});
+
 add_task(function* params_nonJSON() {
-  resetFxA();
   Services.prefs.setCharPref("loop.server", "https://loop.invalid");
-  let result = null;
+  // Reset after changing the server so a new HawkClient is created
+  resetFxA();
+
   let loginPromise = MozLoopService.logInToFxA();
+  let caught = false;
   yield loginPromise.catch(() => {
     ok(true, "The login promise should be rejected due to non-JSON params");
+    caught = true;
   });
-  is(result, null, "No token data should be returned");
+  ok(caught, "Should have caught the rejection");
   Services.prefs.setCharPref("loop.server", BASE_URL);
 });
 
 add_task(function* invalidState() {
   resetFxA();
   let params = {
     client_id: "client_id",
     content_uri: BASE_URL + "/content",
@@ -94,26 +123,41 @@ add_task(function* invalidState() {
   };
   yield promiseOAuthParamsSetup(BASE_URL, params);
   let loginPromise = MozLoopService.logInToFxA();
   yield loginPromise.catch((error) => {
     ok(error, "The login promise should be rejected due to invalid state");
   });
 });
 
+add_task(function* basicRegistrationWithoutSession() {
+  resetFxA();
+  yield promiseDeletedOAuthParams(BASE_URL);
+
+  let caught = false;
+  yield MozLoopServiceInternal.promiseFxAOAuthToken("code1", "state").catch((error) => {
+    caught = true;
+    is(error.code, 401, "Should have returned a 401");
+  });
+  ok(caught, "Should have caught the error requesting /token without a hawk session");
+});
+
 add_task(function* basicRegistration() {
-  resetFxA();
   let params = {
     client_id: "client_id",
     content_uri: BASE_URL + "/content",
     oauth_uri: BASE_URL + "/oauth",
     profile_uri: BASE_URL + "/profile",
     state: "state",
   };
   yield promiseOAuthParamsSetup(BASE_URL, params);
+  resetFxA();
+  // Create a fake FxA hawk session token
+  const fxASessionPref = MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.FXA);
+  Services.prefs.setCharPref(fxASessionPref, "X".repeat(HAWK_TOKEN_LENGTH));
 
   let tokenData = yield MozLoopServiceInternal.promiseFxAOAuthToken("code1", "state");
   is(tokenData.access_token, "code1_access_token", "Check access_token");
   is(tokenData.scope, "profile", "Check scope");
   is(tokenData.token_type, "bearer", "Check token_type");
 });
 
 add_task(function* registrationWithInvalidState() {
@@ -122,16 +166,20 @@ add_task(function* registrationWithInval
     client_id: "client_id",
     content_uri: BASE_URL + "/content",
     oauth_uri: BASE_URL + "/oauth",
     profile_uri: BASE_URL + "/profile",
     state: "invalid_state",
   };
   yield promiseOAuthParamsSetup(BASE_URL, params);
 
+  // Create a fake FxA hawk session token
+  const fxASessionPref = MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.FXA);
+  Services.prefs.setCharPref(fxASessionPref, "X".repeat(HAWK_TOKEN_LENGTH));
+
   let tokenPromise = MozLoopServiceInternal.promiseFxAOAuthToken("code1", "state");
   yield tokenPromise.then(body => {
     ok(false, "Promise should have rejected");
   },
   error => {
     is(error.code, 400, "Check error code");
   });
 });
@@ -166,34 +214,28 @@ add_task(function* basicAuthorizationAnd
     profile_uri: BASE_URL + "/profile",
     state: "state",
   };
   yield promiseOAuthParamsSetup(BASE_URL, params);
 
   info("registering");
   mockPushHandler.pushUrl = "https://localhost/pushUrl/guest";
   yield MozLoopService.register(mockPushHandler);
-  let prefName = MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.GUEST);
-  let padding = new Array(HAWK_TOKEN_LENGTH - mockPushHandler.pushUrl.length).fill("X").join("");
-  ise(Services.prefs.getCharPref(prefName), mockPushHandler.pushUrl + padding, "Check guest hawk token");
 
   // Normally the same pushUrl would be registered but we change it in the test
   // to be able to check for success on the second registration.
   mockPushHandler.pushUrl = "https://localhost/pushUrl/fxa";
 
   let tokenData = yield MozLoopService.logInToFxA();
   ise(tokenData.access_token, "code1_access_token", "Check access_token");
   ise(tokenData.scope, "profile", "Check scope");
   ise(tokenData.token_type, "bearer", "Check token_type");
 
   let registrationResponse = yield promiseOAuthGetRegistration(BASE_URL);
   ise(registrationResponse.response.simplePushURL, "https://localhost/pushUrl/fxa", "Check registered push URL");
-  prefName = MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.FXA);
-  padding = new Array(HAWK_TOKEN_LENGTH - mockPushHandler.pushUrl.length).fill("X").join("");
-  ise(Services.prefs.getCharPref(prefName), mockPushHandler.pushUrl + padding, "Check FxA hawk token");
 });
 
 add_task(function* loginWithParams401() {
   resetFxA();
   let params = {
     client_id: "client_id",
     content_uri: BASE_URL + "/content",
     oauth_uri: BASE_URL + "/oauth",
--- a/browser/components/loop/test/mochitest/browser_loop_fxa_server.js
+++ b/browser/components/loop/test/mochitest/browser_loop_fxa_server.js
@@ -61,16 +61,17 @@ add_task(function* token_request() {
   let params = {
     client_id: "my_client_id",
     content_uri: "https://example.com/content/",
     oauth_uri: "https://example.com/oauth/",
     profile_uri: "https://example.com/profile/",
     state: "my_state",
   };
   yield promiseOAuthParamsSetup(BASE_URL, params);
+
   let request = yield promiseToken("my_code", params.state);
   ise(request.status, 200, "Check token response status");
   ise(request.response.access_token, "my_code_access_token", "Check access_token");
   ise(request.response.scope, "profile", "Check scope");
   ise(request.response.token_type, "bearer", "Check token_type");
 });
 
 add_task(function* token_request_invalid_state() {
@@ -106,16 +107,17 @@ function promiseParams() {
   return deferred.promise;
 }
 
 function promiseToken(code, state) {
   let deferred = Promise.defer();
   let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
               createInstance(Ci.nsIXMLHttpRequest);
   xhr.open("POST", BASE_URL + "/fxa-oauth/token", true);
+  xhr.setRequestHeader("Authorization", "Hawk ...");
   xhr.responseType = "json";
   xhr.addEventListener("load", () => {
     info("/fxa-oauth/token response:\n" + JSON.stringify(xhr.response, null, 4));
     deferred.resolve(xhr);
   });
   xhr.addEventListener("error", deferred.reject);
   let payload = {
     code: code,
--- a/browser/components/loop/test/mochitest/head.js
+++ b/browser/components/loop/test/mochitest/head.js
@@ -1,13 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-const MozLoopServiceInternal = Cu.import("resource:///modules/loop/MozLoopService.jsm", {}).
-                               MozLoopServiceInternal;
+const HAWK_TOKEN_LENGTH = 64;
+const {
+  LOOP_SESSION_TYPE,
+  MozLoopServiceInternal,
+} = Cu.import("resource:///modules/loop/MozLoopService.jsm", {});
 
 var gMozLoopAPI;
 
 function promiseGetMozLoopAPI() {
   let deferred = Promise.defer();
   let loopPanel = document.getElementById("loop-notification-panel");
   let btn = document.getElementById("loop-call-button");
 
@@ -91,19 +94,22 @@ function promiseOAuthParamsSetup(baseURL
   xhr.addEventListener("error", error => deferred.reject(error));
   xhr.send();
 
   return deferred.promise;
 }
 
 function resetFxA() {
   let global = Cu.import("resource:///modules/loop/MozLoopService.jsm", {});
+  global.gHawkClient = null;
   global.gFxAOAuthClientPromise = null;
   global.gFxAOAuthClient = null;
   global.gFxAOAuthTokenData = null;
+  const fxASessionPref = MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.FXA);
+  Services.prefs.clearUserPref(fxASessionPref);
 }
 
 function promiseDeletedOAuthParams(baseURL) {
   let deferred = Promise.defer();
   let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
               createInstance(Ci.nsIXMLHttpRequest);
   xhr.open("DELETE", baseURL + "/setup_params", true);
   xhr.addEventListener("load", () => deferred.resolve(xhr));
--- a/browser/components/loop/test/mochitest/loop_fxa.sjs
+++ b/browser/components/loop/test/mochitest/loop_fxa.sjs
@@ -12,16 +12,17 @@ const HAWK_TOKEN_LENGTH = 64;
 
 Components.utils.import("resource://gre/modules/NetUtil.jsm");
 
 /**
  * Entry point for HTTP requests.
  */
 function handleRequest(request, response) {
   // Look at the query string but ignore past the encoded ? when deciding on the handler.
+  dump("loop_fxa.sjs request for: " + request.queryString + "\n");
   switch (request.queryString.replace(/%3F.*/,"")) {
     case "/setup_params": // Test-only
       setup_params(request, response);
       return;
     case "/fxa-oauth/params":
       params(request, response);
       return;
     case encodeURIComponent("/oauth/authorization"):
@@ -97,16 +98,24 @@ function params(request, response) {
     if (!(paramName in params)) {
       dump("Warning: " + paramName + " is a required parameter\n");
     }
   }
 
   // Save the result so we have the effective `state` value.
   setSharedState("/fxa-oauth/params", JSON.stringify(params));
   response.setHeader("Content-Type", "application/json; charset=utf-8", false);
+
+  let client_id = params.client_id || "";
+  // Pad the client_id with "X" until the token length to simulate a token
+  let padding = "X".repeat(HAWK_TOKEN_LENGTH - client_id.length);
+  if (params.test_error !== "params_no_hawk") {
+    response.setHeader("Hawk-Session-Token", client_id + padding, false);
+  }
+
   response.write(JSON.stringify(params, null, 2));
 }
 
 /**
  * GET /oauth/authorization endpoint for the test params.
  *
  * Redirect to a test page that uses WebChannel to complete the web flow.
  */
@@ -127,16 +136,23 @@ function token(request, response) {
   let params = JSON.parse(getSharedState("/fxa-oauth/params") || "{}");
 
   if (params.test_error && params.test_error == "token_401") {
     response.setStatusLine(request.httpVersion, 401, "Unauthorized");
     response.write("401 Unauthorized");
     return;
   }
 
+  if (!request.hasHeader("Authorization") ||
+        !request.getHeader("Authorization").startsWith("Hawk")) {
+    response.setStatusLine(request.httpVersion, 401, "Missing Hawk");
+    response.write("401 Missing Hawk Authorization header");
+    return;
+  }
+
   let body = NetUtil.readInputStreamToString(request.bodyInputStream,
                                              request.bodyInputStream.available());
   let payload = JSON.parse(body);
   if (!params.state || params.state !== payload.state) {
     response.setStatusLine(request.httpVersion, 400, "State mismatch");
     response.write("State mismatch");
     return;
   }
@@ -148,28 +164,30 @@ function token(request, response) {
   };
   response.setHeader("Content-Type", "application/json; charset=utf-8", false);
   response.write(JSON.stringify(tokenData, null, 2));
 }
 
 /**
  * POST /registration
  *
- * Mock Loop registration endpoint which simply returns the simplePushURL with
- * padding as the hawk session token.
+ * Mock Loop registration endpoint. Hawk Authorization headers are expected only for FxA sessions.
  */
 function registration(request, response) {
   let body = NetUtil.readInputStreamToString(request.bodyInputStream,
                                              request.bodyInputStream.available());
   let payload = JSON.parse(body);
+  if (payload.simplePushURL == "https://localhost/pushUrl/fxa" &&
+       (!request.hasHeader("Authorization") ||
+        !request.getHeader("Authorization").startsWith("Hawk"))) {
+    response.setStatusLine(request.httpVersion, 401, "Missing Hawk");
+    response.write("401 Missing Hawk Authorization header");
+    return;
+  }
   setSharedState("/registration", body);
-  let pushURL = payload.simplePushURL;
-  // Pad the pushURL with "X" to the token length to simulate a token
-  let padding = new Array(HAWK_TOKEN_LENGTH - pushURL.length).fill("X").join("");
-  response.setHeader("hawk-session-token", pushURL + padding, false);
 }
 
 /**
  * GET /get_registration
  *
  * Used for testing purposes to check if registration succeeded by returning the POST body.
  */
 function get_registration(request, response) {
--- a/browser/components/loop/test/shared/index.html
+++ b/browser/components/loop/test/shared/index.html
@@ -29,23 +29,25 @@
   <script>
     /*global chai, mocha */
     chai.Assertion.includeStack = true;
     mocha.setup('bdd');
   </script>
 
   <!-- App scripts -->
   <script src="../../content/shared/js/models.js"></script>
+  <script src="../../content/shared/js/mixins.js"></script>
   <script src="../../content/shared/js/views.js"></script>
   <script src="../../content/shared/js/router.js"></script>
   <script src="../../content/shared/js/websocket.js"></script>
   <script src="../../content/shared/js/feedbackApiClient.js"></script>
 
   <!-- Test scripts -->
   <script src="models_test.js"></script>
+  <script src="mixins_test.js"></script>
   <script src="views_test.js"></script>
   <script src="router_test.js"></script>
   <script src="websocket_test.js"></script>
   <script src="feedbackApiClient_test.js"></script>
   <script>
     mocha.run(function () {
       $("#mocha").append("<p id='complete'>Complete.</p>");
     });
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/test/shared/mixins_test.js
@@ -0,0 +1,70 @@
+/* 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/. */
+
+/* global loop, sinon */
+/* jshint newcap:false */
+
+var expect = chai.expect;
+
+describe("loop.shared.mixins", function() {
+  "use strict";
+
+  var sandbox;
+  var sharedMixins = loop.shared.mixins;
+
+  beforeEach(function() {
+    sandbox = sinon.sandbox.create();
+  });
+
+  afterEach(function() {
+    sandbox.restore();
+  });
+
+  describe("loop.panel.DocumentVisibilityMixin", function() {
+    var comp, TestComp, onDocumentVisibleStub, onDocumentHiddenStub;
+
+    beforeEach(function() {
+      onDocumentVisibleStub = sandbox.stub();
+      onDocumentHiddenStub = sandbox.stub();
+
+      TestComp = React.createClass({
+        mixins: [loop.shared.mixins.DocumentVisibilityMixin],
+        onDocumentHidden: onDocumentHiddenStub,
+        onDocumentVisible: onDocumentVisibleStub,
+        render: function() {
+          return React.DOM.div();
+        }
+      });
+    });
+
+    function setupFakeVisibilityEventDispatcher(event) {
+      loop.shared.mixins.setRootObject({
+        document: {
+          addEventListener: function(_, fn) {
+            fn(event);
+          },
+          removeEventListener: sandbox.stub()
+        }
+      });
+    }
+
+    it("should call onDocumentVisible when document visibility changes to visible",
+      function() {
+        setupFakeVisibilityEventDispatcher({target: {hidden: false}});
+
+        comp = TestUtils.renderIntoDocument(TestComp());
+
+        sinon.assert.calledOnce(onDocumentVisibleStub);
+      });
+
+    it("should call onDocumentVisible when document visibility changes to hidden",
+      function() {
+        setupFakeVisibilityEventDispatcher({target: {hidden: true}});
+
+        comp = TestUtils.renderIntoDocument(TestComp());
+
+        sinon.assert.calledOnce(onDocumentHiddenStub);
+      });
+  });
+});
--- a/browser/components/loop/test/standalone/index.html
+++ b/browser/components/loop/test/standalone/index.html
@@ -28,16 +28,17 @@
   <script src="../shared/sdk_mock.js"></script>
   <script>
     chai.Assertion.includeStack = true;
     mocha.setup('bdd');
   </script>
   <!-- App scripts -->
   <script src="../../content/shared/js/utils.js"></script>
   <script src="../../content/shared/js/models.js"></script>
+  <script src="../../content/shared/js/mixins.js"></script>
   <script src="../../content/shared/js/views.js"></script>
   <script src="../../content/shared/js/router.js"></script>
   <script src="../../content/shared/js/websocket.js"></script>
   <script src="../../standalone/content/js/standaloneClient.js"></script>
   <script src="../../standalone/content/js/webapp.js"></script>
  <!-- Test scripts -->
   <script src="standalone_client_test.js"></script>
   <script src="webapp_test.js"></script>
--- a/browser/components/loop/ui/index.html
+++ b/browser/components/loop/ui/index.html
@@ -29,16 +29,17 @@
     <script src="../content/shared/libs/react-0.11.1.js"></script>
     <script src="../content/shared/libs/jquery-2.1.0.js"></script>
     <script src="../content/shared/libs/lodash-2.4.1.js"></script>
     <script src="../content/shared/libs/backbone-1.1.2.js"></script>
     <script src="../content/shared/js/feedbackApiClient.js"></script>
     <script src="../content/shared/js/utils.js"></script>
     <script src="../content/shared/js/models.js"></script>
     <script src="../content/shared/js/router.js"></script>
+    <script src="../content/shared/js/mixins.js"></script>
     <script src="../content/shared/js/views.js"></script>
     <script src="../content/js/client.js"></script>
     <script src="../content/js/desktopRouter.js"></script>
     <script src="../standalone/content/js/webapp.js"></script>
     <script src="../content/js/panel.js"></script>
     <script src="../content/js/conversation.js"></script>
     <script src="ui-showcase.js"></script>
   </body>
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -295,16 +295,17 @@
 @BINPATH@/components/services-crypto-component.xpt
 #ifdef MOZ_CAPTIVEDETECT
 @BINPATH@/components/captivedetect.xpt
 #endif
 @BINPATH@/browser/components/shellservice.xpt
 @BINPATH@/components/shistory.xpt
 @BINPATH@/components/spellchecker.xpt
 @BINPATH@/components/storage.xpt
+@BINPATH@/components/toolkit_asyncshutdown.xpt
 @BINPATH@/components/toolkit_filewatcher.xpt
 @BINPATH@/components/toolkit_finalizationwitness.xpt
 @BINPATH@/components/toolkit_formautofill.xpt
 @BINPATH@/components/toolkit_osfile.xpt
 @BINPATH@/components/toolkit_xulstore.xpt
 @BINPATH@/components/toolkitprofile.xpt
 #ifdef MOZ_ENABLE_XREMOTE
 @BINPATH@/components/toolkitremote.xpt
@@ -575,16 +576,19 @@
 @BINPATH@/chrome/marionette.manifest
 @BINPATH@/components/MarionetteComponents.manifest
 @BINPATH@/components/marionettecomponent.js
 
 #ifdef MOZ_WEBSPEECH
 @BINPATH@/components/dom_webspeechsynth.xpt
 #endif
 
+@BINPATH@/components/nsAsyncShutdown.manifest
+@BINPATH@/components/nsAsyncShutdown.js
+
 ; InputMethod API
 @BINPATH@/components/MozKeyboard.js
 @BINPATH@/components/InputMethod.manifest
 
 #ifdef MOZ_DEBUG
 @BINPATH@/components/TestInterfaceJS.js
 @BINPATH@/components/TestInterfaceJS.manifest
 #endif
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -1593,16 +1593,109 @@ toolbarbutton[sdk-button="true"][cui-are
   }
 
   #forward-button:-moz-window-inactive:not(:-moz-lwtheme) {
     background-image: none;
     border-color: hsla(0,0%,0%,.2);
   }
 }
 
+@media (-moz-mac-yosemite-theme) {
+  /* Base and hover styles */
+  #forward-button:not(:-moz-lwtheme),
+  #back-button:not(:-moz-lwtheme),
+  #forward-button:hover:not(:-moz-lwtheme),
+  #back-button:hover:not(:-moz-lwtheme) {
+    background-image: none;
+    background-color: #fbfbfb;
+    background-clip: border-box;
+    border: 1px solid #aeaeae;
+    box-shadow: none;
+  }
+
+  #back-button:not(:-moz-lwtheme) {
+    /* On non-lwtheme OS X, we use a 32-px image in a 33px container with negative margin bottom.
+     * Except that on Yosemite, we want to use a border instead of an image - like for lwthemes.
+     * So we need to readjust the height, padding and margin-bottom of the back button
+     * similarly to how we do this for lwthemes (but keep a specific enough selector to
+     * override the other :not(:-moz-lwtheme) selector above): */
+    height: 32px;
+    padding: 4px 5px 4px 3px;
+    margin-bottom: 0;
+  }
+
+  /* White inset shadow on top of the back button */
+  #back-button:not(:-moz-lwtheme),
+  #back-button:hover:not(:-moz-lwtheme) {
+    box-shadow: inset 0 1px 0.5px 0 #fff;
+  }
+
+  /* Bottom shadow and right border for the forward button to match the location bar */
+  #forward-button:hover:not(:-moz-lwtheme),
+  #forward-button:not(:-moz-lwtheme) {
+    box-shadow: 0 1px 0 0 rgba(0,0,0,0.2);
+    border: 0 none;
+    border-right: 1px solid rgba(0,0,0,0.3);
+  }
+
+  /* Active styling: transparent white over toolbar colors */
+  #forward-button:hover:active:not(:-moz-lwtheme),
+  #back-button:hover:active:not(:-moz-lwtheme) {
+    background-image: linear-gradient(to bottom, rgba(255,255,255, 0.3), rgba(255,255,255, 0.4));
+    background-color: transparent;
+    box-shadow: none;
+  }
+
+  /* Add white detail on top of forward button only when active */
+  #forward-button:hover:active:not(:-moz-lwtheme) {
+    box-shadow: inset 0 1px 0 #f1f1f1, 0 1px 0 0 rgba(0,0,0,0.2);
+  }
+
+  /* Inactive window styling (hover styling is identical;
+   * we need to make this explicit because it is different on lion, see above. */
+  #forward-button:-moz-window-inactive:not(:-moz-lwtheme),
+  #back-button:-moz-window-inactive:not(:-moz-lwtheme),
+  #forward-button:hover:-moz-window-inactive:not(:-moz-lwtheme),
+  #back-button:hover:-moz-window-inactive:not(:-moz-lwtheme) {
+    background-image: none;
+    background-color: #f0f0f0;
+    background-clip: border-box;
+    border: 1px solid rgba(0,0,0,0.1);
+    box-shadow: none;
+  }
+
+  /* Lightweight theme styles */
+  #forward-button:-moz-lwtheme,
+  #forward-button:hover:-moz-lwtheme {
+    border: 0 none;
+    border-right: 1px solid rgba(0,0,0,0.3);
+    box-shadow: 0 1px 0 0 rgba(0,0,0,0.2);
+  }
+
+  #forward-button:-moz-lwtheme,
+  #forward-button:hover:-moz-lwtheme,
+  #back-button:-moz-lwtheme,
+  #back-button:hover:-moz-lwtheme {
+    background-color: rgba(255,255,255,0.5);
+    background-image: none;
+    background-clip: border-box;
+  }
+
+  #forward-button:hover:active:-moz-lwtheme,
+  #back-button:hover:active:-moz-lwtheme {
+    background-image: linear-gradient(to bottom, rgba(255,255,255, 0.7), rgba(255,255,255, 0.8));
+    background-color: transparent;
+    box-shadow: none;
+  }
+
+  #forward-button:hover:active:-moz-lwtheme {
+    box-shadow: 0 1px 0 0 rgba(0,0,0,0.2);
+  }
+}
+
 @conditionalForwardWithUrlbar@:not([switchingtabs]) > #forward-button {
   transition: margin-left @forwardTransitionLength@ ease-out;
 }
 
 @conditionalForwardWithUrlbar@ > #forward-button[disabled] {
   margin-left: -@conditionalForwardWithUrlbarWidth@px;
 }
 
@@ -1794,23 +1887,23 @@ toolbarbutton[sdk-button="true"][cui-are
   }
 }
 
 @media (-moz-mac-yosemite-theme) {
   .searchbar-textbox,
   #urlbar {
     border-color: #fff;
     border-radius: 3px;
-    box-shadow: 0 1px 0 0 #aeaeae, 1px 2px 0 0 #d8d8d8;
+    box-shadow: 0 1px 0 0 rgba(0,0,0,0.2);
     background-image: none;
   }
   .searchbar-textbox:-moz-window-inactive,
   #urlbar:-moz-window-inactive {
     box-shadow: none;
-    border-color: #dbdbdb;
+    border-color: rgba(0,0,0,0.1);
   }
 }
 
 #urlbar[focused="true"],
 .searchbar-textbox[focused="true"] {
   border-color: -moz-mac-focusring;
   box-shadow: @focusRingShadow@;
 }
--- a/build/autoconf/frameptr.m4
+++ b/build/autoconf/frameptr.m4
@@ -23,18 +23,19 @@ AC_DEFUN([MOZ_SET_FRAMEPTR_FLAGS], [
     case "$target" in
     *-mingw*)
       MOZ_ENABLE_FRAME_PTR="-Oy-"
       MOZ_DISABLE_FRAME_PTR="-Oy"
     ;;
     esac
   fi
 
-  # if we are debugging, profiling or using ASAN, we want a frame pointer.
+  # if we are debugging, profiling or using sanitizers, we want a frame pointer.
   if test -z "$MOZ_OPTIMIZE" -o \
           -n "$MOZ_PROFILING" -o \
           -n "$MOZ_DEBUG" -o \
+          -n "$MOZ_MSAN" -o \
           -n "$MOZ_ASAN"; then
     MOZ_FRAMEPTR_FLAGS="$MOZ_ENABLE_FRAME_PTR"
   else
     MOZ_FRAMEPTR_FLAGS="$MOZ_DISABLE_FRAME_PTR"
   fi
 ])
--- a/build/automationutils.py
+++ b/build/automationutils.py
@@ -18,18 +18,16 @@ import zipfile
 import mozinfo
 
 __all__ = [
   "ZipFileReader",
   "addCommonOptions",
   "dumpLeakLog",
   "isURL",
   "processLeakLog",
-  "getDebuggerInfo",
-  "DEBUGGER_INFO",
   "replaceBackSlashes",
   'KeyValueParseError',
   'parseKeyValue',
   'systemMemory',
   'environment',
   'dumpScreen',
   "ShutdownLeaks",
   "setAutomationLog",
@@ -43,64 +41,16 @@ def resetGlobalLog():
   log.setLevel(logging.INFO)
   log.addHandler(handler)
 resetGlobalLog()
 
 def setAutomationLog(alt_logger):
   global log
   log = alt_logger
 
-# Map of debugging programs to information about them, like default arguments
-# and whether or not they are interactive.
-DEBUGGER_INFO = {
-  # gdb requires that you supply the '--args' flag in order to pass arguments
-  # after the executable name to the executable.
-  "gdb": {
-    "interactive": True,
-    "args": "-q --args"
-  },
-
-  "cgdb": {
-    "interactive": True,
-    "args": "-q --args"
-  },
-
-  "lldb": {
-    "interactive": True,
-    "args": "--",
-    "requiresEscapedArgs": True
-  },
-
-  # Visual Studio Debugger Support
-  "devenv.exe": {
-    "interactive": True,
-    "args": "-debugexe"
-  },
-
-  # Visual C++ Express Debugger Support
-  "wdexpress.exe": {
-    "interactive": True,
-    "args": "-debugexe"
-  },
-
-  # valgrind doesn't explain much about leaks unless you set the
-  # '--leak-check=full' flag. But there are a lot of objects that are
-  # semi-deliberately leaked, so we set '--show-possibly-lost=no' to avoid
-  # uninteresting output from those objects. We set '--smc-check==all-non-file'
-  # and '--vex-iropt-register-updates=allregs-at-mem-access' so that valgrind
-  # deals properly with JIT'd JavaScript code.
-  "valgrind": {
-    "interactive": False,
-    "args": " ".join(["--leak-check=full",
-                      "--show-possibly-lost=no",
-                      "--smc-check=all-non-file",
-                      "--vex-iropt-register-updates=allregs-at-mem-access"])
-  }
-}
-
 class ZipFileReader(object):
   """
   Class to read zip files in Python 2.5 and later. Limited to only what we
   actually use.
   """
 
   def __init__(self, filename):
     self._zipfile = zipfile.ZipFile(filename, "r")
@@ -219,68 +169,16 @@ def addCommonOptions(parser, defaults={}
                     action = "store", dest = "debuggerArgs",
                     help = "pass the given args to the debugger _before_ "
                            "the application on the command line")
   parser.add_option("--debugger-interactive",
                     action = "store_true", dest = "debuggerInteractive",
                     help = "prevents the test harness from redirecting "
                         "stdout and stderr for interactive debuggers")
 
-def getFullPath(directory, path):
-  "Get an absolute path relative to 'directory'."
-  return os.path.normpath(os.path.join(directory, os.path.expanduser(path)))
-
-def searchPath(directory, path):
-  "Go one step beyond getFullPath and try the various folders in PATH"
-  # Try looking in the current working directory first.
-  newpath = getFullPath(directory, path)
-  if os.path.isfile(newpath):
-    return newpath
-
-  # At this point we have to fail if a directory was given (to prevent cases
-  # like './gdb' from matching '/usr/bin/./gdb').
-  if not os.path.dirname(path):
-    for dir in os.environ['PATH'].split(os.pathsep):
-      newpath = os.path.join(dir, path)
-      if os.path.isfile(newpath):
-        return newpath
-  return None
-
-def getDebuggerInfo(directory, debugger, debuggerArgs, debuggerInteractive = False):
-
-  debuggerInfo = None
-
-  if debugger:
-    debuggerPath = searchPath(directory, debugger)
-    if not debuggerPath:
-      print "Error: Path %s doesn't exist." % debugger
-      sys.exit(1)
-
-    debuggerName = os.path.basename(debuggerPath).lower()
-
-    def getDebuggerInfo(type, default):
-      if debuggerName in DEBUGGER_INFO and type in DEBUGGER_INFO[debuggerName]:
-        return DEBUGGER_INFO[debuggerName][type]
-      return default
-
-    debuggerInfo = {
-      "path": debuggerPath,
-      "interactive" : getDebuggerInfo("interactive", False),
-      "args": getDebuggerInfo("args", "").split(),
-      "requiresEscapedArgs": getDebuggerInfo("requiresEscapedArgs", False)
-    }
-
-    if debuggerArgs:
-      debuggerInfo["args"] = debuggerArgs.split()
-    if debuggerInteractive:
-      debuggerInfo["interactive"] = debuggerInteractive
-
-  return debuggerInfo
-
-
 def dumpLeakLog(leakLogFile, filter = False):
   """Process the leak log, without parsing it.
 
   Use this function if you want the raw log only.
   Use it preferably with the |XPCOM_MEM_LEAK_LOG| environment variable.
   """
 
   # Don't warn (nor "info") if the log file is not there.
--- a/build/mach_bootstrap.py
+++ b/build/mach_bootstrap.py
@@ -44,16 +44,17 @@ SEARCH_PATHS = [
     'testing',
     'testing/xpcshell',
     'testing/web-platform',
     'testing/web-platform/harness',
     'testing/marionette/client',
     'testing/marionette/client/marionette',
     'testing/marionette/transport',
     'testing/mozbase/mozcrash',
+    'testing/mozbase/mozdebug',
     'testing/mozbase/mozdevice',
     'testing/mozbase/mozfile',
     'testing/mozbase/mozhttpd',
     'testing/mozbase/mozlog',
     'testing/mozbase/moznetwork',
     'testing/mozbase/mozprocess',
     'testing/mozbase/mozprofile',
     'testing/mozbase/mozrunner',
--- a/configure.in
+++ b/configure.in
@@ -1259,16 +1259,32 @@ MOZ_ARG_ENABLE_BOOL(address-sanitizer,
     MOZ_ASAN=1,
     MOZ_ASAN= )
 if test -n "$MOZ_ASAN"; then
     MOZ_LLVM_HACKS=1
     AC_DEFINE(MOZ_ASAN)
     MOZ_PATH_PROG(LLVM_SYMBOLIZER, llvm-symbolizer)
 fi
 AC_SUBST(MOZ_ASAN)
+
+dnl ========================================================
+dnl = Use Memory Sanitizer
+dnl ========================================================
+MOZ_ARG_ENABLE_BOOL(memory-sanitizer,
+[  --enable-memory-sanitizer       Enable Memory Sanitizer (default=no)],
+    MOZ_MSAN=1,
+    MOZ_MSAN= )
+if test -n "$MOZ_MSAN"; then
+    MOZ_LLVM_HACKS=1
+    AC_DEFINE(MOZ_MSAN)
+    MOZ_PATH_PROG(LLVM_SYMBOLIZER, llvm-symbolizer)
+fi
+AC_SUBST(MOZ_MSAN)
+
+# The LLVM symbolizer is used by all sanitizers
 AC_SUBST(LLVM_SYMBOLIZER)
 
 dnl ========================================================
 dnl = Enable hacks required for LLVM instrumentations
 dnl ========================================================
 MOZ_ARG_ENABLE_BOOL(llvm-hacks,
 [  --enable-llvm-hacks       Enable workarounds required for several LLVM instrumentations (default=no)],
     MOZ_LLVM_HACKS=1,
--- a/content/html/document/test/mochitest.ini
+++ b/content/html/document/test/mochitest.ini
@@ -66,17 +66,17 @@ skip-if = toolkit == 'android'
 [test_bug486741.html]
 [test_bug489532.html]
 [test_bug497242.xhtml]
 [test_bug499092.html]
 [test_bug512367.html]
 [test_bug677495.html]
 [test_bug677495-1.html]
 [test_bug741266.html]
-skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s # b2g(needs control of popup window size) b2g-debug(needs control of popup window size) b2g-desktop(needs control of popup window size)
+skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e10s # b2g(needs control of popup window size) b2g-debug(needs control of popup window size) b2g-desktop(needs control of popup window size)
 [test_non-ascii-cookie.html]
 skip-if = buildapp == 'b2g' || e10s
 [test_bug765780.html]
 [test_bug871161.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') || e10s #Bug 931116, b2g desktop specific, initial triage
 support-files = file_bug871161-1.html file_bug871161-2.html
 
 [test_bug1013316.html]
--- a/content/media/mediasource/MediaSource.cpp
+++ b/content/media/mediasource/MediaSource.cpp
@@ -502,16 +502,30 @@ MediaSource::InitializationEvent()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MSE_DEBUG("MediaSource(%p)::InitializationEvent()", this);
   if (mDecoder) {
     mDecoder->PrepareReaderInitialization();
   }
 }
 
+#if defined(DEBUG)
+void
+MediaSource::Dump(const char* aPath)
+{
+  char buf[255];
+  PR_snprintf(buf, sizeof(buf), "%s/mediasource-%p", aPath, this);
+  PR_MkDir(buf, 0700);
+
+  if (mSourceBuffers) {
+    mSourceBuffers->Dump(buf);
+  }
+}
+#endif
+
 nsPIDOMWindow*
 MediaSource::GetParentObject() const
 {
   return GetOwner();
 }
 
 JSObject*
 MediaSource::WrapObject(JSContext* aCx)
--- a/content/media/mediasource/MediaSource.h
+++ b/content/media/mediasource/MediaSource.h
@@ -93,16 +93,22 @@ public:
   // Queue InitializationEvent to run on the main thread.  Called when a
   // SourceBuffer has an initialization segment appended, but only
   // dispatched the first time (using mFirstSourceBufferInitialization).
   // Demarcates the point in time at which only currently registered
   // TrackBuffers are treated as essential by the MediaSourceReader for
   // initialization.
   void QueueInitializationEvent();
 
+#if defined(DEBUG)
+  // Dump the contents of each SourceBuffer to a series of files under aPath.
+  // aPath must exist.  Debug only, invoke from your favourite debugger.
+  void Dump(const char* aPath);
+#endif
+
 private:
   ~MediaSource();
 
   explicit MediaSource(nsPIDOMWindow* aWindow);
 
   friend class AsyncEventRunner<MediaSource>;
   void DispatchSimpleEvent(const char* aName);
   void QueueAsyncSimpleEvent(const char* aName);
--- a/content/media/mediasource/ResourceQueue.h
+++ b/content/media/mediasource/ResourceQueue.h
@@ -129,16 +129,33 @@ public:
     for (uint32_t i = 0; i < uint32_t(GetSize()); ++i) {
       const ResourceItem* item = ResourceAt(i);
       size += item->SizeOfIncludingThis(aMallocSizeOf);
     }
 
     return size;
   }
 
+#if defined(DEBUG)
+  void Dump(const char* aPath) {
+    for (uint32_t i = 0; i < uint32_t(GetSize()); ++i) {
+      ResourceItem* item = ResourceAt(i);
+
+      char buf[255];
+      PR_snprintf(buf, sizeof(buf), "%s/%08u.bin", aPath, i);
+      FILE* fp = fopen(buf, "wb");
+      if (!fp) {
+        return;
+      }
+      fwrite(item->mData.Elements(), item->mData.Length(), 1, fp);
+      fclose(fp);
+    }
+  }
+#endif
+
 private:
   ResourceItem* ResourceAt(uint32_t aIndex) const {
     return static_cast<ResourceItem*>(ObjectAt(aIndex));
   }
 
   // Returns the index of the resource that contains the given
   // logical offset. aResourceOffset will contain the offset into
   // the resource at the given index returned if it is not null.  If
@@ -168,11 +185,10 @@ private:
 
   // Logical length of the resource.
   uint64_t mLogicalLength;
 
   // Logical offset into the resource of the first element in the queue.
   uint64_t mOffset;
 };
 
-
 } // namespace mozilla
 #endif /* MOZILLA_RESOURCEQUEUE_H_ */
--- a/content/media/mediasource/SourceBuffer.cpp
+++ b/content/media/mediasource/SourceBuffer.cpp
@@ -1,8 +1,9 @@
+/* -*- 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 "SourceBuffer.h"
 
 #include "AsyncEventRunner.h"
@@ -682,16 +683,26 @@ SourceBuffer::Evict(double aStart, doubl
   double evictTime = aEnd;
   const double safety_threshold = 5;
   if (currentTime + safety_threshold >= evictTime) {
     evictTime -= safety_threshold;
   }
   mTrackBuffer->EvictBefore(evictTime);
 }
 
+#if defined(DEBUG)
+void
+SourceBuffer::Dump(const char* aPath)
+{
+  if (mTrackBuffer) {
+    mTrackBuffer->Dump(aPath);
+  }
+}
+#endif
+
 NS_IMPL_CYCLE_COLLECTION_INHERITED(SourceBuffer, DOMEventTargetHelper,
                                    mMediaSource)
 
 NS_IMPL_ADDREF_INHERITED(SourceBuffer, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(SourceBuffer, DOMEventTargetHelper)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(SourceBuffer)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
--- a/content/media/mediasource/SourceBuffer.h
+++ b/content/media/mediasource/SourceBuffer.h
@@ -104,16 +104,20 @@ public:
   void Ended();
 
   // Evict data in the source buffer in the given time range.
   void Evict(double aStart, double aEnd);
 
   double GetBufferedStart();
   double GetBufferedEnd();
 
+#if defined(DEBUG)
+  void Dump(const char* aPath);
+#endif
+
 private:
   ~SourceBuffer();
 
   friend class AsyncEventRunner<SourceBuffer>;
   void DispatchSimpleEvent(const char* aName);
   void QueueAsyncSimpleEvent(const char* aName);
 
   // Create a new decoder for mType, and store the result in mDecoder.
--- a/content/media/mediasource/SourceBufferDecoder.cpp
+++ b/content/media/mediasource/SourceBufferDecoder.cpp
@@ -1,8 +1,9 @@
+/* -*- 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 "SourceBufferDecoder.h"
 #include "prlog.h"
 #include "AbstractMediaDecoder.h"
--- a/content/media/mediasource/SourceBufferList.cpp
+++ b/content/media/mediasource/SourceBufferList.cpp
@@ -162,16 +162,26 @@ SourceBufferList::DispatchSimpleEvent(co
 void
 SourceBufferList::QueueAsyncSimpleEvent(const char* aName)
 {
   MSE_DEBUG("SourceBufferList(%p) Queuing event '%s'", this, aName);
   nsCOMPtr<nsIRunnable> event = new AsyncEventRunner<SourceBufferList>(this, aName);
   NS_DispatchToMainThread(event);
 }
 
+#if defined(DEBUG)
+void
+SourceBufferList::Dump(const char* aPath)
+{
+  for (uint32_t i = 0; i < mSourceBuffers.Length(); ++i) {
+    mSourceBuffers[i]->Dump(aPath);
+  }
+}
+#endif
+
 SourceBufferList::SourceBufferList(MediaSource* aMediaSource)
   : DOMEventTargetHelper(aMediaSource->GetParentObject())
   , mMediaSource(aMediaSource)
 {
   MOZ_ASSERT(aMediaSource);
 }
 
 MediaSource*
--- a/content/media/mediasource/SourceBufferList.h
+++ b/content/media/mediasource/SourceBufferList.h
@@ -74,16 +74,20 @@ public:
   void Ended();
 
   // Evicts data for the given time range from each SourceBuffer in the list.
   void Evict(double aStart, double aEnd);
 
   // Returns the highest end time of any of the Sourcebuffers.
   double GetHighestBufferedEndTime();
 
+#if defined(DEBUG)
+  void Dump(const char* aPath);
+#endif
+
 private:
   ~SourceBufferList();
 
   friend class AsyncEventRunner<SourceBufferList>;
   void DispatchSimpleEvent(const char* aName);
   void QueueAsyncSimpleEvent(const char* aName);
 
   nsRefPtr<MediaSource> mMediaSource;
--- a/content/media/mediasource/SourceBufferResource.h
+++ b/content/media/mediasource/SourceBufferResource.h
@@ -115,16 +115,22 @@ public:
   void Ended();
   // Remove data from resource if it holds more than the threshold
   // number of bytes. Returns true if some data was evicted.
   bool EvictData(uint32_t aThreshold);
 
   // Remove data from resource before the given offset.
   void EvictBefore(uint64_t aOffset);
 
+#if defined(DEBUG)
+  void Dump(const char* aPath) {
+    mInputBuffer.Dump(aPath);
+  }
+#endif
+
 private:
   ~SourceBufferResource();
   nsresult SeekInternal(int64_t aOffset);
 
   const nsCString mType;
 
   // Provides synchronization between SourceBuffers and InputAdapters.
   // Protects all of the member variables below.  Read() will await a
--- a/content/media/mediasource/TrackBuffer.cpp
+++ b/content/media/mediasource/TrackBuffer.cpp
@@ -1,8 +1,9 @@
+/* -*- 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 "TrackBuffer.h"
 
 #include "MediaSourceDecoder.h"
@@ -14,16 +15,21 @@
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/dom/MediaSourceBinding.h"
 #include "mozilla/dom/TimeRanges.h"
 #include "nsError.h"
 #include "nsIRunnable.h"
 #include "nsThreadUtils.h"
 #include "prlog.h"
 
+#if defined(DEBUG)
+#include <sys/stat.h>
+#include <sys/types.h>
+#endif
+
 struct JSContext;
 class JSObject;
 
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* GetMediaSourceLog();
 extern PRLogModuleInfo* GetMediaSourceAPILog();
 
 #define MSE_DEBUG(...) PR_LOG(GetMediaSourceLog(), PR_LOG_DEBUG, (__VA_ARGS__))
@@ -344,9 +350,27 @@ TrackBuffer::ResetDecode()
 
 const nsTArray<nsRefPtr<SourceBufferDecoder>>&
 TrackBuffer::Decoders()
 {
   // XXX assert OnDecodeThread
   return mInitializedDecoders;
 }
 
+#if defined(DEBUG)
+void
+TrackBuffer::Dump(const char* aPath)
+{
+  char path[255];
+  PR_snprintf(path, sizeof(path), "%s/trackbuffer-%p", aPath, this);
+  PR_MkDir(path, 0700);
+
+  for (uint32_t i = 0; i < mDecoders.Length(); ++i) {
+    char buf[255];
+    PR_snprintf(buf, sizeof(buf), "%s/reader-%p", path, mDecoders[i]->GetReader());
+    PR_MkDir(buf, 0700);
+
+    mDecoders[i]->GetResource()->Dump(buf);
+  }
+}
+#endif
+
 } // namespace mozilla
--- a/content/media/mediasource/TrackBuffer.h
+++ b/content/media/mediasource/TrackBuffer.h
@@ -77,16 +77,20 @@ public:
   // Call ResetDecode() on each decoder in mDecoders.
   void ResetDecode();
 
   // Returns a reference to mInitializedDecoders, used by MediaSourceReader
   // to select decoders.
   // TODO: Refactor to a cleaner interface between TrackBuffer and MediaSourceReader.
   const nsTArray<nsRefPtr<SourceBufferDecoder>>& Decoders();
 
+#if defined(DEBUG)
+  void Dump(const char* aPath);
+#endif
+
 private:
   ~TrackBuffer();
 
   // Queue execution of InitializeDecoder on mTaskQueue.
   bool QueueInitializeDecoder(nsRefPtr<SourceBufferDecoder> aDecoder);
 
   // Runs decoder initialization including calling ReadMetadata.  Runs as an
   // event on the decode thread pool.
--- a/content/media/omx/MediaCodecProxy.cpp
+++ b/content/media/omx/MediaCodecProxy.cpp
@@ -13,16 +13,72 @@
 #include "stagefright/MediaErrors.h"
 
 #define LOG_TAG "MediaCodecProxy"
 #include <android/log.h>
 #define ALOG(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
 #define TIMEOUT_DEQUEUE_INPUTBUFFER_MS 1000000ll
 namespace android {
 
+// General Template: MediaCodec::getOutputGraphicBufferFromIndex(...)
+template <typename T, bool InterfaceSupported>
+struct OutputGraphicBufferStub
+{
+  static status_t GetOutputGraphicBuffer(T *aMediaCodec,
+                                         size_t aIndex,
+                                         sp<GraphicBuffer> *aGraphicBuffer)
+  {
+    return ERROR_UNSUPPORTED;
+  }
+};
+
+// Class Template Specialization: MediaCodec::getOutputGraphicBufferFromIndex(...)
+template <typename T>
+struct OutputGraphicBufferStub<T, true>
+{
+  static status_t GetOutputGraphicBuffer(T *aMediaCodec,
+                                         size_t aIndex,
+                                         sp<GraphicBuffer> *aGraphicBuffer)
+  {
+    if (aMediaCodec == nullptr || aGraphicBuffer == nullptr) {
+      return BAD_VALUE;
+    }
+    *aGraphicBuffer = aMediaCodec->getOutputGraphicBufferFromIndex(aIndex);
+    return OK;
+  }
+};
+
+// Wrapper class to handle interface-difference of MediaCodec.
+struct MediaCodecInterfaceWrapper
+{
+  typedef int8_t Supported;
+  typedef int16_t Unsupported;
+
+  template <typename T>
+  static auto TestOutputGraphicBuffer(T *aMediaCodec) -> decltype(aMediaCodec->getOutputGraphicBufferFromIndex(0), Supported());
+
+  template <typename T>
+  static auto TestOutputGraphicBuffer(...) -> Unsupported;
+
+  // SFINAE: Substitution Failure Is Not An Error
+  static const bool OutputGraphicBufferSupported = sizeof(TestOutputGraphicBuffer<MediaCodec>(nullptr)) == sizeof(Supported);
+
+  // Class Template Specialization
+  static OutputGraphicBufferStub<MediaCodec, OutputGraphicBufferSupported> sOutputGraphicBufferStub;
+
+  // Wrapper Function
+  static status_t GetOutputGraphicBuffer(MediaCodec *aMediaCodec,
+                                         size_t aIndex,
+                                         sp<GraphicBuffer> *aGraphicBuffer)
+  {
+    return sOutputGraphicBufferStub.GetOutputGraphicBuffer(aMediaCodec, aIndex, aGraphicBuffer);
+  }
+
+};
+
 sp<MediaCodecProxy>
 MediaCodecProxy::CreateByType(sp<ALooper> aLooper,
                               const char *aMime,
                               bool aEncoder,
                               bool aAsync,
                               wp<CodecResourceListener> aListener)
 {
   sp<MediaCodecProxy> codec = new MediaCodecProxy(aLooper, aMime, aEncoder, aAsync, aListener);
@@ -358,16 +414,47 @@ MediaCodecProxy::requestActivityNotifica
   RWLock::AutoRLock arl(mCodecLock);
 
   if (mCodec == nullptr) {
     return;
   }
   mCodec->requestActivityNotification(aNotify);
 }
 
+status_t
+MediaCodecProxy::getOutputGraphicBufferFromIndex(size_t aIndex,
+                                                 sp<GraphicBuffer> *aGraphicBuffer)
+{
+  // Read Lock for mCodec
+  RWLock::AutoRLock arl(mCodecLock);
+
+  if (mCodec == nullptr) {
+    return NO_INIT;
+  }
+  return MediaCodecInterfaceWrapper::GetOutputGraphicBuffer(mCodec.get(), aIndex, aGraphicBuffer);
+}
+
+status_t
+MediaCodecProxy::getCapability(uint32_t *aCapability)
+{
+  if (aCapability == nullptr) {
+    return BAD_VALUE;
+  }
+
+  uint32_t capability = kEmptyCapability;
+
+  if (MediaCodecInterfaceWrapper::OutputGraphicBufferSupported) {
+    capability |= kCanExposeGraphicBuffer;
+  }
+
+  *aCapability = capability;
+
+  return OK;
+}
+
 // Called on a Binder thread
 void
 MediaCodecProxy::resourceReserved()
 {
   // Create MediaCodec
   releaseCodec();
   if (!allocateCodec()) {
     cancelResource();
--- a/content/media/omx/MediaCodecProxy.h
+++ b/content/media/omx/MediaCodecProxy.h
@@ -35,19 +35,25 @@ public:
     virtual void codecReserved() = 0;
     /* The codec resource is not reserved any more.
      * The client should release the resource as soon as possible if the
      * resource is still being held.
      */
     virtual void codecCanceled() = 0;
   };
 
+  enum Capability {
+    kEmptyCapability        = 0x00000000,
+    kCanExposeGraphicBuffer = 0x00000001,
+  };
+
   enum {
     kKeyBufferIndex = 'bfin',
   };
+
   // Check whether MediaCodec has been allocated.
   bool allocated() const;
 
   // Static MediaCodec methods
   // Only support MediaCodec::CreateByType()
   static sp<MediaCodecProxy> CreateByType(sp<ALooper> aLooper,
                                           const char *aMime,
                                           bool aEncoder,
@@ -107,24 +113,33 @@ public:
   status_t getInputBuffers(Vector<sp<ABuffer>> *aBuffers) const;
 
   status_t getOutputBuffers(Vector<sp<ABuffer>> *aBuffers) const;
 
   // Notification will be posted once there "is something to do", i.e.
   // an input/output buffer has become available, a format change is
   // pending, an error is pending.
   void requestActivityNotification(const sp<AMessage> &aNotify);
+
+  status_t getOutputGraphicBufferFromIndex(size_t aIndex,
+                                           sp<GraphicBuffer> *aGraphicBuffer);
+
+  status_t getCapability(uint32_t *aCapability);
+
+  // Utility functions
+
   // If aData is null, will notify decoder input EOS
   status_t Input(const uint8_t* aData, uint32_t aDataSize,
                  int64_t aTimestampUsecs, uint64_t flags);
   status_t Output(MediaBuffer** aBuffer, int64_t aTimeoutUs);
   bool Prepare();
   bool IsWaitingResources();
   bool IsDormantNeeded();
   void ReleaseMediaResources();
+
 protected:
   virtual ~MediaCodecProxy();
 
   // MediaResourceHandler::EventListener::resourceReserved()
   virtual void resourceReserved();
   // MediaResourceHandler::EventListener::resourceCanceled()
   virtual void resourceCanceled();
 
@@ -160,16 +175,17 @@ private:
   wp<CodecResourceListener> mListener;
 
   // Media Resource Management
   sp<MediaResourceHandler> mResourceHandler;
 
   // MediaCodec instance
   mutable RWLock mCodecLock;
   sp<MediaCodec> mCodec;
+
   //MediaCodec buffers to hold input/output data.
   Vector<sp<ABuffer> > mInputBuffers;
   Vector<sp<ABuffer> > mOutputBuffers;
 
 };
 
 } // namespace android
 
--- a/docshell/base/nsDefaultURIFixup.cpp
+++ b/docshell/base/nsDefaultURIFixup.cpp
@@ -946,67 +946,138 @@ void nsDefaultURIFixup::KeywordURIFixup(
     // "nonQualifiedHost?args"
     // "nonQualifiedHost?some args"
     // "blah.com."
 
     // Note: uint32_t(kNotFound) is greater than any actual location
     // in practice.  So if we cast all locations to uint32_t, then a <
     // b guarantees that either b is kNotFound and a is found, or both
     // are found and a found before b.
-    uint32_t dotLoc   = uint32_t(aURIString.FindChar('.'));
-    nsAutoCString tmpURIString(aURIString);
-    uint32_t lastDotLoc = uint32_t(tmpURIString.RFindChar('.'));
-    uint32_t colonLoc = uint32_t(aURIString.FindChar(':'));
-    uint32_t spaceLoc = uint32_t(aURIString.FindChar(' '));
-    if (spaceLoc == 0) {
-        // Treat this as not found
-        spaceLoc = uint32_t(kNotFound);
+
+    uint32_t firstDotLoc = uint32_t(kNotFound);
+    uint32_t firstColonLoc = uint32_t(kNotFound);
+    uint32_t firstQuoteLoc = uint32_t(kNotFound);
+    uint32_t firstSpaceLoc = uint32_t(kNotFound);
+    uint32_t firstQMarkLoc = uint32_t(kNotFound);
+    uint32_t lastLSBracketLoc = uint32_t(kNotFound);
+    uint32_t pos = 0;
+    uint32_t foundDots = 0;
+    uint32_t foundColons = 0;
+    uint32_t foundDigits = 0;
+    uint32_t foundRSBrackets = 0;
+    bool looksLikeIpv6 = true;
+    bool hasAsciiAlpha = false;
+
+    nsACString::const_iterator iterBegin;
+    nsACString::const_iterator iterEnd;
+    aURIString.BeginReading(iterBegin);
+    aURIString.EndReading(iterEnd);
+    nsACString::const_iterator iter = iterBegin;
+
+    while (iter != iterEnd) {
+        if (pos >= 1 && foundRSBrackets == 0) {
+            if (!(lastLSBracketLoc == 0 &&
+                  (*iter == ':' ||
+                   *iter == '.' ||
+                   *iter == ']' ||
+                   (*iter >= 'a' && *iter <= 'f') ||
+                   (*iter >= 'A' && *iter <= 'F') ||
+                   nsCRT::IsAsciiDigit(*iter)))) {
+                looksLikeIpv6 = false;
+            }
+        }
+        if (*iter == '.') {
+            ++foundDots;
+            if (firstDotLoc == uint32_t(kNotFound)) {
+                firstDotLoc = pos;
+            }
+        } else if (*iter == ':') {
+            ++foundColons;
+            if (firstColonLoc == uint32_t(kNotFound)) {
+                firstColonLoc = pos;
+            }
+        } else if (*iter == ' ' && firstSpaceLoc == uint32_t(kNotFound)) {
+            firstSpaceLoc = pos;
+        } else if (*iter == '?' && firstQMarkLoc == uint32_t(kNotFound)) {
+            firstQMarkLoc = pos;
+        } else if ((*iter == '\'' || *iter == '"') && firstQuoteLoc == uint32_t(kNotFound)) {
+            firstQuoteLoc = pos;
+        } else if (*iter == '[') {
+            lastLSBracketLoc = pos;
+        } else if (*iter == ']') {
+            foundRSBrackets++;
+        } else if (nsCRT::IsAsciiAlpha(*iter)) {
+            hasAsciiAlpha = true;
+        } else if (nsCRT::IsAsciiDigit(*iter)) {
+            ++foundDigits;
+        }
+
+        pos++;
+        iter++;
     }
-    uint32_t qMarkLoc = uint32_t(aURIString.FindChar('?'));
-    uint32_t quoteLoc = std::min(uint32_t(aURIString.FindChar('"')),
-                               uint32_t(aURIString.FindChar('\'')));
+
+    if (lastLSBracketLoc > 0 || foundRSBrackets != 1) {
+        looksLikeIpv6 = false;
+    }
 
     nsresult rv;
+    nsAutoCString asciiHost;
+    nsAutoCString host;
+
+    bool isValidAsciiHost = aFixupInfo->mFixedURI &&
+        NS_SUCCEEDED(aFixupInfo->mFixedURI->GetAsciiHost(asciiHost)) &&
+        !asciiHost.IsEmpty();
+
+    bool isValidHost = aFixupInfo->mFixedURI &&
+        NS_SUCCEEDED(aFixupInfo->mFixedURI->GetHost(host)) &&
+        !host.IsEmpty();
+
+    // If there are 3 dots and only numbers between them, then don't do a
+    // keyword lookup (ipv4)
+    if (foundDots == 3 && (foundDots + foundDigits == pos)) {
+        return;
+    }
+
+    // If there are only colons and only hexadecimal characters ([a-z][0-9])
+    // enclosed in [], then don't do a keyword lookup
+    if (looksLikeIpv6) {
+        return;
+    }
+
     // We do keyword lookups if a space or quote preceded the dot, colon
-    // or question mark (or if the latter were not found):
-    if (((spaceLoc < dotLoc || quoteLoc < dotLoc) &&
-         (spaceLoc < colonLoc || quoteLoc < colonLoc) &&
-         (spaceLoc < qMarkLoc || quoteLoc < qMarkLoc)) ||
-        qMarkLoc == 0)
-    {
+    // or question mark (or if the latter were not found)
+    // or when the host is the same as asciiHost and there are no
+    // characters from [a-z][A-Z]
+    if (((firstSpaceLoc < firstDotLoc || firstQuoteLoc < firstDotLoc) &&
+         (firstSpaceLoc < firstColonLoc || firstQuoteLoc < firstColonLoc) &&
+         (firstSpaceLoc < firstQMarkLoc || firstQuoteLoc < firstQMarkLoc)) || firstQMarkLoc == 0 ||
+        (isValidAsciiHost && isValidHost && !hasAsciiAlpha &&
+         host.EqualsIgnoreCase(asciiHost.get()))) {
+
         rv = KeywordToURI(aFixupInfo->mOriginalInput, aPostData,
                           getter_AddRefs(aFixupInfo->mPreferredURI));
-        if (NS_SUCCEEDED(rv) && aFixupInfo->mPreferredURI)
-        {
+        if (NS_SUCCEEDED(rv) && aFixupInfo->mPreferredURI) {
             aFixupInfo->mFixupUsedKeyword = true;
         }
     }
     // ... or if there is no question mark or colon, and there is either no
     // dot, or exactly 1 and it is the first or last character of the input:
-    else if ((dotLoc == uint32_t(kNotFound) ||
-              (dotLoc == lastDotLoc && (dotLoc == 0 || dotLoc == aURIString.Length() - 1))) &&
-             colonLoc == uint32_t(kNotFound) && qMarkLoc == uint32_t(kNotFound))
-    {
+    else if ((firstDotLoc == uint32_t(kNotFound) ||
+              (foundDots == 1 && (firstDotLoc == 0 || firstDotLoc == aURIString.Length() - 1))) &&
+              firstColonLoc == uint32_t(kNotFound) && firstQMarkLoc == uint32_t(kNotFound)) {
 
-        nsAutoCString asciiHost;
-        if (aFixupInfo->mFixedURI &&
-            NS_SUCCEEDED(aFixupInfo->mFixedURI->GetAsciiHost(asciiHost)) &&
-            !asciiHost.IsEmpty()) {
-
-            if (IsDomainWhitelisted(asciiHost, dotLoc)) {
-                return;
-            }
+        if (isValidAsciiHost && IsDomainWhitelisted(asciiHost, firstDotLoc)) {
+            return;
         }
 
         // If we get here, we don't have a valid URI, or we did but the
         // host is not whitelisted, so we do a keyword search *anyway*:
         rv = KeywordToURI(aFixupInfo->mOriginalInput, aPostData,
                           getter_AddRefs(aFixupInfo->mPreferredURI));
-        if (NS_SUCCEEDED(rv) && aFixupInfo->mPreferredURI)
-        {
+        if (NS_SUCCEEDED(rv) && aFixupInfo->mPreferredURI) {
             aFixupInfo->mFixupUsedKeyword = true;
         }
     }
 }
 
 bool nsDefaultURIFixup::IsDomainWhitelisted(const nsAutoCString aAsciiHost,
                                             const uint32_t aDotLoc)
 {
--- a/docshell/test/navigation/mochitest.ini
+++ b/docshell/test/navigation/mochitest.ini
@@ -24,17 +24,17 @@ support-files =
 skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s #RANDOM
 [test_bug270414.html]
 skip-if = buildapp == 'b2g' || toolkit == "android" || e10s
 [test_bug278916.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
 [test_bug279495.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
 [test_bug344861.html]
-skip-if = (buildapp == 'b2g' && (toolkit != 'gonk' || debug)) || toolkit == 'android' || e10s
+skip-if = buildapp == 'mulet' || (buildapp == 'b2g' && (toolkit != 'gonk' || debug)) || toolkit == 'android' || e10s
 [test_bug386782.html]
 skip-if = (buildapp == 'b2g' && (toolkit != 'gonk' || debug))
 [test_bug430624.html]
 [test_bug430723.html]
 skip-if = (buildapp == 'b2g' && (toolkit != 'gonk' || debug)) || toolkit == 'android' #TIMED_OUT # b2g-debug(bug 874423) b2g-desktop(Bug 931116, b2g desktop specific, initial triage)
 [test_child.html]
 [test_grandchild.html]
 [test_not-opener.html]
--- a/docshell/test/unit/test_nsDefaultURIFixup_info.js
+++ b/docshell/test/unit/test_nsDefaultURIFixup_info.js
@@ -83,16 +83,55 @@ let testcases = [ {
     input: "http://test./",
     fixedURI: "http://test./",
     alternateURI: "http://www.test./",
   }, {
     input: "127.0.0.1",
     fixedURI: "http://127.0.0.1/",
     protocolChange: true,
   }, {
+    input: "192.168.10.110",
+    fixedURI: "http://192.168.10.110/",
+    protocolChange: true,
+  }, {
+    input: "[::1]",
+    fixedURI: "http://[::1]/",
+    alternateURI: "http://[::1]/",
+    protocolChange: true,
+    affectedByWhitelist: true
+  }, {
+    input: "[fe80:cd00:0:cde:1257:0:211e:729c]",
+    fixedURI: "http://[fe80:cd00:0:cde:1257:0:211e:729c]/",
+    alternateURI: "http://[fe80:cd00:0:cde:1257:0:211e:729c]/",
+    protocolChange: true,
+    affectedByWhitelist: true
+  }, {
+    input: "[64:ff9b::8.8.8.8]",
+    fixedURI: "http://[64:ff9b::8.8.8.8]/",
+    protocolChange: true
+  }, {
+    input: "[64:ff9b::8.8.8.8]/~moz",
+    fixedURI: "http://[64:ff9b::8.8.8.8]/~moz",
+    protocolChange: true
+  }, {
+    input: "[::1][::1]",
+    keywordLookup: true,
+    protocolChange: true
+  }, {
+    input: "[::1][100",
+    fixedURI: "http://[::1][100/",
+    alternateURI: "http://[::1][100/",
+    keywordLookup: true,
+    protocolChange: true,
+    affectedByWhitelist: true
+  }, {
+    input: "[::1]]",
+    keywordLookup: true,
+    protocolChange: true
+  }, {
     input: "1234",
     fixedURI: "http://1234/",
     alternateURI: "http://www.1234.com/",
     keywordLookup: true,
     protocolChange: true,
     affectedByWhitelist: true,
   }, {
     input: "host/foo.txt",
@@ -120,16 +159,32 @@ let testcases = [ {
     alternateURI: "http://www..test/",
     keywordLookup: true,
     protocolChange: true,
   }, {
     input: "mozilla is amazing",
     keywordLookup: true,
     protocolChange: true,
   }, {
+    input: "search ?mozilla",
+    keywordLookup: true,
+    protocolChange: true,
+  }, {
+    input: "mozilla .com",
+    keywordLookup: true,
+    protocolChange: true,
+  }, {
+    input: "what if firefox?",
+    keywordLookup: true,
+    protocolChange: true,
+  }, {
+    input: "london's map",
+    keywordLookup: true,
+    protocolChange: true,
+  }, {
     input: "mozilla ",
     fixedURI: "http://mozilla/",
     alternateURI: "http://www.mozilla.com/",
     keywordLookup: true,
     protocolChange: true,
     affectedByWhitelist: true,
   }, {
     input: "   mozilla  ",
@@ -185,16 +240,122 @@ let testcases = [ {
     keywordLookup: true,
     protocolChange: true,
   }, {
     input: "http://whitelisted/",
     fixedURI: "http://whitelisted/",
     alternateURI: "http://www.whitelisted.com/",
     affectedByWhitelist: true,
     inWhitelist: true,
+  }, {
+    input: "café.local",
+    fixedURI: "http://café.local/",
+    alternateURI: "http://www.café.local/",
+    protocolChange: true
+  }, {
+    input: "47.6182,-122.830",
+    fixedURI: "http://47.6182,-122.830/",
+    keywordLookup: true,
+    protocolChange: true
+  }, {
+    input: "-47.6182,-23.51",
+    fixedURI: "http://-47.6182,-23.51/",
+    keywordLookup: true,
+    protocolChange: true
+  }, {
+    input: "-22.14,23.51-",
+    fixedURI: "http://-22.14,23.51-/",
+    keywordLookup: true,
+    protocolChange: true
+  }, {
+    input: "32.7",
+    fixedURI: "http://32.7/",
+    alternateURI: "http://www.32.7/",
+    keywordLookup: true,
+    protocolChange: true
+  }, {
+    input: "5+2",
+    fixedURI: "http://5+2/",
+    alternateURI: "http://www.5+2.com/",
+    keywordLookup: true,
+    protocolChange: true,
+    affectedByWhitelist: true
+  }, {
+    input: "moz ?.::%27",
+    keywordLookup: true,
+    protocolChange: true
+  }, {
+    input: "mozilla.com/?q=search",
+    fixedURI: "http://mozilla.com/?q=search",
+    alternateURI: "http://www.mozilla.com/?q=search",
+    protocolChange: true
+  }, {
+    input: "mozilla.com?q=search",
+    fixedURI: "http://mozilla.com/?q=search",
+    alternateURI: "http://www.mozilla.com/?q=search",
+    protocolChange: true
+  }, {
+    input: "mozilla.com ?q=search",
+    keywordLookup: true,
+    protocolChange: true
+  }, {
+    input: "mozilla.com.?q=search",
+    fixedURI: "http://mozilla.com./?q=search",
+    protocolChange: true
+  }, {
+    input: "mozilla.com'?q=search",
+    fixedURI: "http://mozilla.com'/?q=search",
+    alternateURI: "http://www.mozilla.com'/?q=search",
+    protocolChange: true
+  }, {
+    input: "mozilla.com':search",
+    keywordLookup: true,
+    protocolChange: true
+  }, {
+    input: "[mozilla]",
+    keywordLookup: true,
+    protocolChange: true
+  }, {
+    input: "':?",
+    fixedURI: "http://'/?",
+    alternateURI: "http://www.'.com/?",
+    keywordLookup: true,
+    protocolChange: true,
+    affectedByWhitelist: true
+  }, {
+    input: "a?.com",
+    fixedURI: "http://a/?.com",
+    alternateURI: "http://www.a.com/?.com",
+    protocolChange: true,
+    affectedByWhitelist: true
+  }, {
+    input: "?'.com",
+    fixedURI: "http:///?%27.com",
+    alternateURI: "http://www..com/?%27.com",
+    keywordLookup: true,
+    protocolChange: true,
+    affectedByWhitelist: true
+  }, {
+    input: "' ?.com",
+    keywordLookup: true,
+    protocolChange: true
+  }, {
+    input: "?mozilla",
+    fixedURI: "http:///?mozilla",
+    alternateURI: "http://www..com/?mozilla",
+    keywordLookup: true,
+    protocolChange: true,
+    affectedByWhitelist: true
+  }, {
+    input: "??mozilla",
+    fixedURI: "http:///??mozilla",
+    alternateURI: "http://www..com/??mozilla",
+    keywordLookup: true,
+    protocolChange: true,
+    affectedByWhitelist: true
   }];
 
 if (Services.appinfo.OS.toLowerCase().startsWith("win")) {
   testcases.push({
     input: "C:\\some\\file.txt",
     fixedURI: "file:///C:/some/file.txt",
     protocolChange: true,
   });
@@ -292,24 +453,30 @@ function run_test() {
 
       // Check booleans on input:
       let couldDoKeywordLookup = flags & urifixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
       do_check_eq(info.fixupUsedKeyword, couldDoKeywordLookup && expectKeywordLookup);
       do_check_eq(info.fixupChangedProtocol, expectProtocolChange);
       do_check_eq(info.fixupCreatedAlternateURI, makeAlternativeURI && alternativeURI != null);
 
       // Check the preferred URI
-      let requiresWhitelistedDomain = flags & urifixup.FIXUP_FLAG_REQUIRE_WHITELISTED_HOST
+      let requiresWhitelistedDomain = flags & urifixup.FIXUP_FLAG_REQUIRE_WHITELISTED_HOST;
       if (couldDoKeywordLookup) {
         if (expectKeywordLookup) {
           if (!affectedByWhitelist || (affectedByWhitelist && !inWhitelist)) {
-        let urlparamInput = encodeURIComponent(sanitize(testInput)).replace("%20", "+", "g");
-        let searchURL = kSearchEngineURL.replace("{searchTerms}", urlparamInput);
-        do_check_eq(info.preferredURI.spec, searchURL);
-      } else {
+            let urlparamInput = encodeURIComponent(sanitize(testInput)).replace("%20", "+", "g");
+            // If the input starts with `?`, then info.preferredURI.spec will omit it
+            // In order to test this behaviour, remove `?` only if it is the first character
+            if (urlparamInput.startsWith("%3F")) {
+              urlparamInput = urlparamInput.replace("%3F", "");
+            }
+            let searchURL = kSearchEngineURL.replace("{searchTerms}", urlparamInput);
+            let spec = info.preferredURI.spec.replace("%27", "'", "g");
+            do_check_eq(spec, searchURL);
+          } else {
             do_check_eq(info.preferredURI, null);
           }
         } else {
           do_check_eq(info.preferredURI.spec, info.fixedURI.spec);
         }
       } else if (requiresWhitelistedDomain) {
         // Not a keyword search, but we want to enforce the host whitelist
         if (!affectedByWhitelist || (affectedByWhitelist && inWhitelist))
--- a/dom/audiochannel/tests/mochitest.ini
+++ b/dom/audiochannel/tests/mochitest.ini
@@ -1,11 +1,11 @@
 [DEFAULT]
 support-files =
   audio.ogg
   file_audio.html
   file_telephonyPolicy.html
   AudioChannelChromeScript.js
 
 [test_telephonyPolicy.html]
-skip-if = buildapp == 'mulet' || (toolkit == 'gonk' || e10s)
+skip-if = buildapp == 'mulet' || (toolkit == 'gonk' || e10s) || os == "android"
 [test_audioChannelChange.html]
 skip-if = (toolkit != 'gonk')
--- a/dom/base/ImageEncoder.cpp
+++ b/dom/base/ImageEncoder.cpp
@@ -411,22 +411,21 @@ ImageEncoder::ExtractDataInternal(const 
   } else {
     // no context, so we have to encode an empty image
     // note that if we didn't have a current context, the spec says we're
     // supposed to just return transparent black pixels of the canvas
     // dimensions.
     RefPtr<DataSourceSurface> emptyCanvas =
       Factory::CreateDataSourceSurfaceWithStride(IntSize(aSize.width, aSize.height),
                                                  SurfaceFormat::B8G8R8A8,
-                                                 4 * aSize.width);
+                                                 4 * aSize.width, true);
     if (NS_WARN_IF(!emptyCanvas)) {
       return NS_ERROR_INVALID_ARG;
     }
 
-    ClearDataSourceSurface(emptyCanvas);
     DataSourceSurface::MappedSurface map;
     if (!emptyCanvas->Map(DataSourceSurface::MapType::WRITE, &map)) {
       return NS_ERROR_INVALID_ARG;
     }
     rv = aEncoder->InitFromData(map.mData,
                                 aSize.width * aSize.height * 4,
                                 aSize.width,
                                 aSize.height,
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -86,16 +86,20 @@
 # entry, just make it not a list.
 
 DOMInterfaces = {
 
 'MozActivity': {
     'nativeType': 'mozilla::dom::Activity',
 },
 
+'MozAbortablePromise': {
+    'nativeType': 'mozilla::dom::AbortablePromise',
+},
+
 'AbstractWorker': {
     'concrete': False
 },
 
 'ArchiveReader': {
     'nativeType': 'mozilla::dom::archivereader::ArchiveReader',
 },
 
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -892,51 +892,40 @@ CanvasRenderingContext2D::CheckSizeForSk
   // absolute max size threshold
   if (maxsize > 0) {
     return size.width <= maxsize && size.height <= maxsize;
   }
 
   // Cache the number of pixels on the primary screen
   static int32_t gScreenPixels = -1;
   if (gScreenPixels < 0) {
-    // Default to historical mobile screen size of 980x480.  In addition,
-    // allow skia use up to this size even if the screen is smaller.  A lot
-    // content expects this size to work well.
-    gScreenPixels = 980 * 480;
+    // Default to historical mobile screen size of 980x480, like FishIEtank.
+    // In addition, allow skia use up to this size even if the screen is smaller.
+    // A lot content expects this size to work well.
+    // See Bug 999841
+    if (gfxPlatform::GetPlatform()->HasEnoughTotalSystemMemoryForSkiaGL()) {
+      gScreenPixels = 980 * 480;
+    }
 
     nsCOMPtr<nsIScreenManager> screenManager =
       do_GetService("@mozilla.org/gfx/screenmanager;1");
     if (screenManager) {
       nsCOMPtr<nsIScreen> primaryScreen;
       screenManager->GetPrimaryScreen(getter_AddRefs(primaryScreen));
       if (primaryScreen) {
         int32_t x, y, width, height;
         primaryScreen->GetRect(&x, &y, &width, &height);
 
         gScreenPixels = std::max(gScreenPixels, width * height);
       }
     }
   }
 
-  // On high DPI devices the screen pixels may be scaled up.  Make
-  // sure to apply that scaling here as well if we are hooked up
-  // to a widget.
-  static double gDefaultScale = 0.0;
-  if (gDefaultScale < 1.0) {
-    nsIPresShell* ps = GetPresShell();
-    if (ps) {
-      nsIFrame* frame = ps->GetRootFrame();
-      if (frame) {
-        nsIWidget* widget = frame->GetNearestWidget();
-        if (widget) {
-          gDefaultScale = widget->GetDefaultScale().scale;
-        }
-      }
-    }
-  }
+  // Just always use a scale of 1.0. It can be changed if a lot of contents need it.
+  static double gDefaultScale = 1.0;
 
   double scale = gDefaultScale > 0 ? gDefaultScale : 1.0;
   int32_t threshold = ceil(scale * scale * gScreenPixels);
 
   // screen size acts as max threshold
   return threshold < 0 || (size.width * size.height) <= threshold;
 }
 
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -1870,16 +1870,19 @@ TabChild::RecvAcknowledgeScrollUpdate(co
 {
   APZCCallbackHelper::AcknowledgeScrollUpdate(aScrollId, aScrollGeneration);
   return true;
 }
 
 bool
 TabChild::RecvHandleDoubleTap(const CSSPoint& aPoint, const ScrollableLayerGuid& aGuid)
 {
+    TABC_LOG("Handling double tap at %s with %p %p\n",
+      Stringify(aPoint).c_str(), mGlobal.get(), mTabChildGlobal.get());
+
     if (!mGlobal || !mTabChildGlobal) {
         return true;
     }
 
     CSSPoint point = APZCCallbackHelper::ApplyCallbackTransform(aPoint, aGuid);
     nsString data;
     data.AppendLiteral("{ \"x\" : ");
     data.AppendFloat(point.x);
@@ -1890,16 +1893,19 @@ TabChild::RecvHandleDoubleTap(const CSSP
     DispatchMessageManagerMessage(NS_LITERAL_STRING("Gesture:DoubleTap"), data);
 
     return true;
 }
 
 bool
 TabChild::RecvHandleSingleTap(const CSSPoint& aPoint, const ScrollableLayerGuid& aGuid)
 {
+  TABC_LOG("Handling single tap at %s with %p %p %d\n",
+    Stringify(aPoint).c_str(), mGlobal.get(), mTabChildGlobal.get(), mTouchEndCancelled);
+
   if (!mGlobal || !mTabChildGlobal) {
     return true;
   }
 
   if (mTouchEndCancelled) {
     return true;
   }
 
@@ -1919,45 +1925,53 @@ TabChild::RecvHandleSingleTap(const CSSP
 }
 
 void
 TabChild::FireSingleTapEvent(LayoutDevicePoint aPoint)
 {
   if (mDestroyed) {
     return;
   }
+  TABC_LOG("Dispatching single-tap component events to %s\n",
+    Stringify(aPoint).c_str());
   int time = 0;
   DispatchSynthesizedMouseEvent(NS_MOUSE_MOVE, time, aPoint, mWidget);
   DispatchSynthesizedMouseEvent(NS_MOUSE_BUTTON_DOWN, time, aPoint, mWidget);
   DispatchSynthesizedMouseEvent(NS_MOUSE_BUTTON_UP, time, aPoint, mWidget);
 }
 
 bool
 TabChild::RecvHandleLongTap(const CSSPoint& aPoint, const ScrollableLayerGuid& aGuid)
 {
+  TABC_LOG("Handling long tap at %s with %p %p\n",
+    Stringify(aPoint).c_str(), mGlobal.get(), mTabChildGlobal.get());
+
   if (!mGlobal || !mTabChildGlobal) {
     return true;
   }
 
   SendPendingTouchPreventedResponse(false, aGuid);
 
   bool eventHandled =
       DispatchMouseEvent(NS_LITERAL_STRING("contextmenu"),
                          APZCCallbackHelper::ApplyCallbackTransform(aPoint, aGuid),
                          2, 1, 0, true,
                          nsIDOMMouseEvent::MOZ_SOURCE_TOUCH);
 
+  TABC_LOG("Contextmenu event handled: %d\n", eventHandled);
+
   // If no one handle context menu, fire MOZLONGTAP event
   if (!eventHandled) {
     LayoutDevicePoint currentPoint =
       APZCCallbackHelper::ApplyCallbackTransform(aPoint, aGuid) * mWidget->GetDefaultScale();
     int time = 0;
     nsEventStatus status =
       DispatchSynthesizedMouseEvent(NS_MOUSE_MOZLONGTAP, time, currentPoint, mWidget);
     eventHandled = (status == nsEventStatus_eConsumeNoDefault);
+    TABC_LOG("MOZLONGTAP event handled: %d\n", eventHandled);
   }
 
   SendContentReceivedTouch(aGuid, eventHandled);
 
   return true;
 }
 
 bool
@@ -2221,16 +2235,18 @@ TabChild::CancelTapTracking()
   }
   mTapHoldTimer = nullptr;
 }
 
 bool
 TabChild::RecvRealTouchEvent(const WidgetTouchEvent& aEvent,
                              const ScrollableLayerGuid& aGuid)
 {
+  TABC_LOG("Receiving touch event of type %d\n", aEvent.message);
+
   WidgetTouchEvent localEvent(aEvent);
   localEvent.widget = mWidget;
   for (size_t i = 0; i < localEvent.touches.Length(); i++) {
     aEvent.touches[i]->mRefPoint = APZCCallbackHelper::ApplyCallbackTransform(aEvent.touches[i]->mRefPoint, aGuid, mWidget->GetDefaultScale());
   }
 
   nsEventStatus status = DispatchWidgetEvent(localEvent);
 
new file mode 100644
--- /dev/null
+++ b/dom/promise/AbortablePromise.cpp
@@ -0,0 +1,118 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/AbortablePromise.h"
+
+#include "mozilla/dom/AbortablePromiseBinding.h"
+#include "mozilla/dom/PromiseNativeAbortCallback.h"
+#include "mozilla/ErrorResult.h"
+#include "nsThreadUtils.h"
+#include "PromiseCallback.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(PromiseNativeAbortCallback)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(PromiseNativeAbortCallback)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PromiseNativeAbortCallback)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+NS_IMPL_CYCLE_COLLECTION_0(PromiseNativeAbortCallback)
+
+NS_IMPL_ADDREF_INHERITED(AbortablePromise, Promise)
+NS_IMPL_RELEASE_INHERITED(AbortablePromise, Promise)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(AbortablePromise)
+NS_INTERFACE_MAP_END_INHERITING(Promise)
+NS_IMPL_CYCLE_COLLECTION_INHERITED(AbortablePromise, Promise, mAbortCallback)
+
+AbortablePromise::AbortablePromise(nsIGlobalObject* aGlobal,
+                                   PromiseNativeAbortCallback& aAbortCallback)
+  : Promise(aGlobal)
+  , mAbortCallback(&aAbortCallback)
+{
+}
+
+AbortablePromise::AbortablePromise(nsIGlobalObject* aGlobal)
+  : Promise(aGlobal)
+{
+}
+
+AbortablePromise::~AbortablePromise()
+{
+}
+
+/* static */ already_AddRefed<AbortablePromise>
+AbortablePromise::Create(nsIGlobalObject* aGlobal,
+                         PromiseNativeAbortCallback& aAbortCallback,
+                         ErrorResult& aRv)
+{
+  nsRefPtr<AbortablePromise> p = new AbortablePromise(aGlobal, aAbortCallback);
+  p->CreateWrapper(aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+  return p.forget();
+}
+
+JSObject*
+AbortablePromise::WrapObject(JSContext* aCx)
+{
+  return MozAbortablePromiseBinding::Wrap(aCx, this);
+}
+
+/* static */ already_AddRefed<AbortablePromise>
+AbortablePromise::Constructor(const GlobalObject& aGlobal, PromiseInit& aInit,
+                              AbortCallback& aAbortCallback, ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global;
+  global = do_QueryInterface(aGlobal.GetAsSupports());
+  if (!global) {
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+    return nullptr;
+  }
+
+  nsRefPtr<AbortablePromise> promise = new AbortablePromise(global);
+  promise->CreateWrapper(aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  promise->CallInitFunction(aGlobal, aInit, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  promise->mAbortCallback = &aAbortCallback;
+
+  return promise.forget();
+}
+
+void
+AbortablePromise::Abort()
+{
+  if (IsPending()) {
+    return;
+  }
+  MaybeReject(NS_ERROR_ABORT);
+
+  nsCOMPtr<nsIRunnable> runnable =
+    NS_NewRunnableMethod(this, &AbortablePromise::DoAbort);
+  Promise::DispatchToMainOrWorkerThread(runnable);
+}
+
+void
+AbortablePromise::DoAbort()
+{
+  if (mAbortCallback.HasWebIDLCallback()) {
+    ErrorResult rv;
+    mAbortCallback.GetWebIDLCallback()->Call(rv);
+    return;
+  }
+  mAbortCallback.GetXPCOMCallback()->Call();
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/promise/AbortablePromise.h
@@ -0,0 +1,66 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_AbortablePromise_h__
+#define mozilla_dom_AbortablePromise_h__
+
+#include "js/TypeDecls.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/CallbackObject.h"
+
+namespace mozilla {
+namespace dom {
+
+class AbortCallback;
+class PromiseNativeAbortCallback;
+
+class AbortablePromise
+  : public Promise
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(AbortablePromise, Promise)
+
+public:
+  // It is the same as Promise::Create except that this takes an extra
+  // aAbortCallback parameter to set the abort callback handler.
+  static already_AddRefed<AbortablePromise>
+  Create(nsIGlobalObject* aGlobal, PromiseNativeAbortCallback& aAbortCallback,
+         ErrorResult& aRv);
+
+protected:
+  // Constructor used to create native AbortablePromise with C++.
+  AbortablePromise(nsIGlobalObject* aGlobal,
+                   PromiseNativeAbortCallback& aAbortCallback);
+
+  // Constructor used to create AbortablePromise for JavaScript. It should be
+  // called by the static AbortablePromise::Constructor.
+  AbortablePromise(nsIGlobalObject* aGlobal);
+
+  virtual ~AbortablePromise();
+
+public:
+  virtual JSObject*
+  WrapObject(JSContext* aCx) MOZ_OVERRIDE;
+
+  static already_AddRefed<AbortablePromise>
+  Constructor(const GlobalObject& aGlobal, PromiseInit& aInit,
+              AbortCallback& aAbortCallback, ErrorResult& aRv);
+
+  void Abort();
+
+private:
+  void DoAbort();
+
+  // The callback functions to abort the promise.
+  CallbackObjectHolder<AbortCallback,
+                       PromiseNativeAbortCallback> mAbortCallback;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_AbortablePromise_h__
--- a/dom/promise/Promise.cpp
+++ b/dom/promise/Promise.cpp
@@ -320,36 +320,43 @@ JSObject*
 Promise::WrapObject(JSContext* aCx)
 {
   return PromiseBinding::Wrap(aCx, this);
 }
 
 already_AddRefed<Promise>
 Promise::Create(nsIGlobalObject* aGlobal, ErrorResult& aRv)
 {
+  nsRefPtr<Promise> p = new Promise(aGlobal);
+  p->CreateWrapper(aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+  return p.forget();
+}
+
+void
+Promise::CreateWrapper(ErrorResult& aRv)
+{
   AutoJSAPI jsapi;
-  if (!jsapi.Init(aGlobal)) {
+  if (!jsapi.Init(mGlobal)) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
-    return nullptr;
+    return;
   }
   JSContext* cx = jsapi.cx();
 
-  nsRefPtr<Promise> p = new Promise(aGlobal);
-
   JS::Rooted<JS::Value> ignored(cx);
-  if (!WrapNewBindingObject(cx, p, &ignored)) {
+  if (!WrapNewBindingObject(cx, this, &ignored)) {
     JS_ClearPendingException(cx);
     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
-    return nullptr;
+    return;
   }
 
   // Need the .get() bit here to get template deduction working right
-  dom::PreserveWrapper(p.get());
-
-  return p.forget();
+  dom::PreserveWrapper(this);
 }
 
 void
 Promise::MaybeResolve(JSContext* aCx,
                       JS::Handle<JS::Value> aValue)
 {
   MaybeResolveInternal(aCx, aValue);
 }
@@ -478,64 +485,74 @@ Promise::CreateThenableFunction(JSContex
 
   return obj;
 }
 
 /* static */ already_AddRefed<Promise>
 Promise::Constructor(const GlobalObject& aGlobal,
                      PromiseInit& aInit, ErrorResult& aRv)
 {
-  JSContext* cx = aGlobal.Context();
-
   nsCOMPtr<nsIGlobalObject> global;
   global = do_QueryInterface(aGlobal.GetAsSupports());
   if (!global) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
   nsRefPtr<Promise> promise = Create(global, aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
 
+  promise->CallInitFunction(aGlobal, aInit, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  return promise.forget();
+}
+
+void
+Promise::CallInitFunction(const GlobalObject& aGlobal,
+                          PromiseInit& aInit, ErrorResult& aRv)
+{
+  JSContext* cx = aGlobal.Context();
+
   JS::Rooted<JSObject*> resolveFunc(cx,
-                                    CreateFunction(cx, aGlobal.Get(), promise,
+                                    CreateFunction(cx, aGlobal.Get(), this,
                                                    PromiseCallback::Resolve));
   if (!resolveFunc) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
-    return nullptr;
+    return;
   }
 
   JS::Rooted<JSObject*> rejectFunc(cx,
-                                   CreateFunction(cx, aGlobal.Get(), promise,
+                                   CreateFunction(cx, aGlobal.Get(), this,
                                                   PromiseCallback::Reject));
   if (!rejectFunc) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
-    return nullptr;
+    return;
   }
 
   aInit.Call(resolveFunc, rejectFunc, aRv, CallbackObject::eRethrowExceptions);
   aRv.WouldReportJSException();
 
   if (aRv.IsJSException()) {
     JS::Rooted<JS::Value> value(cx);
     aRv.StealJSException(cx, &value);
 
     // we want the same behavior as this JS implementation:
     // function Promise(arg) { try { arg(a, b); } catch (e) { this.reject(e); }}
     if (!JS_WrapValue(cx, &value)) {
       aRv.Throw(NS_ERROR_UNEXPECTED);
-      return nullptr;
+      return;
     }
 
-    promise->MaybeRejectInternal(cx, value);
+    MaybeRejectInternal(cx, value);
   }
-
-  return promise.forget();
 }
 
 /* static */ already_AddRefed<Promise>
 Promise::Resolve(const GlobalObject& aGlobal,
                  JS::Handle<JS::Value> aValue, ErrorResult& aRv)
 {
   // If a Promise was passed, just return it.
   if (aValue.isObject()) {
--- a/dom/promise/Promise.h
+++ b/dom/promise/Promise.h
@@ -45,33 +45,31 @@ public:
   {
     MOZ_ASSERT(mPromise);
   }
 
   virtual bool
   Notify(JSContext* aCx, workers::Status aStatus) MOZ_OVERRIDE;
 };
 
-class Promise MOZ_FINAL : public nsISupports,
-                          public nsWrapperCache,
-                          public SupportsWeakPtr<Promise>
+class Promise : public nsISupports,
+                public nsWrapperCache,
+                public SupportsWeakPtr<Promise>
 {
   friend class NativePromiseCallback;
   friend class PromiseResolverTask;
   friend class PromiseTask;
   friend class PromiseReportRejectFeature;
   friend class PromiseWorkerProxy;
   friend class PromiseWorkerProxyRunnable;
   friend class RejectPromiseCallback;
   friend class ResolvePromiseCallback;
   friend class ThenableResolverTask;
   friend class WrapperPromiseCallback;
 
-  ~Promise();
-
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Promise)
   MOZ_DECLARE_REFCOUNTED_TYPENAME(Promise)
 
   // Promise creation tries to create a JS reflector for the Promise, so is
   // fallible.  Furthermore, we don't want to do JS-wrapping on a 0-refcount
   // object, so we addref before doing that and return the addrefed pointer
@@ -154,21 +152,42 @@ public:
       const Sequence<JS::Value>& aIterable, ErrorResult& aRv);
 
   static already_AddRefed<Promise>
   Race(const GlobalObject& aGlobal,
        const Sequence<JS::Value>& aIterable, ErrorResult& aRv);
 
   void AppendNativeHandler(PromiseNativeHandler* aRunnable);
 
-private:
+protected:
   // Do NOT call this unless you're Promise::Create.  I wish we could enforce
   // that from inside this class too, somehow.
   explicit Promise(nsIGlobalObject* aGlobal);
 
+  virtual ~Promise();
+
+  // Queue an async task to current main or worker thread.
+  static void
+  DispatchToMainOrWorkerThread(nsIRunnable* aRunnable);
+
+  // Do JS-wrapping after Promise creation.
+  void CreateWrapper(ErrorResult& aRv);
+
+  // Create the JS resolving functions of resolve() and reject(). And provide
+  // references to the two functions by calling PromiseInit passed from Promise
+  // constructor.
+  void CallInitFunction(const GlobalObject& aGlobal, PromiseInit& aInit,
+                        ErrorResult& aRv);
+
+  bool IsPending()
+  {
+    return mResolvePending;
+  }
+
+private:
   friend class PromiseDebugging;
 
   enum PromiseState {
     Pending,
     Resolved,
     Rejected
   };
 
@@ -184,20 +203,16 @@ private:
     mState = aState;
   }
 
   void SetResult(JS::Handle<JS::Value> aValue)
   {
     mResult = aValue;
   }
 
-  // Queue an async task to current main or worker thread.
-  static void
-  DispatchToMainOrWorkerThread(nsIRunnable* aRunnable);
-
   // This method processes promise's resolve/reject callbacks with promise's
   // result. It's executed when the resolver.resolve() or resolver.reject() is
   // called or when the promise already has a result and new callbacks are
   // appended by then(), catch() or done().
   void RunTask();
 
   void RunResolveTask(JS::Handle<JS::Value> aValue,
                       Promise::PromiseState aState,
--- a/dom/promise/PromiseCallback.cpp
+++ b/dom/promise/PromiseCallback.cpp
@@ -15,23 +15,17 @@ namespace dom {
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(PromiseCallback)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(PromiseCallback)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PromiseCallback)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
-NS_IMPL_CYCLE_COLLECTION_CLASS(PromiseCallback)
-
-NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PromiseCallback)
-NS_IMPL_CYCLE_COLLECTION_UNLINK_END
-
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(PromiseCallback)
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+NS_IMPL_CYCLE_COLLECTION_0(PromiseCallback)
 
 PromiseCallback::PromiseCallback()
 {
 }
 
 PromiseCallback::~PromiseCallback()
 {
 }
new file mode 100644
--- /dev/null
+++ b/dom/promise/PromiseNativeAbortCallback.h
@@ -0,0 +1,36 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this file,
+* You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PromiseNativeAbortCallback_h
+#define mozilla_dom_PromiseNativeAbortCallback_h
+
+#include "nsISupports.h"
+
+namespace mozilla {
+namespace dom {
+
+/*
+ * PromiseNativeAbortCallback allows C++ to react to an AbortablePromise being
+ * aborted.
+ */
+class PromiseNativeAbortCallback : public nsISupports
+{
+protected:
+  virtual ~PromiseNativeAbortCallback()
+  { }
+
+public:
+  // Implemented in AbortablePromise.cpp.
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS(PromiseNativeAbortCallback)
+
+  virtual void Call() = 0;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PromiseNativeAbortCallback_h
--- a/dom/promise/moz.build
+++ b/dom/promise/moz.build
@@ -1,22 +1,25 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 EXPORTS.mozilla.dom += [
+    'AbortablePromise.h',
     'Promise.h',
     'PromiseDebugging.h',
+    'PromiseNativeAbortCallback.h',
     'PromiseNativeHandler.h',
     'PromiseWorkerProxy.h',
 ]
 
 UNIFIED_SOURCES += [
+    'AbortablePromise.cpp',
     'Promise.cpp',
     'PromiseCallback.cpp',
     'PromiseDebugging.cpp'
 ]
 
 FAIL_ON_WARNINGS = True
 
 LOCAL_INCLUDES += [
--- a/dom/promise/tests/mochitest.ini
+++ b/dom/promise/tests/mochitest.ini
@@ -1,6 +1,7 @@
 [DEFAULT]
 
 [test_bug883683.html]
 [test_promise.html]
 [test_promise_utils.html]
 [test_resolve.html]
+[test_abortable_promise.html]
new file mode 100644
--- /dev/null
+++ b/dom/promise/tests/test_abortable_promise.html
@@ -0,0 +1,115 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>Promise.resolve(anything) Test</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript"><!--
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({"set": [["dom.abortablepromise.enabled", true]]},
+                          runTest);
+var gTests = [testPending, testResolved, testRejected];
+
+function runTest() {
+   if (gTests.length == 0) {
+     SimpleTest.finish();
+     return;
+   }
+   new Promise(gTests.shift()).then(runTest, SimpleTest.finish);
+}
+
+// Aborting pending promise should first reject the promise and then call the
+// abortable callback.
+// The test succeeds once both the rejection handler and the abort handler have
+// been called.
+function testPending(succeed, fail) {
+  var rejected = false;
+  var aborted = false;
+
+  var p = new MozAbortablePromise(function(resolve, reject) {
+    // Wait for a while so that the promise can be aborted before resolved.
+    SimpleTest.executeSoon(function() {
+      resolve(0);
+    });
+  }, function abortable() {
+    aborted = true;
+    ok(true, "Promise aborted.");
+    if (rejected) {
+      succeed();
+    }
+  });
+
+  p.then(function() {
+    ok(false, "Failed to abort pending promise.");
+    fail();
+  }, function(what) {
+    rejected = true;
+    var isAbortException = (what && what.name) == "NS_ERROR_ABORT";
+    ok(isAbortException, "Should have NS_ERROR_ABORT exception");
+    if (!isAbortException) {
+      fail();
+    }
+    if (aborted) {
+      succeed();
+    }
+  });
+
+  // Abort the promise on creation.
+  p.abort();
+}
+
+// Do nothing when aborting resolved promise.
+function testResolved(succeed, fail) {
+  var p = new MozAbortablePromise(function(resolve, reject) {
+    resolve();
+  }, function abortable() {
+    ok(false, "Should not abort a resolved promise.");
+    fail();
+  });
+  p.then(function() {
+    ok(true, "Promise resolved.");
+    // Wait for a while to ensure abort handle won't be called.
+    setTimeout(succeed, 1000);
+  }, function(what) {
+    ok(false, "Failed to resolve promise: " + what);
+    fail();
+  });
+  p.abort();
+}
+
+// Do nothing when aborting rejected promise.
+function testRejected(succeed, fail) {
+  var p = new MozAbortablePromise(function(resolve, reject) {
+    reject(0);
+  }, function abortable() {
+    ok(false, "Should not abort a rejected promise.");
+    fail();
+  });
+
+  p.then(function() {
+    ok(false, "Failed to reject promise.");
+    fail();
+  }, function(what) {
+    is(what, 0, "promise rejected: " + what);
+    // Wait for a while to ensure abort handle won't be called.
+    setTimeout(succeed, 1000);
+  });
+  p.abort();
+}
+// -->
+</script>
+</pre>
+</body>
+</html>
+
--- a/dom/tests/mochitest/bugs/mochitest.ini
+++ b/dom/tests/mochitest/bugs/mochitest.ini
@@ -52,17 +52,17 @@ skip-if = buildapp == 'mulet' || buildap
 [test_bug335976.xhtml]
 skip-if = (buildapp == 'b2g' && (toolkit != 'gonk' || debug))
 [test_bug342448.html]
 [test_bug345521.html]
 [test_bug346659.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
 [test_bug351601.html]
 [test_bug369306.html]
-skip-if = (buildapp == 'b2g' && (toolkit != 'gonk' || debug)) || toolkit == 'android' || e10s #TIMED_OUT # b2g-debug(test timed out, can't focus back from popup window to opener?) b2g-desktop(test timed out, can't focus back from popup window to opener?)
+skip-if = buildapp == 'mulet' || (buildapp == 'b2g' && (toolkit != 'gonk' || debug)) || toolkit == 'android' || e10s #TIMED_OUT # b2g-debug(test timed out, can't focus back from popup window to opener?) b2g-desktop(test timed out, can't focus back from popup window to opener?)
 [test_bug370098.html]
 [test_bug377539.html]
 [test_bug384122.html]
 [test_bug389366.html]
 [test_bug38959.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
 [test_bug393974.html]
 [test_bug394769.html]
@@ -158,10 +158,10 @@ skip-if = toolkit == 'android' || e10s #
 [test_onerror_message.html]
 [test_protochains.html]
 [test_resize_move_windows.html]
 skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e10s #Windows can't change size and position on Android # b2g(Windows can't change size and position on B2G) b2g-debug(Windows can't change size and position on B2G) b2g-desktop(Windows can't change size and position on B2G)
 [test_sizetocontent_clamp.html]
 skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e10s #Windows can't change size on Android # b2g(Windows can't change size on B2G) b2g-debug(Windows can't change size on B2G) b2g-desktop(Windows can't change size on B2G)
 [test_toJSON.html]
 [test_window_bar.html]
-skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s
+skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e10s
 [test_bug1022869.html]
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -114,16 +114,18 @@ var legacyMozPrefixedInterfaces =
   ];
 // IMPORTANT: Do not change the list above without review from a DOM peer,
 //            except to remove items from it!
 
 // IMPORTANT: Do not change the list below without review from a DOM peer!
 var interfaceNamesInGlobalScope =
   [
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "MozAbortablePromise", pref: "dom.abortablepromise.enabled"},
+// IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "AlarmsManager", pref: "dom.mozAlarms.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "AnalyserNode",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "AnimationEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "Animation", pref: "dom.animations-api.core.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
new file mode 100644
--- /dev/null
+++ b/dom/webidl/AbortablePromise.webidl
@@ -0,0 +1,19 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/.
+ */
+
+callback AbortCallback = void ();
+
+[Constructor(PromiseInit init, AbortCallback abortCallback),
+ Pref="dom.abortablepromise.enabled"]
+interface MozAbortablePromise : _Promise {
+  /*
+   * Aborts the promise.
+   * If the promise has not been resolved or rejected, it should be rejected
+   * with an Exception of type abort and then AbortCallback is called
+   * asynchronously. Otherwise, nothing should be done.
+   */
+  void abort();
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -11,16 +11,17 @@ GENERATED_WEBIDL_FILES = [
 PREPROCESSED_WEBIDL_FILES = [
     'Crypto.webidl',
     'HTMLMediaElement.webidl',
     'Navigator.webidl',
     'Window.webidl',
 ]
 
 WEBIDL_FILES = [
+    'AbortablePromise.webidl',
     'AbstractWorker.webidl',
     'ActivityRequestHandler.webidl',
     'AlarmsManager.webidl',
     'AnalyserNode.webidl',
     'Animatable.webidl',
     'Animation.webidl',
     'AnimationEffect.webidl',
     'AnimationEvent.webidl',
--- a/dom/workers/ServiceWorkerRegistration.cpp
+++ b/dom/workers/ServiceWorkerRegistration.cpp
@@ -103,17 +103,17 @@ namespace {
 
 class UnregisterCallback MOZ_FINAL : public nsIServiceWorkerUnregisterCallback
 {
   nsRefPtr<Promise> mPromise;
 
 public:
   NS_DECL_ISUPPORTS
 
-  UnregisterCallback(Promise* aPromise)
+  explicit UnregisterCallback(Promise* aPromise)
     : mPromise(aPromise)
   {
     MOZ_ASSERT(mPromise);
   }
 
   NS_IMETHODIMP
   UnregisterSucceeded(bool aState)
   {
--- a/extensions/cookie/nsPermissionManager.cpp
+++ b/extensions/cookie/nsPermissionManager.cpp
@@ -15,29 +15,32 @@
 #include "nsNetUtil.h"
 #include "nsCOMArray.h"
 #include "nsArrayEnumerator.h"
 #include "nsTArray.h"
 #include "nsReadableUtils.h"
 #include "nsILineInputStream.h"
 #include "nsIIDNService.h"
 #include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceDefs.h"
 #include "prprf.h"
 #include "mozilla/storage.h"
 #include "mozilla/Attributes.h"
 #include "nsXULAppAPI.h"
 #include "nsIPrincipal.h"
 #include "nsContentUtils.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIAppsService.h"
 #include "mozIApplication.h"
 #include "nsIEffectiveTLDService.h"
 #include "nsPIDOMWindow.h"
 #include "nsIDocument.h"
 #include "mozilla/net/NeckoMessageUtils.h"
+#include "mozilla/Preferences.h"
+#include "nsReadLine.h"
 
 static nsPermissionManager *gPermissionManager = nullptr;
 
 using mozilla::dom::ContentParent;
 using mozilla::dom::ContentChild;
 using mozilla::unused; // ha!
 
 static bool
@@ -353,16 +356,22 @@ nsPermissionManager::AppClearDataObserve
 ////////////////////////////////////////////////////////////////////////////////
 // nsPermissionManager Implementation
 
 static const char kPermissionsFileName[] = "permissions.sqlite";
 #define HOSTS_SCHEMA_VERSION 3
 
 static const char kHostpermFileName[] = "hostperm.1";
 
+// Default permissions are read from a URL - this is the preference we read
+// to find that URL.
+static const char kDefaultsUrlPrefName[] = "permissions.manager.defaultsUrl";
+// If the pref above doesn't exist, the URL we use by default.
+static const char kDefaultsUrl[] = "resource://app/chrome/browser/default_permissions";
+
 static const char kPermissionChangeNotification[] = PERM_CHANGE_NOTIFICATION;
 
 NS_IMPL_ISUPPORTS(nsPermissionManager, nsIPermissionManager, nsIObserver, nsISupportsWeakReference)
 
 nsPermissionManager::nsPermissionManager()
  : mLargestID(0)
  , mIsShuttingDown(false)
 {
@@ -584,16 +593,18 @@ nsPermissionManager::InitDB(bool aRemove
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = mDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
     "UPDATE moz_hosts "
     "SET permission = ?2, expireType= ?3, expireTime = ?4 WHERE id = ?1"),
     getter_AddRefs(mStmtUpdate));
   NS_ENSURE_SUCCESS(rv, rv);
 
+  // Always import default permissions.
+  ImportDefaults();
   // check whether to import or just read in the db
   if (tableExists)
     return Read();
 
   return Import();
 }
 
 // sets the schema version and creates the moz_hosts table.
@@ -738,16 +749,22 @@ nsPermissionManager::AddInternal(nsIPrin
     // changed and the expire type is time, otherwise, don't modify.  There's
     // no need to modify a permission that doesn't expire with time when the
     // only thing changed is the expire time.
     if (aPermission == oldPermissionEntry.mPermission &&
         aExpireType == oldPermissionEntry.mExpireType &&
         (aExpireType == nsIPermissionManager::EXPIRE_NEVER ||
          aExpireTime == oldPermissionEntry.mExpireTime))
       op = eOperationNone;
+    else if (oldPermissionEntry.mID == cIDPermissionIsDefault)
+      // The existing permission is one added as a default and the new permission
+      // doesn't exactly match so we are replacing the default.  This is true
+      // even if the new permission is UNKNOWN_ACTION (which means a "logical
+      // remove" of the default)
+      op = eOperationReplacingDefault;
     else if (aPermission == nsIPermissionManager::UNKNOWN_ACTION)
       op = eOperationRemoving;
     else
       op = eOperationChanging;
   }
 
   // do the work for adding, deleting, or changing a permission:
   // update the in-memory list, write to the db, and notify consumers.
@@ -864,16 +881,72 @@ nsPermissionManager::AddInternal(nsIPrin
                                       aPermission,
                                       aExpireType,
                                       aExpireTime,
                                       MOZ_UTF16("changed"));
       }
 
       break;
     }
+  case eOperationReplacingDefault:
+    {
+      // this is handling the case when we have an existing permission
+      // entry that was created as a "default" (and thus isn't in the DB) with
+      // an explicit permission (that may include UNKNOWN_ACTION.)
+      // Note we will *not* get here if we are replacing an already replaced
+      // default value - that is handled as eOperationChanging.
+
+      // So this is a hybrid of eOperationAdding (as we are writing a new entry
+      // to the DB) and eOperationChanging (as we are replacing the in-memory
+      // repr and sending a "changed" notification).
+
+      // We want a new ID even if not writing to the DB, so the modified entry
+      // in memory doesn't have the magic cIDPermissionIsDefault value.
+      id = ++mLargestID;
+
+      // The default permission being replaced can't have session expiry.
+      NS_ENSURE_TRUE(entry->GetPermissions()[index].mExpireType != nsIPermissionManager::EXPIRE_SESSION,
+                     NS_ERROR_UNEXPECTED);
+      // We don't support the new entry having any expiry - supporting that would
+      // make things far more complex and none of the permissions we set as a
+      // default support that.
+      NS_ENSURE_TRUE(aExpireType == EXPIRE_NEVER, NS_ERROR_UNEXPECTED);
+
+      // update the existing entry in memory.
+      entry->GetPermissions()[index].mID = id;
+      entry->GetPermissions()[index].mPermission = aPermission;
+      entry->GetPermissions()[index].mExpireType = aExpireType;
+      entry->GetPermissions()[index].mExpireTime = aExpireTime;
+
+      // If requested, create the entry in the DB.
+      if (aDBOperation == eWriteToDB) {
+        uint32_t appId;
+        rv = aPrincipal->GetAppId(&appId);
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        bool isInBrowserElement;
+        rv = aPrincipal->GetIsInBrowserElement(&isInBrowserElement);
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        UpdateDB(eOperationAdding, mStmtInsert, id, host, aType, aPermission, aExpireType, aExpireTime, appId, isInBrowserElement);
+      }
+
+      if (aNotifyOperation == eNotify) {
+        NotifyObserversWithPermission(host,
+                                      entry->GetKey()->mAppId,
+                                      entry->GetKey()->mIsInBrowserElement,
+                                      mTypeArray[typeIndex],
+                                      aPermission,
+                                      aExpireType,
+                                      aExpireTime,
+                                      MOZ_UTF16("changed"));
+      }
+
+    }
+    break;
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsPermissionManager::Remove(const nsACString &aHost,
                             const char       *aType)
@@ -939,16 +1012,20 @@ nsPermissionManager::CloseDB(bool aRebui
 
 nsresult
 nsPermissionManager::RemoveAllInternal(bool aNotifyObservers)
 {
   // Remove from memory and notify immediately. Since the in-memory
   // database is authoritative, we do not need confirmation from the
   // on-disk database to notify observers.
   RemoveAllFromMemory();
+
+  // Re-import the defaults
+  ImportDefaults();
+
   if (aNotifyObservers) {
     NotifyObservers(nullptr, MOZ_UTF16("cleared"));
   }
 
   // clear the db
   if (mDBConn) {
     nsCOMPtr<mozIStorageAsyncStatement> removeStmt;
     nsresult rv = mDBConn->
@@ -1257,16 +1334,23 @@ struct nsGetEnumeratorData
 static PLDHashOperator
 AddPermissionsToList(nsPermissionManager::PermissionHashKey* entry, void *arg)
 {
   nsGetEnumeratorData *data = static_cast<nsGetEnumeratorData *>(arg);
 
   for (uint32_t i = 0; i < entry->GetPermissions().Length(); ++i) {
     nsPermissionManager::PermissionEntry& permEntry = entry->GetPermissions()[i];
 
+    // given how "default" permissions work and the possibility of them being
+    // overridden with UNKNOWN_ACTION, we might see this value here - but we
+    // do *not* want to return them via the enumerator.
+    if (permEntry.mPermission == nsIPermissionManager::UNKNOWN_ACTION) {
+      continue;
+    }
+
     nsPermission *perm = new nsPermission(entry->GetKey()->mHost,
                                           entry->GetKey()->mAppId,
                                           entry->GetKey()->mIsInBrowserElement,
                                           data->types->ElementAt(permEntry.mType),
                                           permEntry.mPermission,
                                           permEntry.mExpireType,
                                           permEntry.mExpireTime);
 
@@ -1628,60 +1712,127 @@ nsPermissionManager::Read()
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
 static const char kMatchTypeHost[] = "host";
 
+// Import() will read a file from the profile directory and add them to the
+// database before deleting the file - ie, this is a one-shot operation that
+// will not succeed on subsequent runs as the file imported from is removed.
 nsresult
 nsPermissionManager::Import()
 {
-  ENSURE_NOT_CHILD_PROCESS;
-
   nsresult rv;
 
   nsCOMPtr<nsIFile> permissionsFile;
   rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(permissionsFile));
   if (NS_FAILED(rv)) return rv;
 
   rv = permissionsFile->AppendNative(NS_LITERAL_CSTRING(kHostpermFileName));
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIInputStream> fileInputStream;
   rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream),
                                   permissionsFile);
-  if (NS_FAILED(rv)) return rv;
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = _DoImport(fileInputStream, mDBConn);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // we successfully imported and wrote to the DB - delete the old file.
+  permissionsFile->Remove(false);
+  return NS_OK;
+}
 
-  nsCOMPtr<nsILineInputStream> lineInputStream = do_QueryInterface(fileInputStream, &rv);
+// ImportDefaults will read a URL with default permissions and add them to the
+// in-memory copy of permissions.  The database is *not* written to.
+nsresult
+nsPermissionManager::ImportDefaults()
+{
+  // We allow prefs to override the default permissions URI, mainly as a hook
+  // for testing.
+  nsCString defaultsURL;
+  if (mozilla::Preferences::HasUserValue(kDefaultsUrlPrefName)) {
+    defaultsURL = mozilla::Preferences::GetCString(kDefaultsUrlPrefName);
+  } else {
+    defaultsURL = NS_LITERAL_CSTRING(kDefaultsUrl);
+  }
+
+  nsresult rv;
+  nsCOMPtr<nsIIOService> ioservice =
+    do_GetService("@mozilla.org/network/io-service;1", &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIURI> defaultsURI;
+  rv = NS_NewURI(getter_AddRefs(defaultsURI), defaultsURL,
+                 nullptr, nullptr, ioservice);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  nsCOMPtr<nsIChannel> channel;
+  rv = ioservice->NewChannelFromURI(defaultsURI, getter_AddRefs(channel));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIInputStream> inputStream;
+  rv = channel->Open(getter_AddRefs(inputStream));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = _DoImport(inputStream, nullptr);
+  inputStream->Close();
+  return rv;
+}
+
+// _DoImport reads the specified stream and adds the parsed elements.  If
+// |conn| is passed, the imported data will be written to the database, but if
+// |conn| is null the data will be added only to the in-memory copy of the
+// database.
+nsresult
+nsPermissionManager::_DoImport(nsIInputStream *inputStream, mozIStorageConnection *conn)
+{
+  ENSURE_NOT_CHILD_PROCESS;
+
+  nsresult rv;
   // start a transaction on the storage db, to optimize insertions.
   // transaction will automically commit on completion
-  mozStorageTransaction transaction(mDBConn, true);
+  // (note the transaction is a no-op if a null connection is passed)
+  mozStorageTransaction transaction(conn, true);
+
+  // The DB operation - we only try and write if a connection was passed.
+  DBOperationType operation = conn ? eWriteToDB : eNoDBOperation;
+  // and if no DB connection was passed we assume this is a "default" permission,
+  // so use the special ID which indicates this.
+  int64_t id = conn ? 0 : cIDPermissionIsDefault;
 
   /* format is:
    * matchtype \t type \t permission \t host
    * Only "host" is supported for matchtype
    * type is a string that identifies the type of permission (e.g. "cookie")
    * permission is an integer between 1 and 15
    */
 
-  nsAutoCString buffer;
+  // Ideally we'd do this with nsILineInputString, but this is called with an
+  // nsIInputStream that comes from a resource:// URI, which doesn't support
+  // that interface.  So NS_ReadLine to the rescue...
+  nsLineBuffer<char> lineBuffer;
+  nsCString line;
   bool isMore = true;
-  while (isMore && NS_SUCCEEDED(lineInputStream->ReadLine(buffer, &isMore))) {
-    if (buffer.IsEmpty() || buffer.First() == '#') {
+  do {
+    rv = NS_ReadLine(inputStream, &lineBuffer, line, &isMore);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (line.IsEmpty() || line.First() == '#') {
       continue;
     }
 
     nsTArray<nsCString> lineArray;
 
     // Split the line at tabs
-    ParseString(buffer, '\t', lineArray);
+    ParseString(line, '\t', lineArray);
 
     if (lineArray[0].EqualsLiteral(kMatchTypeHost) &&
         lineArray.Length() == 4) {
 
       nsresult error;
       uint32_t permission = lineArray[2].ToInteger(&error);
       if (NS_FAILED(error))
         continue;
@@ -1692,24 +1843,22 @@ nsPermissionManager::Import()
         if (NS_FAILED(rv))
           continue;
       }
 
       nsCOMPtr<nsIPrincipal> principal;
       nsresult rv = GetPrincipal(lineArray[3], getter_AddRefs(principal));
       NS_ENSURE_SUCCESS(rv, rv);
 
-      rv = AddInternal(principal, lineArray[1], permission, 0,
-                       nsIPermissionManager::EXPIRE_NEVER, 0, eDontNotify, eWriteToDB);
+      rv = AddInternal(principal, lineArray[1], permission, id,
+                       nsIPermissionManager::EXPIRE_NEVER, 0, eDontNotify, operation);
       NS_ENSURE_SUCCESS(rv, rv);
     }
-  }
 
-  // we're done importing - delete the old file
-  permissionsFile->Remove(false);
+  } while (isMore);
 
   return NS_OK;
 }
 
 nsresult
 nsPermissionManager::NormalizeToACE(nsCString &aHost)
 {
   // lazily init the IDN service
--- a/extensions/cookie/nsPermissionManager.h
+++ b/extensions/cookie/nsPermissionManager.h
@@ -6,17 +6,17 @@
 #ifndef nsPermissionManager_h__
 #define nsPermissionManager_h__
 
 #include "nsIPermissionManager.h"
 #include "nsIObserver.h"
 #include "nsIObserverService.h"
 #include "nsWeakReference.h"
 #include "nsCOMPtr.h"
-#include "nsIFile.h"
+#include "nsIInputStream.h"
 #include "nsTHashtable.h"
 #include "nsTArray.h"
 #include "nsString.h"
 #include "nsPermission.h"
 #include "nsHashKeys.h"
 #include "nsAutoPtr.h"
 #include "nsCOMArray.h"
 #include "nsDataHashtable.h"
@@ -170,29 +170,35 @@ public:
   static nsIPermissionManager* GetXPCOMSingleton();
   nsresult Init();
 
   // enums for AddInternal()
   enum OperationType {
     eOperationNone,
     eOperationAdding,
     eOperationRemoving,
-    eOperationChanging
+    eOperationChanging,
+    eOperationReplacingDefault
   };
 
   enum DBOperationType {
     eNoDBOperation,
     eWriteToDB
   };
 
   enum NotifyOperationType {
     eDontNotify,
     eNotify
   };
 
+  // A special value for a permission ID that indicates the ID was loaded as
+  // a default value.  These will never be written to the database, but may
+  // be overridden with an explicit permission (including UNKNOWN_ACTION)
+  static const int64_t cIDPermissionIsDefault = -1;
+
   nsresult AddInternal(nsIPrincipal* aPrincipal,
                        const nsAFlatCString &aType,
                        uint32_t aPermission,
                        int64_t aID,
                        uint32_t aExpireType,
                        int64_t  aExpireTime,
                        NotifyOperationType aNotifyOperation,
                        DBOperationType aDBOperation);
@@ -221,16 +227,18 @@ private:
                                 const char *aType,
                                 uint32_t   *aPermission,
                                 bool        aExactHostMatch,
                                 bool        aIncludingSession);
 
   nsresult InitDB(bool aRemoveFile);
   nsresult CreateTable();
   nsresult Import();
+  nsresult ImportDefaults();
+  nsresult _DoImport(nsIInputStream *inputStream, mozIStorageConnection *aConn);
   nsresult Read();
   void     NotifyObserversWithPermission(const nsACString &aHost,
                                          uint32_t          aAppId,
                                          bool              aIsInBrowserElement,
                                          const nsCString  &aType,
                                          uint32_t          aPermission,
                                          uint32_t          aExpireType,
                                          int64_t           aExpireTime,
new file mode 100644
--- /dev/null
+++ b/extensions/cookie/test/unit/test_permmanager_defaults.js
@@ -0,0 +1,177 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// The origin we use in most of the tests.
+const TEST_ORIGIN = "example.org";
+const TEST_PERMISSION = "test-permission";
+
+function run_test() {
+  run_next_test();
+}
+
+add_task(function* do_test() {
+  // setup a profile.
+  do_get_profile();
+
+  // create a file in the temp directory with the defaults.
+  let file = do_get_tempdir();
+  file.append("test_default_permissions");
+
+  // write our test data to it.
+  let ostream = Cc["@mozilla.org/network/file-output-stream;1"].
+                createInstance(Ci.nsIFileOutputStream);
+  ostream.init(file, -1, 0666, 0);
+  let conv = Cc["@mozilla.org/intl/converter-output-stream;1"].
+             createInstance(Ci.nsIConverterOutputStream);
+  conv.init(ostream, "UTF-8", 0, 0);
+
+  conv.writeString("# this is a comment\n");
+  conv.writeString("\n"); // a blank line!
+  conv.writeString("host\t" + TEST_PERMISSION + "\t1\t" + TEST_ORIGIN + "\n");
+  ostream.close();
+
+  // Set the preference used by the permission manager so the file is read.
+  Services.prefs.setCharPref("permissions.manager.defaultsUrl", "file://" + file.path);
+
+  // initialize the permission manager service - it will read that default.
+  let pm = Cc["@mozilla.org/permissionmanager;1"].
+           getService(Ci.nsIPermissionManager);
+
+  // test the default permission was applied.
+  let permURI = NetUtil.newURI("http://" + TEST_ORIGIN);
+  let principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(permURI);
+
+  do_check_eq(Ci.nsIPermissionManager.ALLOW_ACTION,
+              pm.testPermissionFromPrincipal(principal, TEST_PERMISSION));
+
+  // the permission should exist in the enumerator.
+  do_check_eq(Ci.nsIPermissionManager.ALLOW_ACTION, findCapabilityViaEnum());
+  // but should not have been written to the DB
+  yield checkCapabilityViaDB(null);
+
+  // remove all should not throw and the default should remain
+  pm.removeAll();
+
+  do_check_eq(Ci.nsIPermissionManager.ALLOW_ACTION,
+              pm.testPermissionFromPrincipal(principal, TEST_PERMISSION));
+
+  // Asking for this permission to be removed should result in that permission
+  // having UNKNOWN_ACTION
+  pm.removeFromPrincipal(principal, TEST_PERMISSION);
+  do_check_eq(Ci.nsIPermissionManager.UNKNOWN_ACTION,
+              pm.testPermissionFromPrincipal(principal, TEST_PERMISSION));
+  // and we should have this UNKNOWN_ACTION reflected in the DB
+  yield checkCapabilityViaDB(Ci.nsIPermissionManager.UNKNOWN_ACTION);
+  // but the permission should *not* appear in the enumerator.
+  do_check_eq(null, findCapabilityViaEnum());
+
+  // and a subsequent RemoveAll should restore the default
+  pm.removeAll();
+
+  do_check_eq(Ci.nsIPermissionManager.ALLOW_ACTION,
+              pm.testPermissionFromPrincipal(principal, TEST_PERMISSION));
+  // and allow it to again be seen in the enumerator.
+  do_check_eq(Ci.nsIPermissionManager.ALLOW_ACTION, findCapabilityViaEnum());
+
+  // now explicitly add a permission - this too should override the default.
+  pm.addFromPrincipal(principal, TEST_PERMISSION, Ci.nsIPermissionManager.DENY_ACTION);
+
+  // it should be reflected in a permission check, in the enumerator and the DB
+  do_check_eq(Ci.nsIPermissionManager.DENY_ACTION,
+              pm.testPermissionFromPrincipal(principal, TEST_PERMISSION));
+  do_check_eq(Ci.nsIPermissionManager.DENY_ACTION, findCapabilityViaEnum());
+  yield checkCapabilityViaDB(Ci.nsIPermissionManager.DENY_ACTION);
+
+  // explicitly add a different permission - in this case we are no longer
+  // replacing the default, but instead replacing the replacement!
+  pm.addFromPrincipal(principal, TEST_PERMISSION, Ci.nsIPermissionManager.PROMPT_ACTION);
+
+  // it should be reflected in a permission check, in the enumerator and the DB
+  do_check_eq(Ci.nsIPermissionManager.PROMPT_ACTION,
+              pm.testPermissionFromPrincipal(principal, TEST_PERMISSION));
+  do_check_eq(Ci.nsIPermissionManager.PROMPT_ACTION, findCapabilityViaEnum());
+  yield checkCapabilityViaDB(Ci.nsIPermissionManager.PROMPT_ACTION);
+
+  // remove the temp file we created.
+  file.remove(false);
+});
+
+// use an enumerator to find the requested permission.  Returns the permission
+// value (ie, the "capability" in nsIPermission parlance) or null if it can't
+// be found.
+function findCapabilityViaEnum(host = TEST_ORIGIN, type = TEST_PERMISSION) {
+  let result = undefined;
+  let e = Services.perms.enumerator;
+  while (e.hasMoreElements()) {
+    let perm = e.getNext().QueryInterface(Ci.nsIPermission);
+    if (perm.host == host &&
+        perm.type == type) {
+      if (result !== undefined) {
+        // we've already found one previously - that's bad!
+        do_throw("enumerator found multiple entries");
+      }
+      result = perm.capability;
+    }
+  }
+  return result || null;
+}
+
+// A function to check the DB has the specified capability.  As the permission
+// manager uses async DB operations without a completion callback, the
+// distinct possibility exists that our checking of the DB will happen before
+// the permission manager update has completed - so we just retry a few times.
+// Returns a promise.
+function checkCapabilityViaDB(expected, host = TEST_ORIGIN, type = TEST_PERMISSION) {
+  let deferred = Promise.defer();
+  let count = 0;
+  let max = 20;
+  let do_check = () => {
+    let got = findCapabilityViaDB(host, type);
+    if (got == expected) {
+      // the do_check_eq() below will succeed - which is what we want.
+      do_check_eq(got, expected, "The database has the expected value");
+      deferred.resolve();
+      return;
+    }
+    // value isn't correct - see if we've retried enough
+    if (count++ == max) {
+      // the do_check_eq() below will fail - which is what we want.
+      do_check_eq(got, expected, "The database wasn't updated with the expected value");
+      deferred.resolve();
+      return;
+    }
+    // we can retry...
+    do_timeout(100, do_check);
+  }
+  do_check();
+  return deferred.promise;
+}
+
+// use the DB to find the requested permission.   Returns the permission
+// value (ie, the "capability" in nsIPermission parlance) or null if it can't
+// be found.
+function findCapabilityViaDB(host = TEST_ORIGIN, type = TEST_PERMISSION) {
+  let file = Services.dirsvc.get("ProfD", Ci.nsIFile);
+  file.append("permissions.sqlite");
+
+  let storage = Cc["@mozilla.org/storage/service;1"]
+                  .getService(Ci.mozIStorageService);
+
+  let connection = storage.openDatabase(file);
+
+  let query = connection.createStatement(
+      "SELECT permission FROM moz_hosts WHERE host = :host AND type = :type");
+  query.bindByName("host", host);
+  query.bindByName("type", type);
+
+  if (!query.executeStep()) {
+    // no row
+    return null;
+  }
+  let result = query.getInt32(0);
+  if (query.executeStep()) {
+    // this is bad - we never expect more than 1 row here.
+    do_throw("More than 1 row found!")
+  }
+  return result;
+}
--- a/extensions/cookie/test/unit/xpcshell.ini
+++ b/extensions/cookie/test/unit/xpcshell.ini
@@ -13,16 +13,17 @@ support-files =
 [test_cookies_privatebrowsing.js]
 [test_cookies_profile_close.js]
 [test_cookies_read.js]
 [test_cookies_sync_failure.js]
 [test_cookies_thirdparty.js]
 [test_cookies_thirdparty_session.js]
 [test_domain_eviction.js]
 [test_eviction.js]
+[test_permmanager_defaults.js]
 [test_permmanager_expiration.js]
 [test_permmanager_getPermissionObject.js]
 [test_permmanager_notifications.js]
 [test_permmanager_removeall.js]
 [test_permmanager_load_invalid_entries.js]
 skip-if = debug == true
 [test_permmanager_idn.js]
 [test_permmanager_subdomains.js]
--- a/gfx/2d/2D.h
+++ b/gfx/2d/2D.h
@@ -1112,30 +1112,30 @@ public:
    */
   static TemporaryRef<ScaledFont>
     CreateScaledFontWithCairo(const NativeFont &aNativeFont, Float aSize, cairo_scaled_font_t* aScaledFont);
 
   /**
    * This creates a simple data source surface for a certain size. It allocates
    * new memory for the surface. This memory is freed when the surface is
    * destroyed.  The caller is responsible for handing the case where nullptr
-   * is returned.
+   * is returned. The surface is not zeroed unless requested.
    */
   static TemporaryRef<DataSourceSurface>
-    CreateDataSourceSurface(const IntSize &aSize, SurfaceFormat aFormat);
+    CreateDataSourceSurface(const IntSize &aSize, SurfaceFormat aFormat, bool aZero = false);
 
   /**
    * This creates a simple data source surface for a certain size with a
    * specific stride, which must be large enough to fit all pixels.
    * It allocates new memory for the surface. This memory is freed when
    * the surface is destroyed.  The caller is responsible for handling the case
-   * where nullptr is returned.
+   * where nullptr is returned. The surface is not zeroed unless requested.
    */
   static TemporaryRef<DataSourceSurface>
-    CreateDataSourceSurfaceWithStride(const IntSize &aSize, SurfaceFormat aFormat, int32_t aStride);
+    CreateDataSourceSurfaceWithStride(const IntSize &aSize, SurfaceFormat aFormat, int32_t aStride, bool aZero = false);
 
   /**
    * This creates a simple data source surface for some existing data. It will
    * wrap this data and the data for this source surface. The caller is
    * responsible for deallocating the memory only after destruction of the
    * surface.
    */
   static TemporaryRef<DataSourceSurface>
--- a/gfx/2d/DataSurfaceHelpers.h
+++ b/gfx/2d/DataSurfaceHelpers.h
@@ -43,16 +43,18 @@ SurfaceToPackedBGRA(DataSourceSurface *a
  * color components).
  */
 uint8_t*
 SurfaceToPackedBGR(DataSourceSurface *aSurface);
 
 /**
  * Clears all the bytes in a DataSourceSurface's data array to zero (so to
  * transparent black for SurfaceFormat::B8G8R8A8, for example).
+ * Note that DataSourceSurfaces can be initialized to zero, which is
+ * more efficient than zeroing the surface after initialization.
  */
 void
 ClearDataSourceSurface(DataSourceSurface *aSurface);
 
 /**
  * Multiplies aStride and aHeight and makes sure the result is limited to
  * something sane. To keep things consistent, this should always be used
  * wherever we allocate a buffer based on surface stride and height.
--- a/gfx/2d/DrawTargetCG.cpp
+++ b/gfx/2d/DrawTargetCG.cpp
@@ -1353,19 +1353,18 @@ DrawTargetCG::Init(BackendType aType,
     size_t bufLen = BufferSizeFromStrideAndHeight(aStride, aSize.height);
     if (bufLen == 0) {
       mColorSpace = nullptr;
       mCg = nullptr;
       return false;
     }
     static_assert(sizeof(decltype(mData[0])) == 1,
                   "mData.Realloc() takes an object count, so its objects must be 1-byte sized if we use bufLen");
-    mData.Realloc(/* actually an object count */ bufLen);
+    mData.Realloc(/* actually an object count */ bufLen, true);
     aData = static_cast<unsigned char*>(mData);
-    memset(aData, 0, bufLen);
   }
 
   mSize = aSize;
 
   if (aType == BackendType::COREGRAPHICS_ACCELERATED) {
     RefPtr<MacIOSurface> ioSurface = MacIOSurface::CreateIOSurface(aSize.width, aSize.height);
     mCg = ioSurface->CreateIOSurfaceContext();
     // If we don't have the symbol for 'CreateIOSurfaceContext' mCg will be null
--- a/gfx/2d/Factory.cpp
+++ b/gfx/2d/Factory.cpp
@@ -695,44 +695,46 @@ Factory::CreateWrappingDataSourceSurface
     return newSurf.forget();
   }
 
   return nullptr;
 }
 
 TemporaryRef<DataSourceSurface>
 Factory::CreateDataSourceSurface(const IntSize &aSize,
-                                 SurfaceFormat aFormat)
+                                 SurfaceFormat aFormat,
+                                 bool aZero)
 {
   if (!CheckSurfaceSize(aSize)) {
     gfxWarning() << "CreateDataSourceSurface failed with bad size";
     return nullptr;
   }
 
   RefPtr<SourceSurfaceAlignedRawData> newSurf = new SourceSurfaceAlignedRawData();
-  if (newSurf->Init(aSize, aFormat)) {
+  if (newSurf->Init(aSize, aFormat, aZero)) {
     return newSurf.forget();
   }
 
   gfxWarning() << "CreateDataSourceSurface failed in init";
   return nullptr;
 }
 
 TemporaryRef<DataSourceSurface>
 Factory::CreateDataSourceSurfaceWithStride(const IntSize &aSize,
                                            SurfaceFormat aFormat,
-                                           int32_t aStride)
+                                           int32_t aStride,
+                                           bool aZero)
 {
   if (aStride < aSize.width * BytesPerPixel(aFormat)) {
     gfxWarning() << "CreateDataSourceSurfaceWithStride failed with bad stride";
     return nullptr;
   }
 
   RefPtr<SourceSurfaceAlignedRawData> newSurf = new SourceSurfaceAlignedRawData();
-  if (newSurf->InitWithStride(aSize, aFormat, aStride)) {
+  if (newSurf->InitWithStride(aSize, aFormat, aStride, aZero)) {
     return newSurf.forget();
   }
 
   gfxWarning() << "CreateDataSourceSurfaceWithStride failed to initialize";
   return nullptr;
 }
 
 TemporaryRef<DrawEventRecorder>
--- a/gfx/2d/FilterNodeSoftware.cpp
+++ b/gfx/2d/FilterNodeSoftware.cpp
@@ -487,26 +487,23 @@ GetDataSurfaceInRect(SourceSurface *aSur
     return aSurface ? aSurface->GetDataSurface() : nullptr;
   }
 
   IntRect intersect = sourceRect.Intersect(aDestRect);
   IntRect intersectInSourceSpace = intersect - sourceRect.TopLeft();
   IntRect intersectInDestSpace = intersect - aDestRect.TopLeft();
   SurfaceFormat format = aSurface ? aSurface->GetFormat() : SurfaceFormat(SurfaceFormat::B8G8R8A8);
 
+  bool clear = aEdgeMode == EDGE_MODE_NONE && !aSurfaceRect.Contains(aDestRect);
   RefPtr<DataSourceSurface> target =
-    Factory::CreateDataSourceSurface(aDestRect.Size(), format);
+    Factory::CreateDataSourceSurface(aDestRect.Size(), format, clear);
   if (MOZ2D_WARN_IF(!target)) {
     return nullptr;
   }
 
-  if (aEdgeMode == EDGE_MODE_NONE && !aSurfaceRect.Contains(aDestRect)) {
-    ClearDataSourceSurface(target);
-  }
-
   if (!aSurface) {
     return target.forget();
   }
 
   RefPtr<DataSourceSurface> dataSource = aSurface->GetDataSurface();
   MOZ_ASSERT(dataSource);
 
   if (aEdgeMode == EDGE_MODE_WRAP) {
@@ -2366,21 +2363,20 @@ FilterNodeConvolveMatrixSoftware::DoRend
 
   if (!input) {
     return nullptr;
   }
 
   DebugOnlyAutoColorSamplingAccessControl accessControl(input);
 
   RefPtr<DataSourceSurface> target =
-    Factory::CreateDataSourceSurface(aRect.Size(), SurfaceFormat::B8G8R8A8);
+    Factory::CreateDataSourceSurface(aRect.Size(), SurfaceFormat::B8G8R8A8, true);
   if (MOZ2D_WARN_IF(!target)) {
     return nullptr;
   }
-  ClearDataSourceSurface(target);
 
   IntPoint offset = aRect.TopLeft() - srcRect.TopLeft();
 
   uint8_t* sourceData = DataAtOffset(input, offset);
   int32_t sourceStride = input->Stride();
   uint8_t* targetData = target->GetData();
   int32_t targetStride = target->Stride();
 
@@ -2771,25 +2767,23 @@ FilterNodeCompositeSoftware::SetAttribut
 }
 
 TemporaryRef<DataSourceSurface>
 FilterNodeCompositeSoftware::Render(const IntRect& aRect)
 {
   RefPtr<DataSourceSurface> start =
     GetInputDataSourceSurface(IN_COMPOSITE_IN_START, aRect, NEED_COLOR_CHANNELS);
   RefPtr<DataSourceSurface> dest =
-    Factory::CreateDataSourceSurface(aRect.Size(), SurfaceFormat::B8G8R8A8);
+    Factory::CreateDataSourceSurface(aRect.Size(), SurfaceFormat::B8G8R8A8, !start);
   if (MOZ2D_WARN_IF(!dest)) {
     return nullptr;
   }
 
   if (start) {
     CopyRect(start, dest, aRect - aRect.TopLeft(), IntPoint());
-  } else {
-    ClearDataSourceSurface(dest);
   }
 
   for (size_t inputIndex = 1; inputIndex < NumberOfSetInputs(); inputIndex++) {
     RefPtr<DataSourceSurface> input =
       GetInputDataSourceSurface(IN_COMPOSITE_IN_START + inputIndex, aRect, NEED_COLOR_CHANNELS);
     if (input) {
       FilterProcessing::ApplyComposition(input, dest, mOperator);
     } else {
--- a/gfx/2d/SourceSurfaceRawData.cpp
+++ b/gfx/2d/SourceSurfaceRawData.cpp
@@ -39,48 +39,50 @@ SourceSurfaceRawData::GuaranteePersistan
   mRawData = new uint8_t[mStride * mSize.height];
 
   memcpy(mRawData, oldData, mStride * mSize.height);
   mOwnData = true;
 }
 
 bool
 SourceSurfaceAlignedRawData::Init(const IntSize &aSize,
-                                  SurfaceFormat aFormat)
+                                  SurfaceFormat aFormat,
+                                  bool aZero)
 {
   mFormat = aFormat;
   mStride = GetAlignedStride<16>(aSize.width * BytesPerPixel(aFormat));
 
   size_t bufLen = BufferSizeFromStrideAndHeight(mStride, aSize.height);
   if (bufLen > 0) {
     static_assert(sizeof(decltype(mArray[0])) == 1,
                   "mArray.Realloc() takes an object count, so its objects must be 1-byte sized if we use bufLen");
-    mArray.Realloc(/* actually an object count */ bufLen);
+    mArray.Realloc(/* actually an object count */ bufLen, aZero);
     mSize = aSize;
   } else {
     mArray.Dealloc();
     mSize.SizeTo(0, 0);
   }
 
   return mArray != nullptr;
 }
 
 bool
 SourceSurfaceAlignedRawData::InitWithStride(const IntSize &aSize,
                                             SurfaceFormat aFormat,
-                                            int32_t aStride)
+                                            int32_t aStride,
+                                            bool aZero)
 {
   mFormat = aFormat;
   mStride = aStride;
 
   size_t bufLen = BufferSizeFromStrideAndHeight(mStride, aSize.height);
   if (bufLen > 0) {
     static_assert(sizeof(decltype(mArray[0])) == 1,
                   "mArray.Realloc() takes an object count, so its objects must be 1-byte sized if we use bufLen");
-    mArray.Realloc(/* actually an object count */ bufLen);
+    mArray.Realloc(/* actually an object count */ bufLen, aZero);
     mSize = aSize;
   } else {
     mArray.Dealloc();
     mSize.SizeTo(0, 0);
   }
 
   return mArray != nullptr;
 }
--- a/gfx/2d/SourceSurfaceRawData.h
+++ b/gfx/2d/SourceSurfaceRawData.h
@@ -51,20 +51,22 @@ public:
   virtual uint8_t *GetData() { return mArray; }
   virtual int32_t Stride() { return mStride; }
 
   virtual SurfaceType GetType() const { return SurfaceType::DATA; }
   virtual IntSize GetSize() const { return mSize; }
   virtual SurfaceFormat GetFormat() const { return mFormat; }
 
   bool Init(const IntSize &aSize,
-            SurfaceFormat aFormat);
+            SurfaceFormat aFormat,
+            bool aZero);
   bool InitWithStride(const IntSize &aSize,
                       SurfaceFormat aFormat,
-                      int32_t aStride);
+                      int32_t aStride,
+                      bool aZero);
 
 private:
   AlignedArray<uint8_t> mArray;
   int32_t mStride;
   SurfaceFormat mFormat;
   IntSize mSize;
 };
 
--- a/gfx/2d/Tools.h
+++ b/gfx/2d/Tools.h
@@ -98,21 +98,21 @@ struct AlignedArray
   typedef T value_type;
 
   AlignedArray()
     : mPtr(nullptr)
     , mStorage(nullptr)
   {
   }
 
-  explicit MOZ_ALWAYS_INLINE AlignedArray(size_t aCount)
+  explicit MOZ_ALWAYS_INLINE AlignedArray(size_t aCount, bool aZero = false)
     : mStorage(nullptr)
     , mCount(0)
   {
-    Realloc(aCount);
+    Realloc(aCount, aZero);
   }
 
   MOZ_ALWAYS_INLINE ~AlignedArray()
   {
     Dealloc();
   }
 
   void Dealloc()
@@ -133,30 +133,34 @@ struct AlignedArray
     }
 #endif
 
     delete [] mStorage;
     mStorage = nullptr;
     mPtr = nullptr;
   }
 
-  MOZ_ALWAYS_INLINE void Realloc(size_t aCount)
+  MOZ_ALWAYS_INLINE void Realloc(size_t aCount, bool aZero = false)
   {
     delete [] mStorage;
     CheckedInt32 storageByteCount =
       CheckedInt32(sizeof(T)) * aCount + (alignment - 1);
     if (!storageByteCount.isValid()) {
       mStorage = nullptr;
       mPtr = nullptr;
       mCount = 0;
       return;
     }
     // We don't create an array of T here, since we don't want ctors to be
     // invoked at the wrong places if we realign below.
-    mStorage = new (std::nothrow) uint8_t[storageByteCount.value()];
+    if (aZero) {
+      mStorage = static_cast<uint8_t *>(calloc(1, storageByteCount.value()));
+    } else {
+      mStorage = new (std::nothrow) uint8_t[storageByteCount.value()];
+    }
     if (!mStorage) {
       mStorage = nullptr;
       mPtr = nullptr;
       mCount = 0;
       return;
     }
     if (uintptr_t(mStorage) % alignment) {
       // Our storage does not start at a <alignment>-byte boundary. Make sure mPtr does!
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -809,16 +809,24 @@ AsyncPanZoomController::AssertOnControll
     // If the assertion below fires, it is possible that it is because
     // sControllerThread is not actually the controller thread.
     sControllerThread = PR_GetCurrentThread();
     sControllerThreadDetermined = true;
   }
   MOZ_ASSERT(sControllerThread == PR_GetCurrentThread());
 }
 
+void
+AsyncPanZoomController::AssertOnCompositorThread()
+{
+  if (GetThreadAssertionsEnabled()) {
+    Compositor::AssertOnCompositorThread();
+  }
+}
+
 /*static*/ void
 AsyncPanZoomController::InitializeGlobalState()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   static bool sInitialized = false;
   if (sInitialized)
     return;
@@ -865,19 +873,17 @@ AsyncPanZoomController::AsyncPanZoomCont
 
 AsyncPanZoomController::~AsyncPanZoomController() {
   MOZ_COUNT_DTOR(AsyncPanZoomController);
 }
 
 PCompositorParent*
 AsyncPanZoomController::GetSharedFrameMetricsCompositor()
 {
-  if (GetThreadAssertionsEnabled()) {
-    Compositor::AssertOnCompositorThread();
-  }
+  AssertOnCompositorThread();
 
   if (mSharingFrameMetricsAcrossProcesses) {
     const CompositorParent::LayerTreeState* state = CompositorParent::GetIndirectShadowTree(mLayersId);
     // |state| may be null here if the CrossProcessCompositorParent has already been destroyed.
     return state ? state->mCrossProcessParent : nullptr;
   }
   return mCompositorParent.get();
 }
@@ -894,16 +900,18 @@ AsyncPanZoomController::GetGestureEventL
   MonitorAutoLock lock(mRefPtrMonitor);
   nsRefPtr<GestureEventListener> listener = mGestureEventListener;
   return listener.forget();
 }
 
 void
 AsyncPanZoomController::Destroy()
 {
+  AssertOnCompositorThread();
+
   CancelAnimation();
 
   mTouchBlockQueue.Clear();
 
   { // scope the lock
     MonitorAutoLock lock(mRefPtrMonitor);
     mGeckoContentController = nullptr;
     mGestureEventListener = nullptr;
@@ -2352,16 +2360,18 @@ AsyncPanZoomController::FireAsyncScrollO
     SendAsyncScrollEvent();
   }
   mAsyncScrollTimeoutTask = nullptr;
 }
 
 bool AsyncPanZoomController::UpdateAnimation(const TimeStamp& aSampleTime,
                                              Vector<Task*>* aOutDeferredTasks)
 {
+  AssertOnCompositorThread();
+
   // This function may get called multiple with the same sample time, because
   // there may be multiple layers with this APZC, and each layer invokes this
   // function during composition. However we only want to do one animation step
   // per composition so we need to deduplicate these calls first.
   if (mLastSampleTime == aSampleTime) {
     return false;
   }
   TimeDuration sampleTimeDelta = aSampleTime - mLastSampleTime;
@@ -2424,16 +2434,18 @@ void AsyncPanZoomController::GetOverscro
   // Combine the transformations into a matrix.
   ScreenPoint screenTranslation = translation * mFrameMetrics.GetZoom();
   *aTransform = Matrix4x4().Scale(scaleX, scaleY, 1)
                            .PostTranslate(screenTranslation.x, screenTranslation.y, 0);
 }
 
 bool AsyncPanZoomController::AdvanceAnimations(const TimeStamp& aSampleTime)
 {
+  AssertOnCompositorThread();
+
   // The eventual return value of this function. The compositor needs to know
   // whether or not to advance by a frame as soon as it can. For example, if a
   // fling is happening, it has to keep compositing so that the animation is
   // smooth. If an animation frame is requested, it is the compositor's
   // responsibility to schedule a composite.
   mAsyncTransformAppliedToContent = false;
   bool requestAnimationFrame = false;
   Vector<Task*> deferredTasks;
@@ -2569,16 +2581,18 @@ Matrix4x4 AsyncPanZoomController::GetTra
 
   float zoomChange = mLastContentPaintMetrics.GetZoom().scale / mLastDispatchedPaintMetrics.GetZoom().scale;
 
   return Matrix4x4().Translate(scrollChange.x, scrollChange.y, 0) *
          Matrix4x4().Scale(zoomChange, zoomChange, 1);
 }
 
 void AsyncPanZoomController::NotifyLayersUpdated(const FrameMetrics& aLayerMetrics, bool aIsFirstPaint) {
+  AssertOnCompositorThread();
+
   ReentrantMonitorAutoEnter lock(mMonitor);
   bool isDefault = mFrameMetrics.IsDefault();
 
   mLastContentPaintMetrics = aLayerMetrics;
   UpdateTransformScale();
 
   mFrameMetrics.mMayHaveTouchListeners = aLayerMetrics.mMayHaveTouchListeners;
   mFrameMetrics.mMayHaveTouchCaret = aLayerMetrics.mMayHaveTouchCaret;
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -1100,21 +1100,27 @@ public:
    * In the gtest environment everything runs on one thread, so we
    * shouldn't assert that we're on a particular thread. This enables
    * that behaviour.
    */
   static void SetThreadAssertionsEnabled(bool aEnabled);
   static bool GetThreadAssertionsEnabled();
   /**
    * This can be used to assert that the current thread is the
-   * controller/UI thread (on which input events are received.
+   * controller/UI thread (on which input events are received).
    * This does nothing if thread assertions are disabled.
    */
   static void AssertOnControllerThread();
   /**
+   * This can be used to assert that the current thread is the
+   * compositor thread (which applies the async transform).
+   * This does nothing if thread assertions are disabled.
+   */
+  static void AssertOnCompositorThread();
+  /**
    * Set an extra offset for testing async scrolling.
    */
   void SetTestAsyncScrollOffset(const CSSPoint& aPoint)
   {
     mTestAsyncScrollOffset = aPoint;
   }
 
   void MarkAsyncTransformAppliedToContent()
--- a/gfx/layers/ipc/CompositorParent.cpp
+++ b/gfx/layers/ipc/CompositorParent.cpp
@@ -144,16 +144,22 @@ CompositorThreadHolder::CreateCompositor
   /* Timeout values are powers-of-two to enable us get better data.
      128ms is chosen for transient hangs because 8Hz should be the minimally
      acceptable goal for Compositor responsiveness (normal goal is 60Hz). */
   options.transient_hang_timeout = 128; // milliseconds
   /* 2048ms is chosen for permanent hangs because it's longer than most
    * Compositor hangs seen in the wild, but is short enough to not miss getting
    * native hang stacks. */
   options.permanent_hang_timeout = 2048; // milliseconds
+#if defined(_WIN32)
+  /* With d3d9 the compositor thread creates native ui, see DeviceManagerD3D9. As
+   * such the thread is a gui thread, and must process a windows message queue or
+   * risk deadlocks. Chromium message loop TYPE_UI does exactly what we need. */
+  options.message_loop_type = MessageLoop::TYPE_UI;
+#endif
 
   if (!compositorThread->StartWithOptions(options)) {
     delete compositorThread;
     return nullptr;
   }
 
   CreateCompositorMap();
 
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -273,16 +273,17 @@ gfxPlatform::gfxPlatform()
     mBidiNumeralOption = UNINITIALIZED_VALUE;
 
     mSkiaGlue = nullptr;
 
     uint32_t canvasMask = BackendTypeBit(BackendType::CAIRO) | BackendTypeBit(BackendType::SKIA);
     uint32_t contentMask = BackendTypeBit(BackendType::CAIRO);
     InitBackendPrefs(canvasMask, BackendType::CAIRO,
                      contentMask, BackendType::CAIRO);
+    mTotalSystemMemory = mozilla::hal::GetTotalSystemMemory();
 }
 
 gfxPlatform*
 gfxPlatform::GetPlatform()
 {
     if (!gPlatform) {
         Init();
     }
@@ -847,24 +848,22 @@ gfxPlatform::InitializeSkiaCacheLimits()
     bool usingDynamicCache = gfxPrefs::CanvasSkiaGLDynamicCache();
     int cacheItemLimit = gfxPrefs::CanvasSkiaGLCacheItems();
     int cacheSizeLimit = gfxPrefs::CanvasSkiaGLCacheSize();
 
     // Prefs are in megabytes, but we want the sizes in bytes
     cacheSizeLimit *= 1024*1024;
 
     if (usingDynamicCache) {
-      uint32_t totalMemory = mozilla::hal::GetTotalSystemMemory();
-
-      if (totalMemory < 512*1024*1024) {
+      if (mTotalSystemMemory < 512*1024*1024) {
         // We need a very minimal cache on anything smaller than 512mb.
         // Note the large jump as we cross 512mb (from 2mb to 32mb).
         cacheSizeLimit = 2*1024*1024;
-      } else if (totalMemory > 0) {
-        cacheSizeLimit = totalMemory / 16;
+      } else if (mTotalSystemMemory > 0) {
+        cacheSizeLimit = mTotalSystemMemory / 16;
       }
     }
 
   #ifdef DEBUG
     printf_stderr("Determined SkiaGL cache limits: Size %i, Items: %i\n", cacheSizeLimit, cacheItemLimit);
   #endif
 
 #ifdef USE_SKIA_GPU
@@ -907,16 +906,27 @@ gfxPlatform::PurgeSkiaCache()
 
   mSkiaGlue->GetGrContext()->freeGpuResources();
   // GrContext::flush() doesn't call glFlush. Call it here.
   mSkiaGlue->GetGLContext()->MakeCurrent();
   mSkiaGlue->GetGLContext()->fFlush();
 #endif
 }
 
+bool
+gfxPlatform::HasEnoughTotalSystemMemoryForSkiaGL()
+{
+#ifdef MOZ_WIDGET_GONK
+  if (mTotalSystemMemory < 250*1024*1024) {
+    return false;
+  }
+#endif
+  return true;
+}
+
 TemporaryRef<DrawTarget>
 gfxPlatform::CreateDrawTargetForBackend(BackendType aBackend, const IntSize& aSize, SurfaceFormat aFormat)
 {
   // There is a bunch of knowledge in the gfxPlatform heirarchy about how to
   // create the best offscreen surface for the current system and situation. We
   // can easily take advantage of this for the Cairo backend, so that's what we
   // do.
   // mozilla::gfx::Factory can get away without having all this knowledge for
--- a/gfx/thebes/gfxPlatform.h
+++ b/gfx/thebes/gfxPlatform.h
@@ -542,16 +542,17 @@ public:
 
     mozilla::gl::SkiaGLGlue* GetSkiaGLGlue();
     void PurgeSkiaCache();
 
     virtual bool IsInGonkEmulator() const { return false; }
 
     static bool UsesOffMainThreadCompositing();
 
+    bool HasEnoughTotalSystemMemoryForSkiaGL();
 protected:
     gfxPlatform();
     virtual ~gfxPlatform();
 
     void AppendCJKPrefLangs(eFontPrefLang aPrefLangs[], uint32_t &aLen, 
                             eFontPrefLang aCharLang, eFontPrefLang aPageLang);
 
     /**
@@ -611,16 +612,18 @@ protected:
     int8_t  mFallbackUsesCmaps;
 
     // max character limit for words in word cache
     int32_t mWordCacheCharLimit;
 
     // max number of entries in word cache
     int32_t mWordCacheMaxEntries;
 
+    uint32_t mTotalSystemMemory;
+
 private:
     /**
      * Start up Thebes.
      */
     static void Init();
 
     static void CreateCMSOutputProfile();
 
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -32,16 +32,17 @@
 #include "nsBMPDecoder.h"
 #include "nsICODecoder.h"
 #include "nsIconDecoder.h"
 
 #include "gfxContext.h"
 
 #include "mozilla/gfx/2D.h"
 #include "mozilla/RefPtr.h"
+#include "mozilla/Move.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Services.h"
 #include "mozilla/Preferences.h"
 #include <stdint.h>
 #include "mozilla/Telemetry.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/gfx/Scale.h"
@@ -183,133 +184,123 @@ DiscardingEnabled()
   return enabled;
 }
 
 class ScaleRequest
 {
 public:
   ScaleRequest(RasterImage* aImage,
                const nsIntSize& aSize,
-               imgFrame* aSrcFrame)
-    : dstSize(aSize)
-    , dstLocked(false)
+               RawAccessFrameRef&& aSrcRef)
+    : weakImage(aImage)
+    , srcRef(Move(aSrcRef))
+    , srcRect(srcRef->GetRect())
+    , dstSize(aSize)
     , done(false)
     , stopped(false)
   {
-    MOZ_ASSERT(!aSrcFrame->GetIsPaletted());
+    MOZ_ASSERT(NS_IsMainThread());
+    MOZ_ASSERT(!srcRef->GetIsPaletted());
     MOZ_ASSERT(aSize.width > 0 && aSize.height > 0);
-
-    weakImage = aImage;
-    srcRect = aSrcFrame->GetRect();
   }
 
   // This can only be called on the main thread.
-  bool GetSurfaces(imgFrame* srcFrame)
+  bool AcquireResources()
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     nsRefPtr<RasterImage> image = weakImage.get();
     if (!image) {
       return false;
     }
 
-    bool success = false;
-    if (!dstLocked) {
+    if (!dstFrame) {
       // We need to hold a lock onto the RasterImage object itself so that
       // it (and its associated imgFrames) aren't marked as discardable.
-      bool imgLocked = NS_SUCCEEDED(image->LockImage());
-      bool srcLocked = NS_SUCCEEDED(srcFrame->LockImageData());
-      srcSurface = srcFrame->GetSurface();
-
-      dstLocked = NS_SUCCEEDED(dstFrame->LockImageData());
-      dstSurface = dstFrame->GetSurface();
-
-      success = imgLocked && srcLocked && dstLocked && srcSurface && dstSurface;
-
-      if (success) {
-        srcData = srcFrame->GetImageData();
-        dstData = dstFrame->GetImageData();
-        srcStride = srcFrame->GetImageBytesPerRow();
-        dstStride = dstFrame->GetImageBytesPerRow();
-        srcFormat = srcFrame->GetFormat();
+      if (NS_FAILED(image->LockImage())) {
+        return false;
+      }
+
+      // We'll need a destination frame. It's unconditionally ARGB32 because
+      // that's what the scaler outputs.
+      nsRefPtr<imgFrame> tentativeDstFrame = new imgFrame();
+      nsresult rv =
+        tentativeDstFrame->Init(0, 0, dstSize.width, dstSize.height,
+                                SurfaceFormat::B8G8R8A8);
+      if (NS_FAILED(rv)) {
+        return false;
       }
 
-      // We have references to the surfaces, so we don't need to leave
-      // the source frame (that we don't own) locked. We'll unlock the
-      // destination frame in ReleaseSurfaces(), below.
-      if (srcLocked) {
-        success = NS_SUCCEEDED(srcFrame->UnlockImageData()) && success;
+      // We need a strong reference to the raw data for the destination frame.
+      // (We already got one for the source frame in the constructor.)
+      RawAccessFrameRef tentativeDstRef = tentativeDstFrame->RawAccessRef();
+      if (!tentativeDstRef) {
+        return false;
       }
+
+      dstFrame = tentativeDstFrame.forget();
+      dstRef = Move(tentativeDstRef);
     }
 
-    return success;
+    return true;
   }
 
   // This can only be called on the main thread.
-  bool ReleaseSurfaces()
+  void ReleaseResources()
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     nsRefPtr<RasterImage> image = weakImage.get();
-    if (!image) {
-      return false;
+    if (image) {
+      image->UnlockImage();
+    }
+
+    if (DiscardingEnabled() && dstFrame) {
+      dstFrame->SetDiscardable();
     }
 
-    bool success = false;
-    if (dstLocked) {
-      if (DiscardingEnabled())
-        dstFrame->SetDiscardable();
-      success = NS_SUCCEEDED(dstFrame->UnlockImageData());
-      success = success && NS_SUCCEEDED(image->UnlockImage());
-
-      dstLocked = false;
-      srcData = nullptr;
-      dstData = nullptr;
-      srcSurface = nullptr;
-      dstSurface = nullptr;
-    }
-    return success;
+    // Release everything except dstFrame, which we keep around for RasterImage
+    // to retrieve.
+    srcRef.reset();
+    dstRef.reset();
   }
 
-  // These values may only be touched on the main thread.
+  // These values may only be modified on the main thread.
   WeakPtr<RasterImage> weakImage;
   nsRefPtr<imgFrame> dstFrame;
-  RefPtr<SourceSurface> srcSurface;
-  RefPtr<SourceSurface> dstSurface;
-
-  // Below are the values that may be touched on the scaling thread.
-  uint8_t* srcData;
-  uint8_t* dstData;
+  RawAccessFrameRef srcRef;
+  RawAccessFrameRef dstRef;
+
+  // Below are the values that may be modified on the scaling thread.
   nsIntRect srcRect;
   nsIntSize dstSize;
-  uint32_t srcStride;
-  uint32_t dstStride;
-  SurfaceFormat srcFormat;
-  bool dstLocked;
   bool done;
+
   // This boolean is accessed from both threads simultaneously without locking.
   // That's safe because stopping a ScaleRequest is strictly an optimization;
   // if we're not cache-coherent, at worst we'll do extra work.
   bool stopped;
 };
 
 class DrawRunner : public nsRunnable
 {
 public:
   explicit DrawRunner(ScaleRequest* request)
    : mScaleRequest(request)
   {}
 
   NS_IMETHOD Run()
   {
-    // ScaleWorker is finished with this request, so we can unlock the data now.
-    mScaleRequest->ReleaseSurfaces();
-
+    // Grab the weak image pointer before the request releases it.
     nsRefPtr<RasterImage> image = mScaleRequest->weakImage.get();
 
+    // ScaleWorker is finished with this request, so release everything that we
+    // don't need anymore.
+    mScaleRequest->ReleaseResources();
+
     if (image) {
       RasterImage::ScaleStatus status;
       if (mScaleRequest->done) {
         status = RasterImage::SCALE_DONE;
       } else {
         status = RasterImage::SCALE_INVALID;
       }
 
@@ -323,46 +314,46 @@ private: /* members */
   nsAutoPtr<ScaleRequest> mScaleRequest;
 };
 
 class ScaleRunner : public nsRunnable
 {
 public:
   ScaleRunner(RasterImage* aImage,
               const nsIntSize& aSize,
-              imgFrame* aSrcFrame)
+              RawAccessFrameRef&& aSrcRef)
   {
-    nsAutoPtr<ScaleRequest> request(new ScaleRequest(aImage, aSize, aSrcFrame));
-
-    // Destination is unconditionally ARGB32 because that's what the scaler
-    // outputs.
-    request->dstFrame = new imgFrame();
-    nsresult rv = request->dstFrame->Init(0, 0, request->dstSize.width, request->dstSize.height,
-                                          SurfaceFormat::B8G8R8A8);
-
-    if (NS_FAILED(rv) || !request->GetSurfaces(aSrcFrame)) {
+    nsAutoPtr<ScaleRequest> req(new ScaleRequest(aImage, aSize, Move(aSrcRef)));
+    if (!req->AcquireResources()) {
       return;
     }
 
-    aImage->ScalingStart(request);
-
-    mScaleRequest = request;
+    aImage->ScalingStart(req);
+    mScaleRequest = req;
   }
 
   NS_IMETHOD Run()
   {
-    // An alias just for ease of typing
-    ScaleRequest* request = mScaleRequest;
-
-    if (!request->stopped) {
-      request->done = gfx::Scale(request->srcData, request->srcRect.width, request->srcRect.height, request->srcStride,
-                                 request->dstData, request->dstSize.width, request->dstSize.height, request->dstStride,
-                                 request->srcFormat);
+    ScaleRequest* req = mScaleRequest.get();
+
+    if (!req->stopped) {
+      // Collect information from the frames that we need to scale.
+      uint8_t* srcData = req->srcRef->GetImageData();
+      uint8_t* dstData = req->dstRef->GetImageData();
+      uint32_t srcStride = req->srcRef->GetImageBytesPerRow();
+      uint32_t dstStride = req->dstRef->GetImageBytesPerRow();
+      SurfaceFormat srcFormat = req->srcRef->GetFormat();
+
+      // Actually do the scaling.
+      req->done =
+        gfx::Scale(srcData, req->srcRect.width, req->srcRect.height, srcStride,
+                   dstData, req->dstSize.width, req->dstSize.height, dstStride,
+                   srcFormat);
     } else {
-      request->done = false;
+      req->done = false;
     }
 
     // OK, we've got a new scaled image. Let's get the main thread to unlock and
     // redraw it.
     nsRefPtr<DrawRunner> runner = new DrawRunner(mScaleRequest.forget());
     NS_DispatchToMainThread(runner);
 
     return NS_OK;
@@ -858,17 +849,19 @@ RasterImage::CopyFrame(uint32_t aWhichFr
     return nullptr;
   }
 
   // Create a 32-bit image surface of our size, but draw using the frame's
   // rect, implicitly padding the frame out to the image's size.
 
   IntSize size(mSize.width, mSize.height);
   RefPtr<DataSourceSurface> surf =
-    Factory::CreateDataSourceSurface(size, SurfaceFormat::B8G8R8A8);
+    Factory::CreateDataSourceSurface(size,
+                                     SurfaceFormat::B8G8R8A8,
+                                     /* aZero = */ true);
   if (NS_WARN_IF(!surf)) {
     return nullptr;
   }
 
   DataSourceSurface::MappedSurface mapping;
   DebugOnly<bool> success =
     surf->Map(DataSourceSurface::MapType::WRITE, &mapping);
   NS_ASSERTION(success, "Failed to map surface");
@@ -2617,37 +2610,35 @@ RasterImage::RequestScale(imgFrame* aFra
 {
   // We can't store more than one scaled version of an image right now, so if
   // there's more than one instance of this image, bail.
   if (mLockCount != 1) {
     return;
   }
 
   // We also can't scale if we can't lock the image data for this frame.
-  if (NS_FAILED(aFrame->LockImageData())) {
-    aFrame->UnlockImageData();
+  RawAccessFrameRef frameRef = aFrame->RawAccessRef();
+  if (!frameRef) {
     return;
   }
 
   // If we have an outstanding request, signal it to stop (if it can).
   if (mScaleRequest) {
     mScaleRequest->stopped = true;
   }
 
-  nsRefPtr<ScaleRunner> runner = new ScaleRunner(this, aSize, aFrame);
+  nsRefPtr<ScaleRunner> runner = new ScaleRunner(this, aSize, Move(frameRef));
   if (runner->IsOK()) {
     if (!sScaleWorkerThread) {
       NS_NewNamedThread("Image Scaler", getter_AddRefs(sScaleWorkerThread));
       ClearOnShutdown(&sScaleWorkerThread);
     }
 
     sScaleWorkerThread->Dispatch(runner, NS_DISPATCH_NORMAL);
   }
-
-  aFrame->UnlockImageData();
 }
 
 bool
 RasterImage::DrawWithPreDownscaleIfNeeded(imgFrame *aFrame,
                                           gfxContext *aContext,
                                           const nsIntSize& aSize,
                                           const ImageRegion& aRegion,
                                           GraphicsFilter aFilter,
--- a/image/src/imgFrame.cpp
+++ b/image/src/imgFrame.cpp
@@ -737,25 +737,27 @@ bool imgFrame::ImageComplete() const
 {
   MutexAutoLock lock(mDecodedMutex);
 
   return mDecoded.IsEqualInterior(nsIntRect(mOffset.x, mOffset.y,
                                             mSize.width, mSize.height));
 }
 
 // A hint from the image decoders that this image has no alpha, even
-// though we created is ARGB32.  This changes our format to RGB24,
-// which in turn will cause us to Optimize() to RGB24.  Has no effect
-// after Optimize() is called, though in all cases it will be just a
-// performance win -- the pixels are still correct and have the A byte
-// set to 0xff.
+// though we're decoding it as B8G8R8A8. 
+// Since this is only called during decoding, there is a mImageSurface
+// that should be updated to use B8G8R8X8. This doesn't change the
+// underlying data at all, but allows DrawTargets to avoid blending
+// when drawing known opaque images.
 void imgFrame::SetHasNoAlpha()
 {
   if (mFormat == SurfaceFormat::B8G8R8A8) {
     mFormat = SurfaceFormat::B8G8R8X8;
+    MOZ_ASSERT(mImageSurface);
+    mImageSurface = CreateLockedSurface(mVBuf, mSize, mFormat);
   }
 }
 
 void imgFrame::SetAsNonPremult(bool aIsNonPremult)
 {
   mNonPremult = aIsNonPremult;
 }
 
--- a/image/src/imgTools.cpp
+++ b/image/src/imgTools.cpp
@@ -269,17 +269,18 @@ NS_IMETHODIMP imgTools::EncodeCroppedIma
   }
 
   // Check that the given crop rectangle is within image bounds.
   NS_ENSURE_ARG(frameWidth >= aOffsetX + aWidth &&
                 frameHeight >= aOffsetY + aHeight);
 
   RefPtr<DataSourceSurface> dataSurface =
     Factory::CreateDataSourceSurface(IntSize(aWidth, aHeight),
-                                     SurfaceFormat::B8G8R8A8);
+                                     SurfaceFormat::B8G8R8A8,
+                                     /* aZero = */ true);
   if (NS_WARN_IF(!dataSurface)) {
     return NS_ERROR_FAILURE;
   }
 
   DataSourceSurface::MappedSurface map;
   if (!dataSurface->Map(DataSourceSurface::MapType::WRITE, &map)) {
     return NS_ERROR_FAILURE;
   }
--- a/js/src/configure.in
+++ b/js/src/configure.in
@@ -1072,16 +1072,32 @@ MOZ_ARG_ENABLE_BOOL(address-sanitizer,
     MOZ_ASAN=1,
     MOZ_ASAN= )
 if test -n "$MOZ_ASAN"; then
     MOZ_LLVM_HACKS=1
     AC_DEFINE(MOZ_ASAN)
     MOZ_PATH_PROG(LLVM_SYMBOLIZER, llvm-symbolizer)
 fi
 AC_SUBST(MOZ_ASAN)
+
+dnl ========================================================
+dnl = Use Memory Sanitizer
+dnl ========================================================
+MOZ_ARG_ENABLE_BOOL(memory-sanitizer,
+[  --enable-memory-sanitizer       Enable Memory Sanitizer (default=no)],
+    MOZ_MSAN=1,
+    MOZ_MSAN= )
+if test -n "$MOZ_MSAN"; then
+    MOZ_LLVM_HACKS=1
+    AC_DEFINE(MOZ_MSAN)
+    MOZ_PATH_PROG(LLVM_SYMBOLIZER, llvm-symbolizer)
+fi
+AC_SUBST(MOZ_MSAN)
+
+# The LLVM symbolizer is used by all sanitizers
 AC_SUBST(LLVM_SYMBOLIZER)
 
 dnl ========================================================
 dnl = Enable hacks required for LLVM instrumentations
 dnl ========================================================
 MOZ_ARG_ENABLE_BOOL(llvm-hacks,
 [  --enable-llvm-hacks       Enable workarounds required for several LLVM instrumentations (default=no)],
     MOZ_LLVM_HACKS=1,
@@ -1963,17 +1979,17 @@ dnl ====================================
 dnl = Flags to strip unused symbols from .so components
 dnl ========================================================
 case "$target" in
     *-linux*|*-kfreebsd*-gnu|*-gnu*)
         MOZ_COMPONENTS_VERSION_SCRIPT_LDFLAGS='-Wl,--version-script -Wl,$(BUILD_TOOLS)/gnu-ld-scripts/components-version-script'
         ;;
     *-solaris*)
         if test -z "$GNU_CC"; then
-         eOZ_COMPONENTS_VERSION_SCRIPT_LDFLAGS='-M $(BUILD_TOOLS)/gnu-ld-scripts/components-mapfile'
+         MOZ_COMPONENTS_VERSION_SCRIPT_LDFLAGS='-M $(BUILD_TOOLS)/gnu-ld-scripts/components-mapfile'
         else
          if test -z "$GCC_USE_GNU_LD"; then
           MOZ_COMPONENTS_VERSION_SCRIPT_LDFLAGS='-Wl,-M -Wl,$(BUILD_TOOLS)/gnu-ld-scripts/components-mapfile'
          else
           MOZ_COMPONENTS_VERSION_SCRIPT_LDFLAGS='-Wl,--version-script -Wl,$(BUILD_TOOLS)/gnu-ld-scripts/components-version-script'
          fi
         fi
         ;;
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -2029,17 +2029,18 @@ IonCompile(JSContext *cx, JSScript *scri
 
     types::CompilerConstraintList *constraints = types::NewCompilerConstraintList(*temp);
     if (!constraints)
         return AbortReason_Alloc;
 
     const OptimizationInfo *optimizationInfo = js_IonOptimizations.get(optimizationLevel);
     const JitCompileOptions options(cx);
 
-    IonBuilder *builder = alloc->new_<IonBuilder>(CompileCompartment::get(cx->compartment()),
+    IonBuilder *builder = alloc->new_<IonBuilder>((JSContext *) nullptr,
+                                                  CompileCompartment::get(cx->compartment()),
                                                   options, temp, graph, constraints,
                                                   inspector, info, optimizationInfo,
                                                   baselineFrameInspector);
     if (!builder)
         return AbortReason_Alloc;
 
     JS_ASSERT(recompile == HasIonScript(builder->script(), executionMode));
     JS_ASSERT(CanIonCompile(builder->script(), executionMode));
--- a/js/src/jit/IonAnalysis.cpp
+++ b/js/src/jit/IonAnalysis.cpp
@@ -2787,17 +2787,17 @@ jit::AnalyzeNewScriptDefiniteProperties(
     if (!constraints) {
         js_ReportOutOfMemory(cx);
         return false;
     }
 
     BaselineInspector inspector(script);
     const JitCompileOptions options(cx);
 
-    IonBuilder builder(CompileCompartment::get(cx->compartment()), options, &temp, &graph, constraints,
+    IonBuilder builder(cx, CompileCompartment::get(cx->compartment()), options, &temp, &graph, constraints,
                        &inspector, &info, optimizationInfo, /* baselineFrame = */ nullptr);
 
     if (!builder.build()) {
         if (builder.abortReason() == AbortReason_Alloc)
             return false;
         return true;
     }
 
@@ -3014,17 +3014,17 @@ jit::AnalyzeArgumentsUsage(JSContext *cx
 
     types::CompilerConstraintList *constraints = types::NewCompilerConstraintList(temp);
     if (!constraints)
         return false;
 
     BaselineInspector inspector(script);
     const JitCompileOptions options(cx);
 
-    IonBuilder builder(CompileCompartment::get(cx->compartment()), options, &temp, &graph, constraints,
+    IonBuilder builder(nullptr, CompileCompartment::get(cx->compartment()), options, &temp, &graph, constraints,
                        &inspector, &info, optimizationInfo, /* baselineFrame = */ nullptr);
 
     if (!builder.build()) {
         if (builder.abortReason() == AbortReason_Alloc)
             return false;
         return true;
     }
 
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -101,25 +101,26 @@ jit::NewBaselineFrameInspector(TempAlloc
             types::Type type = types::GetMaybeOptimizedOutValueType(frame->unaliasedLocal(i));
             inspector->varTypes.infallibleAppend(type);
         }
     }
 
     return inspector;
 }
 
-IonBuilder::IonBuilder(CompileCompartment *comp,
+IonBuilder::IonBuilder(JSContext *analysisContext, CompileCompartment *comp,
                        const JitCompileOptions &options, TempAllocator *temp,
                        MIRGraph *graph, types::CompilerConstraintList *constraints,
                        BaselineInspector *inspector, CompileInfo *info,
                        const OptimizationInfo *optimizationInfo,
                        BaselineFrameInspector *baselineFrame, size_t inliningDepth,
                        uint32_t loopDepth)
   : MIRGenerator(comp, options, temp, graph, info, optimizationInfo),
     backgroundCodegen_(nullptr),
+    analysisContext(analysisContext),
     baselineFrame_(baselineFrame),
     constraints_(constraints),
     analysis_(*temp, info->script()),
     thisTypes(nullptr),
     argTypes(nullptr),
     typeArray(nullptr),
     typeArrayHint(0),
     bytecodeTypeMap(nullptr),
@@ -141,24 +142,26 @@ IonBuilder::IonBuilder(CompileCompartmen
     lazyArguments_(nullptr),
     inlineCallInfo_(nullptr)
 {
     script_ = info->script();
     pc = info->startPC();
     abortReason_ = AbortReason_Disable;
 
     JS_ASSERT(script()->hasBaselineScript() == (info->executionMode() != ArgumentsUsageAnalysis));
+    JS_ASSERT(!!analysisContext == (info->executionMode() == DefinitePropertiesAnalysis));
 
     if (!info->executionModeIsAnalysis())
         script()->baselineScript()->setIonCompiledOrInlined();
 }
 
 void
 IonBuilder::clearForBackEnd()
 {
+    JS_ASSERT(!analysisContext);
     baselineFrame_ = nullptr;
 
     // The caches below allocate data from the malloc heap. Release this before
     // later phases of compilation to avoid leaks, as the top level IonBuilder
     // is not explicitly destroyed. Note that builders for inner scripts are
     // constructed on the stack and will release this memory on destruction.
     gsn.purge();
     scopeCoordinateNameCache.purge();
@@ -341,16 +344,32 @@ IonBuilder::InliningDecision
 IonBuilder::canInlineTarget(JSFunction *target, CallInfo &callInfo)
 {
     if (!optimizationInfo().inlineInterpreted())
         return InliningDecision_DontInline;
 
     if (!target->isInterpreted())
         return DontInline(nullptr, "Non-interpreted target");
 
+    // Allow constructing lazy scripts when performing the definite properties
+    // analysis, as baseline has not been used to warm the caller up yet.
+    if (target->isInterpreted() && info().executionMode() == DefinitePropertiesAnalysis) {
+        RootedScript script(analysisContext, target->getOrCreateScript(analysisContext));
+        if (!script)
+            return InliningDecision_Error;
+
+        if (!script->hasBaselineScript() && script->canBaselineCompile()) {
+            MethodStatus status = BaselineCompile(analysisContext, script);
+            if (status == Method_Error)
+                return InliningDecision_Error;
+            if (status != Method_Compiled)
+                return InliningDecision_DontInline;
+        }
+    }
+
     if (!target->hasScript())
         return DontInline(nullptr, "Lazy script");
 
     JSScript *inlineScript = target->nonLazyScript();
     if (callInfo.constructing() && !target->isInterpretedConstructor())
         return DontInline(inlineScript, "Callee is not a constructor");
 
     ExecutionMode executionMode = info().executionMode();
@@ -4179,20 +4198,26 @@ IonBuilder::inlineScriptedCall(CallInfo 
                                                      inlineScriptTree);
     if (!info)
         return false;
 
     MIRGraphReturns returns(alloc());
     AutoAccumulateReturns aar(graph(), returns);
 
     // Build the graph.
-    IonBuilder inlineBuilder(compartment, options, &alloc(), &graph(), constraints(),
+    IonBuilder inlineBuilder(analysisContext, compartment, options, &alloc(), &graph(), constraints(),
                              &inspector, info, &optimizationInfo(), nullptr, inliningDepth_ + 1,
                              loopDepth_);
     if (!inlineBuilder.buildInline(this, outerResumePoint, callInfo)) {
+        if (analysisContext && analysisContext->isExceptionPending()) {
+            JitSpew(JitSpew_Abort, "Inline builder raised exception.");
+            abortReason_ = AbortReason_Error;
+            return false;
+        }
+
         // Inlining the callee failed. Mark the callee as uninlineable only if
         // the inlining was aborted for a non-exception reason.
         if (inlineBuilder.abortReason_ == AbortReason_Disable) {
             calleeScript->setUninlineable();
             abortReason_ = AbortReason_Inlining;
         } else if (inlineBuilder.abortReason_ == AbortReason_Inlining) {
             abortReason_ = AbortReason_Inlining;
         } else if (inlineBuilder.abortReason_ == AbortReason_NewScriptProperties) {
@@ -6413,16 +6438,18 @@ IonBuilder::testSingletonProperty(JSObje
     // reconfigured as a getter/setter then the type information for the
     // property will change and trigger invalidation.
 
     while (obj) {
         if (!ClassHasEffectlessLookup(obj->getClass(), name))
             return nullptr;
 
         types::TypeObjectKey *objType = types::TypeObjectKey::get(obj);
+        if (analysisContext)
+            objType->ensureTrackedProperty(analysisContext, NameToId(name));
 
         if (objType->unknownProperties())
             return nullptr;
 
         types::HeapTypeSetKey property = objType->property(NameToId(name));
         if (property.isOwnProperty(constraints())) {
             if (obj->hasSingletonType())
                 return property.singleton(constraints());
@@ -6494,16 +6521,18 @@ IonBuilder::testSingletonPropertyTypes(M
 
         // For property accesses which may be on many objects, we just need to
         // find a prototype common to all the objects; if that prototype
         // has the singleton property, the access will not be on a missing property.
         for (unsigned i = 0; i < types->getObjectCount(); i++) {
             types::TypeObjectKey *object = types->getObject(i);
             if (!object)
                 continue;
+            if (analysisContext)
+                object->ensureTrackedProperty(analysisContext, NameToId(name));
 
             const Class *clasp = object->clasp();
             if (!ClassHasEffectlessLookup(clasp, name) || ClassHasResolveHook(compartment, clasp, name))
                 return false;
             if (object->unknownProperties())
                 return false;
             types::HeapTypeSetKey property = object->property(NameToId(name));
             if (property.isOwnProperty(constraints()))
@@ -6701,16 +6730,18 @@ IonBuilder::getStaticName(JSObject *stat
             return pushConstant(UndefinedValue());
         if (name == names().NaN)
             return pushConstant(compartment->runtime()->NaNValue());
         if (name == names().Infinity)
             return pushConstant(compartment->runtime()->positiveInfinityValue());
     }
 
     types::TypeObjectKey *staticType = types::TypeObjectKey::get(staticObject);
+    if (analysisContext)
+        staticType->ensureTrackedProperty(analysisContext, NameToId(name));
 
     if (staticType->unknownProperties()) {
         *psucceeded = false;
         return true;
     }
 
     types::HeapTypeSetKey property = staticType->property(id);
     if (!property.maybeTypes() ||
@@ -6719,17 +6750,17 @@ IonBuilder::getStaticName(JSObject *stat
     {
         // The property has been reconfigured as non-configurable, non-enumerable
         // or non-writable.
         *psucceeded = false;
         return true;
     }
 
     types::TemporaryTypeSet *types = bytecodeTypes(pc);
-    BarrierKind barrier = PropertyReadNeedsTypeBarrier(constraints(), staticType,
+    BarrierKind barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(), staticType,
                                                        name, types, /* updateObserved = */ true);
 
     JSObject *singleton = types->getSingleton();
 
     MIRType knownType = types->getKnownMIRType();
     if (barrier == BarrierKind::NoBarrier) {
         // Try to inline properties holding a known constant object.
         if (singleton) {
@@ -7535,17 +7566,18 @@ IonBuilder::getElemTryCache(bool *emitte
     // of this getelem.
     bool nonNativeGetElement = inspector->hasSeenNonNativeGetElement(pc);
     if (index->mightBeType(MIRType_Int32) && nonNativeGetElement)
         return true;
 
     // Emit GetElementCache.
 
     types::TemporaryTypeSet *types = bytecodeTypes(pc);
-    BarrierKind barrier = PropertyReadNeedsTypeBarrier(constraints(), obj, nullptr, types);
+    BarrierKind barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(), obj,
+                                                       nullptr, types);
 
     // Always add a barrier if the index might be a string or symbol, so that
     // the cache can attach stubs for particular properties.
     if (index->mightBeType(MIRType_String) || index->mightBeType(MIRType_Symbol))
         barrier = BarrierKind::TypeSet;
 
     // See note about always needing a barrier in jsop_getprop.
     if (needsToMonitorMissingProperties(types))
@@ -7583,17 +7615,18 @@ IonBuilder::jsop_getelem_dense(MDefiniti
     MOZ_ASSERT(index->type() == MIRType_Int32 || index->type() == MIRType_Double);
     if (JSOp(*pc) == JSOP_CALLELEM) {
         // Indexed call on an element of an array. Populate the observed types
         // with any objects that could be in the array, to avoid extraneous
         // type barriers.
         AddObjectsForPropertyRead(obj, nullptr, types);
     }
 
-    BarrierKind barrier = PropertyReadNeedsTypeBarrier(constraints(), obj, nullptr, types);
+    BarrierKind barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(), obj,
+                                                       nullptr, types);
     bool needsHoleCheck = !ElementAccessIsPacked(constraints(), obj);
 
     // Reads which are on holes in the object do not have to bail out if
     // undefined values have been observed at this access site and the access
     // cannot hit another indexed property on the object or its prototypes.
     bool readOutOfBounds =
         types->hasType(types::Type::UndefinedType()) &&
         !ElementAccessHasExtraIndexedProperty(constraints(), obj);
@@ -8899,17 +8932,18 @@ IonBuilder::jsop_getprop(PropertyName *n
     // Try to optimize arguments.length.
     if (!getPropTryArgumentsLength(&emitted, obj) || emitted)
         return emitted;
 
     // Try to optimize arguments.callee.
     if (!getPropTryArgumentsCallee(&emitted, obj, name) || emitted)
         return emitted;
 
-    BarrierKind barrier = PropertyReadNeedsTypeBarrier(constraints(), obj, name, types);
+    BarrierKind barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(),
+                                                       obj, name, types);
 
     // Always use a call if we are performing analysis and
     // not actually emitting code, to simplify later analysis. Also skip deeper
     // analysis if there are no known types for this operation, as it will
     // always invalidate when executing.
     if (info().executionModeIsAnalysis() || types->empty()) {
         MCallGetProperty *call = MCallGetProperty::New(alloc(), obj, name, *pc == JSOP_CALLPROP);
         current->add(call);
@@ -9581,17 +9615,18 @@ IonBuilder::getPropTryInnerize(bool *emi
     if (!getPropTryConstant(emitted, inner, name, types) || *emitted)
         return *emitted;
 
     if (!getStaticName(&script()->global(), name, emitted) || *emitted)
         return *emitted;
 
     // Passing the inner object to GetProperty IC is safe, see the
     // needsOuterizedThisObject check in IsCacheableGetPropCallNative.
-    BarrierKind barrier = PropertyReadNeedsTypeBarrier(constraints(), inner, name, types);
+    BarrierKind barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(),
+                                                       inner, name, types);
     if (!getPropTryCache(emitted, inner, name, barrier, types) || *emitted)
         return *emitted;
 
     MOZ_ASSERT(*emitted == false);
     return true;
 }
 
 bool
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -209,17 +209,17 @@ class IonBuilder
         static CFGState CondSwitch(IonBuilder *builder, jsbytecode *exitpc, jsbytecode *defaultTarget);
         static CFGState Label(jsbytecode *exitpc);
         static CFGState Try(jsbytecode *exitpc, MBasicBlock *successor);
     };
 
     static int CmpSuccessors(const void *a, const void *b);
 
   public:
-    IonBuilder(CompileCompartment *comp,
+    IonBuilder(JSContext *analysisContext, CompileCompartment *comp,
                const JitCompileOptions &options, TempAllocator *temp,
                MIRGraph *graph, types::CompilerConstraintList *constraints,
                BaselineInspector *inspector, CompileInfo *info,
                const OptimizationInfo *optimizationInfo, BaselineFrameInspector *baselineFrame,
                size_t inliningDepth = 0, uint32_t loopDepth = 0);
 
     bool build();
     bool buildInline(IonBuilder *callerBuilder, MResumePoint *callerResumePoint,
@@ -860,16 +860,17 @@ class IonBuilder
         return callerBuilder_ != nullptr;
     }
 
     const JSAtomState &names() { return compartment->runtime()->names(); }
 
   private:
     bool init();
 
+    JSContext *analysisContext;
     BaselineFrameInspector *baselineFrame_;
 
     // Constraints for recording dependencies on type information.
     types::CompilerConstraintList *constraints_;
 
     // Basic analysis information about the script.
     BytecodeAnalysis analysis_;
     BytecodeAnalysis &analysis() {
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -428,17 +428,18 @@ IonBuilder::inlineArrayPopShift(CallInfo
     callInfo.setImplicitlyUsedUnchecked();
 
     obj = addMaybeCopyElementsForWrite(obj);
 
     types::TemporaryTypeSet *returnTypes = getInlineReturnTypeSet();
     bool needsHoleCheck = thisTypes->hasObjectFlags(constraints(), types::OBJECT_FLAG_NON_PACKED);
     bool maybeUndefined = returnTypes->hasType(types::Type::UndefinedType());
 
-    BarrierKind barrier = PropertyReadNeedsTypeBarrier(constraints(), obj, nullptr, returnTypes);
+    BarrierKind barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(),
+                                                       obj, nullptr, returnTypes);
     if (barrier != BarrierKind::NoBarrier)
         returnType = MIRType_Value;
 
     MArrayPopShift *ins = MArrayPopShift::New(alloc(), obj, mode, needsHoleCheck, maybeUndefined);
     current->add(ins);
     current->push(ins);
     ins->setResultType(returnType);
 
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -3662,17 +3662,18 @@ PropertyReadNeedsTypeBarrier(types::Comp
         }
     }
 
     property.freeze(constraints);
     return BarrierKind::NoBarrier;
 }
 
 BarrierKind
-jit::PropertyReadNeedsTypeBarrier(types::CompilerConstraintList *constraints,
+jit::PropertyReadNeedsTypeBarrier(JSContext *propertycx,
+                                  types::CompilerConstraintList *constraints,
                                   types::TypeObjectKey *object, PropertyName *name,
                                   types::TemporaryTypeSet *observed, bool updateObserved)
 {
     // If this access has never executed, try to add types to the observed set
     // according to any property which exists on the object or its prototype.
     if (updateObserved && observed->empty() && name) {
         JSObject *obj;
         if (object->singleton())
@@ -3682,16 +3683,18 @@ jit::PropertyReadNeedsTypeBarrier(types:
         else
             obj = nullptr;
 
         while (obj) {
             if (!obj->getClass()->isNative())
                 break;
 
             types::TypeObjectKey *typeObj = types::TypeObjectKey::get(obj);
+            if (propertycx)
+                typeObj->ensureTrackedProperty(propertycx, NameToId(name));
 
             if (!typeObj->unknownProperties()) {
                 types::HeapTypeSetKey property = typeObj->property(NameToId(name));
                 if (property.maybeTypes()) {
                     types::TypeSet::TypeList types;
                     if (!property.maybeTypes()->enumerateTypes(&types))
                         break;
                     if (types.length()) {
@@ -3707,34 +3710,35 @@ jit::PropertyReadNeedsTypeBarrier(types:
             obj = obj->getProto();
         }
     }
 
     return PropertyReadNeedsTypeBarrier(constraints, object, name, observed);
 }
 
 BarrierKind
-jit::PropertyReadNeedsTypeBarrier(types::CompilerConstraintList *constraints,
+jit::PropertyReadNeedsTypeBarrier(JSContext *propertycx,
+                                  types::CompilerConstraintList *constraints,
                                   MDefinition *obj, PropertyName *name,
                                   types::TemporaryTypeSet *observed)
 {
     if (observed->unknown())
         return BarrierKind::NoBarrier;
 
     types::TypeSet *types = obj->resultTypeSet();
     if (!types || types->unknownObject())
         return BarrierKind::TypeSet;
 
     BarrierKind res = BarrierKind::NoBarrier;
 
     bool updateObserved = types->getObjectCount() == 1;
     for (size_t i = 0; i < types->getObjectCount(); i++) {
         types::TypeObjectKey *object = types->getObject(i);
         if (object) {
-            BarrierKind kind = PropertyReadNeedsTypeBarrier(constraints, object, name,
+            BarrierKind kind = PropertyReadNeedsTypeBarrier(propertycx, constraints, object, name,
                                                             observed, updateObserved);
             if (kind == BarrierKind::TypeSet)
                 return BarrierKind::TypeSet;
 
             if (kind == BarrierKind::TypeTagOnly) {
                 MOZ_ASSERT(res == BarrierKind::NoBarrier || res == BarrierKind::TypeTagOnly);
                 res = BarrierKind::TypeTagOnly;
             } else {
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -11767,20 +11767,22 @@ MControlInstruction *MDefinition::toCont
 bool ElementAccessIsDenseNative(MDefinition *obj, MDefinition *id);
 bool ElementAccessIsTypedArray(MDefinition *obj, MDefinition *id,
                                Scalar::Type *arrayType);
 bool ElementAccessIsPacked(types::CompilerConstraintList *constraints, MDefinition *obj);
 bool ElementAccessMightBeCopyOnWrite(types::CompilerConstraintList *constraints, MDefinition *obj);
 bool ElementAccessHasExtraIndexedProperty(types::CompilerConstraintList *constraints,
                                           MDefinition *obj);
 MIRType DenseNativeElementType(types::CompilerConstraintList *constraints, MDefinition *obj);
-BarrierKind PropertyReadNeedsTypeBarrier(types::CompilerConstraintList *constraints,
+BarrierKind PropertyReadNeedsTypeBarrier(JSContext *propertycx,
+                                         types::CompilerConstraintList *constraints,
                                          types::TypeObjectKey *object, PropertyName *name,
                                          types::TemporaryTypeSet *observed, bool updateObserved);
-BarrierKind PropertyReadNeedsTypeBarrier(types::CompilerConstraintList *constraints,
+BarrierKind PropertyReadNeedsTypeBarrier(JSContext *propertycx,
+                                         types::CompilerConstraintList *constraints,
                                          MDefinition *obj, PropertyName *name,
                                          types::TemporaryTypeSet *observed);
 BarrierKind PropertyReadOnPrototypeNeedsTypeBarrier(types::CompilerConstraintList *constraints,
                                                     MDefinition *obj, PropertyName *name,
                                                     types::TemporaryTypeSet *observed);
 bool PropertyReadIsIdempotent(types::CompilerConstraintList *constraints,
                               MDefinition *obj, PropertyName *name);
 void AddObjectsForPropertyRead(MDefinition *obj, PropertyName *name,
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -1086,16 +1086,31 @@ TypeObjectKey::property(jsid id)
     property.object_ = this;
     property.id_ = id;
     if (TypeObject *type = maybeType())
         property.maybeTypes_ = type->maybeGetProperty(id);
 
     return property;
 }
 
+void
+TypeObjectKey::ensureTrackedProperty(JSContext *cx, jsid id)
+{
+    // If we are accessing a lazily defined property which actually exists in
+    // the VM and has not been instantiated yet, instantiate it now if we are
+    // on the main thread and able to do so.
+    if (!JSID_IS_VOID(id) && !JSID_IS_EMPTY(id)) {
+        JS_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
+        if (JSObject *obj = singleton()) {
+            if (obj->isNative() && obj->nativeLookupPure(id))
+                EnsureTrackPropertyTypes(cx, obj, id);
+        }
+    }
+}
+
 bool
 HeapTypeSetKey::instantiate(JSContext *cx)
 {
     if (maybeTypes())
         return true;
     if (object()->isSingleObject() && !object()->asSingleObject()->getType(cx)) {
         cx->clearPendingException();
         return false;
--- a/js/src/jsinfer.h
+++ b/js/src/jsinfer.h
@@ -1460,16 +1460,17 @@ struct TypeObjectKey
     JSObject *singleton();
     TypeNewScript *newScript();
 
     bool unknownProperties();
     bool hasFlags(CompilerConstraintList *constraints, TypeObjectFlags flags);
     void watchStateChangeForInlinedCall(CompilerConstraintList *constraints);
     void watchStateChangeForTypedArrayData(CompilerConstraintList *constraints);
     HeapTypeSetKey property(jsid id);
+    void ensureTrackedProperty(JSContext *cx, jsid id);
 
     TypeObject *maybeType();
 };
 
 // Representation of a heap type property which may or may not be instantiated.
 // Heap properties for singleton types are instantiated lazily as they are used
 // by the compiler, but this is only done on the main thread. If we are
 // compiling off thread and use a property which has not yet been instantiated,
--- a/js/src/vm/Compression.cpp
+++ b/js/src/vm/Compression.cpp
@@ -101,16 +101,22 @@ Compressor::compressMore()
     JS_ASSERT_IF(done, ret == Z_STREAM_END);
     return done ? DONE : CONTINUE;
 }
 
 bool
 js::DecompressString(const unsigned char *inp, size_t inplen, unsigned char *out, size_t outlen)
 {
     JS_ASSERT(inplen <= UINT32_MAX);
+
+    // Mark the memory we pass to zlib as initialized for MSan.
+#ifdef MOZ_MSAN
+    __msan_unpoison(out, outlen);
+#endif
+
     z_stream zs;
     zs.zalloc = zlib_alloc;
     zs.zfree = zlib_free;
     zs.opaque = nullptr;
     zs.next_in = (Bytef *)inp;
     zs.avail_in = inplen;
     zs.next_out = out;
     JS_ASSERT(outlen);
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -2230,17 +2230,17 @@ nsDisplayBackgroundImage::GetInsideClipR
     switch (aClip) {
     case NS_STYLE_BG_CLIP_BORDER:
       clipRect = nsRect(aItem->ToReferenceFrame(), frame->GetSize());
       break;
     case NS_STYLE_BG_CLIP_PADDING:
       clipRect = frame->GetPaddingRect() - frame->GetPosition() + aItem->ToReferenceFrame();
       break;
     case NS_STYLE_BG_CLIP_CONTENT:
-      clipRect = frame->GetContentRect() - frame->GetPosition() + aItem->ToReferenceFrame();
+      clipRect = frame->GetContentRectRelativeToSelf() + aItem->ToReferenceFrame();
       break;
     default:
       NS_NOTREACHED("Unknown clip type");
       return result;
     }
   }
 
   return clipRect.Intersect(aRect);
--- a/layout/forms/test/mochitest.ini
+++ b/layout/forms/test/mochitest.ini
@@ -18,25 +18,25 @@ skip-if = toolkit == 'android' #TIMED_OU
 [test_bug402198.html]
 [test_bug411236.html]
 [test_bug446663.html]
 skip-if = buildapp == 'b2g' || toolkit == 'android' # b2g(needs copy support) b2g-debug(needs copy support) b2g-desktop(needs copy support)
 [test_bug476308.html]
 [test_bug477531.html]
 [test_bug477700.html]
 [test_bug478219.xhtml]
-skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s #window.closed not working, bug 907795 # b2g(window.closed not working, bug 907795) b2g-debug(window.closed not working, bug 907795) b2g-desktop(window.closed not working, bug 907795)
+skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e10s #window.closed not working, bug 907795 # b2g(window.closed not working, bug 907795) b2g-debug(window.closed not working, bug 907795) b2g-desktop(window.closed not working, bug 907795)
 [test_bug534785.html]
 [test_bug542914.html]
 skip-if = (toolkit == 'gonk' && debug) #debug-only failure
 [test_bug549170.html]
 [test_bug562447.html]
 [test_bug563642.html]
 [test_bug564115.html]
-skip-if = (buildapp == 'b2g' && (toolkit != 'gonk' || debug)) || toolkit == 'android' || e10s #TIMED_OUT # b2g-debug(times out on window.open and focus event) b2g-desktop(times out on window.open and focus event)
+skip-if = buildapp == 'mulet' || (buildapp == 'b2g' && (toolkit != 'gonk' || debug)) || toolkit == 'android' || e10s #TIMED_OUT # b2g-debug(times out on window.open and focus event) b2g-desktop(times out on window.open and focus event)
 [test_bug571352.html]
 skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' #TIMED_OUT # b2g(shift-click multi-select not working?) b2g-debug(shift-click multi-select not working?) b2g-desktop(shift-click multi-select not working?)
 [test_bug572406.html]
 [test_bug572649.html]
 skip-if = toolkit == 'android' #TIMED_OUT
 [test_bug595310.html]
 [test_bug620936.html]
 [test_bug644542.html]
--- a/layout/generic/Selection.h
+++ b/layout/generic/Selection.h
@@ -210,17 +210,17 @@ public:
   SelectionType GetType(){return mType;}
   void          SetType(SelectionType aType){mType = aType;}
 
   nsresult     NotifySelectionListeners();
 
   friend struct AutoApplyUserSelectStyle;
   struct MOZ_STACK_CLASS AutoApplyUserSelectStyle
   {
-    AutoApplyUserSelectStyle(Selection* aSelection
+    explicit AutoApplyUserSelectStyle(Selection* aSelection
                              MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
       : mSavedValue(aSelection->mApplyUserSelectStyle)
     {
       MOZ_GUARD_OBJECT_NOTIFIER_INIT;
       aSelection->mApplyUserSelectStyle = true;
     }
     AutoRestore<bool> mSavedValue;
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -5868,17 +5868,17 @@ nsIFrame::IsFrameSelected() const
   return nsRange::IsNodeSelected(GetContent(), 0,
                                  GetContent()->GetChildCount());
 }
 
 nsresult
 nsFrame::GetPointFromOffset(int32_t inOffset, nsPoint* outPoint)
 {
   NS_PRECONDITION(outPoint != nullptr, "Null parameter");
-  nsRect contentRect = GetContentRect() - GetPosition();
+  nsRect contentRect = GetContentRectRelativeToSelf();
   nsPoint pt = contentRect.TopLeft();
   if (mContent)
   {
     nsIContent* newContent = mContent->GetParent();
     if (newContent){
       int32_t newOffset = newContent->IndexOf(mContent);
 
       bool isRTL = (NS_GET_EMBEDDING_LEVEL(this) & 1) == 1;
--- a/layout/generic/nsHTMLCanvasFrame.cpp
+++ b/layout/generic/nsHTMLCanvasFrame.cpp
@@ -236,16 +236,18 @@ nsHTMLCanvasFrame::Reflow(nsPresContext*
   NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS,
                   ("exit nsHTMLCanvasFrame::Reflow: size=%d,%d",
                    aMetrics.ISize(wm), aMetrics.BSize(wm)));
   NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aMetrics);
 }
 
 // FIXME taken from nsImageFrame, but then had splittable frame stuff
 // removed.  That needs to be fixed.
+// XXXdholbert As in nsImageFrame, this function's clients should probably
+// just be calling GetContentRectRelativeToSelf().
 nsRect 
 nsHTMLCanvasFrame::GetInnerArea() const
 {
   nsMargin bp = mBorderPadding.GetPhysicalMargin(GetWritingMode());
   nsRect r;
   r.x = bp.left;
   r.y = bp.top;
   r.width = mRect.width - bp.left - bp.right;
@@ -254,17 +256,17 @@ nsHTMLCanvasFrame::GetInnerArea() const
 }
 
 already_AddRefed<Layer>
 nsHTMLCanvasFrame::BuildLayer(nsDisplayListBuilder* aBuilder,
                               LayerManager* aManager,
                               nsDisplayItem* aItem,
                               const ContainerLayerParameters& aContainerParameters)
 {
-  nsRect area = GetContentRect() - GetPosition() + aItem->ToReferenceFrame();
+  nsRect area = GetContentRectRelativeToSelf() + aItem->ToReferenceFrame();
   HTMLCanvasElement* element = static_cast<HTMLCanvasElement*>(GetContent());
   nsIntSize canvasSize = GetCanvasSize();
 
   nsPresContext* presContext = PresContext();
   element->HandlePrintCallback(presContext->Type());
 
   if (canvasSize.width <= 0 || canvasSize.height <= 0 || area.IsEmpty())
     return nullptr;
--- a/layout/generic/nsImageFrame.cpp
+++ b/layout/generic/nsImageFrame.cpp
@@ -807,20 +807,22 @@ nsImageFrame::ComputeSize(nsRenderingCon
                             aRenderingContext, this,
                             intrinsicSize, mIntrinsicRatio,
                             aCBSize,
                             aMargin,
                             aBorder,
                             aPadding);
 }
 
+// XXXdholbert This function's clients should probably just be calling
+// GetContentRectRelativeToSelf() directly.
 nsRect 
 nsImageFrame::GetInnerArea() const
 {
-  return GetContentRect() - GetPosition();
+  return GetContentRectRelativeToSelf();
 }
 
 Element*
 nsImageFrame::GetMapElement() const
 {
   nsAutoString usemap;
   if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::usemap, usemap)) {
     return mContent->OwnerDoc()->FindImageMap(usemap);
--- a/layout/generic/nsVideoFrame.cpp
+++ b/layout/generic/nsVideoFrame.cpp
@@ -169,17 +169,17 @@ CorrectForAspectRatio(const gfxRect& aRe
 }
 
 already_AddRefed<Layer>
 nsVideoFrame::BuildLayer(nsDisplayListBuilder* aBuilder,
                          LayerManager* aManager,
                          nsDisplayItem* aItem,
                          const ContainerLayerParameters& aContainerParameters)
 {
-  nsRect area = GetContentRect() - GetPosition() + aItem->ToReferenceFrame();
+  nsRect area = GetContentRectRelativeToSelf() + aItem->ToReferenceFrame();
   HTMLVideoElement* element = static_cast<HTMLVideoElement*>(GetContent());
   nsIntSize videoSize;
   if (NS_FAILED(element->GetVideoSize(&videoSize)) || area.IsEmpty()) {
     return nullptr;
   }
 
   nsRefPtr<ImageContainer> container = element->GetImageContainer();
   if (!container)
@@ -388,17 +388,17 @@ public:
   // when we come to paint, the video frame is transparent or has gone
   // away completely (e.g. because of a decoder error). The problem would
   // be especially acute if we have off-main-thread rendering.
 
   virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) MOZ_OVERRIDE
   {
     *aSnap = true;
     nsIFrame* f = Frame();
-    return f->GetContentRect() - f->GetPosition() + ToReferenceFrame();
+    return f->GetContentRectRelativeToSelf() + ToReferenceFrame();
   }
 
   virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
                                              LayerManager* aManager,
                                              const ContainerLayerParameters& aContainerParameters) MOZ_OVERRIDE
   {
     return static_cast<nsVideoFrame*>(mFrame)->BuildLayer(aBuilder, aManager, this, aContainerParameters);
   }
--- a/layout/ipc/RenderFrameParent.cpp
+++ b/layout/ipc/RenderFrameParent.cpp
@@ -48,17 +48,17 @@ GetContentRectLayerOffset(nsIFrame* aCon
 {
   nscoord auPerDevPixel = aContainerFrame->PresContext()->AppUnitsPerDevPixel();
 
   // Offset to the content rect in case we have borders or padding
   // Note that aContainerFrame could be a reference frame itself, so
   // we need to be careful here to ensure that we call ToReferenceFrame
   // on aContainerFrame and not its parent.
   nsPoint frameOffset = aBuilder->ToReferenceFrame(aContainerFrame) +
-    (aContainerFrame->GetContentRect().TopLeft() - aContainerFrame->GetPosition());
+    aContainerFrame->GetContentRectRelativeToSelf().TopLeft();
 
   return frameOffset.ToNearestPixels(auPerDevPixel);
 }
 
 // Return true iff |aManager| is a "temporary layer manager".  They're
 // used for small software rendering tasks, like drawWindow.  That's
 // currently implemented by a BasicLayerManager without a backing
 // widget, and hence in non-retained mode.
--- a/layout/style/nsLayoutStylesheetCache.cpp
+++ b/layout/style/nsLayoutStylesheetCache.cpp
@@ -50,195 +50,137 @@ nsLayoutStylesheetCache::Observe(nsISupp
   }
   return NS_OK;
 }
 
 CSSStyleSheet*
 nsLayoutStylesheetCache::ScrollbarsSheet()
 {
   EnsureGlobal();
-  if (!gStyleCache)
-    return nullptr;
 
   if (!gStyleCache->mScrollbarsSheet) {
-    nsCOMPtr<nsIURI> sheetURI;
-    NS_NewURI(getter_AddRefs(sheetURI),
-              NS_LITERAL_CSTRING("chrome://global/skin/scrollbars.css"));
-
     // Scrollbars don't need access to unsafe rules
-    if (sheetURI)
-      LoadSheet(sheetURI, gStyleCache->mScrollbarsSheet, false);
-    NS_ASSERTION(gStyleCache->mScrollbarsSheet, "Could not load scrollbars.css.");
+    LoadSheetURL("chrome://global/skin/scrollbars.css",
+                 gStyleCache->mScrollbarsSheet, false);
   }
 
   return gStyleCache->mScrollbarsSheet;
 }
 
 CSSStyleSheet*
 nsLayoutStylesheetCache::FormsSheet()
 {
   EnsureGlobal();
-  if (!gStyleCache)
-    return nullptr;
 
   if (!gStyleCache->mFormsSheet) {
-    nsCOMPtr<nsIURI> sheetURI;
-      NS_NewURI(getter_AddRefs(sheetURI),
-                NS_LITERAL_CSTRING("resource://gre-resources/forms.css"));
-
     // forms.css needs access to unsafe rules
-    if (sheetURI)
-      LoadSheet(sheetURI, gStyleCache->mFormsSheet, true);
-
-    NS_ASSERTION(gStyleCache->mFormsSheet, "Could not load forms.css.");
+    LoadSheetURL("resource://gre-resources/forms.css",
+                 gStyleCache->mFormsSheet, true);
   }
 
   return gStyleCache->mFormsSheet;
 }
 
 CSSStyleSheet*
 nsLayoutStylesheetCache::NumberControlSheet()
 {
   EnsureGlobal();
-  if (!gStyleCache)
-    return nullptr;
 
   if (!sNumberControlEnabled) {
     return nullptr;
   }
 
   if (!gStyleCache->mNumberControlSheet) {
-    nsCOMPtr<nsIURI> sheetURI;
-    NS_NewURI(getter_AddRefs(sheetURI),
-              NS_LITERAL_CSTRING("resource://gre-resources/number-control.css"));
-
-    if (sheetURI)
-      LoadSheet(sheetURI, gStyleCache->mNumberControlSheet, false);
-
-    NS_ASSERTION(gStyleCache->mNumberControlSheet, "Could not load number-control.css");
+    LoadSheetURL("resource://gre-resources/number-control.css",
+                 gStyleCache->mNumberControlSheet, false);
   }
 
   return gStyleCache->mNumberControlSheet;
 }
 
 CSSStyleSheet*
 nsLayoutStylesheetCache::UserContentSheet()
 {
   EnsureGlobal();
-  if (!gStyleCache)
-    return nullptr;
-
   return gStyleCache->mUserContentSheet;
 }
 
 CSSStyleSheet*
 nsLayoutStylesheetCache::UserChromeSheet()
 {
   EnsureGlobal();
-  if (!gStyleCache)
-    return nullptr;
-
   return gStyleCache->mUserChromeSheet;
 }
 
 CSSStyleSheet*
 nsLayoutStylesheetCache::UASheet()
 {
   EnsureGlobal();
-  if (!gStyleCache)
-    return nullptr;
-
   return gStyleCache->mUASheet;
 }
 
 CSSStyleSheet*
 nsLayoutStylesheetCache::HTMLSheet()
 {
   EnsureGlobal();
-  if (!gStyleCache)
-    return nullptr;
-
   return gStyleCache->mHTMLSheet;
 }
 
 CSSStyleSheet*
 nsLayoutStylesheetCache::MinimalXULSheet()
 {
   EnsureGlobal();
-  if (!gStyleCache)
-    return nullptr;
-
   return gStyleCache->mMinimalXULSheet;
 }
 
 CSSStyleSheet*
 nsLayoutStylesheetCache::XULSheet()
 {
   EnsureGlobal();
-  if (!gStyleCache)
-    return nullptr;
-
   return gStyleCache->mXULSheet;
 }
 
 CSSStyleSheet*
 nsLayoutStylesheetCache::QuirkSheet()
 {
   EnsureGlobal();
-  if (!gStyleCache)
-    return nullptr;
-
   return gStyleCache->mQuirkSheet;
 }
 
 CSSStyleSheet*
 nsLayoutStylesheetCache::FullScreenOverrideSheet()
 {
   EnsureGlobal();
-  if (!gStyleCache)
-    return nullptr;
-
   return gStyleCache->mFullScreenOverrideSheet;
 }
 
 CSSStyleSheet*
 nsLayoutStylesheetCache::SVGSheet()
 {
   EnsureGlobal();
-  if (!gStyleCache)
-    return nullptr;
-
   return gStyleCache->mSVGSheet;
 }
 
 CSSStyleSheet*
 nsLayoutStylesheetCache::MathMLSheet()
 {
   EnsureGlobal();
-  if (!gStyleCache)
-    return nullptr;
 
   if (!gStyleCache->mMathMLSheet) {
-    nsCOMPtr<nsIURI> uri;
-    NS_NewURI(getter_AddRefs(uri), "resource://gre-resources/mathml.css");
-    if (uri) {
-      LoadSheet(uri, gStyleCache->mMathMLSheet, true);
-    }
-    NS_ASSERTION(gStyleCache->mMathMLSheet, "Could not load mathml.css");
+    LoadSheetURL("resource://gre-resources/mathml.css",
+                 gStyleCache->mMathMLSheet, true);
   }
 
   return gStyleCache->mMathMLSheet;
 }
 
 CSSStyleSheet*
 nsLayoutStylesheetCache::CounterStylesSheet()
 {
   EnsureGlobal();
-  if (!gStyleCache)
-    return nullptr;
 
   return gStyleCache->mCounterStylesSheet;
 }
 
 void
 nsLayoutStylesheetCache::Shutdown()
 {
   NS_IF_RELEASE(gCSSLoader);
@@ -260,32 +202,30 @@ nsLayoutStylesheetCache::CollectReports(
 
 size_t
 nsLayoutStylesheetCache::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
 {
   size_t n = aMallocSizeOf(this);
 
   #define MEASURE(s) n += s ? s->SizeOfIncludingThis(aMallocSizeOf) : 0;
 
-  MEASURE(mScrollbarsSheet);
+  MEASURE(mCounterStylesSheet);
   MEASURE(mFormsSheet);
-  MEASURE(mNumberControlSheet);
-  MEASURE(mUserContentSheet);
-  MEASURE(mUserChromeSheet);
-  MEASURE(mUASheet);
+  MEASURE(mFullScreenOverrideSheet);
   MEASURE(mHTMLSheet);
+  MEASURE(mMathMLSheet);
   MEASURE(mMinimalXULSheet);
-  MEASURE(mXULSheet);
+  MEASURE(mNumberControlSheet);
   MEASURE(mQuirkSheet);
-  MEASURE(mFullScreenOverrideSheet);
   MEASURE(mSVGSheet);
-  MEASURE(mCounterStylesSheet);
-  if (mMathMLSheet) {
-    MEASURE(mMathMLSheet);
-  }
+  MEASURE(mScrollbarsSheet);
+  MEASURE(mUASheet);
+  MEASURE(mUserChromeSheet);
+  MEASURE(mUserContentSheet);
+  MEASURE(mXULSheet);
 
   // Measurement of the following members may be added later if DMD finds it is
   // worthwhile:
   // - gCSSLoader
 
   return n;
 }
 
@@ -301,67 +241,35 @@ nsLayoutStylesheetCache::nsLayoutStylesh
     obsSvc->AddObserver(this, "chrome-flush-skin-caches", false);
     obsSvc->AddObserver(this, "chrome-flush-caches", false);
   }
 
   InitFromProfile();
 
   // And make sure that we load our UA sheets.  No need to do this
   // per-profile, since they're profile-invariant.
-  nsCOMPtr<nsIURI> uri;
-  NS_NewURI(getter_AddRefs(uri), "resource://gre-resources/ua.css");
-  if (uri) {
-    LoadSheet(uri, mUASheet, true);
-  }
-  NS_ASSERTION(mUASheet, "Could not load ua.css");
-
-  NS_NewURI(getter_AddRefs(uri), "resource://gre-resources/html.css");
-  if (uri) {
-    LoadSheet(uri, mHTMLSheet, true);
-  }
-  NS_ASSERTION(mHTMLSheet, "Could not load xul.css");
-
-  NS_NewURI(getter_AddRefs(uri), "chrome://global/content/minimal-xul.css");
-  if (uri) {
-    LoadSheet(uri, mMinimalXULSheet, true);
-  }
-  NS_ASSERTION(mMinimalXULSheet, "Could not load minimal-xul.css");
-
-  NS_NewURI(getter_AddRefs(uri), "chrome://global/content/xul.css");
-  if (uri) {
-    LoadSheet(uri, mXULSheet, true);
-  }
-  NS_ASSERTION(mXULSheet, "Could not load xul.css");
+  LoadSheetURL("resource://gre-resources/counterstyles.css",
+               mCounterStylesSheet, true);
+  LoadSheetURL("resource://gre-resources/full-screen-override.css",
+               mFullScreenOverrideSheet, true);
+  LoadSheetURL("resource://gre-resources/html.css",
+               mHTMLSheet, true);
+  LoadSheetURL("chrome://global/content/minimal-xul.css",
+               mMinimalXULSheet, true);
+  LoadSheetURL("resource://gre-resources/quirk.css",
+               mQuirkSheet, true);
+  LoadSheetURL("resource://gre/res/svg.css",
+               mSVGSheet, true);
+  LoadSheetURL("resource://gre-resources/ua.css",
+               mUASheet, true);
+  LoadSheetURL("chrome://global/content/xul.css",
+               mXULSheet, true);
 
-  NS_NewURI(getter_AddRefs(uri), "resource://gre-resources/quirk.css");
-  if (uri) {
-    LoadSheet(uri, mQuirkSheet, true);
-  }
-  NS_ASSERTION(mQuirkSheet, "Could not load quirk.css");
-
-  NS_NewURI(getter_AddRefs(uri), "resource://gre-resources/full-screen-override.css");
-  if (uri) {
-    LoadSheet(uri, mFullScreenOverrideSheet, true);
-  }
-  NS_ASSERTION(mFullScreenOverrideSheet, "Could not load full-screen-override.css");
-
-  NS_NewURI(getter_AddRefs(uri), "resource://gre/res/svg.css");
-  if (uri) {
-    LoadSheet(uri, mSVGSheet, true);
-  }
-  NS_ASSERTION(mSVGSheet, "Could not load svg.css");
-
-  NS_NewURI(getter_AddRefs(uri), "resource://gre-resources/counterstyles.css");
-  if (uri) {
-    LoadSheet(uri, mCounterStylesSheet, true);
-  }
-  NS_ASSERTION(mCounterStylesSheet, "Could not load counterstyles.css");
-
-  // mMathMLSheet is created on-demand since its use is rare. This helps save
-  // memory for Firefox OS apps.
+  // The remaining sheets are created on-demand since their use is rarer. This
+  // helps save memory for Firefox OS apps.
 }
 
 nsLayoutStylesheetCache::~nsLayoutStylesheetCache()
 {
   mozilla::UnregisterWeakMemoryReporter(this);
   MOZ_ASSERT(!gStyleCache);
 }
 
@@ -369,20 +277,21 @@ void
 nsLayoutStylesheetCache::InitMemoryReporter()
 {
   mozilla::RegisterWeakMemoryReporter(this);
 }
 
 void
 nsLayoutStylesheetCache::EnsureGlobal()
 {
+  MOZ_ASSERT(NS_IsMainThread());
+
   if (gStyleCache) return;
 
   gStyleCache = new nsLayoutStylesheetCache();
-  if (!gStyleCache) return;
 
   gStyleCache->InitMemoryReporter();
 
   Preferences::AddBoolVarCache(&sNumberControlEnabled, NUMBER_CONTROL_PREF,
                                true);
 }
 
 void
@@ -410,16 +319,29 @@ nsLayoutStylesheetCache::InitFromProfile
 
   contentFile->Append(NS_LITERAL_STRING("userContent.css"));
   chromeFile->Append(NS_LITERAL_STRING("userChrome.css"));
 
   LoadSheetFile(contentFile, mUserContentSheet);
   LoadSheetFile(chromeFile, mUserChromeSheet);
 }
 
+/* static */ void
+nsLayoutStylesheetCache::LoadSheetURL(const char* aURL,
+                                      nsRefPtr<CSSStyleSheet>& aSheet,
+                                      bool aEnableUnsafeRules)
+{
+  nsCOMPtr<nsIURI> uri;
+  NS_NewURI(getter_AddRefs(uri), aURL);
+  LoadSheet(uri, aSheet, aEnableUnsafeRules);
+  if (!aSheet) {
+    NS_ERROR(nsPrintfCString("Could not load %s", aURL).get());
+  }
+}
+
 void
 nsLayoutStylesheetCache::LoadSheetFile(nsIFile* aFile, nsRefPtr<CSSStyleSheet>& aSheet)
 {
   bool exists = false;
   aFile->Exists(&exists);
 
   if (!exists) return;
 
--- a/layout/style/nsLayoutStylesheetCache.h
+++ b/layout/style/nsLayoutStylesheetCache.h
@@ -55,32 +55,35 @@ class nsLayoutStylesheetCache MOZ_FINAL
 
 private:
   nsLayoutStylesheetCache();
   ~nsLayoutStylesheetCache();
 
   static void EnsureGlobal();
   void InitFromProfile();
   void InitMemoryReporter();
+  static void LoadSheetURL(const char* aURL,
+                           nsRefPtr<mozilla::CSSStyleSheet>& aSheet,
+                           bool aEnableUnsafeRules);
   static void LoadSheetFile(nsIFile* aFile,
                             nsRefPtr<mozilla::CSSStyleSheet>& aSheet);
   static void LoadSheet(nsIURI* aURI, nsRefPtr<mozilla::CSSStyleSheet>& aSheet,
                         bool aEnableUnsafeRules);
 
   static mozilla::StaticRefPtr<nsLayoutStylesheetCache> gStyleCache;
   static mozilla::css::Loader* gCSSLoader;
-  nsRefPtr<mozilla::CSSStyleSheet> mScrollbarsSheet;
+  nsRefPtr<mozilla::CSSStyleSheet> mCounterStylesSheet;
   nsRefPtr<mozilla::CSSStyleSheet> mFormsSheet;
-  nsRefPtr<mozilla::CSSStyleSheet> mNumberControlSheet;
-  nsRefPtr<mozilla::CSSStyleSheet> mUserContentSheet;
-  nsRefPtr<mozilla::CSSStyleSheet> mUserChromeSheet;
-  nsRefPtr<mozilla::CSSStyleSheet> mUASheet;
+  nsRefPtr<mozilla::CSSStyleSheet> mFullScreenOverrideSheet;
   nsRefPtr<mozilla::CSSStyleSheet> mHTMLSheet;
+  nsRefPtr<mozilla::CSSStyleSheet> mMathMLSheet;
   nsRefPtr<mozilla::CSSStyleSheet> mMinimalXULSheet;
-  nsRefPtr<mozilla::CSSStyleSheet> mXULSheet;
+  nsRefPtr<mozilla::CSSStyleSheet> mNumberControlSheet;
   nsRefPtr<mozilla::CSSStyleSheet> mQuirkSheet;
-  nsRefPtr<mozilla::CSSStyleSheet> mFullScreenOverrideSheet;
   nsRefPtr<mozilla::CSSStyleSheet> mSVGSheet;
-  nsRefPtr<mozilla::CSSStyleSheet> mMathMLSheet;
-  nsRefPtr<mozilla::CSSStyleSheet> mCounterStylesSheet;
+  nsRefPtr<mozilla::CSSStyleSheet> mScrollbarsSheet;
+  nsRefPtr<mozilla::CSSStyleSheet> mUASheet;
+  nsRefPtr<mozilla::CSSStyleSheet> mUserChromeSheet;
+  nsRefPtr<mozilla::CSSStyleSheet> mUserContentSheet;
+  nsRefPtr<mozilla::CSSStyleSheet> mXULSheet;
 };
 
 #endif
--- a/layout/svg/nsSVGEffects.h
+++ b/layout/svg/nsSVGEffects.h
@@ -143,17 +143,17 @@ protected:
     nsSVGIDRenderingObserver* mContainer;
   };
 
   SourceReference mElement;
 };
 
 struct nsSVGFrameReferenceFromProperty
 {
-  nsSVGFrameReferenceFromProperty(nsIFrame* aFrame)
+  explicit nsSVGFrameReferenceFromProperty(nsIFrame* aFrame)
     : mFrame(aFrame)
     , mFramePresShell(aFrame->PresContext()->PresShell())
   {}
 
   // Clear our reference to the frame.
   void Detach();
 
   // null if the frame has become invalid
--- a/layout/tables/nsTableCellFrame.cpp
+++ b/layout/tables/nsTableCellFrame.cpp
@@ -719,17 +719,17 @@ nsTableCellFrame::GetCellBaseline() cons
 {
   // Ignore the position of the inner frame relative to the cell frame
   // since we want the position as though the inner were top-aligned.
   nsIFrame *inner = mFrames.FirstChild();
   nscoord borderPadding = GetUsedBorderAndPadding().top;
   nscoord result;
   if (nsLayoutUtils::GetFirstLineBaseline(GetWritingMode(), inner, &result))
     return result + borderPadding;
-  return inner->GetContentRect().YMost() - inner->GetPosition().y +
+  return inner->GetContentRectRelativeToSelf().YMost() +
          borderPadding;
 }
 
 int32_t nsTableCellFrame::GetRowSpan()
 {
   int32_t rowSpan=1;
   nsGenericHTMLElement *hc = nsGenericHTMLElement::FromContent(mContent);
 
--- a/layout/tools/reftest/runreftest.py
+++ b/layout/tools/reftest/runreftest.py
@@ -18,20 +18,20 @@ import sys
 import threading
 
 SCRIPT_DIRECTORY = os.path.abspath(os.path.realpath(os.path.dirname(sys.argv[0])))
 sys.path.insert(0, SCRIPT_DIRECTORY)
 
 from automation import Automation
 from automationutils import (
         addCommonOptions,
-        getDebuggerInfo,
         isURL,
         processLeakLog
 )
+import mozdebug
 import mozprofile
 
 def categoriesToRegex(categoryList):
   return "\\(" + ', '.join(["(?P<%s>\\d+) %s" % c for c in categoryList]) + "\\)"
 summaryLines = [('Successful', [('pass', 'pass'), ('loadOnly', 'load only')]),
                 ('Unexpected', [('fail', 'unexpected fail'),
                                 ('pass', 'unexpected pass'),
                                 ('asserts', 'unexpected asserts'),
@@ -315,17 +315,17 @@ class RefTest(object):
     print 'REFTEST INFO | Result summary:'
     for (summaryObj, (text, categories)) in zip(summaryObjects, summaryLines):
       details = ', '.join(["%d %s" % (summaryObj[attribute], description) for (attribute, description) in categories])
       print 'REFTEST INFO | ' + text + ': ' + str(summaryObj['total']) + ' (' +  details + ')'
 
     return int(any(t.retcode != 0 for t in threads))
 
   def runSerialTests(self, testPath, options, cmdlineArgs = None):
-    debuggerInfo = getDebuggerInfo(self.oldcwd, options.debugger, options.debuggerArgs,
+    debuggerInfo = mozdebug.get_debugger_info(options.debugger, options.debuggerArgs,
         options.debuggerInteractive);
 
     profileDir = None
     try:
       reftestlist = self.getManifestPath(testPath)
       if cmdlineArgs == None:
         cmdlineArgs = ['-reftest', reftestlist]
       profile = self.createReftestProfile(options, reftestlist)
--- a/mfbt/MemoryChecking.h
+++ b/mfbt/MemoryChecking.h
@@ -49,16 +49,39 @@ void MOZ_EXPORT
   __asan_poison_memory_region((addr), (size))
 
 #define MOZ_MAKE_MEM_UNDEFINED(addr, size) \
   __asan_unpoison_memory_region((addr), (size))
 
 #define MOZ_MAKE_MEM_DEFINED(addr, size) \
   __asan_unpoison_memory_region((addr), (size))
 }
+#elif defined(MOZ_MSAN)
+#include <stddef.h>
+
+#include "mozilla/Types.h"
+
+extern "C" {
+/* These definitions are usually provided through the
+ * sanitizer/msan_interface.h header installed by MSan.
+ */
+void MOZ_EXPORT
+__msan_poison(void const volatile *addr, size_t size);
+void MOZ_EXPORT
+__msan_unpoison(void const volatile *addr, size_t size);
+
+#define MOZ_MAKE_MEM_NOACCESS(addr, size) \
+  __msan_poison((addr), (size))
+
+#define MOZ_MAKE_MEM_UNDEFINED(addr, size) \
+  __msan_poison((addr), (size))
+
+#define MOZ_MAKE_MEM_DEFINED(addr, size) \
+  __msan_unpoison((addr), (size))
+}
 #elif defined(MOZ_VALGRIND)
 #define MOZ_MAKE_MEM_NOACCESS(addr, size) \
   VALGRIND_MAKE_MEM_NOACCESS((addr), (size))
 
 #define MOZ_MAKE_MEM_UNDEFINED(addr, size) \
   VALGRIND_MAKE_MEM_UNDEFINED((addr), (size))
 
 #define MOZ_MAKE_MEM_DEFINED(addr, size) \
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -731,16 +731,35 @@ public class BrowserApp extends GeckoApp
         // Register for Prompt:ShowTop so we can foreground this activity even if it's hidden.
         EventDispatcher.getInstance().registerGeckoThreadListener((GeckoEventListener)this,
             "Prompt:ShowTop");
 
         final LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this);
         lbm.unregisterReceiver(mOnboardingReceiver);
     }
 
+    @Override
+    public void onWindowFocusChanged(boolean hasFocus) {
+        super.onWindowFocusChanged(hasFocus);
+        // If Home Page is visible, the layerView surface has to be visible
+        // to avoid a surface issue in Gingerbread phones.
+        // We need to do this on the next iteration.
+        // See bugs: 1058027 and 1003123
+        if (mInitialized && hasFocus &&
+            Versions.preHC && isHomePagerVisible() &&
+            mLayerView.getVisibility() != View.VISIBLE){
+            ThreadUtils.postToUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    mLayerView.showSurface();
+                }
+            });
+        }
+    }
+
     private void setBrowserToolbarListeners() {
         mBrowserToolbar.setOnActivateListener(new BrowserToolbar.OnActivateListener() {
             public void onActivate() {
                 enterEditingMode();
             }
         });
 
         mBrowserToolbar.setOnCommitListener(new BrowserToolbar.OnCommitListener() {
--- a/mobile/android/base/EventDispatcher.java
+++ b/mobile/android/base/EventDispatcher.java
@@ -7,16 +7,17 @@ package org.mozilla.gecko;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.mozglue.RobocopTarget;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.NativeEventListener;
 import org.mozilla.gecko.util.NativeJSContainer;
 import org.mozilla.gecko.util.NativeJSObject;
+import org.mozilla.gecko.util.ThreadUtils;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.util.Log;
 
 import java.util.HashMap;
 import java.util.List;
@@ -222,22 +223,28 @@ public final class EventDispatcher {
     @Deprecated
     public static void sendError(JSONObject message, Object response) {
         sendResponseHelper(STATUS_ERROR, message, response);
     }
 
     @Deprecated
     private static void sendResponseHelper(String status, JSONObject message, Object response) {
         try {
+            final String topic = message.getString("type") + ":Response";
             final JSONObject wrapper = new JSONObject();
             wrapper.put(GUID, message.getString(GUID));
             wrapper.put("status", status);
             wrapper.put("response", response);
-            GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(
-                    message.getString("type") + ":Response", wrapper.toString()));
+
+            if (ThreadUtils.isOnGeckoThread()) {
+                GeckoAppShell.notifyGeckoObservers(topic, wrapper.toString());
+            } else {
+                GeckoAppShell.sendEventToGecko(
+                    GeckoEvent.createBroadcastEvent(topic, wrapper.toString()));
+            }
         } catch (final JSONException e) {
             Log.e(LOGTAG, "Unable to send response", e);
         }
     }
 
     private static class GeckoEventCallback implements EventCallback {
         private final String guid;
         private final String type;
@@ -260,20 +267,26 @@ public final class EventDispatcher {
             if (sent) {
                 throw new IllegalStateException("Callback has already been executed for type=" +
                         type + ", guid=" + guid);
             }
 
             sent = true;
 
             try {
+                final String topic = type + ":Response";
                 final JSONObject wrapper = new JSONObject();
                 wrapper.put(GUID, guid);
                 wrapper.put("status", status);
                 wrapper.put("response", response);
-                GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(type + ":Response",
-                        wrapper.toString()));
+
+                if (ThreadUtils.isOnGeckoThread()) {
+                    GeckoAppShell.notifyGeckoObservers(topic, wrapper.toString());
+                } else {
+                    GeckoAppShell.sendEventToGecko(
+                        GeckoEvent.createBroadcastEvent(topic, wrapper.toString()));
+                }
             } catch (final JSONException e) {
                 Log.e(LOGTAG, "Unable to send response for: " + type, e);
             }
         }
     }
 }
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -293,17 +293,22 @@ public class GeckoAppShell
                 mScanner = null;
             }
         }
     }
 
     private static LayerView sLayerView;
 
     public static void setLayerView(LayerView lv) {
+        if (sLayerView == lv) {
+            return;
+        }
         sLayerView = lv;
+        // Install new Gecko-to-Java editable listener.
+        editableListener = new GeckoEditable();
     }
 
     @RobocopTarget
     public static LayerView getLayerView() {
         return sLayerView;
     }
 
     public static void runGecko(String apkPath, String args, String url, String type) {
@@ -344,40 +349,26 @@ public class GeckoAppShell
             Log.w(LOGTAG, "STARTUP PERFORMANCE WARNING: un-official build: purging the " +
                           "startup (JavaScript) caches.");
             combinedArgs += " -purgecaches";
         }
 
         DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
         combinedArgs += " -width " + metrics.widthPixels + " -height " + metrics.heightPixels;
 
-        ThreadUtils.postToUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    geckoLoaded();
-                }
-            });
-
         if (!AppConstants.MOZILLA_OFFICIAL) {
             Log.d(LOGTAG, "GeckoLoader.nativeRun " + combinedArgs);
         }
         // and go
         GeckoLoader.nativeRun(combinedArgs);
 
         // Remove pumpMessageLoop() idle handler
         Looper.myQueue().removeIdleHandler(idleHandler);
     }
 
-    // Called on the UI thread after Gecko loads.
-    private static void geckoLoaded() {
-        GeckoEditable editable = new GeckoEditable();
-        // install the gecko => editable listener
-        editableListener = editable;
-    }
-
     static void sendPendingEventsToGecko() {
         try {
             while (!PENDING_EVENTS.isEmpty()) {
                 final GeckoEvent e = PENDING_EVENTS.poll();
                 notifyGeckoOfEvent(e);
             }
         } catch (NoSuchElementException e) {}
     }
@@ -442,16 +433,19 @@ public class GeckoAppShell
         }, responseMessage);
 
         sendEventToGecko(GeckoEvent.createBroadcastEvent(request.getName(), request.getData()));
     }
 
     // Tell the Gecko event loop that an event is available.
     public static native void notifyGeckoOfEvent(GeckoEvent event);
 
+    // Synchronously notify a Gecko observer; must be called from Gecko thread.
+    public static native void notifyGeckoObservers(String subject, String data);
+
     /*
      *  The Gecko-side API: API methods that Gecko calls
      */
 
     @WrapElementForJNI(allowMultithread = true, noThrow = true)
     public static void handleUncaughtException(Thread thread, Throwable e) {
         if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoExited)) {
             // We've called System.exit. All exceptions after this point are Android
--- a/mobile/android/base/LightweightTheme.java
+++ b/mobile/android/base/LightweightTheme.java
@@ -13,23 +13,25 @@ import org.mozilla.gecko.AppConstants.Ve
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.util.ThreadUtils.AssertBehavior;
 
 import android.app.Application;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.Shader;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.os.Looper;
+import android.text.TextUtils;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.Gravity;
 import android.view.View;
 import android.view.ViewParent;
 
 public class LightweightTheme implements GeckoEventListener {
     private static final String LOGTAG = "GeckoLightweightTheme";
@@ -72,33 +74,34 @@ public class LightweightTheme implements
         mListeners.remove(listener);
     }
 
     @Override
     public void handleMessage(String event, JSONObject message) {
         try {
             if (event.equals("LightweightTheme:Update")) {
                 JSONObject lightweightTheme = message.getJSONObject("data");
-                final String headerURL = lightweightTheme.getString("headerURL"); 
+                final String headerURL = lightweightTheme.getString("headerURL");
+                final String color = lightweightTheme.optString("accentcolor");
 
                 // Move any heavy lifting off the Gecko thread
                 ThreadUtils.postToBackgroundThread(new Runnable() {
                     @Override
                     public void run() {
                         String croppedURL = headerURL;
                         int mark = croppedURL.indexOf('?');
                         if (mark != -1)
                             croppedURL = croppedURL.substring(0, mark);
 
                         // Get the image and convert it to a bitmap.
                         final Bitmap bitmap = BitmapUtils.decodeUrl(croppedURL);
                         mHandler.post(new Runnable() {
                             @Override
                             public void run() {
-                                setLightweightTheme(bitmap);
+                                setLightweightTheme(bitmap, color);
                             }
                         });
                     }
                 });
             } else if (event.equals("LightweightTheme:Disable")) {
                 mHandler.post(new Runnable() {
                     @Override
                     public void run() {
@@ -112,51 +115,66 @@ public class LightweightTheme implements
     }
 
     /**
      * Set a new lightweight theme with the given bitmap.
      * Note: This should be called on the UI thread to restrict accessing the
      * bitmap to a single thread.
      *
      * @param bitmap The bitmap used for the lightweight theme.
+     * @param color  The background/accent color used for the lightweight theme.
      */
-    private void setLightweightTheme(Bitmap bitmap) {
+    private void setLightweightTheme(Bitmap bitmap, String color) {
         if (bitmap == null || bitmap.getWidth() == 0 || bitmap.getHeight() == 0) {
             mBitmap = null;
             return;
         }
 
-        // To find the dominant color only once, take the bottom 25% of pixels.
+        // Get the max display dimension so we can crop or expand the theme.
         DisplayMetrics dm = mApplication.getResources().getDisplayMetrics();
         int maxWidth = Math.max(dm.widthPixels, dm.heightPixels);
-        int height = (int) (bitmap.getHeight() * 0.25);
 
         // The lightweight theme image's width and height.
-        int bitmapWidth = bitmap.getWidth();
-        int bitmapHeight = bitmap.getHeight();
+        final int bitmapWidth = bitmap.getWidth();
+        final int bitmapHeight = bitmap.getHeight();
+
+        boolean useDominantColor = true;
+        if (!TextUtils.isEmpty(color)) {
+            try {
+                mColor = Color.parseColor(color);
+                useDominantColor = false;
+            } catch (IllegalArgumentException e) {
+                // Malformed color.
+            }
+        }
 
-        // A cropped bitmap of the bottom 25% of pixels.
-        Bitmap cropped = Bitmap.createBitmap(bitmap,
-                                             bitmapWidth > maxWidth ? bitmapWidth - maxWidth : 0,
-                                             bitmapHeight - height, 
-                                             bitmapWidth > maxWidth ? maxWidth : bitmapWidth,
-                                             height);
+        // Calculate the dominant color the hard way, if not given to us.
+        if (useDominantColor) {
+            // To find the dominant color, take <toolbar height> of pixels.
+            int cropLength = mApplication.getResources().getDimensionPixelSize(R.dimen.browser_toolbar_height);
 
-        // Dominant color based on the cropped bitmap.
-        mColor = BitmapUtils.getDominantColor(cropped, false);
+            // A cropped bitmap of the top/left pixels.
+            Bitmap cropped = Bitmap.createBitmap(bitmap,
+                                                 0, 0,
+                                                 cropLength > bitmapWidth ? bitmapWidth : cropLength,
+                                                 cropLength > bitmapHeight ? bitmapHeight : cropLength);
+
+            // Dominant color based on the cropped bitmap.
+            mColor = BitmapUtils.getDominantColor(cropped, false);
+        }
 
         // Calculate the luminance to determine if it's a light or a dark theme.
-        double luminance = (0.2125 * ((mColor & 0x00FF0000) >> 16)) + 
-                           (0.7154 * ((mColor & 0x0000FF00) >> 8)) + 
+        double luminance = (0.2125 * ((mColor & 0x00FF0000) >> 16)) +
+                           (0.7154 * ((mColor & 0x0000FF00) >> 8)) +
                            (0.0721 * (mColor &0x000000FF));
         mIsLight = luminance > 110;
 
         // The bitmap image might be smaller than the device's width.
         // If it's smaller, fill the extra space on the left with the dominant color.
-        if (bitmap.getWidth() >= maxWidth) {
+        if (bitmapWidth >= maxWidth) {
             mBitmap = bitmap;
         } else {
             Paint paint = new Paint();
             paint.setAntiAlias(true);
 
             // Create a bigger image that can fill the device width.
             // By creating a canvas for the bitmap, anything drawn on the canvas
             // will be drawn on the bitmap.
--- a/mobile/android/base/util/ThreadUtils.java
+++ b/mobile/android/base/util/ThreadUtils.java
@@ -177,16 +177,23 @@ public final class ThreadUtils {
         switch (behavior) {
         case THROW:
             throw e;
         default:
             Log.e(LOGTAG, "Method called on wrong thread!", e);
         }
     }
 
+    public static boolean isOnGeckoThread() {
+        if (sGeckoThread != null) {
+            return isOnThread(sGeckoThread);
+        }
+        return false;
+    }
+
     public static boolean isOnUiThread() {
         return isOnThread(getUiThread());
     }
 
     public static boolean isOnBackgroundThread() {
         if (sBackgroundThread == null) {
             return false;
         }
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -243,16 +243,17 @@
 #ifdef MOZ_CAPTIVEDETECT
 @BINPATH@/components/captivedetect.xpt
 #endif
 @BINPATH@/components/shellservice.xpt
 @BINPATH@/components/shistory.xpt
 @BINPATH@/components/spellchecker.xpt
 @BINPATH@/components/storage.xpt
 @BINPATH@/components/telemetry.xpt
+@BINPATH@/components/toolkit_asyncshutdown.xpt
 @BINPATH@/components/toolkit_filewatcher.xpt
 @BINPATH@/components/toolkit_finalizationwitness.xpt
 @BINPATH@/components/toolkit_formautofill.xpt
 @BINPATH@/components/toolkit_osfile.xpt
 @BINPATH@/components/toolkit_xulstore.xpt
 @BINPATH@/components/toolkitprofile.xpt
 #ifdef MOZ_ENABLE_XREMOTE
 @BINPATH@/components/toolkitremote.xpt
@@ -437,16 +438,19 @@
 @BINPATH@/components/dom_webspeechsynth.xpt
 #endif
 
 #ifdef MOZ_DEBUG
 @BINPATH@/components/TestInterfaceJS.js
 @BINPATH@/components/TestInterfaceJS.manifest
 #endif
 
+@BINPATH@/components/nsAsyncShutdown.manifest
+@BINPATH@/components/nsAsyncShutdown.js
+
 ; Modules
 @BINPATH@/modules/*
 
 #ifdef MOZ_SAFE_BROWSING
 ; Safe Browsing
 @BINPATH@/components/nsURLClassifier.manifest
 @BINPATH@/components/nsUrlClassifierHashCompleter.js
 @BINPATH@/components/nsUrlClassifierListManager.js
--- a/modules/libjar/nsIZipReader.idl
+++ b/modules/libjar/nsIZipReader.idl
@@ -10,17 +10,17 @@
 struct PRFileDesc;
 %}
 
 [ptr] native PRFileDescStar(PRFileDesc);
 
 interface nsIUTF8StringEnumerator;
 interface nsIInputStream;
 interface nsIFile;
-interface nsICertificatePrincipal;
+interface nsIX509Cert;
 
 [scriptable, uuid(fad6f72f-13d8-4e26-9173-53007a4afe71)]
 interface nsIZipEntry : nsISupports
 {
     /**
      * The type of compression used for the item.  The possible values and
      * their meanings are defined in the zip file specification at
      * http://www.pkware.com/business_and_developers/developer/appnote/
@@ -58,17 +58,17 @@ interface nsIZipEntry : nsISupports
      */
     readonly attribute boolean          isSynthetic;
     /**
      * The UNIX style file permissions of this item.
      */
     readonly attribute unsigned long    permissions;
 };
 
-[scriptable, uuid(38d6d07a-8a58-4fe7-be8b-ef6472fa83ff)]
+[scriptable, uuid(894c8dc0-37c8-11e4-916c-0800200c9a66)]
 interface nsIZipReader : nsISupports
 {
     /**
      * Opens a zip file for reading.
      * It is allowed to open with another file, 
      * but it needs to be closed first with close().
      */
     void open(in nsIFile zipFile);
@@ -185,18 +185,18 @@ interface nsIZipReader : nsISupports
      /**
      * Returns an object describing the entity which signed 
      * an entry. parseManifest must be called first. If aEntryName is an
      * entry in the jar, getInputStream must be called after parseManifest.
      * If aEntryName is an external file which has meta-information 
      * stored in the jar, verifyExternalFile (not yet implemented) must 
      * be called before getPrincipal.
      */
-    nsICertificatePrincipal getCertificatePrincipal(in AUTF8String aEntryName);   
-    
+    nsIX509Cert getSigningCert(in AUTF8String aEntryName);
+
     readonly attribute uint32_t manifestEntriesCount;
 };
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsIZipReaderCache
 
 [scriptable, uuid(94ecd586-d405-4801-93d3-8ac7bef81bde)]
 interface nsIZipReaderCache : nsISupports
--- a/modules/libjar/nsJAR.cpp
+++ b/modules/libjar/nsJAR.cpp
@@ -1,17 +1,17 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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 <string.h>
 #include "nsJARInputStream.h"
 #include "nsJAR.h"
 #include "nsIFile.h"
-#include "nsICertificatePrincipal.h"
+#include "nsIX509Cert.h"
 #include "nsIConsoleService.h"
 #include "nsICryptoHash.h"
 #include "nsIDataSignatureVerifier.h"
 #include "prprf.h"
 #include "mozilla/Omnijar.h"
 
 #ifdef XP_UNIX
   #include <sys/stat.h>
@@ -318,22 +318,23 @@ nsJAR::GetInputStreamWithSpec(const nsAC
   }
   if (NS_FAILED(rv)) {
     NS_RELEASE(*result);
   }
   return rv;
 }
 
 NS_IMETHODIMP
-nsJAR::GetCertificatePrincipal(const nsACString &aFilename, nsICertificatePrincipal** aPrincipal)
+nsJAR::GetSigningCert(const nsACString& aFilename, nsIX509Cert** aSigningCert)
 {
   //-- Parameter check
-  if (!aPrincipal)
+  if (!aSigningCert) {
     return NS_ERROR_NULL_POINTER;
-  *aPrincipal = nullptr;
+  }
+  *aSigningCert = nullptr;
 
   // Don't check signatures in the omnijar - this is only
   // interesting for extensions/XPIs.
   nsRefPtr<nsZipArchive> greOmni = mozilla::Omnijar::GetReader(mozilla::Omnijar::GRE);
   nsRefPtr<nsZipArchive> appOmni = mozilla::Omnijar::GetReader(mozilla::Omnijar::APP);
 
   if (mZip == greOmni || mZip == appOmni)
     return NS_OK;
@@ -361,22 +362,21 @@ nsJAR::GetCertificatePrincipal(const nsA
       rv = VerifyEntry(manItem, entryData, entryDataLen);
       if (NS_FAILED(rv)) return rv;
     }
     requestedStatus = manItem->status;
   }
   else // User wants identity of signer w/o verifying any entries
     requestedStatus = mGlobalStatus;
 
-  if (requestedStatus != JAR_VALID_MANIFEST)
+  if (requestedStatus != JAR_VALID_MANIFEST) {
     ReportError(aFilename, requestedStatus);
-  else // Valid signature
-  {
-    *aPrincipal = mPrincipal;
-    NS_IF_ADDREF(*aPrincipal);
+  } else { // Valid signature
+    *aSigningCert = mSigningCert;
+    NS_IF_ADDREF(*aSigningCert);
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsJAR::GetManifestEntriesCount(uint32_t* count)
 {
   *count = mTotalItemsInManifest;
@@ -584,24 +584,25 @@ nsJAR::ParseManifest()
     mGlobalStatus = JAR_NO_MANIFEST;
     mParsedManifest = true;
     return NS_OK;
   }
 
   //-- Verify that the signature file is a valid signature of the SF file
   int32_t verifyError;
   rv = verifier->VerifySignature(sigBuffer, sigLen, manifestBuffer, manifestLen,
-                                 &verifyError, getter_AddRefs(mPrincipal));
+                                 &verifyError, getter_AddRefs(mSigningCert));
   if (NS_FAILED(rv)) return rv;
-  if (mPrincipal && verifyError == nsIDataSignatureVerifier::VERIFY_OK)
+  if (mSigningCert && verifyError == nsIDataSignatureVerifier::VERIFY_OK) {
     mGlobalStatus = JAR_VALID_MANIFEST;
-  else if (verifyError == nsIDataSignatureVerifier::VERIFY_ERROR_UNKNOWN_ISSUER)
+  } else if (verifyError == nsIDataSignatureVerifier::VERIFY_ERROR_UNKNOWN_ISSUER) {
     mGlobalStatus = JAR_INVALID_UNKNOWN_CA;
-  else
+  } else {
     mGlobalStatus = JAR_INVALID_SIG;
+  }
 
   //-- Parse the SF file. If the verification above failed, principal
   // is null, and ParseOneFile will mark the relevant entries as invalid.
   // if ParseOneFile fails, then it has no effect, and we can safely
   // continue to the next SF file, or return.
   ParseOneFile(manifestBuffer, JAR_SF);
   mParsedManifest = true;
 
--- a/modules/libjar/nsJAR.h
+++ b/modules/libjar/nsJAR.h
@@ -24,17 +24,17 @@
 #include "nsTHashtable.h"
 #include "nsIZipReader.h"
 #include "nsZipArchive.h"
 #include "nsIObserverService.h"
 #include "nsWeakReference.h"
 #include "nsIObserver.h"
 #include "mozilla/Attributes.h"
 
-class nsICertificatePrincipal;
+class nsIX509Cert;
 class nsIInputStream;
 class nsJARManifestItem;
 class nsZipReaderCache;
 
 /* For mManifestStatus */
 typedef enum
 {
   JAR_MANIFEST_NOT_PARSED = 0,
@@ -101,17 +101,17 @@ class nsJAR MOZ_FINAL : public nsIZipRea
     typedef nsClassHashtable<nsCStringHashKey, nsJARManifestItem> ManifestDataHashtable;
 
     //-- Private data members
     nsCOMPtr<nsIFile>        mZipFile;        // The zip/jar file on disk
     nsCString                mOuterZipEntry;  // The entry in the zip this zip is reading from
     nsRefPtr<nsZipArchive>   mZip;            // The underlying zip archive
     ManifestDataHashtable    mManifestData;   // Stores metadata for each entry
     bool                     mParsedManifest; // True if manifest has been parsed
-    nsCOMPtr<nsICertificatePrincipal> mPrincipal; // The entity which signed this file
+    nsCOMPtr<nsIX509Cert>    mSigningCert;    // The entity which signed this file
     int16_t                  mGlobalStatus;   // Global signature verification status
     PRIntervalTime           mReleaseTime;    // used by nsZipReaderCache for flushing entries
     nsZipReaderCache*        mCache;          // if cached, this points to the cache it's contained in
     mozilla::Mutex           mLock;
     int64_t                  mMtime;
     int32_t                  mTotalItemsInManifest;
     bool                     mOpened;
 
--- a/modules/libjar/test/chrome/test_bug386153.html
+++ b/modules/libjar/test/chrome/test_bug386153.html
@@ -40,38 +40,40 @@ function openZip(path) {
   var zip = Cc["@mozilla.org/libjar/zip-reader;1"].
             createInstance(Ci.nsIZipReader);
   zip.open(resolved);
   return zip;
 }
 
 // Gets the pretty name from the signing cert or null if the zip is unsigned.
 function getSigner(zip) {
-  var principal = zip.getCertificatePrincipal(null);
-  if (principal && principal.hasCertificate)
-    return principal.prettyName;
+  var signingCert = zip.getSigningCert(null);
+  if (signingCert) {
+    return signingCert.organization;
+  }
   return null;
 }
 
 function verifySigning(zip) {
-  var principal = zip.getCertificatePrincipal(null);
+  var signingCert = zip.getSigningCert(null);
   var count = 0;
   var entries = zip.findEntries(null);
   while (entries.hasMore()) {
     var entry = entries.getNext();
     // Nothing in META-INF is in the manifest.
     if (entry.substr(0, 9) == "META-INF/")
       continue;
     // Directory entries aren't in the manifest.
     if (entry.substr(-1) == "/")
       continue;
     count++;
-    var entryPrincipal = zip.getCertificatePrincipal(entry);
-    if (!entryPrincipal || !principal.equals(entryPrincipal))
+    var entryCert = zip.getSigningCert(entry);
+    if (!entryCert || !signingCert.equals(entryCert)) {
       return false;
+    }
   }
   return zip.manifestEntriesCount == count;
 }
 
 var zip = openZip("unsigned.zip");
 is(getSigner(zip), null, "Should not be signed");
 
 zip = openZip("signed.zip");
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -102,16 +102,19 @@ pref("offline-apps.quota.warn",        5
 
 // zlib compression level used for cache compression:
 // 0 => disable compression
 // 1 => best speed
 // 9 => best compression
 // cache compression turned off for now - see bug #715198
 pref("browser.cache.compression_level", 0);
 
+// Whether or not MozAbortablePromise is enabled.
+pref("dom.abortablepromise.enabled", false);
+
 // Whether or not testing features are enabled.
 pref("dom.quotaManager.testing", false);
 
 // Whether or not indexedDB is enabled.
 pref("dom.indexedDB.enabled", true);
 // Space to allow indexedDB databases before prompting (in MB).
 pref("dom.indexedDB.warningQuota", 50);
 // Whether or not indexedDB experimental features are enabled.
--- a/mozglue/android/jni-stubs.inc
+++ b/mozglue/android/jni-stubs.inc
@@ -357,16 +357,35 @@ Java_org_mozilla_gecko_GeckoAppShell_not
 #endif
 
 #ifdef JNI_BINDINGS
   xul_dlsym("Java_org_mozilla_gecko_GeckoAppShell_notifyGeckoOfEvent", &f_Java_org_mozilla_gecko_GeckoAppShell_notifyGeckoOfEvent);
 #endif
 
 #ifdef JNI_STUBS
 
+typedef void (*Java_org_mozilla_gecko_GeckoAppShell_notifyGeckoObservers_t)(JNIEnv *, jclass, jstring, jstring);
+static Java_org_mozilla_gecko_GeckoAppShell_notifyGeckoObservers_t f_Java_org_mozilla_gecko_GeckoAppShell_notifyGeckoObservers;
+extern "C" NS_EXPORT void JNICALL
+Java_org_mozilla_gecko_GeckoAppShell_notifyGeckoObservers(JNIEnv * arg0, jclass arg1, jstring arg2, jstring arg3) {
+    if (!f_Java_org_mozilla_gecko_GeckoAppShell_notifyGeckoObservers) {
+        arg0->ThrowNew(arg0->FindClass("java/lang/UnsupportedOperationException"),
+                       "JNI Function called before it was loaded");
+        return ;
+    }
+     f_Java_org_mozilla_gecko_GeckoAppShell_notifyGeckoObservers(arg0, arg1, arg2, arg3);
+}
+#endif
+
+#ifdef JNI_BINDINGS
+  xul_dlsym("Java_org_mozilla_gecko_GeckoAppShell_notifyGeckoObservers", &f_Java_org_mozilla_gecko_GeckoAppShell_notifyGeckoObservers);
+#endif
+
+#ifdef JNI_STUBS
+
 typedef jlong (*Java_org_mozilla_gecko_GeckoAppShell_runUiThreadCallback_t)(JNIEnv *, jclass);
 static Java_org_mozilla_gecko_GeckoAppShell_runUiThreadCallback_t f_Java_org_mozilla_gecko_GeckoAppShell_runUiThreadCallback;
 extern "C" NS_EXPORT jlong JNICALL
 Java_org_mozilla_gecko_GeckoAppShell_runUiThreadCallback(JNIEnv * arg0, jclass arg1) {
     if (!f_Java_org_mozilla_gecko_GeckoAppShell_runUiThreadCallback) {
         arg0->ThrowNew(arg0->FindClass("java/lang/UnsupportedOperationException"),
                        "JNI Function called before it was loaded");
         return 0;
--- a/python/mozbuild/mozbuild/mach_commands.py
+++ b/python/mozbuild/mozbuild/mach_commands.py
@@ -819,75 +819,62 @@ class DebugProgram(MachCommandBase):
         help='Command-line arguments to be passed through to the program. Not specifying a -profile or -P option will result in a temporary profile being used.')
     @CommandArgument('+remote', '+r', action='store_true',
         help='Do not pass the -no-remote argument by default')
     @CommandArgument('+background', '+b', action='store_true',
         help='Do not pass the -foreground argument by default on Mac')
     @CommandArgument('+debugger', default=None, type=str,
         help='Name of debugger to launch')
     @CommandArgument('+debugparams', default=None, metavar='params', type=str,
-        help='Command-line arguments to pass to GDB or LLDB itself; split as the Bourne shell would.')
+        help='Command-line arguments to pass to the debugger itself; split as the Bourne shell would.')
     # Bug 933807 introduced JS_DISABLE_SLOW_SCRIPT_SIGNALS to avoid clever
     # segfaults induced by the slow-script-detecting logic for Ion/Odin JITted
     # code.  If we don't pass this, the user will need to periodically type
     # "continue" to (safely) resume execution.  There are ways to implement
     # automatic resuming; see the bug.
     @CommandArgument('+slowscript', action='store_true',
         help='Do not set the JS_DISABLE_SLOW_SCRIPT_SIGNALS env variable; when not set, recoverable but misleading SIGSEGV instances may occur in Ion/Odin JIT code')
     def debug(self, params, remote, background, debugger, debugparams, slowscript):
-        import which
-        if debugger:
-            try:
-                debugger = which.which(debugger)
-            except Exception as e:
-                print("You don't have %s in your PATH" % (debugger))
-                print(e)
-                return 1
-        else:
-            try:
-                debugger = which.which('gdb')
-            except Exception:
-                try:
-                    debugger = which.which('lldb')
-                except Exception as e:
-                    print("You don't have gdb or lldb in your PATH")
-                    print(e)
-                    return 1
-        args = [debugger]
-        extra_env = { 'MOZ_CRASHREPORTER_DISABLE' : '1' }
+        # Parameters come from the CLI. We need to convert them before their use.
         if debugparams:
             import pymake.process
             argv, badchar = pymake.process.clinetoargv(debugparams, os.getcwd())
             if badchar:
                 print("The +debugparams you passed require a real shell to parse them.")
                 print("(We can't handle the %r character.)" % (badchar,))
                 return 1
-            args.extend(argv)
+            debugparams = argv;
+
+        import mozdebug
+
+        if not debugger:
+            # No debugger name was provided. Look for the default ones on current OS.
+            debugger = mozdebug.get_default_debugger_name(mozdebug.DebuggerSearch.KeepLooking)
+
+        self.debuggerInfo = mozdebug.get_debugger_info(debugger, debugparams)
+
+        # We could not find the information about the desired debugger.
+        if not self.debuggerInfo:
+            print("Could not find a suitable debugger in your PATH.")
+            return 1
+
+        extra_env = { 'MOZ_CRASHREPORTER_DISABLE' : '1' }
 
         binpath = None
 
         try:
             binpath = self.get_binary_path('app')
         except Exception as e:
             print("It looks like your program isn't built.",
                 "You can run |mach build| to build it.")
             print(e)
             return 1
 
-        # args added to separate the debugger and process arguments.
-        args_separator = {
-            'gdb': '--args',
-            'ddd': '--args',
-            'cgdb': '--args',
-            'lldb': '--'
-        }
-
-        debugger_name = os.path.basename(debugger)
-        if debugger_name in args_separator:
-            args.append(args_separator[debugger_name])
+        # Build the list of arguments to pass to run_process
+        args = [self.debuggerInfo.path] + self.debuggerInfo.args
         args.append(binpath)
 
         if not remote:
             args.append('-no-remote')
         if not background and sys.platform == 'darwin':
             args.append('-foreground')
         if params:
             args.extend(params)
--- a/security/manager/boot/src/StaticHPKPins.h
+++ b/security/manager/boot/src/StaticHPKPins.h
@@ -1087,9 +1087,9 @@ static const TransportSecurityPreload kP
   { "youtube.com", true, false, false, -1, &kPinset_google_root_pems },
   { "ytimg.com", true, false, false, -1, &kPinset_google_root_pems },
 };
 
 // Pinning Preload List Length = 331;
 
 static const int32_t kUnknownId = -1;
 
-static const PRTime kPreloadPKPinsExpirationTime = INT64_C(1418659817121000);
+static const PRTime kPreloadPKPinsExpirationTime = INT64_C(1418960624394000);
--- a/security/manager/boot/src/nsSTSPreloadList.errors
+++ b/security/manager/boot/src/nsSTSPreloadList.errors
@@ -58,72 +58,75 @@ googleplex.com: could not connect to hos
 googleplex.com: could not connect to host (error ignored - included regardless)
 goto.google.com: did not receive HSTS header (error ignored - included regardless)
 gparent.org: did not receive HSTS header
 greplin.com: did not receive HSTS header
 groups.google.com: did not receive HSTS header (error ignored - included regardless)
 hackerone-user-content.com: could not connect to host
 haste.ch: could not connect to host
 history.google.com: did not receive HSTS header (error ignored - included regardless)
+hoerbuecher-und-hoerspiele.de: did not receive HSTS header
 honeytracks.com: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]"  nsresult: "0x80004005 (NS_ERROR_FAILURE)"  location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 124"  data: no]
 hostedtalkgadget.google.com: did not receive HSTS header (error ignored - included regardless)
 howrandom.org: could not connect to host
 in.xero.com: max-age too low: 3600
 intercom.io: did not receive HSTS header
 iop.intuit.com: max-age too low: 86400
 irccloud.com: did not receive HSTS header
 jitsi.org: did not receive HSTS header
+jonaswitmer.ch: could not connect to host
 jottit.com: could not connect to host
 keymaster.lookout.com: did not receive HSTS header
 kiwiirc.com: max-age too low: 5256000
-klaxn.com: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]"  nsresult: "0x80004005 (NS_ERROR_FAILURE)"  location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 124"  data: no]
 ledgerscope.net: did not receive HSTS header
 liberty.lavabit.com: could not connect to host
 lifeguard.aecom.com: did not receive HSTS header
 lists.mayfirst.org: did not receive HSTS header
 login.corp.google.com: max-age too low: 7776000 (error ignored - included regardless)
 logotype.se: did not receive HSTS header
 ludwig.im: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]"  nsresult: "0x80004005 (NS_ERROR_FAILURE)"  location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 124"  data: no]
 lumi.do: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]"  nsresult: "0x80004005 (NS_ERROR_FAILURE)"  location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 124"  data: no]
 m.gparent.org: could not connect to host
 mail.google.com: did not receive HSTS header (error ignored - included regardless)
 market.android.com: did not receive HSTS header (error ignored - included regardless)
+matteomarescotti.name: could not connect to host
 mobilethreat.net: could not connect to host
 mobilethreatnetwork.net: could not connect to host
 my.alfresco.com: did not receive HSTS header
 mydigipass.com: did not receive HSTS header
 mykolab.com: did not receive HSTS header
 nachsenden.info: did not receive HSTS header
 neonisi.com: could not connect to host
+netzpolitik.org: did not receive HSTS header
 nexth.de: could not connect to host
 nexth.net: could not connect to host
 nexth.us: could not connect to host
 noexpect.org: could not connect to host
 openshift.redhat.com: did not receive HSTS header
 ottospora.nl: could not connect to host
-passwd.io: could not connect to host
 passwordbox.com: did not receive HSTS header
 paypal.com: max-age too low: 14400
 payroll.xero.com: max-age too low: 3600
 piratenlogin.de: did not receive HSTS header
 platform.lookout.com: could not connect to host
 play.google.com: did not receive HSTS header (error ignored - included regardless)
 prodpad.com: did not receive HSTS header
 profiles.google.com: did not receive HSTS header (error ignored - included regardless)
 promecon-gmbh.de: did not receive HSTS header
 rapidresearch.me: could not connect to host
 riseup.net: did not receive HSTS header
-roddis.net: could not connect to host
 sah3.net: could not connect to host
 saturngames.co.uk: did not receive HSTS header
 script.google.com: did not receive HSTS header (error ignored - included regardless)
 security.google.com: did not receive HSTS header (error ignored - included regardless)
+securityheaders.com: did not receive HSTS header
 semenkovich.com: did not receive HSTS header
 serverdensity.io: did not receive HSTS header
 shops.neonisi.com: could not connect to host
+siammedia.co: did not receive HSTS header
 silentcircle.org: could not connect to host
 simon.butcher.name: max-age too low: 2629743
 sites.google.com: did not receive HSTS header (error ignored - included regardless)
 sol.io: could not connect to host
 souyar.de: could not connect to host
 souyar.net: could not connect to host
 souyar.us: could not connect to host
 spreadsheets.google.com: did not receive HSTS header (error ignored - included regardless)
@@ -135,16 +138,18 @@ sunshinepress.org: could not connect to 
 surfeasy.com: did not receive HSTS header
 talk.google.com: did not receive HSTS header (error ignored - included regardless)
 talkgadget.google.com: did not receive HSTS header (error ignored - included regardless)
 tektoria.de: did not receive HSTS header
 translate.googleapis.com: did not receive HSTS header (error ignored - included regardless)
 uprotect.it: could not connect to host
 wallet.google.com: did not receive HSTS header (error ignored - included regardless)
 webmail.mayfirst.org: did not receive HSTS header
+wf-training-master.appspot.com: could not connect to host
+wf-training-master.appspot.com: could not connect to host (error ignored - included regardless)
 whonix.org: did not receive HSTS header
 www.calyxinstitute.org: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]"  nsresult: "0x80004005 (NS_ERROR_FAILURE)"  location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 124"  data: no]
 www.cueup.com: could not connect to host
 www.developer.mydigipass.com: could not connect to host
 www.dropbox.com: max-age too low: 2592000
 www.elanex.biz: did not receive HSTS header
 www.gmail.com: did not receive HSTS header (error ignored - included regardless)
 www.googlemail.com: did not receive HSTS header (error ignored - included regardless)
@@ -152,12 +157,12 @@ www.greplin.com: did not receive HSTS he
 www.jitsi.org: did not receive HSTS header
 www.ledgerscope.net: did not receive HSTS header
 www.logentries.com: did not receive HSTS header
 www.moneybookers.com: did not receive HSTS header
 www.neonisi.com: could not connect to host
 www.paycheckrecords.com: max-age too low: 86400
 www.paypal.com: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]"  nsresult: "0x80004005 (NS_ERROR_FAILURE)"  location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 124"  data: no]
 www.rme.li: did not receive HSTS header
-www.roddis.net: could not connect to host
+www.roddis.net: did not receive HSTS header
 www.sandbox.mydigipass.com: could not connect to host
 www.surfeasy.com: did not receive HSTS header
 zoo24.de: max-age too low: 2592000
--- a/security/manager/boot/src/nsSTSPreloadList.inc
+++ b/security/manager/boot/src/nsSTSPreloadList.inc
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /*****************************************************************************/
 /* This is an automatically generated file. If you're not                    */
 /* nsSiteSecurityService.cpp, you shouldn't be #including it.     */
 /*****************************************************************************/
 
 #include <stdint.h>
-const PRTime gPreloadListExpirationTime = INT64_C(1420884435199000);
+const PRTime gPreloadListExpirationTime = INT64_C(1421379819168000);
 
 class nsSTSPreload
 {
   public:
     const char *mHost;
     const bool mIncludeSubdomains;
 };
 
@@ -63,17 +63,17 @@ static const nsSTSPreload kSTSPreloadLis
   { "bl4ckb0x.com", true },
   { "bl4ckb0x.de", true },
   { "bl4ckb0x.info", true },
   { "bl4ckb0x.net", true },
   { "bl4ckb0x.org", true },
   { "blacklane.com", true },
   { "blocksatz-medien.de", true },
   { "blog.cyveillance.com", true },
-  { "blog.gparent.org", false },
+  { "blog.gparent.org", true },
   { "blog.linode.com", false },
   { "blog.torproject.org", false },
   { "bohramt.de", true },
   { "boxcryptor.com", true },
   { "brunosouza.org", true },
   { "buddhistische-weisheiten.org", true },
   { "bugzil.la", true },
   { "bugzilla.mozilla.org", true },
@@ -191,17 +191,16 @@ static const nsSTSPreload kSTSPreloadLis
   { "haufschild.de", true },
   { "hausverbrauch.de", true },
   { "heha.co", true },
   { "helichat.de", true },
   { "help.simpletax.ca", false },
   { "helpium.de", true },
   { "hex2013.com", true },
   { "history.google.com", true },
-  { "hoerbuecher-und-hoerspiele.de", true },
   { "hostedtalkgadget.google.com", true },
   { "hostinginnederland.nl", true },
   { "hostix.de", true },
   { "howrandom.org", true },
   { "iban.is", true },
   { "id.atlassian.com", false },
   { "id.mayfirst.org", false },
   { "ihrlotto.de", true },
@@ -295,17 +294,16 @@ static const nsSTSPreload kSTSPreloadLis
   { "mwe.st", true },
   { "my.onlime.ch", false },
   { "my.xero.com", false },
   { "mylookout.com", false },
   { "mynigma.org", true },
   { "neg9.org", false },
   { "neilwynne.com", false },
   { "netzbit.de", true },
-  { "netzpolitik.org", true },
   { "ng-security.com", true },
   { "npw.net", true },
   { "onedrive.com", true },
   { "onedrive.live.com", false },
   { "oplop.appspot.com", true },
   { "opsmate.com", false },
   { "optimus.io", true },
   { "otakuworld.de", true },
@@ -367,23 +365,21 @@ static const nsSTSPreload kSTSPreloadLis
   { "sandbox.mydigipass.com", false },
   { "schokokeks.org", false },
   { "schwarzer.it", true },
   { "scrambl.is", true },
   { "script.google.com", true },
   { "sdsl-speedtest.de", true },
   { "security-carpet.com", true },
   { "security.google.com", true },
-  { "securityheaders.com", true },
   { "secuvera.de", true },
   { "seifried.org", true },
   { "servethecity-karlsruhe.de", true },
   { "sherbers.de", true },
   { "shodan.io", true },
-  { "siammedia.co", true },
   { "silentcircle.com", true },
   { "simbolo.co.uk", false },
   { "simple.com", false },
   { "simpletax.ca", false },
   { "simplystudio.com", true },
   { "sites.google.com", true },
   { "skydrive.live.com", false },
   { "spreadsheets.google.com", true },
--- a/security/manager/ssl/public/moz.build
+++ b/security/manager/ssl/public/moz.build
@@ -6,17 +6,16 @@
 
 XPIDL_SOURCES += [
     'nsIASN1Object.idl',
     'nsIASN1PrintableItem.idl',
     'nsIASN1Sequence.idl',
     'nsIAssociatedContentSecurity.idl',
     'nsIBadCertListener2.idl',
     'nsICertificateDialogs.idl',
-    'nsICertificatePrincipal.idl',
     'nsICertOverrideService.idl',
     'nsICertPickDialogs.idl',
     'nsIClientAuthDialogs.idl',
     'nsIDataSignatureVerifier.idl',
     'nsIDOMCryptoDialogs.idl',
     'nsIGenKeypairInfoDlg.idl',
     'nsIIdentityInfo.idl',
     'nsIKeygenThread.idl',
deleted file mode 100644
--- a/security/manager/ssl/public/nsICertificatePrincipal.idl
+++ /dev/null
@@ -1,33 +0,0 @@
-/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
- *
- * 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"
-
-/*
- * Historically, principals, certificates, and signed JARs were all linked
- * together in one big mess. When that mess was cleaned up, it turned out that
- * the principals used to store certificate information didn't overlap at all
- * with the principals used for security policy. So this interface was created
- * so that real principals wouldn't have to carry around all that baggage.
- *
- * The name here is totally a misnomer. This isn't a principal at all, and would
- * better be called nsICertificateHolder or something. But that would require
- * renaming some APIs, so let's just let this be for now.
- */
-
-[scriptable, uuid(7cd4af5a-64d3-44a8-9700-804a42a6109a)]
-interface nsICertificatePrincipal : nsISupports
-{
-    readonly attribute AUTF8String fingerprint;
-    readonly attribute AUTF8String prettyName;
-    readonly attribute AUTF8String subjectName;
-    readonly attribute nsISupports certificate;
-    readonly attribute boolean     hasCertificate; // For compat; always true.
-
-    bool equals(in nsICertificatePrincipal aOther);
-};
-
-////////////////////////////////////////////////////////////////////////////////
--- a/security/manager/ssl/public/nsIDataSignatureVerifier.idl
+++ b/security/manager/ssl/public/nsIDataSignatureVerifier.idl
@@ -1,23 +1,22 @@
 /* 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"
 
-// NB: This isn't actually a principal at all. The naming is just historical.
-interface nsICertificatePrincipal;
+interface nsIX509Cert;
 
 /**
  * An interface for verifying that a given string of data was signed by the
  * private key matching the given public key.
  */
-[scriptable, uuid(577f097f-15e4-4043-bc0e-6d2fadcacae2)]
+[scriptable, uuid(94066a00-37c9-11e4-916c-0800200c9a66)]
 interface nsIDataSignatureVerifier : nsISupports
 {
   /**
    * Verifies that the data matches the data that was used to generate the
    * signature.
    *
    * @param aData      The data to be tested.
    * @param aSignature The signature of the data, base64 encoded.
@@ -27,15 +26,15 @@ interface nsIDataSignatureVerifier : nsI
    */
   boolean verifyData(in ACString aData, in ACString aSignature, in ACString aPublicKey);
 
    /* Sig Verification Error Codes */
   const long VERIFY_OK = 0;
   const long VERIFY_ERROR_UNKNOWN_ISSUER = 1;
   const long VERIFY_ERROR_OTHER = 2;
 
-  nsICertificatePrincipal verifySignature(in string aSignature,
-                                          in unsigned long aSignatureLen,
-                                          in string plaintext,
-                                          in unsigned long plaintextLen,
-                                          out long errorCode);
+  nsIX509Cert verifySignature(in string aSignature,
+                              in unsigned long aSignatureLen,
+                              in string plaintext,
+                              in unsigned long plaintextLen,
+                              out long errorCode);
 
 };
--- a/security/manager/ssl/src/moz.build
+++ b/security/manager/ssl/src/moz.build
@@ -14,17 +14,16 @@ EXPORTS += [
 ]
 
 EXPORTS.mozilla += [
     'PublicSSL.h',
 ]
 
 UNIFIED_SOURCES += [
     'CryptoTask.cpp',
-    'nsCertificatePrincipal.cpp',
     'nsCertOverrideService.cpp',
     'nsCertPicker.cpp',
     'nsCertVerificationThread.cpp',
     'nsClientAuthRemember.cpp',
     'nsCrypto.cpp',
     'nsDataSignatureVerifier.cpp',
     'nsKeygenHandler.cpp',
     'nsKeygenThread.cpp',
deleted file mode 100644
--- a/security/manager/ssl/src/nsCertificatePrincipal.cpp
+++ /dev/null
@@ -1,67 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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 "nsCertificatePrincipal.h"
-
-NS_IMPL_ISUPPORTS(nsCertificatePrincipal, nsICertificatePrincipal)
-
-NS_IMETHODIMP
-nsCertificatePrincipal::GetFingerprint(nsACString& aFingerprint)
-{
-  aFingerprint = mFingerprint;
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsCertificatePrincipal::GetSubjectName(nsACString& aSubjectName)
-{
-  aSubjectName = mSubjectName;
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsCertificatePrincipal::GetPrettyName(nsACString& aPrettyName)
-{
-  aPrettyName = mPrettyName;
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsCertificatePrincipal::GetCertificate(nsISupports** aCert)
-{
-  nsCOMPtr<nsISupports> cert = mCert;
-  cert.forget(aCert);
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsCertificatePrincipal::GetHasCertificate(bool* rv)
-{
-  *rv = true;
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsCertificatePrincipal::Equals(nsICertificatePrincipal* aOther, bool* rv)
-{
-  nsAutoCString str;
-  aOther->GetFingerprint(str);
-  if (!str.Equals(mFingerprint)) {
-    *rv = false;
-    return NS_OK;
-  }
-
-  // If either subject name is empty, just let the result stand, but if they're
-  // both non-empty, only claim equality if they're equal.
-  if (!mSubjectName.IsEmpty()) {
-    // Check the other principal's subject name
-    aOther->GetSubjectName(str);
-    *rv = str.Equals(mSubjectName) || str.IsEmpty();
-    return NS_OK;
-  }
-
-  *rv = true;
-  return NS_OK;
-}
deleted file mode 100644
--- a/security/manager/ssl/src/nsCertificatePrincipal.h
+++ /dev/null
@@ -1,39 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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 __NS_CERTIFICATEPRINCIPAL_H
-#define __NS_CERTIFICATEPRINCIPAL_H
-
-#include "nsICertificatePrincipal.h"
-#include "nsString.h"
-#include "nsCOMPtr.h"
-
-class nsCertificatePrincipal : public nsICertificatePrincipal
-{
-public:
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSICERTIFICATEPRINCIPAL
-
-  nsCertificatePrincipal(const nsACString& aFingerprint,
-                         const nsACString& aSubjectName,
-                         const nsACString& aPrettyName,
-                         nsISupports* aCert)
-                         : mFingerprint(aFingerprint)
-                         , mSubjectName(aSubjectName)
-                         , mPrettyName(aPrettyName)
-                         , mCert(aCert)
-    {}
-
-protected:
-  virtual ~nsCertificatePrincipal() {}
-
-private:
-  nsCString             mFingerprint;
-  nsCString             mSubjectName;
-  nsCString             mPrettyName;
-  nsCOMPtr<nsISupports> mCert;
-};
-
-#endif /* __NS_CERTIFICATEPRINCIPAL_H */
--- a/security/manager/ssl/src/nsDataSignatureVerifier.cpp
+++ b/security/manager/ssl/src/nsDataSignatureVerifier.cpp
@@ -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/. */
 
 #include "nsDataSignatureVerifier.h"
 
 #include "cms.h"
 #include "cryptohi.h"
 #include "keyhi.h"
-#include "nsCertificatePrincipal.h"
 #include "nsCOMPtr.h"
 #include "nsNSSComponent.h"
 #include "nssb64.h"
 #include "pkix/pkixtypes.h"
 #include "ScopedNSSTypes.h"
 #include "secerr.h"
 #include "SharedCertVerifier.h"
 
@@ -217,17 +216,17 @@ VerifyCMSDetachedSignatureIncludingCerti
 }
 
 } // namespace mozilla
 
 namespace {
 
 struct VerifyCertificateContext
 {
-  nsCOMPtr<nsICertificatePrincipal> principal;
+  nsCOMPtr<nsIX509Cert> signingCert;
   ScopedCERTCertList builtChain;
 };
 
 static nsresult
 VerifyCertificate(CERTCertificate* cert, void* voidContext, void* pinArg)
 {
   // XXX: missing pinArg is tolerated
   if (NS_WARN_IF(!cert) || NS_WARN_IF(!voidContext)) {
@@ -237,37 +236,17 @@ VerifyCertificate(CERTCertificate* cert,
   VerifyCertificateContext* context =
     reinterpret_cast<VerifyCertificateContext*>(voidContext);
 
   nsCOMPtr<nsIX509Cert> xpcomCert(nsNSSCertificate::Create(cert));
   if (!xpcomCert) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
-  nsAutoString fingerprint;
-  nsresult rv = xpcomCert->GetSha1Fingerprint(fingerprint);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-  nsAutoString orgName;
-  rv = xpcomCert->GetOrganization(orgName);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-  nsAutoString subjectName;
-  rv = xpcomCert->GetSubjectName(subjectName);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
-  context->principal =
-    new nsCertificatePrincipal(NS_ConvertUTF16toUTF8(fingerprint),
-                               NS_ConvertUTF16toUTF8(subjectName),
-                               NS_ConvertUTF16toUTF8(orgName),
-                               xpcomCert);
+  context->signingCert = xpcomCert;
 
   RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
   NS_ENSURE_TRUE(certVerifier, NS_ERROR_UNEXPECTED);
 
   return MapSECStatus(certVerifier->VerifyCert(cert,
                                                certificateUsageObjectSigner,
                                                Now(), pinArg,
                                                nullptr, // hostname
@@ -279,24 +258,24 @@ VerifyCertificate(CERTCertificate* cert,
 } // unnamed namespcae
 
 NS_IMETHODIMP
 nsDataSignatureVerifier::VerifySignature(const char* aRSABuf,
                                          uint32_t aRSABufLen,
                                          const char* aPlaintext,
                                          uint32_t aPlaintextLen,
                                          int32_t* aErrorCode,
-                                         nsICertificatePrincipal** aPrincipal)
+                                         nsIX509Cert** aSigningCert)
 {
-  if (!aPlaintext || !aPrincipal || !aErrorCode) {
+  if (!aPlaintext || !aSigningCert || !aErrorCode) {
     return NS_ERROR_INVALID_ARG;
   }
 
   *aErrorCode = VERIFY_ERROR_OTHER;
-  *aPrincipal = nullptr;
+  *aSigningCert = nullptr;
 
   nsNSSShutDownPreventionLock locker;
 
   Digest digest;
   nsresult rv = digest.DigestBuf(SEC_OID_SHA1,
                                  reinterpret_cast<const uint8_t*>(aPlaintext),
                                  aPlaintextLen);
   if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -320,13 +299,13 @@ nsDataSignatureVerifier::VerifySignature
     if (rv == GetXPCOMFromNSSError(SEC_ERROR_UNKNOWN_ISSUER)) {
       *aErrorCode = VERIFY_ERROR_UNKNOWN_ISSUER;
     } else {
       *aErrorCode = VERIFY_ERROR_OTHER;
     }
     rv = NS_OK;
   }
   if (rv == NS_OK) {
-    context.principal.forget(aPrincipal);
+    context.signingCert.forget(aSigningCert);
   }
 
   return rv;
 }
--- a/testing/config/mozbase_requirements.txt
+++ b/testing/config/mozbase_requirements.txt
@@ -1,10 +1,11 @@
 ../mozbase/manifestparser
 ../mozbase/mozcrash
+../mozbase/mozdebug
 ../mozbase/mozdevice
 ../mozbase/mozfile
 ../mozbase/mozhttpd
 ../mozbase/mozinfo
 ../mozbase/mozinstall
 ../mozbase/mozlog
 ../mozbase/moznetwork
 ../mozbase/mozprocess
--- a/testing/mochitest/runtests.py
+++ b/testing/mochitest/runtests.py
@@ -11,16 +11,17 @@ import os
 import sys
 SCRIPT_DIR = os.path.abspath(os.path.realpath(os.path.dirname(__file__)))
 sys.path.insert(0, SCRIPT_DIR);
 
 import ctypes
 import glob
 import json
 import mozcrash
+import mozdebug
 import mozinfo
 import mozprocess
 import mozrunner
 import optparse
 import re
 import shutil
 import signal
 import subprocess
@@ -29,17 +30,16 @@ import tempfile
 import time
 import traceback
 import urllib2
 import zipfile
 import bisection
 
 from automationutils import (
     environment,
-    getDebuggerInfo,
     isURL,
     KeyValueParseError,
     parseKeyValue,
     processLeakLog,
     dumpScreen,
     ShutdownLeaks,
     printstatus,
     LSANLeaks,
@@ -385,17 +385,17 @@ class WebSocketServer(object):
     # debugger.
     #
     # If we're not in an interactive debugger, the wrapper causes the server to
     # die silently upon receiving a SIGINT.
     scriptPath = 'pywebsocket_wrapper.py'
     script = os.path.join(self._scriptdir, scriptPath)
 
     cmd = [sys.executable, script]
-    if self.debuggerInfo and self.debuggerInfo['interactive']:
+    if self.debuggerInfo and self.debuggerInfo.interactive:
         cmd += ['--interactive']
     cmd += ['-p', str(self.port), '-w', self._scriptdir, '-l',      \
            os.path.join(self._scriptdir, "websock.log"),            \
            '--log-level=debug', '--allow-handlers-outside-root-dir']
     # start the process
     self._process = mozprocess.ProcessHandler(cmd, cwd=SCRIPT_DIR)
     self._process.run()
     pid = self._process.pid
@@ -1372,18 +1372,18 @@ class Mochitest(MochitestUtilsMixin):
 
     # configure the message logger buffering
     self.message_logger.buffering = quiet
 
     # debugger information
     interactive = False
     debug_args = None
     if debuggerInfo:
-        interactive = debuggerInfo['interactive']
-        debug_args = [debuggerInfo['path']] + debuggerInfo['args']
+        interactive = debuggerInfo.interactive
+        debug_args = [debuggerInfo.path] + debuggerInfo.args
 
     # fix default timeout
     if timeout == -1:
       timeout = self.DEFAULT_TIMEOUT
 
     # copy env so we don't munge the caller's environment
     env = env.copy()
 
@@ -1402,17 +1402,17 @@ class Mochitest(MochitestUtilsMixin):
 
       # build command line
       cmd = os.path.abspath(app)
       args = list(extraArgs)
       # TODO: mozrunner should use -foreground at least for mac
       # https://bugzilla.mozilla.org/show_bug.cgi?id=916512
       args.append('-foreground')
       if testUrl:
-        if debuggerInfo and debuggerInfo['requiresEscapedArgs']:
+        if debuggerInfo and debuggerInfo.requiresEscapedArgs:
           testUrl = testUrl.replace("&", "\\&")
         args.append(testUrl)
 
       if detectShutdownLeaks:
         shutdownLeaks = ShutdownLeaks(self.log.info)
       else:
         shutdownLeaks = None
 
@@ -1689,20 +1689,20 @@ class Mochitest(MochitestUtilsMixin):
       self.initializeLooping(options)
 
     # get debugger info, a dict of:
     # {'path': path to the debugger (string),
     #  'interactive': whether the debugger is interactive or not (bool)
     #  'args': arguments to the debugger (list)
     # TODO: use mozrunner.local.debugger_arguments:
     # https://github.com/mozilla/mozbase/blob/master/mozrunner/mozrunner/local.py#L42
-    debuggerInfo = getDebuggerInfo(self.oldcwd,
-                                   options.debugger,
-                                   options.debuggerArgs,
-                                   options.debuggerInteractive)
+
+    debuggerInfo = mozdebug.get_debugger_info(options.debugger,
+                                              options.debuggerArgs,
+                                              options.debuggerInteractive)
 
     if options.useTestMediaDevices:
       devices = findTestMediaDevices(self.log)
       if not devices:
         self.log.error("Could not find test media devices to use")
         return 1
       self.mediaDevices = devices
 
@@ -1711,16 +1711,22 @@ class Mochitest(MochitestUtilsMixin):
     # https://bugzilla.mozilla.org/show_bug.cgi?id=919300
     self.manifest = self.buildProfile(options)
     if self.manifest is None:
       return 1
 
     self.leak_report_file = os.path.join(options.profilePath, "runtests_leaks.log")
 
     self.browserEnv = self.buildBrowserEnv(options, debuggerInfo is not None)
+
+    # If there are any Mulet-specific tests doing remote network access,
+    # we will not be aware since we are explicitely allowing this, as for B2G
+    if mozinfo.info.get('buildapp') == 'mulet' and 'MOZ_DISABLE_NONLOCAL_CONNECTIONS' in self.browserEnv:
+      del self.browserEnv['MOZ_DISABLE_NONLOCAL_CONNECTIONS']
+
     if self.browserEnv is None:
       return 1
 
     try:
       self.startServers(options, debuggerInfo)
 
       # testsToFilter parameter is used to filter out the test list that is sent to buildTestPath
       testURL = self.buildTestPath(options, testsToFilter)
--- a/testing/mozbase/Makefile.in
+++ b/testing/mozbase/Makefile.in
@@ -3,16 +3,17 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 include $(topsrcdir)/config/rules.mk
 
 # Harness packages from the srcdir
 MOZBASE_PACKAGES = \
   manifestparser \
   mozcrash \
+  mozdebug \
   mozfile \
   mozhttpd \
   mozinfo \
   mozinstall \
   mozlog \
   mozprocess \
   mozprofile \
   mozrunner \
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/docs/mozdebug.rst
@@ -0,0 +1,5 @@
+:mod:`mozdebug` --- Configure and launch compatible debuggers.
+======================================================================================
+
+.. automodule:: mozdebug
+   :members: get_debugger_info, get_default_debugger_name, DebuggerSearch
--- a/testing/mozbase/docs/setuprunning.rst
+++ b/testing/mozbase/docs/setuprunning.rst
@@ -9,8 +9,9 @@ correctly handling the case where the sy
 .. toctree::
    :maxdepth: 2
 
    mozfile
    mozprofile
    mozprocess
    mozrunner
    mozcrash
+   mozdebug
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozdebug/mozdebug/__init__.py
@@ -0,0 +1,30 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+"""
+This module contains a set of function to gather information about the
+debugging capabilities of the platform. It allows to look for a specific
+debugger or to query the system for a compatible/default debugger.
+
+The following simple example looks for the default debugger on the
+current platform and launches a debugger process with the correct
+debugger-specific arguments:
+
+::
+
+  import mozdebug
+
+  debugger = mozdebug.get_default_debugger_name()
+  debuggerInfo = mozdebug.get_debugger_info(debugger)
+
+  debuggeePath = "toDebug"
+
+  processArgs = [self.debuggerInfo.path] + self.debuggerInfo.args
+  processArgs.append(debuggeePath)
+
+  run_process(args, ...)
+
+"""
+
+from mozdebug import *
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozdebug/mozdebug/mozdebug.py
@@ -0,0 +1,168 @@
+#!/usr/bin/env python
+
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+import mozinfo
+from collections import namedtuple
+from distutils.spawn import find_executable
+
+__all__ = ['get_debugger_info',
+           'get_default_debugger_name',
+           'DebuggerSearch']
+
+'''
+Map of debugging programs to information about them, like default arguments
+and whether or not they are interactive.
+
+To add support for a new debugger, simply add the relative entry in
+_DEBUGGER_INFO and optionally update the _DEBUGGER_PRIORITIES.
+'''
+_DEBUGGER_INFO = {
+    # gdb requires that you supply the '--args' flag in order to pass arguments
+    # after the executable name to the executable.
+    'gdb': {
+        'interactive': True,
+        'args': ['-q', '--args']
+    },
+
+    'cgdb': {
+        'interactive': True,
+        'args': ['-q', '--args']
+    },
+
+    'lldb': {
+        'interactive': True,
+        'args': ['--'],
+        'requiresEscapedArgs': True
+    },
+
+    # Visual Studio Debugger Support.
+    'devenv.exe': {
+        'interactive': True,
+        'args': ['-debugexe']
+    },
+
+    # Visual C++ Express Debugger Support.
+    'wdexpress.exe': {
+        'interactive': True,
+        'args': ['-debugexe']
+    },
+
+    # valgrind doesn't explain much about leaks unless you set the
+    # '--leak-check=full' flag. But there are a lot of objects that are
+    # semi-deliberately leaked, so we set '--show-possibly-lost=no' to avoid
+    # uninteresting output from those objects. We set '--smc-check==all-non-file'
+    # and '--vex-iropt-register-updates=allregs-at-mem-access' so that valgrind
+    # deals properly with JIT'd JavaScript code.
+    'valgrind': {
+        'interactive': False,
+        'args': ['--leak-check=full',
+                '--show-possibly-lost=no',
+                '--smc-check=all-non-file',
+                '--vex-iropt-register-updates=allregs-at-mem-access']
+    }
+}
+
+# Maps each OS platform to the preferred debugger programs found in _DEBUGGER_INFO.
+_DEBUGGER_PRIORITIES = {
+      'win': ['devenv.exe', 'wdexpress.exe'],
+      'linux': ['gdb', 'cgdb', 'lldb'],
+      'mac': ['lldb', 'gdb'],
+      'unknown': ['gdb']
+}
+
+def get_debugger_info(debugger, debuggerArgs = None, debuggerInteractive = False):
+    '''
+    Get the information about the requested debugger.
+
+    Returns a dictionary containing the |path| of the debugger executable,
+    if it will run in |interactive| mode, its arguments and whether it needs
+    to escape arguments it passes to the debugged program (|requiresEscapedArgs|).
+    If the debugger cannot be found in the system, returns |None|.
+
+    :param debugger: The name of the debugger.
+    :param debuggerArgs: If specified, it's the list of arguments to pass to the
+     debugger. A debugger specific separator arguments is appended at the end of
+     that list.
+    :param debuggerInteractive: If specified, forces the debugger to be interactive.
+    '''
+
+    debuggerPath = None
+
+    if debugger:
+        # Append '.exe' to the debugger on Windows if it's not present,
+        # so things like '--debugger=devenv' work.
+        if (os.name == 'nt'
+            and not debugger.lower().endswith('.exe')):
+            debugger += '.exe'
+
+        debuggerPath = find_executable(debugger)
+
+    if not debuggerPath:
+        print 'Error: Could not find debugger %s.' % debugger
+        return None
+
+    debuggerName = os.path.basename(debuggerPath).lower()
+
+    def get_debugger_info(type, default):
+        if debuggerName in _DEBUGGER_INFO and type in _DEBUGGER_INFO[debuggerName]:
+            return _DEBUGGER_INFO[debuggerName][type]
+        return default
+
+    # Define a namedtuple to access the debugger information from the outside world.
+    DebuggerInfo = namedtuple(
+        'DebuggerInfo',
+        ['path', 'interactive', 'args', 'requiresEscapedArgs']
+    )
+
+    debugger_arguments = get_debugger_info('args', '')
+
+    # Extend the default arguments for the chosen debugger with the ones passed in, if any.
+    if debuggerArgs:
+        # Make sure to append the argument separator (if any) after the
+        # provided arguments.
+        debugger_arguments = debuggerArgs + debugger_arguments;
+
+    # Override the default debugger interactive mode if needed.
+    debugger_interactive = get_debugger_info('interactive', False)
+    if debuggerInteractive:
+        debugger_interactive = debuggerInteractive
+
+    d = DebuggerInfo(
+        debuggerPath,
+        debugger_interactive,
+        debugger_arguments,
+        get_debugger_info('requiresEscapedArgs', False)
+    )
+
+    return d
+
+# Defines the search policies to use in get_default_debugger_name.
+class DebuggerSearch:
+  OnlyFirst = 1
+  KeepLooking = 2
+
+def get_default_debugger_name(search=DebuggerSearch.OnlyFirst):
+    '''
+    Get the debugger name for the default debugger on current platform.
+
+    :param search: If specified, stops looking for the debugger if the
+     default one is not found (|DebuggerSearch.OnlyFirst|) or keeps
+     looking for other compatible debuggers (|DebuggerSearch.KeepLooking|).
+    '''
+
+    # Find out which debuggers are preferred for use on this platform.
+    debuggerPriorities = _DEBUGGER_PRIORITIES[mozinfo.os if mozinfo.os in _DEBUGGER_PRIORITIES else 'unknown']
+
+    # Finally get the debugger information.
+    for debuggerName in debuggerPriorities:
+        debuggerPath = find_executable(debuggerName)
+        if debuggerPath:
+            return debuggerName
+        elif not search == DebuggerSearch.KeepLooking:
+            return None
+
+    return None
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozdebug/setup.py
@@ -0,0 +1,27 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from setuptools import setup
+
+PACKAGE_VERSION = '0.1'
+
+setup(name='mozdebug',
+      version=PACKAGE_VERSION,
+      description="Utilities for running applications under native code debuggers intended for use in Mozilla testing",
+      long_description="see http://mozbase.readthedocs.org/",
+      classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
+      keywords='mozilla',
+      author='Mozilla Automation and Testing Team',
+      author_email='tools@lists.mozilla.org',
+      url='https://wiki.mozilla.org/Auto-tools/Projects/Mozbase',
+      license='MPL',
+      packages=['mozdebug'],
+      include_package_data=True,
+      zip_safe=False,
+      install_requires=['mozinfo'],
+      entry_points="""
+      # -*- Entry points: -*-
+      """,
+      )
+
--- a/testing/mozbase/mozrunner/setup.py
+++ b/testing/mozbase/mozrunner/setup.py
@@ -1,17 +1,17 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
 # You can obtain one at http://mozilla.org/MPL/2.0/.
 
 import sys
 from setuptools import setup, find_packages
 
 PACKAGE_NAME = 'mozrunner'
-PACKAGE_VERSION = '6.2'
+PACKAGE_VERSION = '6.3'
 
 desc = """Reliable start/stop/configuration of Mozilla Applications (Firefox, Thunderbird, etc.)"""
 
 deps = ['mozcrash >= 0.11',
         'mozdevice >= 0.37',
         'mozfile >= 1.0',
         'mozinfo >= 0.7',
         'mozlog >= 1.5',
--- a/testing/mozbase/mozversion/setup.py
+++ b/testing/mozbase/mozversion/setup.py
@@ -1,15 +1,15 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
 # You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from setuptools import setup
 
-PACKAGE_VERSION = '0.6'
+PACKAGE_VERSION = '0.7'
 
 dependencies = ['mozdevice >= 0.29',
                 'mozfile >= 1.0',
                 'mozlog >= 1.5']
 
 setup(name='mozversion',
       version=PACKAGE_VERSION,
       description='Library to get version information for applications',
--- a/testing/mozbase/packages.txt
+++ b/testing/mozbase/packages.txt
@@ -1,11 +1,12 @@
 manifestparser.pth:testing/mozbase/manifestparser
 mozb2g.pth:testing/mozbase/mozb2g
 mozcrash.pth:testing/mozbase/mozcrash
+mozdebug.pth:testing/mozbase/mozdebug
 mozdevice.pth:testing/mozbase/mozdevice
 mozfile.pth:testing/mozbase/mozfile
 mozhttpd.pth:testing/mozbase/mozhttpd
 mozinfo.pth:testing/mozbase/mozinfo
 mozinstall.pth:testing/mozbase/mozinstall
 mozlog.pth:testing/mozbase/mozlog
 moznetwork.pth:testing/mozbase/moznetwork
 mozprocess.pth:testing/mozbase/mozprocess
--- a/testing/xpcshell/mach_commands.py
+++ b/testing/xpcshell/mach_commands.py
@@ -141,25 +141,19 @@ class XPCShellRunner(MozbuildObject):
         tests_dir = os.path.join(self.topobjdir, '_tests', 'xpcshell')
         modules_dir = os.path.join(self.topobjdir, '_tests', 'modules')
         # We want output from the test to be written immediately if we are only
         # running a single test.
         verbose_output = (test_path is not None or
                           (manifest and len(manifest.test_paths())==1) or
                           verbose)
 
-        # We need to attach the '.exe' extension on Windows for the debugger to
-        # work properly.
-        xpcsExecutable = 'xpcshell'
-        if os.name == 'nt':
-          xpcsExecutable += '.exe'
-
         args = {
             'manifest': manifest,
-            'xpcshell': os.path.join(self.bindir, xpcsExecutable),
+            'xpcshell': self.get_binary_path('xpcshell'),
             'mozInfo': os.path.join(self.topobjdir, 'mozinfo.json'),
             'symbolsPath': os.path.join(self.distdir, 'crashreporter-symbols'),
             'interactive': interactive,
             'keepGoing': keep_going,
             'logfiles': False,
             'sequential': sequential,
             'shuffle': shuffle,
             'testsRootDir': tests_dir,
--- a/testing/xpcshell/runxpcshelltests.py
+++ b/testing/xpcshell/runxpcshelltests.py
@@ -2,16 +2,17 @@
 #
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 import copy
 import json
 import math
+import mozdebug
 import os
 import os.path
 import random
 import re
 import shutil
 import signal
 import socket
 import sys
@@ -392,17 +393,17 @@ class XPCShellTestThread(Thread):
             self.xpcsCmd.extend([
                 '-e',
                 'const _TESTING_MODULES_DIR = "%s";' % sanitized
             ])
 
         self.xpcsCmd.extend(['-f', os.path.join(self.testharnessdir, 'head.js')])
 
         if self.debuggerInfo:
-            self.xpcsCmd = [self.debuggerInfo["path"]] + self.debuggerInfo["args"] + self.xpcsCmd
+            self.xpcsCmd = [self.debuggerInfo.path] + self.debuggerInfo.args + self.xpcsCmd
 
         # Automation doesn't specify a pluginsPath and xpcshell defaults to
         # $APPDIR/plugins. We do the same here so we can carry on with
         # setting up every test with its own plugins directory.
         if not self.pluginsPath:
             self.pluginsPath = os.path.join(self.appPath, 'plugins')
 
         self.pluginsDir = self.setupPluginsDir()
@@ -752,17 +753,16 @@ class XPCShellTestThread(Thread):
                 self.keep_going = False
                 return
 
         self.keep_going = True
 
 class XPCShellTests(object):
 
     log = getGlobalLog()
-    oldcwd = os.getcwd()
 
     def __init__(self, log=None):
         """ Init logging and node status """
         if log:
             resetGlobalLog(log)
 
         # Each method of the underlying logger must acquire the log
         # mutex before writing to stdout.
@@ -899,17 +899,17 @@ class XPCShellTests(object):
         """
           Determine the value of the stdout and stderr for the test.
           Return value is a list (pStdout, pStderr).
         """
         if self.interactive:
             pStdout = None
             pStderr = None
         else:
-            if (self.debuggerInfo and self.debuggerInfo["interactive"]):
+            if (self.debuggerInfo and self.debuggerInfo.interactive):
                 pStdout = None
                 pStderr = None
             else:
                 if sys.platform == 'os2emx':
                     pStdout = None
                 else:
                     pStdout = PIPE
                 pStderr = STDOUT
@@ -1185,18 +1185,21 @@ class XPCShellTests(object):
         |pluginsPath|, if provided, custom plugins directory to be returned from
           the xpcshell dir svc provider for NS_APP_PLUGINS_DIR_LIST.
         |interactive|, if set to True, indicates to provide an xpcshell prompt
           instead of automatically executing the test.
         |verbose|, if set to True, will cause stdout/stderr from tests to
           be printed always
         |logfiles|, if set to False, indicates not to save output to log files.
           Non-interactive only option.
-        |debuggerInfo|, if set, specifies the debugger and debugger arguments
-          that will be used to launch xpcshell.
+        |debugger|, if set, specifies the name of the debugger that will be used
+          to launch xpcshell.
+        |debuggerArgs|, if set, specifies arguments to use with the debugger.
+        |debuggerInteractive|, if set, allows the debugger to be run in interactive
+          mode.
         |profileName|, if set, specifies the name of the application for the profile
           directory if running only a subset of tests.
         |mozInfo|, if set, specifies specifies build configuration information, either as a filename containing JSON, or a dict.
         |shuffle|, if True, execute tests in random order.
         |testsRootDir|, absolute path to root directory of all tests. This is used
           by xUnit generation to determine the package name of the tests.
         |xunitFilename|, if set, specifies the filename to which to write xUnit XML
           results.
@@ -1242,31 +1245,40 @@ class XPCShellTests(object):
             testingModulesDir = os.path.normpath(testingModulesDir)
 
             if not os.path.isabs(testingModulesDir):
                 testingModulesDir = os.path.abspath(testingModulesDir)
 
             if not testingModulesDir.endswith(os.path.sep):
                 testingModulesDir += os.path.sep
 
+        self.debuggerInfo = None
+
+        if debugger:
+            # We need a list of arguments, not a string, to feed into
+            # the debugger
+            if debuggerArgs:
+                debuggerArgs = debuggerArgs.split();
+
+            self.debuggerInfo = mozdebug.get_debugger_info(debugger, debuggerArgs, debuggerInteractive)
+
         self.xpcshell = xpcshell
         self.xrePath = xrePath
         self.appPath = appPath
         self.symbolsPath = symbolsPath
         self.manifest = manifest
         self.testdirs = testdirs
         self.testPath = testPath
         self.interactive = interactive
         self.verbose = verbose
         self.keepGoing = keepGoing
         self.logfiles = logfiles
         self.on_message = on_message
         self.totalChunks = totalChunks
         self.thisChunk = thisChunk
-        self.debuggerInfo = getDebuggerInfo(self.oldcwd, debugger, debuggerArgs, debuggerInteractive)
         self.profileName = profileName or "xpcshell"
         self.mozInfo = mozInfo
         self.testingModulesDir = testingModulesDir
         self.pluginsPath = pluginsPath
         self.sequential = sequential
 
         if not testdirs and not manifest:
             # nothing to test!
@@ -1357,17 +1369,17 @@ class XPCShellTests(object):
             # when we are only running tests sequentially.
             signal.signal(signal.SIGINT, markGotSIGINT)
 
         if self.debuggerInfo:
             # Force a sequential run
             self.sequential = True
 
             # If we have an interactive debugger, disable SIGINT entirely.
-            if self.debuggerInfo["interactive"]:
+            if self.debuggerInfo.interactive:
                 signal.signal(signal.SIGINT, lambda signum, frame: None)
 
         # create a queue of all tests that will run
         tests_queue = deque()
         # also a list for the tests that need to be run sequentially
         sequential_tests = []
         for test_object in self.alltests:
             name = test_object['path']
rename from toolkit/modules/AsyncShutdown.jsm
rename to toolkit/components/asyncshutdown/AsyncShutdown.jsm
--- a/toolkit/modules/AsyncShutdown.jsm
+++ b/toolkit/components/asyncshutdown/AsyncShutdown.jsm
@@ -112,27 +112,35 @@ function fatalerr(msg, error = null) {
 function safeGetState(fetchState) {
   if (!fetchState) {
     return "(none)";
   }
   let data, string;
   try {
     // Evaluate fetchState(), normalize the result into something that we can
     // safely stringify or upload.
-    string = JSON.stringify(fetchState());
+    let state = fetchState();
+    if (!state) {
+      return "(none)";
+    }
+    string = JSON.stringify(state);
     data = JSON.parse(string);
     // Simplify the rest of the code by ensuring that we can simply
     // concatenate the result to a message.
     if (data && typeof data == "object") {
       data.toString = function() {
         return string;
       };
     }
     return data;
   } catch (ex) {
+
+    // Make sure that this causes test failures
+    Promise.reject(ex);
+
     if (string) {
       return string;
     }
     try {
       return "Error getting state: " + ex + " at " + ex.stack;
     } catch (ex2) {
       return "Error getting state but could not display error";
     }
@@ -212,20 +220,30 @@ function getPhase(topic) {
      * for instance "OS.File: flushing all pending I/O";
      * @param {function|promise|*} condition A condition blocking the
      * completion of the phase. Generally, this is a function
      * returning a promise. This function is evaluated during the
      * phase and the phase is guaranteed to not terminate until the
      * resulting promise is either resolved or rejected. If
      * |condition| is not a function but another value |v|, it behaves
      * as if it were a function returning |v|.
-     * @param {function*} fetchState Optionally, a function returning
-     * information about the current state of the blocker as an
-     * object. Used for providing more details when logging errors or
-     * crashing.
+     * @param {object*} details Optionally, an object with details
+     * that may be useful for error reporting, as a subset of of the following
+     * fields:
+     * - fetchState (strongly recommended) A function returning
+     *    information about the current state of the blocker as an
+     *    object. Used for providing more details when logging errors or
+     *    crashing.
+     * - stack. A string containing stack information. This module can
+     *    generally infer stack information if it is not provided.
+     * - lineNumber A number containing the line number for the caller.
+     *    This module can generally infer this information if it is not
+     *    provided.
+     * - filename A string containing the filename for the caller. This
+     *    module can generally infer  the information if it is not provided.
      *
      * Examples:
      * AsyncShutdown.profileBeforeChange.addBlocker("Module: just a promise",
      *      promise); // profileBeforeChange will not complete until
      *                // promise is resolved or rejected
      *
      * AsyncShutdown.profileBeforeChange.addBlocker("Module: a callback",
      *     function callback() {
@@ -238,34 +256,39 @@ function getPhase(topic) {
      *
      * AsyncShutdown.profileBeforeChange.addBlocker("Module: trivial callback",
      *     function callback() {
      *       // ...
      *       // Execute this code during profileBeforeChange
      *       // No specific guarantee about completion of profileBeforeChange
      * });
      */
-    addBlocker: function(name, condition, fetchState = null) {
-      spinner.addBlocker(name, condition, fetchState);
+    addBlocker: function(name, condition, details = null) {
+      spinner.addBlocker(name, condition, details);
     },
     /**
      * Remove the blocker for a condition.
      *
      * If several blockers have been registered for the same
      * condition, remove all these blockers. If no blocker has been
      * registered for this condition, this is a noop.
      *
      * @return {boolean} true if a blocker has been removed, false
      * otherwise. Note that a result of false may mean either that
      * the blocker has never been installed or that the phase has
      * completed and the blocker has already been resolved.
      */
     removeBlocker: function(condition) {
       return spinner.removeBlocker(condition);
     },
+
+    get name() {
+      return spinner.name;
+    },
+
     /**
      * Trigger the phase without having to broadcast a
      * notification. For testing purposes only.
      */
     get _trigger() {
       let accepted = false;
       try {
         accepted = Services.prefs.getBoolPref("toolkit.asyncshutdown.testing");
@@ -293,45 +316,41 @@ function Spinner(topic) {
   this._topic = topic;
   Services.obs.addObserver(this, topic, false);
 }
 
 Spinner.prototype = {
   /**
    * Register a new condition for this phase.
    *
-   * @param {object} condition A Condition that must be fulfilled before
-   * we complete this Phase.
-   * Must contain fields:
-   * - {string} name The human-readable name of the condition. Used
-   * for debugging/error reporting.
-   * - {function} action An action that needs to be completed
-   * before we proceed to the next runstate. If |action| returns a promise,
-   * we wait until the promise is resolved/rejected before proceeding
-   * to the next runstate.
+   * See the documentation of `addBlocker` in property `client`
+   * of instances of `Barrier`.
    */
-  addBlocker: function(name, condition, fetchState) {
-    this._barrier.client.addBlocker(name, condition, fetchState);
+  addBlocker: function(name, condition, details) {
+    this._barrier.client.addBlocker(name, condition, details);
   },
   /**
    * Remove the blocker for a condition.
    *
-   * If several blockers have been registered for the same
-   * condition, remove all these blockers. If no blocker has been
-   * registered for this condition, this is a noop.
+   * See the documentation of `removeBlocker` in rpoperty `client`
+   * of instances of `Barrier`
    *
    * @return {boolean} true if a blocker has been removed, false
    * otherwise. Note that a result of false may mean either that
    * the blocker has never been installed or that the phase has
    * completed and the blocker has already been resolved.
    */
   removeBlocker: function(condition) {
     return this._barrier.client.removeBlocker(condition);
   },
 
+  get name() {
+    return this._barrier.client.name;
+  },
+
   // nsIObserver.observe
   observe: function() {
     let topic = this._topic;
     let barrier = this._barrier;
     Services.obs.removeObserver(this, topic);
 
     let satisfied = false; // |true| once we have satisfied all conditions
     let promise = this._barrier.wait({
@@ -363,21 +382,26 @@ Spinner.prototype = {
  * all registered blockers have been resolved. The owner of the
  * |Barrier| may wait for the resolution of the barrier and obtain
  * information on which blockers have not been resolved yet.
  *
  * @param {string} name The name of the blocker. Used mainly for error-
  *     reporting.
  */
 function Barrier(name) {
+  if (!name) {
+    throw new TypeError("Instances of Barrier need a (non-empty) name");
+  }
+
   /**
    * The set of conditions registered by clients, as a map.
    *
    * Key: condition (function)
-   * Value: Array of {name: string, fetchState: function}
+   * Value: Array of {name: string, fetchState: function, filename: string,
+   *   lineNumber: number, stack: string}
    */
   this._conditions = new Map();
 
   /**
    * Indirections, used to let clients cancel a blocker when they
    * call removeBlocker().
    *
    * Key: condition (function)
@@ -401,64 +425,102 @@ function Barrier(name) {
   this._monitors = null;
 
   /**
    * The capability of adding blockers. This object may safely be returned
    * or passed to clients.
    */
   this.client = {
     /**
+     * The name of the barrier owning this client.
+     */
+    get name() {
+      return name;
+    },
+
+    /**
      * Register a blocker for the completion of this barrier.
      *
      * @param {string} name The human-readable name of the blocker. Used
      * for debugging/error reporting. Please make sure that the name
      * respects the following model: "Some Service: some action in progress" -
      * for instance "OS.File: flushing all pending I/O";
      * @param {function|promise|*} condition A condition blocking the
      * completion of the phase. Generally, this is a function
      * returning a promise. This function is evaluated during the
      * phase and the phase is guaranteed to not terminate until the
      * resulting promise is either resolved or rejected. If
      * |condition| is not a function but another value |v|, it behaves
      * as if it were a function returning |v|.
-     * @param {function*} fetchState Optionally, a function returning
-     * information about the current state of the blocker as an
-     * object. Used for providing more details when logging errors or
-     * crashing.
+     * @param {object*} details Optionally, an object with details
+     * that may be useful for error reporting, as a subset of of the following
+     * fields:
+     * - fetchState (strongly recommended) A function returning
+     *    information about the current state of the blocker as an
+     *    object. Used for providing more details when logging errors or
+     *    crashing.
+     * - stack. A string containing stack information. This module can
+     *    generally infer stack information if it is not provided.
+     * - lineNumber A number containing the line number for the caller.
+     *    This module can generally infer this information if it is not
+     *    provided.
+     * - filename A string containing the filename for the caller. This
+     *    module can generally infer  the information if it is not provided.
      */
-    addBlocker: function(name, condition, fetchState) {
+    addBlocker: function(name, condition, details) {
       if (typeof name != "string") {
         throw new TypeError("Expected a human-readable name as first argument");
       }
-      if (fetchState && typeof fetchState != "function") {
-        throw new TypeError("Expected nothing or a function as third argument");
+      if (details && typeof details == "function") {
+        details = {
+          fetchState: details
+        };
+      } else if (!details) {
+        details = {};
+      }
+      if (typeof details != "object") {
+        throw new TypeError("Expected an object as third argument to `addBlocker`, got " + details);
       }
       if (!this._conditions) {
 	throw new Error("Phase " + this._name +
 			" has already begun, it is too late to register" +
 			" completion condition '" + name + "'.");
       }
 
-      // Determine the filename and line number of the caller.
-      let leaf = Components.stack;
-      let frame;
-      for (frame = leaf; frame != null && frame.filename == leaf.filename; frame = frame.caller) {
-        // Climb up the stack
-      }
-      let filename = frame ? frame.filename : "?";
-      let lineNumber = frame ? frame.lineNumber : -1;
+      let fetchState = details.fetchState || null;
+      let filename = details.filename || "?";
+      let lineNumber = details.lineNumber || -1;
+      let stack = details.stack || undefined;
+
+      if (filename == "?" || lineNumber == -1 || stack === undefined) {
+        // Determine the filename and line number of the caller.
+        let leaf = Components.stack;
+        let frame;
+        for (frame = leaf; frame != null && frame.filename == leaf.filename; frame = frame.caller) {
+          // Climb up the stack
+        }
 
-      // Now build the rest of the stack as a string, using Task.jsm's rewriting
-      // to ensure that we do not lose information at each call to `Task.spawn`.
-      let frames = [];
-      while (frame != null) {
-        frames.push(frame.filename + ":" + frame.name + ":" + frame.lineNumber);
-        frame = frame.caller;
+        if (filename == "?") {
+          filename = frame ? frame.filename : "?";
+        }
+        if (lineNumber == -1) {
+          lineNumber = frame ? frame.lineNumber : -1;
+        }
+
+        // Now build the rest of the stack as a string, using Task.jsm's rewriting
+        // to ensure that we do not lose information at each call to `Task.spawn`.
+        let frames = [];
+        while (frame != null) {
+          frames.push(frame.filename + ":" + frame.name + ":" + frame.lineNumber);
+          frame = frame.caller;
+        }
+        if (stack === undefined) {
+          stack = Task.Debugging.generateReadableStack(frames.join("\n")).split("\n");
+        }
       }
-      let stack = Task.Debugging.generateReadableStack(frames.join("\n")).split("\n");
 
       let set = this._conditions.get(condition);
       if (!set) {
         set = [];
         this._conditions.set(condition, set);
       }
       set.push({name: name,
                 fetchState: fetchState,
@@ -487,17 +549,16 @@ function Barrier(name) {
         // wait() is in progress
         let deferred = this._indirections.get(condition);
         if (deferred) {
           // Unlock the blocker
           deferred.resolve();
         }
         return this._indirections.delete(condition);
       }
-
       // wait() is complete.
       return false;
     }.bind(this),
   };
 }
 Barrier.prototype = Object.freeze({
   /**
    * The current state of the barrier, as a JSON-serializable object
@@ -770,12 +831,13 @@ Barrier.prototype = Object.freeze({
 // Ideally, phases should be registered from the component that decides
 // when they start/stop. For compatibility with existing startup/shutdown
 // mechanisms, we register a few phases here.
 
 this.AsyncShutdown.profileChangeTeardown = getPhase("profile-change-teardown");
 this.AsyncShutdown.profileBeforeChange = getPhase("profile-before-change");
 this.AsyncShutdown.sendTelemetry = getPhase("profile-before-change2");
 this.AsyncShutdown.webWorkersShutdown = getPhase("web-workers-shutdown");
+this.AsyncShutdown.xpcomThreadsShutdown = getPhase("xpcom-threads-shutdown");
 
 this.AsyncShutdown.Barrier = Barrier;
 
 Object.freeze(this.AsyncShutdown);
new file mode 100644
--- /dev/null
+++ b/toolkit/components/asyncshutdown/moz.build
@@ -0,0 +1,22 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell/xpcshell.ini']
+
+XPIDL_MODULE = 'toolkit_asyncshutdown'
+
+XPIDL_SOURCES += [
+    'nsIAsyncShutdown.idl',
+]
+
+EXTRA_JS_MODULES += [
+    'AsyncShutdown.jsm',
+]
+
+EXTRA_COMPONENTS += [
+    'nsAsyncShutdown.js',
+    'nsAsyncShutdown.manifest',
+]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/asyncshutdown/nsAsyncShutdown.js
@@ -0,0 +1,269 @@
+/* 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/. */
+
+/**
+ * An implementation of nsIAsyncShutdown* based on AsyncShutdown.jsm
+ */
+
+"use strict";
+
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cr = Components.results;
+
+let XPCOMUtils = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {}).XPCOMUtils;
+XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
+  "resource://gre/modules/AsyncShutdown.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+  "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+  "resource://gre/modules/Task.jsm");
+
+
+/**
+ * Conversion between nsIPropertyBag and JS object
+ */
+let PropertyBagConverter = {
+  // From nsIPropertyBag to JS
+  toObject: function(bag) {
+    if (!(bag instanceof Ci.nsIPropertyBag)) {
+      throw new TypeError("Not a property bag");
+    }
+    let result = {};
+    let enumerator = bag.enumerator;
+    while (enumerator.hasMoreElements()) {
+      let {name, value: property} = enumerator.getNext().QueryInterface(Ci.nsIProperty);
+      let value = this.toValue(property);
+      result[name] = value;
+    }
+    return result;
+  },
+  toValue: function(property) {
+    if (typeof property != "object") {
+      return property;
+    }
+    if (Array.isArray(property)) {
+      return property.map(this.toValue, this);
+    }
+    if (property && property instanceof Ci.nsIPropertyBag) {
+      return this.toObject(property);
+    }
+    return property;
+  },
+
+  // From JS to nsIPropertyBag
+  fromObject: function(obj) {
+    if (obj == null || typeof obj != "object") {
+      throw new TypeError("Invalid object: " + obj);
+    }
+    let bag = Cc["@mozilla.org/hash-property-bag;1"].
+      createInstance(Ci.nsIWritablePropertyBag);
+    for (let k of Object.keys(obj)) {
+      let value = this.fromValue(obj[k]);
+      bag.setProperty(k, value);
+    }
+    return bag;
+  },
+  fromValue: function(value) {
+    if (typeof value == "function") {
+      return null; // Emulating the behavior of JSON.stringify with functions
+    }
+    if (Array.isArray(value)) {
+      return value.map(this.fromValue, this);
+    }
+    if (value == null || typeof value != "object") {
+      // Auto-converted to nsIVariant
+      return value;
+    }
+    return this.fromObject(value);
+  },
+};
+
+
+
+/**
+ * Construct an instance of nsIAsyncShutdownClient from a
+ * AsyncShutdown.Barrier client.
+ *
+ * @param {object} moduleClient A client, as returned from the `client`
+ * property of an instance of `AsyncShutdown.Barrier`. This client will
+ * serve as back-end for methods `addBlocker` and `removeBlocker`.
+ * @constructor
+ */
+function nsAsyncShutdownClient(moduleClient) {
+  if (!moduleClient) {
+    throw new TypeError("nsAsyncShutdownClient expects one argument");
+  }
+  this._moduleClient = moduleClient;
+  this._byName = new Map();
+}
+nsAsyncShutdownClient.prototype = {
+  _getPromisified: function(xpcomBlocker) {
+    let candidate = this._byName.get(xpcomBlocker.name);
+    if (!candidate) {
+      return null;
+    }
+    if (candidate.xpcom === xpcomBlocker) {
+      return candidate.jsm;
+    }
+    return null;
+  },
+  _setPromisified: function(xpcomBlocker, moduleBlocker) {
+    let candidate = this._byName.get(xpcomBlocker.name);
+    if (!candidate) {
+      this._byName.set(xpcomBlocker.name, {xpcom: xpcomBlocker,
+                                           jsm: moduleBlocker});
+      return;
+    }
+    if (candidate.xpcom === xpcomBlocker) {
+      return;
+    }
+    throw new Error("We have already registered a distinct blocker with the same name: " + xpcomBlocker.name);
+  },
+  _deletePromisified: function(xpcomBlocker) {
+    let candidate = this._byName.get(xpcomBlocker.name);
+    if (!candidate || candidate.xpcom !== xpcomBlocker) {
+      return false;
+    }
+    this._byName.delete(xpcomBlocker.name);
+    return true;
+  },
+  get jsclient() {
+    return this._moduleClient;
+  },
+  get name() {
+    return this._moduleClient.name;
+  },
+  addBlocker: function(/*nsIAsyncShutdownBlocker*/ xpcomBlocker,
+      fileName, lineNumber, stack) {
+    // We need a Promise-based function with the same behavior as
+    // `xpcomBlocker`. Furthermore, to support `removeBlocker`, we
+    // need to ensure that we always get the same Promise-based
+    // function if we call several `addBlocker`/`removeBlocker` several
+    // times with the same `xpcomBlocker`.
+    //
+    // Ideally, this should be done with a WeakMap() with xpcomBlocker
+    // as a key, but XPConnect NativeWrapped objects cannot serve as
+    // WeakMap keys.
+    //
+    let moduleBlocker = this._getPromisified(xpcomBlocker);
+    if (!moduleBlocker) {
+      moduleBlocker = () => new Promise(
+        // This promise is never resolved. By opposition to AsyncShutdown
+        // blockers, `nsIAsyncShutdownBlocker`s are always lifted by calling
+        // `removeBlocker`.
+        () => xpcomBlocker.blockShutdown(this)
+      );
+
+      this._setPromisified(xpcomBlocker, moduleBlocker);
+    }
+
+    this._moduleClient.addBlocker(xpcomBlocker.name,
+      moduleBlocker,
+      {
+        fetchState: () => {
+          let state = xpcomBlocker.state;
+          if (state) {
+            return PropertyBagConverter.toValue(state);
+          }
+          return null;
+        },
+        filename: fileName,
+        lineNumber: lineNumber,
+        stack: stack,
+      });
+  },
+
+  removeBlocker: function(xpcomBlocker) {
+    let moduleBlocker = this._getPromisified(xpcomBlocker);
+    if (!moduleBlocker) {
+      return false;
+    }
+    this._deletePromisified(xpcomBlocker);
+    return this._moduleClient.removeBlocker(moduleBlocker);
+  },
+
+  /* ........ QueryInterface .............. */
+  QueryInterface :  XPCOMUtils.generateQI([Ci.nsIAsyncShutdownBarrier]),
+  classID:          Components.ID("{314e9e96-cc37-4d5c-843b-54709ce11426}"),
+};
+
+/**
+ * Construct an instance of nsIAsyncShutdownBarrier from an instance
+ * of AsyncShutdown.Barrier.
+ *
+ * @param {object} moduleBarrier an instance if
+ * `AsyncShutdown.Barrier`. This instance will serve as back-end for
+ * all methods.
+ * @constructor
+ */
+function nsAsyncShutdownBarrier(moduleBarrier) {
+  this._client = new nsAsyncShutdownClient(moduleBarrier.client);
+  this._moduleBarrier = moduleBarrier;
+};
+nsAsyncShutdownBarrier.prototype = {
+  get state() {
+    return PropertyBagConverter.fromValue(this._moduleBarrier.state);
+  },
+  get client() {
+    return this._client;
+  },
+  wait: function(onReady) {
+    this._moduleBarrier.wait().then(() => {
+      onReady.done();
+    });
+    // By specification, _moduleBarrier.wait() cannot reject.
+  },
+
+  /* ........ QueryInterface .............. */
+  QueryInterface :  XPCOMUtils.generateQI([Ci.nsIAsyncShutdownBarrier]),
+  classID:          Components.ID("{29a0e8b5-9111-4c09-a0eb-76cd02bf20fa}"),
+};
+
+function nsAsyncShutdownService() {
+  // Cache for the getters
+
+  for (let _k of
+   ["profileBeforeChange",
+    "profileChangeTeardown",
+    "sendTelemetry",
+    "webWorkersShutdown",
+    "xpcomThreadsShutdown"]) {
+    let k = _k;
+    Object.defineProperty(this, k, {
+      configurable: true,
+      get: function() {
+        delete this[k];
+        let result = new nsAsyncShutdownClient(AsyncShutdown[k]);
+        Object.defineProperty(this, k, {
+          value: result
+        });
+        return result;
+      }
+    });
+  }
+
+  // Hooks for testing purpose
+  this.wrappedJSObject = {
+    _propertyBagConverter: PropertyBagConverter
+  };
+}
+nsAsyncShutdownService.prototype = {
+  makeBarrier: function(name) {
+    return new nsAsyncShutdownBarrier(new AsyncShutdown.Barrier(name));
+  },
+
+  /* ........ QueryInterface .............. */
+  QueryInterface :  XPCOMUtils.generateQI([Ci.nsIAsyncShutdownService]),
+  classID:          Components.ID("{35c496de-a115-475d-93b5-ffa3f3ae6fe3}"),
+};
+
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([
+    nsAsyncShutdownService,
+    nsAsyncShutdownBarrier,
+    nsAsyncShutdownClient,
+]);
+
new file mode 100644
--- /dev/null
+++ b/toolkit/components/asyncshutdown/nsAsyncShutdown.manifest
@@ -0,0 +1,2 @@
+component {35c496de-a115-475d-93b5-ffa3f3ae6fe3} nsAsyncShutdown.js
+contract @mozilla.org/async-shutdown-service;1 {35c496de-a115-475d-93b5-ffa3f3ae6fe3}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/asyncshutdown/nsIAsyncShutdown.idl
@@ -0,0 +1,207 @@
+/* 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/. */
+
+/**
+ * A mechanism for specifying shutdown dependencies between
+ * asynchronous services.
+ *
+ * Note that this XPCOM component is designed primarily for C++
+ * clients. JavaScript clients should rather use AsyncShutdown.jsm,
+ * which provides a better API and better error reporting for them.
+ */
+
+
+#include "nsISupports.idl"
+#include "nsIPropertyBag.idl"
+#include "nsIVariant.idl"
+
+interface nsIAsyncShutdownClient;
+
+/**
+ * A blocker installed by a client to be informed during some stage of
+ * shutdown and block shutdown asynchronously until some condition is
+ * complete.
+ *
+ * If you wish to use AsyncShutdown, you will need to implement this
+ * interface (and only this interface).
+ */
+[scriptable, uuid(4ef43f29-6715-4b57-a750-2ff83695ddce)]
+interface nsIAsyncShutdownBlocker: nsISupports {
+  /**
+   * The *unique* name of the blocker.
+   *
+   * By convention, it should respect the following format:
+   * "MyModuleName: Doing something while it's time"
+   * e.g.
+   * "OS.File: Flushing before profile-before-change"
+   *
+   * This attribute is uploaded as part of crash reports.
+   */
+  readonly attribute AString name;
+
+  /**
+   * Inform the blocker that the stage of shutdown has started.
+   * Shutdown will NOT proceed until `aBarrierClient.removeBlocker(this)`
+   * has been called.
+   */
+  void blockShutdown(in nsIAsyncShutdownClient aBarrierClient);
+
+  /**
+   * The current state of the blocker.
+   *
+   * In case of crash, this is converted to JSON and attached to
+   * the crash report.
+   *
+   * This field may be used to provide JSON-style data structures.
+   * For this purpose, use
+   * - nsIPropertyBag to represent objects;
+   * - nsIVariant to represent field values (which may hold nsIPropertyBag
+   * themselves).
+   */
+  readonly attribute nsIPropertyBag state;
+};
+
+/**
+ * A client for a nsIAsyncShutdownBarrier.
+ */
+[scriptable, uuid(d2031049-b990-43a2-95be-59f8a3ca5954)]
+interface nsIAsyncShutdownClient: nsISupports {
+  /**
+   * The name of the barrier.
+   */
+  readonly attribute AString name;
+
+  /**
+   * Add a blocker.
+   *
+   * After a `blocker` has been added with `addBlocker`, if it is not
+   * removed with `removeBlocker`, this will, by design, eventually
+   * CAUSE A CRASH.
+   *
+   * Calling `addBlocker` once nsIAsyncShutdownBarrier::wait() has been
+   * called on the owning barrier returns an error.
+   *
+   * @param aBlocker The blocker to add. Once
+   * nsIAsyncShutdownBarrier::wait() has been called, it will not
+   * call its `aOnReady` callback until all blockers have been
+   * removed, each  by a call to `removeBlocker`.
+   * @param aFileName The filename of the callsite, as given by `__FILE__`.
+   * @param aLineNumber The linenumber of the callsite, as given by `__LINE__`.
+   * @param aStack Information on the stack that lead to this call. Generally
+   * empty when called from C++.
+   */
+  void addBlocker(in nsIAsyncShutdownBlocker aBlocker,
+                  in AString aFileName,
+                  in long aLineNumber,
+                  in AString aStack);
+
+  /**
+   * Remove a blocker.
+   *
+   * @param aBlocker A blocker previously added to this client through
+   * `addBlocker`. Noop if the blocker has never been added or has been
+   * removed already.
+   */
+  void removeBlocker(in nsIAsyncShutdownBlocker aBlocker);
+
+  /**
+   * The JS implementation of the client.
+   *
+   * It is strongly recommended that JS clients of this API use
+   * `jsclient` instead of the `nsIAsyncShutdownClient`. See
+   * AsyncShutdown.jsm for more information on the JS version of
+   * this API.
+   */
+  readonly attribute jsval jsclient;
+};
+
+/**
+ * Callback invoked once all blockers of a barrier have been removed.
+ */
+[scriptable, function, uuid(910c9309-1da0-4dd0-8bdb-a325a38c604e)]
+interface nsIAsyncShutdownCompletionCallback: nsISupports {
+  /**
+   * The operation has been completed.
+   */
+  void done();
+};
+
+/**
+ * A stage of shutdown that supports blocker registration.
+ */
+[scriptable, uuid(50fa8a86-9c91-4256-8389-17d310adec90)]
+interface nsIAsyncShutdownBarrier: nsISupports {
+
+  /**
+   * The blocker registration capability.  Most services may wish to
+   * publish this capability to let services that depend on it register
+   * blockers.
+   */
+  readonly attribute nsIAsyncShutdownClient client;
+
+  /**
+   * The state of all the blockers of the barrier.
+   *
+   * See the documentation of `nsIAsyncShutdownBlocker` for the
+   * format.
+   */
+  readonly attribute nsIPropertyBag state;
+
+  /**
+   * Wait for all blockers to complete.
+   *
+   * Method `aOnReady` will be called once all blockers have finished.
+   * The callback always receives NS_OK.
+   */
+  void wait(in nsIAsyncShutdownCompletionCallback aOnReady);
+};
+
+/**
+ * A service that allows registering shutdown-time dependencies.
+ */
+[scriptable, uuid(8a9a0859-0404-4d50-9e76-10a4f56dfb51)]
+interface nsIAsyncShutdownService: nsISupports {
+  /**
+   * Create a new barrier.
+   *
+   * By convention, the name should respect the following format:
+   * "MyModuleName: Doing something while it's time"
+   * e.g.
+   * "OS.File: Waiting for clients to flush before shutting down"
+   *
+   * This attribute is uploaded as part of crash reports.
+   */
+  nsIAsyncShutdownBarrier makeBarrier(in AString aName);
+
+  // Barriers for global shutdown stages
+
+  /**
+   * Barrier for notification profile-before-change.
+   */
+  readonly attribute nsIAsyncShutdownClient profileBeforeChange;
+
+  /**
+   * Barrier for notification profile-change-teardown.
+   */
+  readonly attribute nsIAsyncShutdownClient profileChangeTeardown;
+
+  /**
+   * Barrier for notification profile-before-change2.
+   */
+  readonly attribute nsIAsyncShutdownClient sendTelemetry;
+
+  /**
+   * Barrier for notification web-workers-shutdown.
+   */
+  readonly attribute nsIAsyncShutdownClient webWorkersShutdown;
+
+  /**
+   * Barrier for notification xpcom-threads-shutdown.
+   */
+  readonly attribute nsIAsyncShutdownClient xpcomThreadsShutdown;
+};
+
+%{C++
+#define NS_ASYNCSHUTDOWNSERVICE_CONTRACTID "@mozilla.org/async-shutdown-service;1"
+%}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/asyncshutdown/tests/xpcshell/head.js
@@ -0,0 +1,175 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+let Cu = Components.utils;
+let Cc = Components.classes;
+let Ci = Components.interfaces;
+let Cr = Components.results;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/AsyncShutdown.jsm");
+
+let asyncShutdownService = Cc["@mozilla.org/async-shutdown-service;1"].
+  getService(Ci.nsIAsyncShutdownService);
+
+
+Services.prefs.setBoolPref("toolkit.asyncshutdown.testing", true);
+
+/**
+ * Utility function used to provide the same API for various sources
+ * of async shutdown barriers.
+ *
+ * @param {string} kind One of
+ * - "phase" to test an AsyncShutdown phase;
+ * - "barrier" to test an instance of AsyncShutdown.Barrier;
+ * - "xpcom-barrier" to test an instance of nsIAsyncShutdownBarrier;
+ * - "xpcom-barrier-unwrapped" to test the field `jsclient` of a nsIAsyncShutdownClient.
+ *
+ * @return An object with the following methods:
+ *   - addBlocker() - the same method as AsyncShutdown phases and barrier clients
+ *   - wait() - trigger the resolution of the lock
+ */
+function makeLock(kind) {
+  if (kind == "phase") {
+    let topic = "test-Phase-" + ++makeLock.counter;
+    let phase = AsyncShutdown._getPhase(topic);
+    return {
+      addBlocker: function(...args) {
+        return phase.addBlocker(...args);
+      },
+      removeBlocker: function(blocker) {
+        return phase.removeBlocker(blocker);
+      },
+      wait: function() {
+        Services.obs.notifyObservers(null, topic, null);
+        return Promise.resolve();
+      }
+    };
+  } else if (kind == "barrier") {
+    let name = "test-Barrier-" + ++makeLock.counter;
+    let barrier = new AsyncShutdown.Barrier(name);
+    return {
+      addBlocker: barrier.client.addBlocker,
+      removeBlocker: barrier.client.removeBlocker,
+      wait: function() {
+        return barrier.wait();
+      }
+    };
+  } else if (kind == "xpcom-barrier") {
+    let name = "test-xpcom-Barrier-" + ++makeLock.counter;
+    let barrier = asyncShutdownService.makeBarrier(name);
+    return {
+      addBlocker: function(name, condition, state) {
+        if (condition == null) {
+          // Slight trick as `null` or `undefined` cannot be used as keys
+          // for `xpcomMap`. Note that this has no incidence on the result
+          // of the test as the XPCOM interface imposes that the condition
+          // is a method, so it cannot be `null`/`undefined`.
+          condition = "<this case can't happen with the xpcom interface>";
+        }
+        let blocker = makeLock.xpcomMap.get(condition);
+        if (!blocker) {
+          blocker = {
+            name: name,
+            state: state,
+            blockShutdown: function(aBarrierClient) {
+              return Task.spawn(function*() {
+                try {
+                  if (typeof condition == "function") {
+                    yield Promise.resolve(condition());
+                  } else {
+                    yield Promise.resolve(condition);
+                  }
+                } finally {
+                  aBarrierClient.removeBlocker(blocker);
+                }
+              });
+            },
+          };
+          makeLock.xpcomMap.set(condition, blocker);
+        }
+        let {fileName, lineNumber, stack} = (new Error());
+        return barrier.client.addBlocker(blocker, fileName, lineNumber, stack);
+      },
+      removeBlocker: function(condition) {
+        let blocker = makeLock.xpcomMap.get(condition);
+        if (!blocker) {
+          return;
+        }
+        barrier.client.removeBlocker(blocker);
+      },
+      wait: function() {
+        return new Promise(resolve => {
+          barrier.wait(resolve);
+        });
+      }
+    };
+  } else if ("unwrapped-xpcom-barrier") {
+    let name = "unwrapped-xpcom-barrier-" + ++makeLock.counter;
+    let barrier = asyncShutdownService.makeBarrier(name);
+    let client = barrier.client.jsclient;
+    return {
+      addBlocker: client.addBlocker,
+      removeBlocker: client.removeBlocker,
+      wait: function() {
+        return new Promise(resolve => {
+          barrier.wait(resolve);
+        });
+      }
+    };
+  } else {
+    throw new TypeError("Unknown kind " + kind);
+  }
+}
+makeLock.counter = 0;
+makeLock.xpcomMap = new Map(); // Note: Not a WeakMap as we wish to handle non-gc-able keys (e.g. strings)
+
+/**
+ * An asynchronous task that takes several ticks to complete.
+ *
+ * @param {*=} resolution The value with which the resulting promise will be
+ * resolved once the task is complete. This may be a rejected promise,
+ * in which case the resulting promise will itself be rejected.
+ * @param {object=} outResult An object modified by side-effect during the task.
+ * Initially, its field |isFinished| is set to |false|. Once the task is
+ * complete, its field |isFinished| is set to |true|.
+ *
+ * @return {promise} A promise fulfilled once the task is complete
+ */
+function longRunningAsyncTask(resolution = undefined, outResult = {}) {
+  outResult.isFinished = false;
+  if (!("countFinished" in outResult)) {
+    outResult.countFinished = 0;
+  }
+  let deferred = Promise.defer();
+  do_timeout(100, function() {
+    ++outResult.countFinished;
+    outResult.isFinished = true;
+    deferred.resolve(resolution);
+  });
+  return deferred.promise;
+}
+
+function get_exn(f) {
+  try {
+    f();
+    return null;
+  } catch (ex) {
+    return ex;
+  }
+}
+
+function do_check_exn(exn, constructor) {
+  do_check_neq(exn, null);
+  if (exn.name == constructor) {
+    do_check_eq(exn.constructor.name, constructor);
+    return;
+  }
+  do_print("Wrong error constructor");
+  do_print(exn.constructor.name);
+  do_print(exn.stack);
+  do_check_true(false);
+}
rename from toolkit/modules/tests/xpcshell/test_AsyncShutdown.js
rename to toolkit/components/asyncshutdown/tests/xpcshell/test_AsyncShutdown.js
--- a/toolkit/modules/tests/xpcshell/test_AsyncShutdown.js
+++ b/toolkit/components/asyncshutdown/tests/xpcshell/test_AsyncShutdown.js
@@ -1,276 +1,122 @@
-let Cu = Components.utils;
-
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://gre/modules/AsyncShutdown.jsm");
-
-Services.prefs.setBoolPref("toolkit.asyncshutdown.testing", true);
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
 
 function run_test() {
   run_next_test();
 }
 
-function get_exn(f) {
-  try {
-    f();
-    return null;
-  } catch (ex) {
-    return ex;
-  }
-}
-
-function do_check_exn(exn, constructor) {
-  do_check_neq(exn, null);
-  if (exn.name == constructor) {
-    do_check_eq(exn.constructor.name, constructor);
-    return;
-  }
-  do_print("Wrong error constructor");
-  do_print(exn.constructor.name);
-  do_print(exn.stack);
-  do_check_true(false);
-}
-
-/**
- * Utility function used to provide the same API for both AsyncShutdown
- * phases and AsyncShutdown barriers.
- *
- * @param {bool} heavy If |true|, use a AsyncShutdown phase, otherwise
- * a AsyncShutdown barrier.
- *
- * @return An object with the following methods:
- *   - addBlocker() - the same method as AsyncShutdown phases and barrier clients
- *   - wait() - trigger the resolution of the lock
- */
-function makeLock(heavy) {
-  if (heavy) {
-    let topic = "test-Phase-" + ++makeLock.counter;
-    let phase = AsyncShutdown._getPhase(topic);
-    return {
-      addBlocker: function(...args) {
-        return phase.addBlocker(...args);
-      },
-      removeBlocker: function(blocker) {
-        return phase.removeBlocker(blocker);
-      },
-      wait: function() {
-        Services.obs.notifyObservers(null, topic, null);
-        return Promise.resolve();
-      }
-    };
-  } else {
-    let name = "test-Barrier-" + ++makeLock.counter;
-    let barrier = new AsyncShutdown.Barrier(name);
-    return {
-      addBlocker: barrier.client.addBlocker,
-      removeBlocker: barrier.client.removeBlocker,
-      wait: function() {
-        return barrier.wait();
-      }
-    };
-  }
-}
-makeLock.counter = 0;
-
-/**
- * An asynchronous task that takes several ticks to complete.
- *
- * @param {*=} resolution The value with which the resulting promise will be
- * resolved once the task is complete. This may be a rejected promise,
- * in which case the resulting promise will itself be rejected.
- * @param {object=} outResult An object modified by side-effect during the task.
- * Initially, its field |isFinished| is set to |false|. Once the task is
- * complete, its field |isFinished| is set to |true|.
- *
- * @return {promise} A promise fulfilled once the task is complete
- */
-function longRunningAsyncTask(resolution = undefined, outResult = {}) {
-  outResult.isFinished = false;
-  if (!("countFinished" in outResult)) {
-    outResult.countFinished = 0;
-  }
-  let deferred = Promise.defer();
-  do_timeout(100, function() {
-    ++outResult.countFinished;
-    outResult.isFinished = true;
-    deferred.resolve(resolution);
-  });
-  return deferred.promise;
-}
-
-
-//////// Tests on AsyncShutdown phases
-
-/*add_task*/(function* test_no_condition() {
-  for (let heavy of [false, true]) {
-    do_print("Testing a barrier with no condition (" + heavy?"heavy":"light" + ")");
-    let lock = makeLock(heavy);
+add_task(function* test_no_condition() {
+  for (let kind of ["phase", "barrier", "xpcom-barrier", "xpcom-barrier-unwrapped"]) {
+    do_print("Testing a barrier with no condition (" + kind + ")");
+    let lock = makeLock(kind);
     yield lock.wait();
     do_print("Barrier with no condition didn't lock");
   }
 });
 
-/*add_task*/(function* test_phase_simple_async() {
-  do_print("Testing various combinations of a phase with a single condition");
-  for (let heavy of [false, true]) {
-    for (let arg of [undefined, null, "foo", 100, new Error("BOOM")]) {
-      for (let resolution of [arg, Promise.reject(arg)]) {
-        for (let success of [false, true]) {
-          for (let state of [[null],
-                             [],
-                             [() => "some state"],
-                             [function() {
-                               throw new Error("State BOOM"); }],
-                             [function() {
-                               return {
-                                 toJSON: function() {
-                                   throw new Error("State.toJSON BOOM");
-                                 }
-                               };
-                             }]]) {
-            // Asynchronous phase
-            do_print("Asynchronous test with " + arg + ", " + resolution + ", " + heavy);
-            let lock = makeLock(heavy);
-            let outParam = { isFinished: false };
-            lock.addBlocker(
-              "Async test",
-               function() {
-                 if (success) {
-                   return longRunningAsyncTask(resolution, outParam);
-                 } else {
-                   throw resolution;
-                 }
-               },
-               ...state
-            );
-            do_check_false(outParam.isFinished);
-            yield lock.wait();
-            do_check_eq(outParam.isFinished, success);
-          }
-        }
 
-        // Synchronous phase - just test that we don't throw/freeze
-        do_print("Synchronous test with " + arg + ", " + resolution + ", " + heavy);
-        let lock = makeLock(heavy);
-        lock.addBlocker(
-          "Sync test",
-          resolution
-        );
-        yield lock.wait();
-      }
-    }
-  }
-});
-
-/*add_task*/(function* test_phase_many() {
-  do_print("Testing various combinations of a phase with many conditions");
-  for (let heavy of [false, true]) {
-    let lock = makeLock(heavy);
-    let outParams = [];
-    for (let arg of [undefined, null, "foo", 100, new Error("BOOM")]) {
-      for (let resolve of [true, false]) {
-        do_print("Testing with " + heavy + ", " + arg + ", " + resolve);
-        let resolution = resolve ? arg : Promise.reject(arg);