Merge mozilla-central to b2g-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 15 Jul 2014 16:21:19 +0200
changeset 216050 13cd3098c602136c25c3e329d4cc6badd89c87ae
parent 216049 0958d9abca75465b5c59ea8d77bad14f8e5c816d (current diff)
parent 216015 835e22069c1a2bf00c4d9a57bac4299d185d8acc (diff)
child 216051 ab95805551fc4cd322d694a9898a9e6c161a3e74
push id515
push userraliiev@mozilla.com
push dateMon, 06 Oct 2014 12:51:51 +0000
treeherdermozilla-release@267c7a481bef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone33.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 mozilla-central to b2g-inbound
browser/components/loop/content/shared/img/audio-highlight-14x14.png
browser/components/loop/content/shared/img/audio-highlight-14x14@2x.png
browser/components/loop/content/shared/img/video-highlight-14x14.png
browser/components/loop/content/shared/img/video-highlight-14x14@2x.png
browser/devtools/shared/test/browser_graphs-11.js
toolkit/modules/RemoteAddonsChild.jsm
toolkit/modules/RemoteAddonsParent.jsm
widget/tests/test_bug462106_perwindow.xul
widget/xpwidgets/nsClipboardPrivacyHandler.cpp
widget/xpwidgets/nsClipboardPrivacyHandler.h
--- a/browser/base/content/browser-context.inc
+++ b/browser/base/content/browser-context.inc
@@ -371,17 +371,17 @@
                 label="&viewPageInfoCmd.label;"
                 accesskey="&viewPageInfoCmd.accesskey;"
                 oncommand="gContextMenu.viewInfo();"/>
       <menuseparator id="spell-separator"/>
       <menuitem id="spell-check-enabled"
                 label="&spellCheckToggle.label;"
                 type="checkbox"
                 accesskey="&spellCheckToggle.accesskey;"
-                oncommand="InlineSpellCheckerUI.toggleEnabled();"/>
+                oncommand="InlineSpellCheckerUI.toggleEnabled(window);"/>
       <menuitem id="spell-add-dictionaries-main"
                 label="&spellAddDictionaries.label;"
                 accesskey="&spellAddDictionaries.accesskey;"
                 oncommand="gContextMenu.addDictionaries();"/>
       <menu id="spell-dictionaries"
             label="&spellDictionaries.label;"
             accesskey="&spellDictionaries.accesskey;">
           <menupopup id="spell-dictionaries-menu">
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -950,42 +950,40 @@ chatbox:-moz-full-screen-ancestor > .cha
   transition-timing-function: ease-out;
 }
 
 #BMB_bookmarksPopup[animate="open"] {
   transform: none;
   opacity: 1.0;
 }
 
-#BMB_bookmarksPopup[arrowposition="after_start"] {
+#BMB_bookmarksPopup[animate="cancel"] {
+  transform: none;
+}
+
+#BMB_bookmarksPopup[arrowposition="after_start"]:-moz-locale-dir(ltr),
+#BMB_bookmarksPopup[arrowposition="after_end"]:-moz-locale-dir(rtl) {
   transform-origin: 20px top;
 }
 
-#BMB_bookmarksPopup[arrowposition="after_end"] {
+#BMB_bookmarksPopup[arrowposition="after_end"]:-moz-locale-dir(ltr),
+#BMB_bookmarksPopup[arrowposition="after_start"]:-moz-locale-dir(rtl) {
   transform-origin: calc(100% - 20px) top;
 }
 
-#BMB_bookmarksPopup[arrowposition="before_start"] {
+#BMB_bookmarksPopup[arrowposition="before_start"]:-moz-locale-dir(ltr),
+#BMB_bookmarksPopup[arrowposition="before_end"]:-moz-locale-dir(rtl) {
   transform-origin: 20px bottom;
 }
 
-#BMB_bookmarksPopup[arrowposition="before_end"] {
+#BMB_bookmarksPopup[arrowposition="before_end"]:-moz-locale-dir(ltr),
+#BMB_bookmarksPopup[arrowposition="before_start"]:-moz-locale-dir(rtl) {
   transform-origin: calc(100% - 20px) bottom;
 }
 
-#BMB_bookmarksPopup[arrowposition="after_start"][animate="cancel"],
-#BMB_bookmarksPopup[arrowposition="before_end"][animate="cancel"] {
-  transform: none;
-}
-
-#BMB_bookmarksPopup[arrowposition="after_end"][animate="cancel"],
-#BMB_bookmarksPopup[arrowposition="before_start"][animate="cancel"] {
-  transform: none;
-}
-
 %endif
 
 /* Customize mode */
 #navigator-toolbox,
 #browser-bottombox,
 #content-deck {
   transition-property: margin-left, margin-right;
   transition-duration: 200ms;
--- a/browser/base/content/test/general/healthreport_testRemoteCommands.html
+++ b/browser/base/content/test/general/healthreport_testRemoteCommands.html
@@ -1,17 +1,28 @@
 <html>
   <head>
     <meta charset="utf-8">
 
 <script>
 
 function init() {
-  window.addEventListener("message", function process(e) {doTest(e)}, false);
-  doTest();
+  window.addEventListener("message", function process(e) {
+    // The init function of abouthealth.js schedules an initial payload event,
+    // which will be sent after the payload data has been collected. This extra
+    // event can cause unexpected successes/failures in this test, so we wait
+    // for the extra event to arrive here before progressing with the actual
+    // test.
+    if (e.data.type == "payload") {
+      window.removeEventListener("message", process, false);
+
+      window.addEventListener("message", doTest, false);
+      doTest();
+    }
+  }, false);
 }
 
 function checkSubmissionValue(payload, expectedValue) {
   return payload.enabled == expectedValue;
 }
 
 function validatePayload(payload) {
   payload = JSON.parse(payload);
--- a/browser/components/customizableui/moz.build
+++ b/browser/components/customizableui/moz.build
@@ -1,12 +1,12 @@
 # -*- 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/.
 
 PARALLEL_DIRS += [
-		'content',
-		'src',
+    'content',
+    'src',
 ]
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
--- a/browser/components/loop/content/conversation.html
+++ b/browser/components/loop/content/conversation.html
@@ -21,16 +21,17 @@
       window.OTProperties = {
         cdnURL: 'loop/',
       };
       window.OTProperties.assetURL = window.OTProperties.cdnURL + 'sdk-content/';
       window.OTProperties.configURL = window.OTProperties.assetURL + 'js/dynamic_config.min.js';
       window.OTProperties.cssURL = window.OTProperties.assetURL + 'css/ot.css';
     </script>
     <script type="text/javascript" src="loop/libs/sdk.js"></script>
+    <script type="text/javascript" src="loop/shared/libs/react-0.10.0.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/models.js"></script>
     <script type="text/javascript" src="loop/shared/js/router.js"></script>
     <script type="text/javascript" src="loop/shared/js/views.js"></script>
     <script type="text/javascript" src="loop/js/client.js"></script>
--- a/browser/components/loop/content/js/conversation.js
+++ b/browser/components/loop/content/js/conversation.js
@@ -1,13 +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 esnext:true */
+/* jshint newcap:false, esnext:true */
 /* global loop:true */
 
 var loop = loop || {};
 loop.conversation = (function(OT, mozL10n) {
   "use strict";
 
   var sharedViews = loop.shared.views;
 
@@ -159,20 +159,20 @@ loop.conversation = (function(OT, mozL10
     conversation: function() {
       if (!this._conversation.isSessionReady()) {
         console.error("Error: navigated to conversation route without " +
           "the start route to initialise the call first");
         this._notifier.errorL10n("cannot_start_call_session_not_ready");
         return;
       }
 
-      this.loadView(
-        new loop.shared.views.ConversationView({
-          sdk: OT,
-          model: this._conversation
+      /*jshint newcap:false*/
+      this.loadReactComponent(sharedViews.ConversationView({
+        sdk: OT,
+        model: this._conversation
       }));
     },
 
     /**
      * XXX: load a view with a close button for now?
      */
     ended: function() {
       this.loadView(new EndedCallView());
--- a/browser/components/loop/content/shared/css/conversation.css
+++ b/browser/components/loop/content/shared/css/conversation.css
@@ -53,78 +53,55 @@
   .conversation .controls .btn-hangup {
     background-image: url(../img/hangup-inverse-14x14@2x.png);
   }
 }
 
 /* Common media control buttons behavior */
 .conversation .controls .media-control {
   background-color: transparent;
-  opacity: .7; /* reduce the opacity for a non-streaming media */
+  opacity: 1;
 }
 .conversation .controls .media-control:hover {
   background-color: rgba(255, 255, 255, .35);
   opacity: 1;
 }
 .conversation .controls .media-control.muted {
   background-color: #0096DD;
   opacity: 1;
 }
-.conversation .controls .media-control.streaming {
-  opacity: 1;
-}
-.conversation .controls .media-control:not(.streaming):hover {
-  background-color: transparent;
-  opacity: 1;
-}
 
 /* Audio mute button */
-.conversation .controls .btn-mute-audio {
+.conversation .controls .local-media.btn-mute-audio {
   background-image: url(../img/audio-inverse-14x14.png);
 }
-.conversation .controls .btn-mute-audio.streaming {
-  background-image: url(../img/audio-highlight-14x14.png);
-}
-.conversation .controls .btn-mute-audio.muted,
-.conversation .controls .btn-mute-audio.streaming:hover {
+.conversation .controls .local-media.btn-mute-audio.muted {
   background-image: url(../img/mute-inverse-14x14.png);
 }
 @media (min-resolution: 2dppx) {
-  .conversation .controls .btn-mute-audio {
+  .conversation .controls .local-media.btn-mute-audio {
     background-image: url(../img/audio-inverse-14x14@2x.png);
   }
-  .conversation .controls .btn-mute-audio.streaming {
-    background-image: url(../img/audio-highlight-14x14@2x.png);
-  }
-  .conversation .controls .btn-mute-audio.muted,
-  .conversation .controls .btn-mute-audio.streaming:hover {
+  .conversation .controls .local-media.btn-mute-audio.muted {
     background-image: url(../img/mute-inverse-14x14@2x.png);
   }
 }
 
 /* Video mute button */
-.conversation .controls .btn-mute-video {
+.conversation .controls .local-media.btn-mute-video {
   background-image: url(../img/video-inverse-14x14.png);
 }
-.conversation .controls .btn-mute-video.streaming {
-  background-image: url(../img/video-highlight-14x14.png);
-}
-.conversation .controls .btn-mute-video.muted,
-.conversation .controls .btn-mute-video.streaming:hover {
+.conversation .controls .local-media.btn-mute-video.muted {
   background-image: url(../img/facemute-14x14.png);
 }
 @media (min-resolution: 2dppx) {
-  .conversation .controls .btn-mute-video {
+  .conversation .controls .local-media.btn-mute-video {
     background-image: url(../img/video-inverse-14x14@2x.png);
   }
-  .conversation .controls .btn-mute-video.streaming {
-    background-image: url(../img/video-highlight-14x14@2x.png);
-  }
-  .conversation .controls .btn-mute-video.muted,
-  .conversation .controls .btn-mute-video.streaming:hover {
+  .conversation .controls .local-media.btn-mute-video.muted {
     background-image: url(../img/facemute-14x14@2x.png);
   }
 }
 
 /* Video elements */
 
 .conversation .media video {
   background: #eee;
deleted file mode 100644
index 62469ecd0e8f07001c0777057a4177fc82ba4241..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index deaa8dda7e694066e0c854bd041b4297e5d3b5cc..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 5d1e7ccc4c9e910f3cd6974e9428aac300cc6412..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 0692ad7087c1b9300af7bdce87daa98b4ff8d9f7..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/browser/components/loop/content/shared/js/views.js
+++ b/browser/components/loop/content/shared/js/views.js
@@ -1,20 +1,23 @@
+/** @jsx React.DOM */
+
 /* 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 */
-
+/* 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 __ = l10n.get;
 
   /**
    * L10n view. Translates resulting view DOM fragment once rendered.
    */
   var L10nView = (function() {
     var L10nViewImpl   = Backbone.View.extend(), // Original View constructor
         originalExtend = L10nViewImpl.extend;    // Original static extend fn
 
@@ -84,215 +87,266 @@ loop.shared.views = (function(_, OT, l10
       if (this.template) {
         this.$el.html(this.template());
       }
       return this;
     }
   });
 
   /**
-   * Conversation view.
+   * Media control button.
+   *
+   * Required props:
+   * - {String}   scope   Media scope, can be "local" or "remote".
+   * - {String}   type    Media type, can be "audio" or "video".
+   * - {Function} action  Function to be executed on click.
+   * - {Enabled}  enabled Stream activation status (default: true).
    */
-  var ConversationView = BaseView.extend({
-    className: "conversation",
+  var MediaControlButton = React.createClass({displayName: 'MediaControlButton',
+    propTypes: {
+      scope: React.PropTypes.string.isRequired,
+      type: React.PropTypes.string.isRequired,
+      action: React.PropTypes.func.isRequired,
+      enabled: React.PropTypes.bool.isRequired
+    },
+
+    getDefaultProps: function() {
+      return {enabled: true};
+    },
+
+    handleClick: function() {
+      this.props.action();
+    },
 
-    /**
-     * Local stream object.
-     * @type {OT.Stream|null}
-     */
-    localStream: null,
+    _getClasses: function() {
+      var cx = React.addons.classSet;
+      // classes
+      var classesObj = {
+        "btn": true,
+        "media-control": true,
+        "local-media": this.props.scope === "local",
+        "muted": !this.props.enabled
+      };
+      classesObj["btn-mute-" + this.props.type] = true;
+      return cx(classesObj);
+    },
+
+    _getTitle: function(enabled) {
+      var prefix = this.props.enabled ? "mute" : "unmute";
+      var suffix = "button_title";
+      var msgId = [prefix, this.props.scope, this.props.type, suffix].join("_");
+      return __(msgId);
+    },
+
+    render: function() {
+      return (
+        React.DOM.button( {className:this._getClasses(),
+                title:this._getTitle(),
+                onClick:this.handleClick})
+      );
+    }
+  });
 
-    template: _.template([
-      '<ul class="controls cf">',
-      '  <li><button class="btn btn-hangup" ',
-      '              data-l10n-id="hangup_button"></button></li>',
-      '  <li><button class="btn media-control btn-mute-video"',
-      '              data-l10n-id="mute_local_video_button"></button></li>',
-      '  <li><button class="btn media-control btn-mute-audio"',
-      '              data-l10n-id="mute_local_audio_button"></button></li>',
-      '</ul>',
-      '<div class="media nested">',
-      // Both these wrappers are required by the SDK; this is fragile and
-      // will break if a future version of the SDK updates this generated DOM,
-      // especially as the SDK seems to actually move wrapped contents into
-      // their own generated stuff.
-      '  <div class="remote"><div class="incoming"></div></div>',
-      '  <div class="local"><div class="outgoing"></div></div>',
-      '</div>'
-    ].join("")),
+  /**
+   * Conversation controls.
+   */
+  var ConversationToolbar = React.createClass({displayName: 'ConversationToolbar',
+    getDefaultProps: function() {
+      return {
+        video: {enabled: true},
+        audio: {enabled: true}
+      };
+    },
+
+    propTypes: {
+      video: React.PropTypes.object.isRequired,
+      audio: React.PropTypes.object.isRequired,
+      hangup: React.PropTypes.func.isRequired,
+      publishStream: React.PropTypes.func.isRequired
+    },
+
+    handleClickHangup: function() {
+      this.props.hangup();
+    },
+
+    handleToggleVideo: function() {
+      this.props.publishStream("video", !this.props.video.enabled);
+    },
+
+    handleToggleAudio: function() {
+      this.props.publishStream("audio", !this.props.audio.enabled);
+    },
+
+    render: function() {
+      return (
+        React.DOM.ul( {className:"controls"}, 
+          React.DOM.li(null, React.DOM.button( {className:"btn btn-hangup",
+                      onClick:this.handleClickHangup,
+                      title:__("hangup_button_title")})),
+          React.DOM.li(null, MediaControlButton( {action:this.handleToggleVideo,
+                                  enabled:this.props.video.enabled,
+                                  scope:"local", type:"video"} )),
+          React.DOM.li(null, MediaControlButton( {action:this.handleToggleAudio,
+                                  enabled:this.props.audio.enabled,
+                                  scope:"local", type:"audio"} ))
+        )
+      );
+    }
+  });
+
+  var ConversationView = React.createClass({displayName: 'ConversationView',
+    mixins: [Backbone.Events],
+
+    propTypes: {
+      sdk: React.PropTypes.object.isRequired,
+      model: React.PropTypes.object.isRequired
+    },
 
     // height set to "auto" to fix video layout on Google Chrome
     // @see https://bugzilla.mozilla.org/show_bug.cgi?id=991122
     publisherConfig: {
       width: "100%",
       height: "auto",
       style: {
         bugDisplayMode: "off",
-        buttonDisplayMode: "off"
+        buttonDisplayMode: "off",
+        nameDisplayMode: "off"
       }
     },
 
-    events: {
-      'click .btn-hangup': 'hangup',
-      'click .btn-mute-audio': 'toggleMuteAudio',
-      'click .btn-mute-video': 'toggleMuteVideo'
+    getInitialState: function() {
+      return {
+        video: {enabled: false},
+        audio: {enabled: false}
+      };
+    },
+
+    componentDidMount: function() {
+      this.props.model.startSession();
     },
 
-    /**
-     * Establishes webrtc communication using OT sdk.
-     */
-    initialize: function(options) {
-      options = options || {};
-      if (!options.sdk) {
-        throw new Error("missing required sdk");
-      }
-      this.sdk = options.sdk;
+    componentWillMount: function() {
+      this.listenTo(this.props.model, "session:connected",
+                                      this.startPublishing);
+      this.listenTo(this.props.model, "session:stream-created",
+                                      this._streamCreated);
+      this.listenTo(this.props.model, ["session:peer-hungup",
+                                       "session:network-disconnected",
+                                       "session:ended"].join(" "),
+                                       this.stopPublishing);
+    },
 
-      this.listenTo(this.model, "session:connected", this.publish);
-      this.listenTo(this.model, "session:stream-created", this._streamCreated);
-      this.listenTo(this.model, ["session:peer-hungup",
-                                 "session:network-disconnected",
-                                 "session:ended"].join(" "), this.unpublish);
-      this.model.startSession();
+    componentWillUnmount: function() {
+      this.hangup();
+    },
+
+    hangup: function() {
+      this.stopPublishing();
+      this.props.model.endSession();
     },
 
     /**
      * Subscribes and attaches each created stream to a DOM element.
      *
      * XXX: for now we only support a single remote stream, hence a single DOM
      *      element.
      *
      * http://tokbox.com/opentok/libraries/client/js/reference/StreamEvent.html
      *
      * @param  {StreamEvent} event
      */
     _streamCreated: function(event) {
-      var incoming = this.$(".incoming").get(0);
+      var incoming = this.getDOMNode().querySelector(".incoming");
       event.streams.forEach(function(stream) {
         if (stream.connection.connectionId !==
-            this.model.session.connection.connectionId) {
-          this.model.session.subscribe(stream, incoming, this.publisherConfig);
+            this.props.model.session.connection.connectionId) {
+          this.props.model.session.subscribe(stream, incoming,
+                                             this.publisherConfig);
         }
       }, this);
     },
 
     /**
-     * Hangs up current conversation.
-     *
-     * @param  {MouseEvent} event
-     */
-    hangup: function(event) {
-      event.preventDefault();
-      this.unpublish();
-      this.model.endSession();
-    },
-
-    /**
-     * Toggles audio mute state.
-     *
-     * @param  {MouseEvent} event
-     */
-    toggleMuteAudio: function(event) {
-      event.preventDefault();
-      if (!this.localStream) {
-        return;
-      }
-      var msgId;
-      var $button = this.$(".btn-mute-audio");
-      var enabled = !this.localStream.hasAudio;
-      this.publisher.publishAudio(enabled);
-      if (enabled) {
-        msgId = "mute_local_audio_button.title";
-        $button.removeClass("muted");
-      } else {
-        msgId = "unmute_local_audio_button.title";
-        $button.addClass("muted");
-      }
-      $button.attr("title", l10n.get(msgId));
-    },
-
-    /**
-     * Toggles video mute state.
-     *
-     * @param  {MouseEvent} event
-     */
-    toggleMuteVideo: function(event) {
-      event.preventDefault();
-      if (!this.localStream) {
-        return;
-      }
-      var msgId;
-      var $button = this.$(".btn-mute-video");
-      var enabled = !this.localStream.hasVideo;
-      this.publisher.publishVideo(enabled);
-      if (enabled) {
-        $button.removeClass("muted");
-        msgId = "mute_local_video_button.title";
-      } else {
-        $button.addClass("muted");
-        msgId = "unmute_local_video_button.title";
-      }
-      $button.attr("title", l10n.get(msgId));
-    },
-
-    /**
      * Publishes remote streams available once a session is connected.
      *
      * http://tokbox.com/opentok/libraries/client/js/reference/SessionConnectEvent.html
      *
      * @param  {SessionConnectEvent} event
      */
-    publish: function(event) {
-      var outgoing = this.$(".outgoing").get(0);
+    startPublishing: function(event) {
+      var outgoing = this.getDOMNode().querySelector(".outgoing");
 
-      this.publisher = this.sdk.initPublisher(outgoing, this.publisherConfig);
+      // XXX move this into its StreamingVideo component?
+      this.publisher = this.props.sdk.initPublisher(
+        outgoing, this.publisherConfig);
 
       // Suppress OT GuM custom dialog, see bug 1018875
       function preventOpeningAccessDialog(event) {
         event.preventDefault();
       }
       this.publisher.on("accessDialogOpened", preventOpeningAccessDialog);
       this.publisher.on("accessDenied", preventOpeningAccessDialog);
+
       this.publisher.on("streamCreated", function(event) {
-        this.localStream = event.stream;
-        if (this.localStream.hasAudio) {
-          this.$(".btn-mute-audio").addClass("streaming");
-        }
-        if (this.localStream.hasVideo) {
-          this.$(".btn-mute-video").addClass("streaming");
-        }
+        this.setState({
+          audio: {enabled: event.stream.hasAudio},
+          video: {enabled: event.stream.hasVideo}
+        });
       }.bind(this));
+
       this.publisher.on("streamDestroyed", function() {
-        this.localStream = null;
-        this.$(".btn-mute-audio").removeClass("streaming muted");
-        this.$(".btn-mute-video").removeClass("streaming muted");
+        this.setState({
+          audio: {enabled: false},
+          video: {enabled: false}
+        });
       }.bind(this));
 
-      this.model.session.publish(this.publisher);
+      this.props.model.session.publish(this.publisher);
+    },
+
+    /**
+     * Toggles streaming status for a given stream type.
+     *
+     * @param  {String}  type     Stream type ("audio" or "video").
+     * @param  {Boolean} enabled  Enabled stream flag.
+     */
+    publishStream: function(type, enabled) {
+      if (type === "audio") {
+        this.publisher.publishAudio(enabled);
+        this.setState({audio: {enabled: enabled}});
+      } else {
+        this.publisher.publishVideo(enabled);
+        this.setState({video: {enabled: enabled}});
+      }
     },
 
     /**
      * Unpublishes local stream.
      */
-    unpublish: function() {
+    stopPublishing: function() {
       // Unregister access OT GuM custom dialog listeners, see bug 1018875
       this.publisher.off("accessDialogOpened");
       this.publisher.off("accessDenied");
 
-      this.model.session.unpublish(this.publisher);
+      this.props.model.session.unpublish(this.publisher);
     },
 
-    /**
-     * Renders this view.
-     *
-     * @return {ConversationView}
-     */
     render: function() {
-      this.$el.html(this.template(this.model.toJSON()));
-      return this;
+      return (
+        React.DOM.div( {className:"conversation"}, 
+          ConversationToolbar( {video:this.state.video,
+                               audio:this.state.audio,
+                               publishStream:this.publishStream,
+                               hangup:this.hangup} ),
+          React.DOM.div( {className:"media nested"}, 
+            React.DOM.div( {className:"remote"}, React.DOM.div( {className:"incoming"})),
+            React.DOM.div( {className:"local"}, React.DOM.div( {className:"outgoing"}))
+          )
+        )
+      );
     }
   });
 
   /**
    * Notification view.
    */
   var NotificationView = BaseView.extend({
     template: _.template([
@@ -451,14 +505,16 @@ loop.shared.views = (function(_, OT, l10
       '</div>'
     ].join(""))
   });
 
   return {
     L10nView: L10nView,
     BaseView: BaseView,
     ConversationView: ConversationView,
+    ConversationToolbar: ConversationToolbar,
+    MediaControlButton: MediaControlButton,
     NotificationListView: NotificationListView,
     NotificationView: NotificationView,
     UnsupportedBrowserView: UnsupportedBrowserView,
     UnsupportedDeviceView: UnsupportedDeviceView
   };
 })(_, window.OT, document.webL10n || document.mozL10n);
copy from browser/components/loop/content/shared/js/views.js
copy to browser/components/loop/content/shared/js/views.jsx
--- a/browser/components/loop/content/shared/js/views.js
+++ b/browser/components/loop/content/shared/js/views.jsx
@@ -1,20 +1,23 @@
+/** @jsx React.DOM */
+
 /* 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 */
-
+/* 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 __ = l10n.get;
 
   /**
    * L10n view. Translates resulting view DOM fragment once rendered.
    */
   var L10nView = (function() {
     var L10nViewImpl   = Backbone.View.extend(), // Original View constructor
         originalExtend = L10nViewImpl.extend;    // Original static extend fn
 
@@ -84,215 +87,266 @@ loop.shared.views = (function(_, OT, l10
       if (this.template) {
         this.$el.html(this.template());
       }
       return this;
     }
   });
 
   /**
-   * Conversation view.
+   * Media control button.
+   *
+   * Required props:
+   * - {String}   scope   Media scope, can be "local" or "remote".
+   * - {String}   type    Media type, can be "audio" or "video".
+   * - {Function} action  Function to be executed on click.
+   * - {Enabled}  enabled Stream activation status (default: true).
    */
-  var ConversationView = BaseView.extend({
-    className: "conversation",
+  var MediaControlButton = React.createClass({
+    propTypes: {
+      scope: React.PropTypes.string.isRequired,
+      type: React.PropTypes.string.isRequired,
+      action: React.PropTypes.func.isRequired,
+      enabled: React.PropTypes.bool.isRequired
+    },
+
+    getDefaultProps: function() {
+      return {enabled: true};
+    },
+
+    handleClick: function() {
+      this.props.action();
+    },
 
-    /**
-     * Local stream object.
-     * @type {OT.Stream|null}
-     */
-    localStream: null,
+    _getClasses: function() {
+      var cx = React.addons.classSet;
+      // classes
+      var classesObj = {
+        "btn": true,
+        "media-control": true,
+        "local-media": this.props.scope === "local",
+        "muted": !this.props.enabled
+      };
+      classesObj["btn-mute-" + this.props.type] = true;
+      return cx(classesObj);
+    },
+
+    _getTitle: function(enabled) {
+      var prefix = this.props.enabled ? "mute" : "unmute";
+      var suffix = "button_title";
+      var msgId = [prefix, this.props.scope, this.props.type, suffix].join("_");
+      return __(msgId);
+    },
+
+    render: function() {
+      return (
+        <button className={this._getClasses()}
+                title={this._getTitle()}
+                onClick={this.handleClick}></button>
+      );
+    }
+  });
 
-    template: _.template([
-      '<ul class="controls cf">',
-      '  <li><button class="btn btn-hangup" ',
-      '              data-l10n-id="hangup_button"></button></li>',
-      '  <li><button class="btn media-control btn-mute-video"',
-      '              data-l10n-id="mute_local_video_button"></button></li>',
-      '  <li><button class="btn media-control btn-mute-audio"',
-      '              data-l10n-id="mute_local_audio_button"></button></li>',
-      '</ul>',
-      '<div class="media nested">',
-      // Both these wrappers are required by the SDK; this is fragile and
-      // will break if a future version of the SDK updates this generated DOM,
-      // especially as the SDK seems to actually move wrapped contents into
-      // their own generated stuff.
-      '  <div class="remote"><div class="incoming"></div></div>',
-      '  <div class="local"><div class="outgoing"></div></div>',
-      '</div>'
-    ].join("")),
+  /**
+   * Conversation controls.
+   */
+  var ConversationToolbar = React.createClass({
+    getDefaultProps: function() {
+      return {
+        video: {enabled: true},
+        audio: {enabled: true}
+      };
+    },
+
+    propTypes: {
+      video: React.PropTypes.object.isRequired,
+      audio: React.PropTypes.object.isRequired,
+      hangup: React.PropTypes.func.isRequired,
+      publishStream: React.PropTypes.func.isRequired
+    },
+
+    handleClickHangup: function() {
+      this.props.hangup();
+    },
+
+    handleToggleVideo: function() {
+      this.props.publishStream("video", !this.props.video.enabled);
+    },
+
+    handleToggleAudio: function() {
+      this.props.publishStream("audio", !this.props.audio.enabled);
+    },
+
+    render: function() {
+      return (
+        <ul className="controls">
+          <li><button className="btn btn-hangup"
+                      onClick={this.handleClickHangup}
+                      title={__("hangup_button_title")}></button></li>
+          <li><MediaControlButton action={this.handleToggleVideo}
+                                  enabled={this.props.video.enabled}
+                                  scope="local" type="video" /></li>
+          <li><MediaControlButton action={this.handleToggleAudio}
+                                  enabled={this.props.audio.enabled}
+                                  scope="local" type="audio" /></li>
+        </ul>
+      );
+    }
+  });
+
+  var ConversationView = React.createClass({
+    mixins: [Backbone.Events],
+
+    propTypes: {
+      sdk: React.PropTypes.object.isRequired,
+      model: React.PropTypes.object.isRequired
+    },
 
     // height set to "auto" to fix video layout on Google Chrome
     // @see https://bugzilla.mozilla.org/show_bug.cgi?id=991122
     publisherConfig: {
       width: "100%",
       height: "auto",
       style: {
         bugDisplayMode: "off",
-        buttonDisplayMode: "off"
+        buttonDisplayMode: "off",
+        nameDisplayMode: "off"
       }
     },
 
-    events: {
-      'click .btn-hangup': 'hangup',
-      'click .btn-mute-audio': 'toggleMuteAudio',
-      'click .btn-mute-video': 'toggleMuteVideo'
+    getInitialState: function() {
+      return {
+        video: {enabled: false},
+        audio: {enabled: false}
+      };
+    },
+
+    componentDidMount: function() {
+      this.props.model.startSession();
     },
 
-    /**
-     * Establishes webrtc communication using OT sdk.
-     */
-    initialize: function(options) {
-      options = options || {};
-      if (!options.sdk) {
-        throw new Error("missing required sdk");
-      }
-      this.sdk = options.sdk;
+    componentWillMount: function() {
+      this.listenTo(this.props.model, "session:connected",
+                                      this.startPublishing);
+      this.listenTo(this.props.model, "session:stream-created",
+                                      this._streamCreated);
+      this.listenTo(this.props.model, ["session:peer-hungup",
+                                       "session:network-disconnected",
+                                       "session:ended"].join(" "),
+                                       this.stopPublishing);
+    },
 
-      this.listenTo(this.model, "session:connected", this.publish);
-      this.listenTo(this.model, "session:stream-created", this._streamCreated);
-      this.listenTo(this.model, ["session:peer-hungup",
-                                 "session:network-disconnected",
-                                 "session:ended"].join(" "), this.unpublish);
-      this.model.startSession();
+    componentWillUnmount: function() {
+      this.hangup();
+    },
+
+    hangup: function() {
+      this.stopPublishing();
+      this.props.model.endSession();
     },
 
     /**
      * Subscribes and attaches each created stream to a DOM element.
      *
      * XXX: for now we only support a single remote stream, hence a single DOM
      *      element.
      *
      * http://tokbox.com/opentok/libraries/client/js/reference/StreamEvent.html
      *
      * @param  {StreamEvent} event
      */
     _streamCreated: function(event) {
-      var incoming = this.$(".incoming").get(0);
+      var incoming = this.getDOMNode().querySelector(".incoming");
       event.streams.forEach(function(stream) {
         if (stream.connection.connectionId !==
-            this.model.session.connection.connectionId) {
-          this.model.session.subscribe(stream, incoming, this.publisherConfig);
+            this.props.model.session.connection.connectionId) {
+          this.props.model.session.subscribe(stream, incoming,
+                                             this.publisherConfig);
         }
       }, this);
     },
 
     /**
-     * Hangs up current conversation.
-     *
-     * @param  {MouseEvent} event
-     */
-    hangup: function(event) {
-      event.preventDefault();
-      this.unpublish();
-      this.model.endSession();
-    },
-
-    /**
-     * Toggles audio mute state.
-     *
-     * @param  {MouseEvent} event
-     */
-    toggleMuteAudio: function(event) {
-      event.preventDefault();
-      if (!this.localStream) {
-        return;
-      }
-      var msgId;
-      var $button = this.$(".btn-mute-audio");
-      var enabled = !this.localStream.hasAudio;
-      this.publisher.publishAudio(enabled);
-      if (enabled) {
-        msgId = "mute_local_audio_button.title";
-        $button.removeClass("muted");
-      } else {
-        msgId = "unmute_local_audio_button.title";
-        $button.addClass("muted");
-      }
-      $button.attr("title", l10n.get(msgId));
-    },
-
-    /**
-     * Toggles video mute state.
-     *
-     * @param  {MouseEvent} event
-     */
-    toggleMuteVideo: function(event) {
-      event.preventDefault();
-      if (!this.localStream) {
-        return;
-      }
-      var msgId;
-      var $button = this.$(".btn-mute-video");
-      var enabled = !this.localStream.hasVideo;
-      this.publisher.publishVideo(enabled);
-      if (enabled) {
-        $button.removeClass("muted");
-        msgId = "mute_local_video_button.title";
-      } else {
-        $button.addClass("muted");
-        msgId = "unmute_local_video_button.title";
-      }
-      $button.attr("title", l10n.get(msgId));
-    },
-
-    /**
      * Publishes remote streams available once a session is connected.
      *
      * http://tokbox.com/opentok/libraries/client/js/reference/SessionConnectEvent.html
      *
      * @param  {SessionConnectEvent} event
      */
-    publish: function(event) {
-      var outgoing = this.$(".outgoing").get(0);
+    startPublishing: function(event) {
+      var outgoing = this.getDOMNode().querySelector(".outgoing");
 
-      this.publisher = this.sdk.initPublisher(outgoing, this.publisherConfig);
+      // XXX move this into its StreamingVideo component?
+      this.publisher = this.props.sdk.initPublisher(
+        outgoing, this.publisherConfig);
 
       // Suppress OT GuM custom dialog, see bug 1018875
       function preventOpeningAccessDialog(event) {
         event.preventDefault();
       }
       this.publisher.on("accessDialogOpened", preventOpeningAccessDialog);
       this.publisher.on("accessDenied", preventOpeningAccessDialog);
+
       this.publisher.on("streamCreated", function(event) {
-        this.localStream = event.stream;
-        if (this.localStream.hasAudio) {
-          this.$(".btn-mute-audio").addClass("streaming");
-        }
-        if (this.localStream.hasVideo) {
-          this.$(".btn-mute-video").addClass("streaming");
-        }
+        this.setState({
+          audio: {enabled: event.stream.hasAudio},
+          video: {enabled: event.stream.hasVideo}
+        });
       }.bind(this));
+
       this.publisher.on("streamDestroyed", function() {
-        this.localStream = null;
-        this.$(".btn-mute-audio").removeClass("streaming muted");
-        this.$(".btn-mute-video").removeClass("streaming muted");
+        this.setState({
+          audio: {enabled: false},
+          video: {enabled: false}
+        });
       }.bind(this));
 
-      this.model.session.publish(this.publisher);
+      this.props.model.session.publish(this.publisher);
+    },
+
+    /**
+     * Toggles streaming status for a given stream type.
+     *
+     * @param  {String}  type     Stream type ("audio" or "video").
+     * @param  {Boolean} enabled  Enabled stream flag.
+     */
+    publishStream: function(type, enabled) {
+      if (type === "audio") {
+        this.publisher.publishAudio(enabled);
+        this.setState({audio: {enabled: enabled}});
+      } else {
+        this.publisher.publishVideo(enabled);
+        this.setState({video: {enabled: enabled}});
+      }
     },
 
     /**
      * Unpublishes local stream.
      */
-    unpublish: function() {
+    stopPublishing: function() {
       // Unregister access OT GuM custom dialog listeners, see bug 1018875
       this.publisher.off("accessDialogOpened");
       this.publisher.off("accessDenied");
 
-      this.model.session.unpublish(this.publisher);
+      this.props.model.session.unpublish(this.publisher);
     },
 
-    /**
-     * Renders this view.
-     *
-     * @return {ConversationView}
-     */
     render: function() {
-      this.$el.html(this.template(this.model.toJSON()));
-      return this;
+      return (
+        <div className="conversation">
+          <ConversationToolbar video={this.state.video}
+                               audio={this.state.audio}
+                               publishStream={this.publishStream}
+                               hangup={this.hangup} />
+          <div className="media nested">
+            <div className="remote"><div className="incoming"></div></div>
+            <div className="local"><div className="outgoing"></div></div>
+          </div>
+        </div>
+      );
     }
   });
 
   /**
    * Notification view.
    */
   var NotificationView = BaseView.extend({
     template: _.template([
@@ -451,14 +505,16 @@ loop.shared.views = (function(_, OT, l10
       '</div>'
     ].join(""))
   });
 
   return {
     L10nView: L10nView,
     BaseView: BaseView,
     ConversationView: ConversationView,
+    ConversationToolbar: ConversationToolbar,
+    MediaControlButton: MediaControlButton,
     NotificationListView: NotificationListView,
     NotificationView: NotificationView,
     UnsupportedBrowserView: UnsupportedBrowserView,
     UnsupportedDeviceView: UnsupportedDeviceView
   };
 })(_, window.OT, document.webL10n || document.mozL10n);
--- a/browser/components/loop/jar.mn
+++ b/browser/components/loop/jar.mn
@@ -20,28 +20,24 @@ browser.jar:
   content/browser/loop/shared/css/common.css        (content/shared/css/common.css)
   content/browser/loop/shared/css/panel.css         (content/shared/css/panel.css)
   content/browser/loop/shared/css/conversation.css  (content/shared/css/conversation.css)
 
   # Shared images
   content/browser/loop/shared/img/icon_32.png                   (content/shared/img/icon_32.png)
   content/browser/loop/shared/img/icon_64.png                   (content/shared/img/icon_64.png)
   content/browser/loop/shared/img/loading-icon.gif              (content/shared/img/loading-icon.gif)
-  content/browser/loop/shared/img/audio-highlight-14x14.png     (content/shared/img/audio-highlight-14x14.png)
-  content/browser/loop/shared/img/audio-highlight-14x14@2x.png  (content/shared/img/audio-highlight-14x14@2x.png)
   content/browser/loop/shared/img/audio-inverse-14x14.png       (content/shared/img/audio-inverse-14x14.png)
   content/browser/loop/shared/img/audio-inverse-14x14@2x.png    (content/shared/img/audio-inverse-14x14@2x.png)
   content/browser/loop/shared/img/facemute-14x14.png            (content/shared/img/facemute-14x14.png)
   content/browser/loop/shared/img/facemute-14x14@2x.png         (content/shared/img/facemute-14x14@2x.png)
   content/browser/loop/shared/img/hangup-inverse-14x14.png      (content/shared/img/hangup-inverse-14x14.png)
   content/browser/loop/shared/img/hangup-inverse-14x14@2x.png   (content/shared/img/hangup-inverse-14x14@2x.png)
   content/browser/loop/shared/img/mute-inverse-14x14.png        (content/shared/img/mute-inverse-14x14.png)
   content/browser/loop/shared/img/mute-inverse-14x14@2x.png     (content/shared/img/mute-inverse-14x14@2x.png)
-  content/browser/loop/shared/img/video-highlight-14x14.png     (content/shared/img/video-highlight-14x14.png)
-  content/browser/loop/shared/img/video-highlight-14x14@2x.png  (content/shared/img/video-highlight-14x14@2x.png)
   content/browser/loop/shared/img/video-inverse-14x14.png       (content/shared/img/video-inverse-14x14.png)
   content/browser/loop/shared/img/video-inverse-14x14@2x.png    (content/shared/img/video-inverse-14x14@2x.png)
 
   # Shared scripts
   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/views.js   (content/shared/js/views.js)
 
--- a/browser/components/loop/standalone/content/index.html
+++ b/browser/components/loop/standalone/content/index.html
@@ -19,16 +19,17 @@
 
     <div id="messages"></div>
 
     <div id="main"></div>
 
     <!-- libs -->
     <script src="https://static.opentok.com/webrtc/v2.2/js/opentok.min.js"></script>
     <script type="text/javascript" src="libs/webl10n-20130617.js"></script>
+    <script type="text/javascript" src="shared/libs/react-0.10.0.js"></script>
     <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/models.js"></script>
     <script type="text/javascript" src="shared/js/views.js"></script>
--- a/browser/components/loop/standalone/content/js/webapp.js
+++ b/browser/components/loop/standalone/content/js/webapp.js
@@ -184,17 +184,17 @@ loop.webapp = (function($, _, OT) {
      * Loads conversation establishment view.
      *
      */
     loadConversation: function(loopToken) {
       if (!this._conversation.isSessionReady()) {
         // User has loaded this url directly, actually setup the call.
         return this.navigate("call/" + loopToken, {trigger: true});
       }
-      this.loadView(new sharedViews.ConversationView({
+      this.loadReactComponent(sharedViews.ConversationView({
         sdk: OT,
         model: this._conversation
       }));
     }
   });
 
   /**
    * Local helpers.
--- a/browser/components/loop/standalone/content/l10n/data.ini
+++ b/browser/components/loop/standalone/content/l10n/data.ini
@@ -1,40 +1,40 @@
 [en]
 call_has_ended=Your call has ended.
 missing_conversation_info=Missing conversation information.
 network_disconnected=The network connection terminated abruptly.
 peer_ended_conversation=Your peer ended the conversation.
 unable_retrieve_call_info=Unable to retrieve conversation information.
-hangup_button.title=Hangup
-mute_local_audio_button.title=Mute your audio
-unmute_local_audio_button.title=Unute your audio
-mute_local_video_button.title=Mute your video
-unmute_local_video_button.title=Unmute your video
+hangup_button_title=Hangup
+mute_local_audio_button_title=Mute your audio
+unmute_local_audio_button_title=Unute your audio
+mute_local_video_button_title=Mute your video
+unmute_local_video_button_title=Unmute your video
 start_call=Start the call
 welcome=Welcome to the Loop web client.
 incompatible_browser=Incompatible Browser
 powered_by_webrtc=The audio and video components of Loop are powered by WebRTC.
 use_latest_firefox.innerHTML=To use Loop, please use the latest version of <a href="{{ff_url}}">Firefox</a>.
 incompatible_device=Incompatible device
 sorry_device_unsupported=Sorry, Loop does not currently support your device.
 use_firefox_windows_mac_linux=Please open this page using the latest Firefox on Windows, Android, Mac or Linux.
 connection_error_see_console_notification=Call failed; see console for details.
 
 [fr]
 call_has_ended=L'appel est terminé.
 missing_conversation_info=Informations de communication manquantes.
 network_disconnected=La connexion réseau semble avoir été interrompue.
 peer_ended_conversation=Votre correspondant a mis fin à la communication.
 unable_retrieve_call_info=Impossible de récupérer les informations liées à cet appel.
-hangup_button.title=Terminer l'appel
-mute_local_audio_button.title=Couper la diffusion audio
-unmute_local_audio_button.title=Reprendre la diffusion audio
-mute_local_video_button.title=Couper la diffusion vidéo
-unmute_local_video_button.title=Reprendre la diffusion vidéo
+hangup_button_title=Terminer l'appel
+mute_local_audio_button_title=Couper la diffusion audio
+unmute_local_audio_button_title=Reprendre la diffusion audio
+mute_local_video_button_title=Couper la diffusion vidéo
+unmute_local_video_button_title=Reprendre la diffusion vidéo
 start_call=Démarrer l'appel
 welcome=Bienvenue sur Loop.
 incompatible_browser=Navigateur non supporté
 powered_by_webrtc=Les fonctionnalités audio et vidéo de Loop utilisent WebRTC.
 use_latest_firefox.innerHTML=Pour utiliser Loop, merci d'utiliser la dernière version de <a href="{{ff_url}}">Firefox</a>.
 incompatible_device=Plateforme non supportée
 sorry_device_unsupported=Désolé, Loop ne fonctionne actuellement pas sur votre appareil.
 use_firefox_windows_mac_linux=Merci d'ouvrir cette page avec une version récente de Firefox pour Windows, Android, Mac ou Linux.
--- a/browser/components/loop/test/desktop-local/conversation_test.js
+++ b/browser/components/loop/test/desktop-local/conversation_test.js
@@ -152,33 +152,38 @@ describe("loop.conversation", function()
           sandbox.stub(window.navigator.mozLoop, "stopAlerting");
           router.accept();
 
           sinon.assert.calledOnce(window.navigator.mozLoop.stopAlerting);
         });
       });
 
       describe("#conversation", function() {
+        beforeEach(function() {
+          sandbox.stub(router, "loadReactComponent");
+        });
+
         it("should load the ConversationView if session is set", function() {
-          sandbox.stub(loop.shared.views.ConversationView.prototype,
-            "initialize");
           conversation.set("sessionId", "fakeSessionId");
 
           router.conversation();
 
-          sinon.assert.calledOnce(router.loadView);
-          sinon.assert.calledWith(router.loadView,
-            sinon.match.instanceOf(loop.shared.views.ConversationView));
+          sinon.assert.calledOnce(router.loadReactComponent);
+          sinon.assert.calledWith(router.loadReactComponent,
+            sinon.match(function(value) {
+              return React.addons.TestUtils.isComponentOfType(
+                value, loop.shared.views.ConversationView);
+            }));
         });
 
         it("should not load the ConversationView if session is not set",
           function() {
             router.conversation();
 
-            sinon.assert.notCalled(router.loadView);
+            sinon.assert.notCalled(router.loadReactComponent);
         });
 
         it("should notify the user when session is not set",
           function() {
             router.conversation();
 
             sinon.assert.calledOnce(router._notifier.errorL10n);
             sinon.assert.calledWithExactly(router._notifier.errorL10n,
--- a/browser/components/loop/test/shared/index.html
+++ b/browser/components/loop/test/shared/index.html
@@ -11,20 +11,21 @@
 <body>
   <div id="mocha">
     <p><a href="../">Index</a></p>
   </div>
   <div id="messages"></div>
   <div id="fixtures"></div>
 
   <!-- libs -->
+  <script src="../../content/shared/libs/react-0.10.0.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="../../standalone/content/libs/webl10n-20130617.js"></script>
+  <script src="../../standalone/content/libs/webl10n-20130617.js"></script>
 
   <!-- test dependencies -->
   <script src="vendor/mocha-1.17.1.js"></script>
   <script src="vendor/chai-1.9.0.js"></script>
   <script src="vendor/sinon-1.9.0.js"></script>
   <script>
     /*global chai, mocha */
     chai.Assertion.includeStack = true;
--- a/browser/components/loop/test/shared/views_test.js
+++ b/browser/components/loop/test/shared/views_test.js
@@ -1,22 +1,25 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-/* global loop, sinon */
+/*global loop, sinon, React */
+/* jshint newcap:false */
 
 var expect = chai.expect;
 var l10n = document.webL10n || document.mozL10n;
+var TestUtils = React.addons.TestUtils;
 
 describe("loop.shared.views", function() {
   "use strict";
 
   var sharedModels = loop.shared.models,
       sharedViews = loop.shared.views,
+      getReactElementByClass = TestUtils.findRenderedDOMComponentWithClass,
       sandbox;
 
   beforeEach(function() {
     sandbox = sinon.sandbox.create();
     sandbox.useFakeTimers(); // exposes sandbox.clock as a fake timer
   });
 
   afterEach(function() {
@@ -35,19 +38,153 @@ describe("loop.shared.views", function()
       var view = new TestView();
       view.render();
 
       sinon.assert.calledOnce(l10n.translate);
       sinon.assert.calledWithExactly(l10n.translate, view.el);
     });
   });
 
+  describe("MediaControlButton", function() {
+    it("should render an enabled local audio button", function() {
+      var comp = TestUtils.renderIntoDocument(sharedViews.MediaControlButton({
+        scope: "local",
+        type: "audio",
+        action: function(){},
+        enabled: true
+      }));
+
+      expect(comp.getDOMNode().classList.contains("muted")).eql(false);
+    });
+
+    it("should render a muted local audio button", function() {
+      var comp = TestUtils.renderIntoDocument(sharedViews.MediaControlButton({
+        scope: "local",
+        type: "audio",
+        action: function(){},
+        enabled: false
+      }));
+
+      expect(comp.getDOMNode().classList.contains("muted")).eql(true);
+    });
+
+    it("should render an enabled local video button", function() {
+      var comp = TestUtils.renderIntoDocument(sharedViews.MediaControlButton({
+        scope: "local",
+        type: "video",
+        action: function(){},
+        enabled: true
+      }));
+
+      expect(comp.getDOMNode().classList.contains("muted")).eql(false);
+    });
+
+    it("should render a muted local video button", function() {
+      var comp = TestUtils.renderIntoDocument(sharedViews.MediaControlButton({
+        scope: "local",
+        type: "video",
+        action: function(){},
+        enabled: false
+      }));
+
+      expect(comp.getDOMNode().classList.contains("muted")).eql(true);
+    });
+  });
+
+  describe("ConversationToolbar", function() {
+    var hangup, publishStream;
+
+    function mountTestComponent(props) {
+      return TestUtils.renderIntoDocument(
+        sharedViews.ConversationToolbar(props));
+    }
+
+    beforeEach(function() {
+      hangup = sandbox.stub();
+      publishStream = sandbox.stub();
+    });
+
+    it("should hangup when hangup button is clicked", function() {
+      var comp = mountTestComponent({
+        hangup: hangup,
+        publishStream: publishStream,
+        audio: {enabled: true}
+      });
+
+      TestUtils.Simulate.click(
+        comp.getDOMNode().querySelector(".btn-hangup"));
+
+      sinon.assert.calledOnce(hangup);
+      sinon.assert.calledWithExactly(hangup);
+    });
+
+    it("should unpublish audio when audio mute btn is clicked", function() {
+      var comp = mountTestComponent({
+        hangup: hangup,
+        publishStream: publishStream,
+        audio: {enabled: true}
+      });
+
+      TestUtils.Simulate.click(
+        comp.getDOMNode().querySelector(".btn-mute-audio"));
+
+      sinon.assert.calledOnce(publishStream);
+      sinon.assert.calledWithExactly(publishStream, "audio", false);
+    });
+
+    it("should publish audio when audio mute btn is clicked", function() {
+      var comp = mountTestComponent({
+        hangup: hangup,
+        publishStream: publishStream,
+        audio: {enabled: false}
+      });
+
+      TestUtils.Simulate.click(
+        comp.getDOMNode().querySelector(".btn-mute-audio"));
+
+      sinon.assert.calledOnce(publishStream);
+      sinon.assert.calledWithExactly(publishStream, "audio", true);
+    });
+
+    it("should unpublish video when video mute btn is clicked", function() {
+      var comp = mountTestComponent({
+        hangup: hangup,
+        publishStream: publishStream,
+        video: {enabled: true}
+      });
+
+      TestUtils.Simulate.click(
+        comp.getDOMNode().querySelector(".btn-mute-video"));
+
+      sinon.assert.calledOnce(publishStream);
+      sinon.assert.calledWithExactly(publishStream, "video", false);
+    });
+
+    it("should publish video when video mute btn is clicked", function() {
+      var comp = mountTestComponent({
+        hangup: hangup,
+        publishStream: publishStream,
+        video: {enabled: false}
+      });
+
+      TestUtils.Simulate.click(
+        comp.getDOMNode().querySelector(".btn-mute-video"));
+
+      sinon.assert.calledOnce(publishStream);
+      sinon.assert.calledWithExactly(publishStream, "video", true);
+    });
+  });
+
   describe("ConversationView", function() {
     var fakeSDK, fakeSessionData, fakeSession, fakePublisher, model;
 
+    function mountTestComponent(props) {
+      return TestUtils.renderIntoDocument(sharedViews.ConversationView(props));
+    }
+
     beforeEach(function() {
       fakeSessionData = {
         sessionId:    "sessionId",
         sessionToken: "sessionToken",
         apiKey:       "apiKey"
       };
       fakeSession = _.extend({
         connection: {connectionId: 42},
@@ -67,206 +204,171 @@ describe("loop.shared.views", function()
         initPublisher: sandbox.stub().returns(fakePublisher),
         initSession: sandbox.stub().returns(fakeSession)
       };
       model = new sharedModels.ConversationModel(fakeSessionData, {
         sdk: fakeSDK
       });
     });
 
-    describe("#initialize", function() {
-      it("should require a sdk object", function() {
-        expect(function() {
-          new sharedViews.ConversationView();
-        }).to.Throw(Error, /sdk/);
-      });
-
+    describe("#componentDidMount", function() {
       it("should start a session", function() {
         sandbox.stub(model, "startSession");
 
-        new sharedViews.ConversationView({sdk: fakeSDK, model: model});
+        mountTestComponent({sdk: fakeSDK, model: model});
 
         sinon.assert.calledOnce(model.startSession);
       });
     });
 
     describe("constructed", function() {
+      var comp;
+
+      beforeEach(function() {
+        comp = mountTestComponent({sdk: fakeSDK, model: model});
+      });
+
       describe("#hangup", function() {
+        beforeEach(function() {
+          comp.startPublishing();
+        });
+
         it("should disconnect the session", function() {
-          var view = new sharedViews.ConversationView({
-            sdk: fakeSDK,
-            model: model
-          });
           sandbox.stub(model, "endSession");
-          view.publish();
 
-          view.hangup({preventDefault: function() {}});
+          comp.hangup();
 
           sinon.assert.calledOnce(model.endSession);
         });
+
+        it("should stop publishing local streams", function() {
+          comp.hangup();
+
+          sinon.assert.calledOnce(fakeSession.unpublish);
+        });
       });
 
-      describe("#publish", function() {
-        var view;
-
-        beforeEach(function() {
-          view = new sharedViews.ConversationView({
-            sdk: fakeSDK,
-            model: model
-          });
-        });
-
+      describe("#startPublishing", function() {
         it("should publish local stream", function() {
-          view.publish();
+          comp.startPublishing();
 
           sinon.assert.calledOnce(fakeSDK.initPublisher);
           sinon.assert.calledOnce(fakeSession.publish);
         });
 
         it("should start listening to OT publisher accessDialogOpened and " +
           " accessDenied events",
           function() {
-            view.publish();
+            comp.startPublishing();
 
             sinon.assert.called(fakePublisher.on);
             sinon.assert.calledWith(fakePublisher.on, "accessDialogOpened");
             sinon.assert.calledWith(fakePublisher.on, "accessDenied");
           });
       });
 
-      describe("#unpublish", function() {
-        var view;
-
+      describe("#stopPublishing", function() {
         beforeEach(function() {
-          view = new sharedViews.ConversationView({
-            sdk: fakeSDK,
-            model: model
-          });
-          view.publish();
+          comp.startPublishing();
         });
 
-        it("should unpublish local stream", function() {
-          view.unpublish();
+        it("should stop publish local stream", function() {
+          comp.stopPublishing();
 
           sinon.assert.calledOnce(fakeSession.unpublish);
         });
 
         it("should unsubscribe from accessDialogOpened and accessDenied events",
           function() {
-            view.unpublish();
+            comp.stopPublishing();
 
             sinon.assert.calledTwice(fakePublisher.off);
             sinon.assert.calledWith(fakePublisher.off, "accessDialogOpened");
             sinon.assert.calledWith(fakePublisher.off, "accessDenied");
           });
       });
 
-      describe("#toggleMuteAudio", function() {
-        var view;
+      describe("#publishStream", function() {
+        var comp;
 
         beforeEach(function() {
-          view = new sharedViews.ConversationView({
-            sdk: fakeSDK,
-            model: model
-          });
-          view.publish();
+          comp = mountTestComponent({sdk: fakeSDK, model: model});
+          comp.startPublishing();
         });
 
-        it("should unpublish local audio when enabled", function() {
-          view.localStream = {hasAudio: true};
+        it("should start streaming local audio", function() {
+          comp.publishStream("audio", true);
 
-          view.toggleMuteAudio({preventDefault: sandbox.spy()});
+          sinon.assert.calledOnce(fakePublisher.publishAudio);
+          sinon.assert.calledWithExactly(fakePublisher.publishAudio, true);
+        });
+
+        it("should stop streaming local audio", function() {
+          comp.publishStream("audio", false);
 
           sinon.assert.calledOnce(fakePublisher.publishAudio);
           sinon.assert.calledWithExactly(fakePublisher.publishAudio, false);
         });
 
-        it("should publish local audio when disabled", function() {
-          view.localStream = {hasAudio: false};
-
-          view.toggleMuteAudio({preventDefault: sandbox.spy()});
-
-          sinon.assert.calledOnce(fakePublisher.publishAudio);
-          sinon.assert.calledWithExactly(fakePublisher.publishAudio, true);
-        });
-      });
+        it("should start streaming local video", function() {
+          comp.publishStream("video", true);
 
-      describe("#toggleMuteVideo", function() {
-        var view;
-
-        beforeEach(function() {
-          view = new sharedViews.ConversationView({
-            sdk: fakeSDK,
-            model: model
-          });
-          view.publish();
+          sinon.assert.calledOnce(fakePublisher.publishVideo);
+          sinon.assert.calledWithExactly(fakePublisher.publishVideo, true);
         });
 
-        it("should unpublish local video when enabled", function() {
-          view.localStream = {hasVideo: true};
-
-          view.toggleMuteVideo({preventDefault: sandbox.spy()});
+        it("should stop streaming local video", function() {
+          comp.publishStream("video", false);
 
           sinon.assert.calledOnce(fakePublisher.publishVideo);
           sinon.assert.calledWithExactly(fakePublisher.publishVideo, false);
         });
-
-        it("should publish local video when disabled", function() {
-          view.localStream = {hasVideo: false};
-
-          view.toggleMuteVideo({preventDefault: sandbox.spy()});
-
-          sinon.assert.calledOnce(fakePublisher.publishVideo);
-          sinon.assert.calledWithExactly(fakePublisher.publishVideo, true);
-        });
       });
 
       describe("Model events", function() {
-        var view;
-
-        beforeEach(function() {
-          sandbox.stub(sharedViews.ConversationView.prototype, "publish");
-          sandbox.stub(sharedViews.ConversationView.prototype, "unpublish");
-          view = new sharedViews.ConversationView({sdk: fakeSDK, model: model});
-        });
-
-        it("should publish local stream on session:connected", function() {
+        it("should start streaming on session:connected", function() {
           model.trigger("session:connected");
 
-          sinon.assert.calledOnce(view.publish);
+          sinon.assert.calledOnce(fakeSDK.initPublisher);
         });
 
         it("should publish remote streams on session:stream-created",
           function() {
             var s1 = {connection: {connectionId: 42}};
             var s2 = {connection: {connectionId: 43}};
 
             model.trigger("session:stream-created", {streams: [s1, s2]});
 
             sinon.assert.calledOnce(fakeSession.subscribe);
             sinon.assert.calledWith(fakeSession.subscribe, s2);
           });
 
         it("should unpublish local stream on session:ended", function() {
+          comp.startPublishing();
+
           model.trigger("session:ended");
 
-          sinon.assert.calledOnce(view.unpublish);
+          sinon.assert.calledOnce(fakeSession.unpublish);
         });
 
         it("should unpublish local stream on session:peer-hungup", function() {
+          comp.startPublishing();
+
           model.trigger("session:peer-hungup");
 
-          sinon.assert.calledOnce(view.unpublish);
+          sinon.assert.calledOnce(fakeSession.unpublish);
         });
 
         it("should unpublish local stream on session:network-disconnected",
           function() {
+            comp.startPublishing();
+
             model.trigger("session:network-disconnected");
 
-            sinon.assert.calledOnce(view.unpublish);
+            sinon.assert.calledOnce(fakeSession.unpublish);
           });
       });
     });
   });
 
   describe("NotificationView", function() {
     var collection, model, view;
 
--- a/browser/components/loop/test/standalone/index.html
+++ b/browser/components/loop/test/standalone/index.html
@@ -11,16 +11,17 @@
 <body>
   <div id="mocha">
     <p><a href="../">Index</a></p>
     <p><a href="../shared/">Shared Tests</a></p>
  </div>
   <div id="messages"></div>
   <div id="fixtures"></div>
   <!-- libs -->
+  <script src="../../content/shared/libs/react-0.10.0.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="../../standalone/content/libs/webl10n-20130617.js"></script>
   <!-- test dependencies -->
   <script src="../shared/vendor/mocha-1.17.1.js"></script>
   <script src="../shared/vendor/chai-1.9.0.js"></script>
   <script src="../shared/vendor/sinon-1.9.0.js"></script>
--- a/browser/components/loop/test/standalone/webapp_test.js
+++ b/browser/components/loop/test/standalone/webapp_test.js
@@ -70,16 +70,17 @@ describe("loop.webapp", function() {
 
     beforeEach(function() {
       conversation = new sharedModels.ConversationModel({}, {sdk: {}});
       router = new loop.webapp.WebappRouter({
         conversation: conversation,
         notifier: notifier
       });
       sandbox.stub(router, "loadView");
+      sandbox.stub(router, "loadReactComponent");
       sandbox.stub(router, "navigate");
     });
 
     describe("#startCall", function() {
       it("should navigate back home if session token is missing", function() {
         router.startCall();
 
         sinon.assert.calledOnce(router.navigate);
@@ -157,24 +158,26 @@ describe("loop.webapp", function() {
           router.initiate("fakeToken");
 
           sinon.assert.calledOnce(conversation.endSession);
         });
       });
 
       describe("#loadConversation", function() {
         it("should load the ConversationView if session is set", function() {
-          sandbox.stub(sharedViews.ConversationView.prototype, "initialize");
           conversation.set("sessionId", "fakeSessionId");
 
           router.loadConversation();
 
-          sinon.assert.calledOnce(router.loadView);
-          sinon.assert.calledWith(router.loadView,
-            sinon.match.instanceOf(sharedViews.ConversationView));
+          sinon.assert.calledOnce(router.loadReactComponent);
+          sinon.assert.calledWith(router.loadReactComponent,
+            sinon.match(function(value) {
+              return React.addons.TestUtils.isComponentOfType(
+                value, loop.shared.views.ConversationView);
+            }));
         });
 
         it("should navigate to #call/{token} if session isn't ready",
           function() {
             router.loadConversation("fakeToken");
 
             sinon.assert.calledOnce(router.navigate);
             sinon.assert.calledWithMatch(router.navigate, "call/fakeToken");
--- a/browser/devtools/shared/test/browser.ini
+++ b/browser/devtools/shared/test/browser.ini
@@ -21,17 +21,18 @@ support-files =
 [browser_graphs-04.js]
 [browser_graphs-05.js]
 [browser_graphs-06.js]
 [browser_graphs-07.js]
 [browser_graphs-08.js]
 [browser_graphs-09.js]
 [browser_graphs-10a.js]
 [browser_graphs-10b.js]
-[browser_graphs-11.js]
+[browser_graphs-11a.js]
+[browser_graphs-11b.js]
 [browser_graphs-12.js]
 [browser_graphs-13.js]
 [browser_graphs-14.js]
 [browser_layoutHelpers.js]
 [browser_layoutHelpers-getBoxQuads.js]
 [browser_num-l10n.js]
 [browser_observableobject.js]
 [browser_outputparser.js]
--- a/browser/devtools/shared/test/browser_graphs-02.js
+++ b/browser/devtools/shared/test/browser_graphs-02.js
@@ -1,12 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-// Tests that graph widgets can properly add data and regions.
+// Tests that graph widgets can properly add data, regions and highlights.
 
 const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
 const TEST_REGIONS = [{ start: 320, end: 460 }, { start: 780, end: 860 }];
 let {LineGraphWidget} = Cu.import("resource:///modules/devtools/Graphs.jsm", {});
 let {DOMHelpers} = Cu.import("resource:///modules/devtools/DOMHelpers.jsm", {});
 let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
 let {Hosts} = devtools.require("devtools/framework/toolbox-hosts");
 
@@ -17,23 +17,24 @@ let test = Task.async(function*() {
   finish();
 });
 
 function* performTest() {
   let [host, win, doc] = yield createHost();
   let graph = new LineGraphWidget(doc.body, "fps");
   yield graph.once("ready");
 
-  testGraph(graph);
+  testDataAndRegions(graph);
+  testHighlights(graph);
 
   graph.destroy();
   host.destroy();
 }
 
-function testGraph(graph) {
+function testDataAndRegions(graph) {
   let thrown1;
   try {
     graph.setRegions(TEST_REGIONS);
   } catch (e) {
     thrown1 = true;
   }
   ok(thrown1, "Setting regions before setting data shouldn't work.");
 
@@ -47,25 +48,39 @@ function testGraph(graph) {
     thrown2 = true;
   }
   ok(thrown2, "Setting regions twice shouldn't work.");
 
   ok(graph.hasData(), "The graph should now have the data source set.");
   ok(graph.hasRegions(), "The graph should now have the regions set.");
 
   is(graph.dataScaleX,
-     graph.width / (4180 - 112), // last & first tick in TEST_DATA
+     graph.width / 4180, // last & first tick in TEST_DATA
     "The data scale on the X axis is correct.");
 
   is(graph.dataScaleY,
      graph.height / 60 * 0.85, // max value in TEST_DATA * GRAPH_DAMPEN_VALUES
     "The data scale on the Y axis is correct.");
 
   for (let i = 0; i < TEST_REGIONS.length; i++) {
     let original = TEST_REGIONS[i];
     let normalized = graph._regions[i];
 
     is(original.start * graph.dataScaleX, normalized.start,
       "The region's start value was properly normalized.");
     is(original.end * graph.dataScaleX, normalized.end,
       "The region's end value was properly normalized.");
   }
 }
+
+function testHighlights(graph) {
+  graph.setMask(TEST_REGIONS);
+  ok(graph.hasMask(),
+    "The graph should now have the highlights set.");
+
+  graph.setMask([]);
+  ok(graph.hasMask(),
+    "The graph shouldn't have anything highlighted.");
+
+  graph.setMask(null);
+  ok(!graph.hasMask(),
+    "The graph should have everything highlighted.");
+}
rename from browser/devtools/shared/test/browser_graphs-11.js
rename to browser/devtools/shared/test/browser_graphs-11a.js
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/test/browser_graphs-11b.js
@@ -0,0 +1,132 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that bar graph's legend items handle mouseover/mouseout.
+
+let {BarGraphWidget} = Cu.import("resource:///modules/devtools/Graphs.jsm", {});
+let {DOMHelpers} = Cu.import("resource:///modules/devtools/DOMHelpers.jsm", {});
+let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
+let {Hosts} = devtools.require("devtools/framework/toolbox-hosts");
+
+const CATEGORIES = [
+  { color: "#46afe3", label: "Foo" },
+  { color: "#eb5368", label: "Bar" },
+  { color: "#70bf53", label: "Baz" }
+];
+
+let test = Task.async(function*() {
+  yield promiseTab("about:blank");
+  yield performTest();
+  gBrowser.removeCurrentTab();
+  finish();
+});
+
+function* performTest() {
+  let [host, win, doc] = yield createHost();
+  doc.body.setAttribute("style", "position: fixed; width: 100%; height: 100%; margin: 0;");
+
+  let graph = new BarGraphWidget(doc.body, 1);
+  graph.fixedWidth = 200;
+  graph.fixedHeight = 100;
+
+  yield graph.once("ready");
+  yield testGraph(graph);
+
+  graph.destroy();
+  host.destroy();
+}
+
+function* testGraph(graph) {
+  graph.format = CATEGORIES;
+  graph.dataOffsetX = 1000;
+  graph.setData([{
+    delta: 1100, values: [0, 2, 3]
+  }, {
+    delta: 1200, values: [1, 0, 2]
+  }, {
+    delta: 1300, values: [2, 1, 0]
+  }, {
+    delta: 1400, values: [0, 3, 1]
+  }, {
+    delta: 1500, values: [3, 0, 2]
+  }, {
+    delta: 1600, values: [3, 2, 0]
+  }]);
+
+  is(graph._blocksBoundingRects.toSource(), "[{type:1, start:0, end:33.33333333333333, top:70, bottom:100}, {type:2, start:0, end:33.33333333333333, top:24, bottom:69}, {type:0, start:34.33333333333333, end:66.66666666666666, top:85, bottom:100}, {type:2, start:34.33333333333333, end:66.66666666666666, top:54, bottom:84}, {type:0, start:67.66666666666666, end:100, top:70, bottom:100}, {type:1, start:67.66666666666666, end:100, top:54, bottom:69}, {type:1, start:101, end:133.33333333333331, top:55, bottom:100}, {type:2, start:101, end:133.33333333333331, top:39, bottom:54}, {type:0, start:134.33333333333331, end:166.66666666666666, top:55, bottom:100}, {type:2, start:134.33333333333331, end:166.66666666666666, top:24, bottom:54}, {type:0, start:167.66666666666666, end:200, top:55, bottom:100}, {type:1, start:167.66666666666666, end:200, top:24, bottom:54}]",
+    "The correct blocks bounding rects were calculated for the bar graph.");
+
+  let legendItems = graph._document.querySelectorAll(".bar-graph-widget-legend-item");
+  is(legendItems.length, 3,
+    "Three legend items should exist in the entire graph.");
+
+  yield testLegend(graph, 0, {
+    highlights: "[{type:0, start:34.33333333333333, end:66.66666666666666, top:85, bottom:100}, {type:0, start:67.66666666666666, end:100, top:70, bottom:100}, {type:0, start:134.33333333333331, end:166.66666666666666, top:55, bottom:100}, {type:0, start:167.66666666666666, end:200, top:55, bottom:100}]",
+    selection: "({start:34.33333333333333, end:200})",
+    leftmost: "({type:0, start:34.33333333333333, end:66.66666666666666, top:85, bottom:100})",
+    rightmost: "({type:0, start:167.66666666666666, end:200, top:55, bottom:100})"
+  });
+  yield testLegend(graph, 1, {
+    highlights: "[{type:1, start:0, end:33.33333333333333, top:70, bottom:100}, {type:1, start:67.66666666666666, end:100, top:54, bottom:69}, {type:1, start:101, end:133.33333333333331, top:55, bottom:100}, {type:1, start:167.66666666666666, end:200, top:24, bottom:54}]",
+    selection: "({start:0, end:200})",
+    leftmost: "({type:1, start:0, end:33.33333333333333, top:70, bottom:100})",
+    rightmost: "({type:1, start:167.66666666666666, end:200, top:24, bottom:54})"
+  });
+  yield testLegend(graph, 2, {
+    highlights: "[{type:2, start:0, end:33.33333333333333, top:24, bottom:69}, {type:2, start:34.33333333333333, end:66.66666666666666, top:54, bottom:84}, {type:2, start:101, end:133.33333333333331, top:39, bottom:54}, {type:2, start:134.33333333333331, end:166.66666666666666, top:24, bottom:54}]",
+    selection: "({start:0, end:166.66666666666666})",
+    leftmost: "({type:2, start:0, end:33.33333333333333, top:24, bottom:69})",
+    rightmost: "({type:2, start:134.33333333333331, end:166.66666666666666, top:24, bottom:54})"
+  });
+}
+
+function* testLegend(graph, index, { highlights, selection, leftmost, rightmost }) {
+  // Hover.
+
+  let legendItems = graph._document.querySelectorAll(".bar-graph-widget-legend-item");
+  let colorBlock = legendItems[index].querySelector("[view=color]");
+
+  let debounced = graph.once("legend-hover");
+  graph._onLegendMouseOver({ target: colorBlock });
+  ok(!graph.hasMask(), "The graph shouldn't get highlights immediately.");
+
+  let [type, rects] = yield debounced;
+  ok(graph.hasMask(), "The graph should now have highlights.");
+
+  is(type, index,
+    "The legend item was correctly hovered.");
+  is(rects.toSource(), highlights,
+    "The legend item highlighted the correct regions.");
+
+  // Unhover.
+
+  let unhovered = graph.once("legend-unhover");
+  graph._onLegendMouseOut();
+  ok(!graph.hasMask(), "The graph shouldn't have highlights anymore.");
+
+  yield unhovered;
+  ok(true, "The 'legend-mouseout' event was emitted.");
+
+  // Select.
+
+  let selected = graph.once("legend-selection");
+  graph._onLegendMouseDown(mockEvent(colorBlock));
+  ok(graph.hasSelection(), "The graph should now have a selection.");
+  is(graph.getSelection().toSource(), selection, "The graph has a correct selection.");
+
+  let [left, right] = yield selected;
+  is(left.toSource(), leftmost, "The correct leftmost data block was found.");
+  is(right.toSource(), rightmost, "The correct rightmost data block was found.");
+
+  // Deselect.
+
+  graph.dropSelection();
+}
+
+function mockEvent(node) {
+  return {
+    target: node,
+    preventDefault: () => {},
+    stopPropagation: () => {}
+  };
+}
--- a/browser/devtools/shared/widgets/Graphs.jsm
+++ b/browser/devtools/shared/widgets/Graphs.jsm
@@ -71,16 +71,21 @@ const BAR_GRAPH_BACKGROUND_GRADIENT_END 
 
 const BAR_GRAPH_CLIPHEAD_LINE_COLOR = "#666";
 const BAR_GRAPH_SELECTION_LINE_COLOR = "#555";
 const BAR_GRAPH_SELECTION_BACKGROUND_COLOR = "rgba(0,136,204,0.25)";
 const BAR_GRAPH_SELECTION_STRIPES_COLOR = "rgba(255,255,255,0.1)";
 const BAR_GRAPH_REGION_BACKGROUND_COLOR = "transparent";
 const BAR_GRAPH_REGION_STRIPES_COLOR = "rgba(237,38,85,0.2)";
 
+const BAR_GRAPH_HIGHLIGHTS_MASK_BACKGROUND = "rgba(255,255,255,0.75)";
+const BAR_GRAPH_HIGHLIGHTS_MASK_STRIPES = "rgba(255,255,255,0.5)";
+
+const BAR_GRAPH_LEGEND_MOUSEOVER_DEBOUNCE = 50; // ms
+
 /**
  * Small data primitives for all graphs.
  */
 this.GraphCursor = function() {}
 this.GraphSelection = function() {}
 this.GraphSelectionDragger = function() {}
 this.GraphSelectionResizer = function() {}
 
@@ -227,21 +232,27 @@ AbstractCanvasGraph.prototype = {
 
     let ownerWindow = this._parent.ownerDocument.defaultView;
     ownerWindow.removeEventListener("resize", this._onResize);
 
     this._window.cancelAnimationFrame(this._animationId);
     this._iframe.remove();
 
     this._data = null;
+    this._mask = null;
+    this._maskArgs = null;
     this._regions = null;
+
     this._cachedBackgroundImage = null;
     this._cachedGraphImage = null;
+    this._cachedMaskImage = null;
     this._renderTargets.clear();
     gCachedStripePattern.clear();
+
+    this.emit("destroyed");
   },
 
   /**
    * Rendering options. Subclasses should override these.
    */
   clipheadLineWidth: 1,
   clipheadLineColor: "transparent",
   selectionLineWidth: 1,
@@ -271,16 +282,25 @@ AbstractCanvasGraph.prototype = {
    * in `setData`. The graph image is not rebuilt on each frame, but
    * only when the data source changes.
    */
   buildGraphImage: function() {
     throw "This method needs to be implemented by inheriting classes.";
   },
 
   /**
+   * Optionally builds and caches a mask image for this graph, composited
+   * over the data image created via `buildGraphImage`. Inheriting classes
+   * may override this method.
+   */
+  buildMaskImage: function() {
+    return null;
+  },
+
+  /**
    * When setting the data source, the coordinates and values may be
    * stretched or squeezed on the X/Y axis, to fit into the available space.
    */
   dataScaleX: 1,
   dataScaleY: 1,
 
   /**
    * Sets the data source for this graph.
@@ -304,16 +324,29 @@ AbstractCanvasGraph.prototype = {
    *         A promise resolved once the data is set.
    */
   setDataWhenReady: Task.async(function*(data) {
     yield this.ready();
     this.setData(data);
   }),
 
   /**
+   * Adds a mask to this graph.
+   *
+   * @param any mask, options
+   *        See `buildMaskImage` in inheriting classes for the required args.
+   */
+  setMask: function(mask, ...options) {
+    this._mask = mask;
+    this._maskArgs = [mask, ...options];
+    this._cachedMaskImage = this.buildMaskImage.apply(this, this._maskArgs);
+    this._shouldRedraw = true;
+  },
+
+  /**
    * Adds regions to this graph.
    *
    * See the "Language" section in the constructor documentation
    * for details about what "regions" represent.
    *
    * @param array regions
    *        A list of { start, end } values.
    */
@@ -336,16 +369,24 @@ AbstractCanvasGraph.prototype = {
    * Gets whether or not this graph has a data source.
    * @return boolean
    */
   hasData: function() {
     return !!this._data;
   },
 
   /**
+   * Gets whether or not this graph has any mask applied.
+   * @return boolean
+   */
+  hasMask: function() {
+    return !!this._mask;
+  },
+
+  /**
    * Gets whether or not this graph has any regions.
    * @return boolean
    */
   hasRegions: function() {
     return !!this._regions;
   },
 
   /**
@@ -577,16 +618,19 @@ AbstractCanvasGraph.prototype = {
     this._iframe.setAttribute("height", bounds.height);
     this._width = this._canvas.width = bounds.width * this._pixelRatio;
     this._height = this._canvas.height = bounds.height * this._pixelRatio;
 
     if (this.hasData()) {
       this._cachedBackgroundImage = this.buildBackgroundImage();
       this._cachedGraphImage = this.buildGraphImage();
     }
+    if (this.hasMask()) {
+      this._cachedMaskImage = this.buildMaskImage.apply(this, this._maskArgs);
+    }
     if (this.hasRegions()) {
       this._bakeRegions(this._regions, this._cachedGraphImage);
     }
 
     this._shouldRedraw = true;
     this.emit("refresh");
   },
 
@@ -643,21 +687,31 @@ AbstractCanvasGraph.prototype = {
    */
   _drawWidget: function() {
     if (!this._shouldRedraw) {
       return;
     }
     let ctx = this._ctx;
     ctx.clearRect(0, 0, this._width, this._height);
 
+    if (this._cachedGraphImage) {
+      ctx.drawImage(this._cachedGraphImage, 0, 0, this._width, this._height);
+    }
+    if (this._cachedMaskImage) {
+      ctx.globalCompositeOperation = "destination-out";
+      ctx.drawImage(this._cachedMaskImage, 0, 0, this._width, this._height);
+    }
     if (this._cachedBackgroundImage) {
+      ctx.globalCompositeOperation = "destination-over";
       ctx.drawImage(this._cachedBackgroundImage, 0, 0, this._width, this._height);
     }
-    if (this._cachedGraphImage) {
-      ctx.drawImage(this._cachedGraphImage, 0, 0, this._width, this._height);
+
+    // Revert to the original global composition operation.
+    if (this._cachedMaskImage || this._cachedBackgroundImage) {
+      ctx.globalCompositeOperation = "source-over";
     }
 
     if (this.hasCursor()) {
       this._drawCliphead();
     }
     if (this.hasSelection() || this.hasSelectionInProgress()) {
       this._drawSelection();
     }
@@ -1108,23 +1162,28 @@ LineGraphWidget.prototype = Heritage.ext
   clipheadLineColor: LINE_GRAPH_CLIPHEAD_LINE_COLOR,
   selectionLineColor: LINE_GRAPH_SELECTION_LINE_COLOR,
   selectionBackgroundColor: LINE_GRAPH_SELECTION_BACKGROUND_COLOR,
   selectionStripesColor: LINE_GRAPH_SELECTION_STRIPES_COLOR,
   regionBackgroundColor: LINE_GRAPH_REGION_BACKGROUND_COLOR,
   regionStripesColor: LINE_GRAPH_REGION_STRIPES_COLOR,
 
   /**
+   * Optionally offsets the `delta` in the data source by this scalar.
+   */
+  dataOffsetX: 0,
+
+  /**
    * Points that are too close too each other in the graph will not be rendered.
    * This scalar specifies the required minimum squared distance between points.
    */
   minDistanceBetweenPoints: LINE_GRAPH_MIN_SQUARED_DISTANCE_BETWEEN_POINTS,
 
   /**
-   * Renders the graph on a canvas.
+   * Renders the graph's data source.
    * @see AbstractCanvasGraph.prototype.buildGraphImage
    */
   buildGraphImage: function() {
     let { canvas, ctx } = this._getNamedCanvas("line-graph-data");
     let width = this._width;
     let height = this._height;
 
     let totalTicks = this._data.length;
@@ -1135,17 +1194,17 @@ LineGraphWidget.prototype = Heritage.ext
     let sumValues = 0;
 
     for (let { delta, value } of this._data) {
       maxValue = Math.max(value, maxValue);
       minValue = Math.min(value, minValue);
       sumValues += value;
     }
 
-    let dataScaleX = this.dataScaleX = width / (lastTick - firstTick);
+    let dataScaleX = this.dataScaleX = width / (lastTick - this.dataOffsetX);
     let dataScaleY = this.dataScaleY = height / maxValue * LINE_GRAPH_DAMPEN_VALUES;
 
     /**
      * Calculates the squared distance between two 2D points.
      */
     function distSquared(x0, y0, x1, y1) {
       let xs = x1 - x0;
       let ys = y1 - y0;
@@ -1161,17 +1220,17 @@ LineGraphWidget.prototype = Heritage.ext
     ctx.strokeStyle = LINE_GRAPH_STROKE_COLOR;
     ctx.lineWidth = LINE_GRAPH_STROKE_WIDTH * this._pixelRatio;
     ctx.beginPath();
 
     let prevX = 0;
     let prevY = 0;
 
     for (let { delta, value } of this._data) {
-      let currX = (delta - firstTick) * dataScaleX;
+      let currX = (delta - this.dataOffsetX) * dataScaleX;
       let currY = height - value * dataScaleY;
 
       if (delta == firstTick) {
         ctx.moveTo(-LINE_GRAPH_STROKE_WIDTH, height);
         ctx.lineTo(-LINE_GRAPH_STROKE_WIDTH, currY);
       }
 
       let distance = distSquared(prevX, prevY, currX, currY);
@@ -1346,19 +1405,34 @@ LineGraphWidget.prototype = Heritage.ext
  * represents a "block" inside that "bar", plotted at the "delta" position.
  *
  * @param nsIDOMNode parent
  *        The parent node holding the graph.
  */
 this.BarGraphWidget = function(parent, ...args) {
   AbstractCanvasGraph.apply(this, [parent, "bar-graph", ...args]);
 
+  // Populated with [node, event, listener] entries which need to be removed
+  // when this graph is being destroyed.
+  this.outstandingEventListeners = [];
+
   this.once("ready", () => {
+    this._onLegendMouseOver = this._onLegendMouseOver.bind(this);
+    this._onLegendMouseOut = this._onLegendMouseOut.bind(this);
+    this._onLegendMouseDown = this._onLegendMouseDown.bind(this);
+    this._onLegendMouseUp = this._onLegendMouseUp.bind(this);
     this._createLegend();
   });
+
+  this.once("destroyed", () => {
+    for (let [node, event, listener] of this.outstandingEventListeners) {
+      node.removeEventListener(event, listener);
+    }
+    this.outstandingEventListeners = null;
+  });
 }
 
 BarGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
   clipheadLineColor: BAR_GRAPH_CLIPHEAD_LINE_COLOR,
   selectionLineColor: BAR_GRAPH_SELECTION_LINE_COLOR,
   selectionBackgroundColor: BAR_GRAPH_SELECTION_BACKGROUND_COLOR,
   selectionStripesColor: BAR_GRAPH_SELECTION_STRIPES_COLOR,
   regionBackgroundColor: BAR_GRAPH_REGION_BACKGROUND_COLOR,
@@ -1366,16 +1440,21 @@ BarGraphWidget.prototype = Heritage.exte
 
   /**
    * List of colors used to fill each block inside every bar, also
    * corresponding to labels displayed in this graph's legend.
    */
   format: null,
 
   /**
+   * Optionally offsets the `delta` in the data source by this scalar.
+   */
+  dataOffsetX: 0,
+
+  /**
    * Bars that are too close too each other in the graph will be combined.
    * This scalar specifies the required minimum width of each bar.
    */
   minBarsWidth: BAR_GRAPH_MIN_BARS_WIDTH,
 
   /**
    * Blocks in a bar that are too thin inside the bar will not be rendered.
    * This scalar specifies the required minimum height of each block.
@@ -1396,141 +1475,222 @@ BarGraphWidget.prototype = Heritage.exte
     gradient.addColorStop(1, BAR_GRAPH_BACKGROUND_GRADIENT_END);
     ctx.fillStyle = gradient;
     ctx.fillRect(0, 0, width, height);
 
     return canvas;
   },
 
   /**
-   * Renders the graph on a canvas.
+   * Renders the graph's data source.
    * @see AbstractCanvasGraph.prototype.buildGraphImage
    */
   buildGraphImage: function() {
     if (!this.format || !this.format.length) {
       throw "The graph format traits are mandatory to style the data source.";
     }
     let { canvas, ctx } = this._getNamedCanvas("bar-graph-data");
     let width = this._width;
     let height = this._height;
 
     let totalTypes = this.format.length;
     let totalTicks = this._data.length;
-    let firstTick = this._data[0].delta;
     let lastTick = this._data[totalTicks - 1].delta;
 
     let minBarsWidth = this.minBarsWidth * this._pixelRatio;
     let minBlocksHeight = this.minBlocksHeight * this._pixelRatio;
 
-    let dataScaleX = this.dataScaleX = width / (lastTick - firstTick);
+    let dataScaleX = this.dataScaleX = width / (lastTick - this.dataOffsetX);
     let dataScaleY = this.dataScaleY = height / this._calcMaxHeight({
       data: this._data,
       dataScaleX: dataScaleX,
-      dataOffsetX: firstTick,
       minBarsWidth: minBarsWidth
     }) * BAR_GRAPH_DAMPEN_VALUES;
 
     // Draw the graph.
 
     // Iterate over the blocks, then the bars, to draw all rectangles of
     // the same color in a single pass. See the @constructor for more
     // information about the data source, and how a "bar" contains "blocks".
 
+    this._blocksBoundingRects = [];
     let prevHeight = [];
     let scaledMarginEnd = BAR_GRAPH_BARS_MARGIN_END * this._pixelRatio;
     let unscaledMarginTop = BAR_GRAPH_BARS_MARGIN_TOP;
 
     for (let type = 0; type < totalTypes; type++) {
       ctx.fillStyle = this.format[type].color || "#000";
       ctx.beginPath();
 
-      let prevLeft = 0;
+      let prevRight = 0;
       let skippedCount = 0;
       let skippedHeight = 0;
 
       for (let tick = 0; tick < totalTicks; tick++) {
         let delta = this._data[tick].delta;
         let value = this._data[tick].values[type] || 0;
-        let blockLeft = (delta - firstTick) * dataScaleX;
+        let blockRight = (delta - this.dataOffsetX) * dataScaleX;
         let blockHeight = value * dataScaleY;
 
-        let blockWidth = blockLeft - prevLeft;
+        let blockWidth = blockRight - prevRight;
         if (blockWidth < minBarsWidth) {
           skippedCount++;
           skippedHeight += blockHeight;
           continue;
         }
 
         let averageHeight = (blockHeight + skippedHeight) / (skippedCount + 1);
         if (averageHeight >= minBlocksHeight) {
           let bottom = height - ~~prevHeight[tick];
-          ctx.moveTo(prevLeft, bottom);
-          ctx.lineTo(prevLeft, bottom - averageHeight);
-          ctx.lineTo(blockLeft, bottom - averageHeight);
-          ctx.lineTo(blockLeft, bottom);
+          ctx.moveTo(prevRight, bottom);
+          ctx.lineTo(prevRight, bottom - averageHeight);
+          ctx.lineTo(blockRight, bottom - averageHeight);
+          ctx.lineTo(blockRight, bottom);
+
+          // Remember this block's type and location.
+          this._blocksBoundingRects.push({
+            type: type,
+            start: prevRight,
+            end: blockRight,
+            top: bottom - averageHeight,
+            bottom: bottom
+          });
 
           if (prevHeight[tick] === undefined) {
             prevHeight[tick] = averageHeight + unscaledMarginTop;
           } else {
             prevHeight[tick] += averageHeight + unscaledMarginTop;
           }
         }
 
-        prevLeft += blockWidth + scaledMarginEnd;
+        prevRight += blockWidth + scaledMarginEnd;
         skippedHeight = 0;
         skippedCount = 0;
       }
 
       ctx.fill();
     }
 
+    // The blocks bounding rects isn't guaranteed to be sorted ascending by
+    // block location on the X axis. This should be the case, for better
+    // cache cohesion and a faster `buildMaskImage`.
+    this._blocksBoundingRects.sort((a, b) => a.start > b.start ? 1 : -1);
+
     // Update the legend.
 
     while (this._legendNode.hasChildNodes()) {
       this._legendNode.firstChild.remove();
     }
     for (let { color, label } of this.format) {
       this._createLegendItem(color, label);
     }
 
     return canvas;
   },
 
   /**
+   * Renders the graph's mask.
+   * Fades in only the parts of the graph that are inside the specified areas.
+   *
+   * @param array highlights
+   *        A list of { start, end } values. Optionally, each object
+   *        in the list may also specify { top, bottom } pixel values if the
+   *        highlighting shouldn't span across the full height of the graph.
+   * @param boolean inPixels
+   *        Set this to true if the { start, end } values in the highlights
+   *        list are pixel values, and not values from the data source.
+   * @param function unpack [optional]
+   *        @see AbstractCanvasGraph.prototype.getMappedSelection
+   */
+  buildMaskImage: function(highlights, inPixels = false, unpack = e => e.delta) {
+    // A null `highlights` array is used to clear the mask. An empty array
+    // will mask the entire graph.
+    if (!highlights) {
+      return null;
+    }
+
+    // Get a render target for the highlights. It will be overlaid on top of
+    // the existing graph, masking the areas that aren't highlighted.
+
+    let { canvas, ctx } = this._getNamedCanvas("graph-highlights");
+    let width = this._width;
+    let height = this._height;
+
+    // Draw the background mask.
+
+    let pattern = AbstractCanvasGraph.getStripePattern({
+      ownerDocument: this._document,
+      backgroundColor: BAR_GRAPH_HIGHLIGHTS_MASK_BACKGROUND,
+      stripesColor: BAR_GRAPH_HIGHLIGHTS_MASK_STRIPES
+    });
+    ctx.fillStyle = pattern;
+    ctx.fillRect(0, 0, width, height);
+
+    // Clear highlighted areas.
+
+    let totalTicks = this._data.length;
+    let firstTick = unpack(this._data[0]);
+    let lastTick = unpack(this._data[totalTicks - 1]);
+
+    for (let { start, end, top, bottom } of highlights) {
+      if (!inPixels) {
+        start = map(start, firstTick, lastTick, 0, width);
+        end = map(end, firstTick, lastTick, 0, width);
+      }
+      let firstSnap = findFirst(this._blocksBoundingRects, e => e.start >= start);
+      let lastSnap = findLast(this._blocksBoundingRects, e => e.start >= start && e.end <= end);
+
+      let x1 = firstSnap ? firstSnap.start : start;
+      let x2 = lastSnap ? lastSnap.end : firstSnap ? firstSnap.end : end;
+      let y1 = top || 0;
+      let y2 = bottom || height;
+      ctx.clearRect(x1, y1, x2 - x1, y2 - y1);
+    }
+
+    return canvas;
+  },
+
+  /**
+   * A list storing the bounding rectangle for each drawn block in the graph.
+   * Created whenever `buildGraphImage` is invoked.
+   */
+  _blocksBoundingRects: null,
+
+  /**
    * Calculates the height of the tallest bar that would eventially be rendered
    * in this graph.
    *
    * Bars that are too close too each other in the graph will be combined.
    * @see `minBarsWidth`
    *
    * @return number
    *         The tallest bar height in this graph.
    */
-  _calcMaxHeight: function({ data, dataScaleX, dataOffsetX, minBarsWidth }) {
+  _calcMaxHeight: function({ data, dataScaleX, minBarsWidth }) {
     let maxHeight = 0;
-    let prevLeft = 0;
+    let prevRight = 0;
     let skippedCount = 0;
     let skippedHeight = 0;
     let scaledMarginEnd = BAR_GRAPH_BARS_MARGIN_END * this._pixelRatio;
 
     for (let { delta, values } of data) {
-      let barLeft = (delta - dataOffsetX) * dataScaleX;
+      let barRight = (delta - this.dataOffsetX) * dataScaleX;
       let barHeight = values.reduce((a, b) => a + b, 0);
 
-      let barWidth = barLeft - prevLeft;
+      let barWidth = barRight - prevRight;
       if (barWidth < minBarsWidth) {
         skippedCount++;
         skippedHeight += barHeight;
         continue;
       }
 
       let averageHeight = (barHeight + skippedHeight) / (skippedCount + 1);
       maxHeight = Math.max(averageHeight, maxHeight);
 
-      prevLeft += barWidth + scaledMarginEnd;
+      prevRight += barWidth + scaledMarginEnd;
       skippedHeight = 0;
       skippedCount = 0;
     }
 
     return maxHeight;
   },
 
   /**
@@ -1546,25 +1706,94 @@ BarGraphWidget.prototype = Heritage.exte
    * Creates a legend item when constructing this graph.
    */
   _createLegendItem: function(color, label) {
     let itemNode = this._document.createElementNS(HTML_NS, "div");
     itemNode.className = "bar-graph-widget-legend-item";
 
     let colorNode = this._document.createElementNS(HTML_NS, "span");
     colorNode.setAttribute("view", "color");
+    colorNode.setAttribute("data-index", this._legendNode.childNodes.length);
     colorNode.style.backgroundColor = color;
+    colorNode.addEventListener("mouseover", this._onLegendMouseOver);
+    colorNode.addEventListener("mouseout", this._onLegendMouseOut);
+    colorNode.addEventListener("mousedown", this._onLegendMouseDown);
+    colorNode.addEventListener("mouseup", this._onLegendMouseUp);
+
+    this.outstandingEventListeners.push([colorNode, "mouseover", this._onLegendMouseOver]);
+    this.outstandingEventListeners.push([colorNode, "mouseout", this._onLegendMouseOut]);
+    this.outstandingEventListeners.push([colorNode, "mousedown", this._onLegendMouseDown]);
+    this.outstandingEventListeners.push([colorNode, "mouseup", this._onLegendMouseUp]);
 
     let labelNode = this._document.createElementNS(HTML_NS, "span");
     labelNode.setAttribute("view", "label");
     labelNode.textContent = label;
 
     itemNode.appendChild(colorNode);
     itemNode.appendChild(labelNode);
     this._legendNode.appendChild(itemNode);
+  },
+
+  /**
+   * Invoked whenever a color node in the legend is hovered.
+   */
+  _onLegendMouseOver: function(e) {
+    setNamedTimeout("bar-graph-debounce", BAR_GRAPH_LEGEND_MOUSEOVER_DEBOUNCE, () => {
+      let type = e.target.dataset.index;
+      let rects = this._blocksBoundingRects.filter(e => e.type == type);
+
+      this._originalHighlights = this._mask;
+      this._hasCustomHighlights = true;
+      this.setMask(rects, true);
+
+      this.emit("legend-hover", [type, rects]);
+    });
+  },
+
+  /**
+   * Invoked whenever a color node in the legend is unhovered.
+   */
+  _onLegendMouseOut: function() {
+    clearNamedTimeout("bar-graph-debounce");
+
+    if (this._hasCustomHighlights) {
+      this.setMask(this._originalHighlights);
+      this._hasCustomHighlights = false;
+      this._originalHighlights = null;
+    }
+
+    this.emit("legend-unhover");
+  },
+
+  /**
+   * Invoked whenever a color node in the legend is pressed.
+   */
+  _onLegendMouseDown: function(e) {
+    e.preventDefault();
+    e.stopPropagation();
+
+    let type = e.target.dataset.index;
+    let rects = this._blocksBoundingRects.filter(e => e.type == type);
+    let leftmost = rects[0];
+    let rightmost = rects[rects.length - 1];
+    if (!leftmost || !rightmost) {
+      this.dropSelection();
+    } else {
+      this.setSelection({ start: leftmost.start, end: rightmost.end });
+    }
+
+    this.emit("legend-selection", [leftmost, rightmost]);
+  },
+
+  /**
+   * Invoked whenever a color node in the legend is released.
+   */
+  _onLegendMouseUp: function(e) {
+    e.preventDefault();
+    e.stopPropagation();
   }
 });
 
 // Helper functions.
 
 /**
  * Creates an iframe element with the provided source URL, appends it to
  * the specified node and invokes the callback once the content is loaded.
@@ -1689,12 +1918,40 @@ this.CanvasGraphUtils = {
     graph2.on("deselecting", () => {
       graph1.dropSelection();
     });
   }
 };
 
 /**
  * Maps a value from one range to another.
+ * @param number value, istart, istop, ostart, ostop
+ * @return number
  */
 function map(value, istart, istop, ostart, ostop) {
   return ostart + (ostop - ostart) * ((value - istart) / (istop - istart));
 }
+
+/**
+ * Finds the first element in an array that validates a predicate.
+ * @param array
+ * @param function predicate
+ * @return number
+ */
+function findFirst(array, predicate) {
+  for (let i = 0, len = array.length; i < len; i++) {
+    let element = array[i];
+    if (predicate(element)) return element;
+  }
+}
+
+/**
+ * Finds the last element in an array that validates a predicate.
+ * @param array
+ * @param function predicate
+ * @return number
+ */
+function findLast(array, predicate) {
+  for (let i = array.length - 1; i >= 0; i--) {
+    let element = array[i];
+    if (predicate(element)) return element;
+  }
+}
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -406,16 +406,18 @@
 @BINPATH@/components/nsBlocklistService.js
 #ifdef MOZ_UPDATER
 @BINPATH@/components/nsUpdateService.manifest
 @BINPATH@/components/nsUpdateService.js
 @BINPATH@/components/nsUpdateServiceStub.js
 #endif
 @BINPATH@/components/nsUpdateTimerManager.manifest
 @BINPATH@/components/nsUpdateTimerManager.js
+@BINPATH@/components/addoncompat.manifest
+@BINPATH@/components/multiprocessShims.js
 @BINPATH@/components/pluginGlue.manifest
 @BINPATH@/browser/components/nsSessionStore.manifest
 @BINPATH@/browser/components/nsSessionStartup.js
 @BINPATH@/browser/components/nsSessionStore.js
 @BINPATH@/components/nsURLFormatter.manifest
 @BINPATH@/components/nsURLFormatter.js
 @BINPATH@/browser/components/@DLL_PREFIX@browsercomps@DLL_SUFFIX@
 @BINPATH@/components/txEXSLTRegExFunctions.manifest
--- a/browser/locales/en-US/chrome/browser/loop/loop.properties
+++ b/browser/locales/en-US/chrome/browser/loop/loop.properties
@@ -13,21 +13,21 @@ display_name_available_status=Available
 unable_retrieve_url=Sorry, we were unable to retrieve a call url.
 
 # Conversation Window Strings
 
 incoming_call_title=Incoming Call…
 incoming_call=Incoming call
 accept_button=Accept
 decline_button=Decline
-hangup_button.title=Hangup
-mute_local_audio_button.title=Mute your audio
-unmute_local_audio_button.title=Unute your audio
-mute_local_video_button.title=Mute your video
-unmute_local_video_button.title=Unmute your video
+hangup_button_title=Hangup
+mute_local_audio_button_title=Mute your audio
+unmute_local_audio_button_title=Unmute your audio
+mute_local_video_button_title=Mute your video
+unmute_local_video_button_title=Unmute your video
 
 peer_ended_conversation=Your peer ended the conversation.
 call_has_ended=Your call has ended.
 close_window=Close this window
 
 cannot_start_call_session_not_ready=Can't start call, session is not ready.
 network_disconnected=The network connection terminated abruptly.
 
--- a/browser/modules/UITour.jsm
+++ b/browser/modules/UITour.jsm
@@ -837,17 +837,17 @@ this.UITour = {
       } else {
         highlighter.style.borderRadius = "";
       }
 
       highlighter.style.height = highlightHeight + "px";
       highlighter.style.width = highlightWidth + "px";
 
       // Close a previous highlight so we can relocate the panel.
-      if (highlighter.parentElement.state == "open") {
+      if (highlighter.parentElement.state == "showing" || highlighter.parentElement.state == "open") {
         highlighter.parentElement.hidePopup();
       }
       /* The "overlap" position anchors from the top-left but we want to centre highlights at their
          minimum size. */
       let highlightWindow = aTargetEl.ownerDocument.defaultView;
       let containerStyle = highlightWindow.getComputedStyle(highlighter.parentElement);
       let paddingTopPx = 0 - parseFloat(containerStyle.paddingTop);
       let paddingLeftPx = 0 - parseFloat(containerStyle.paddingLeft);
@@ -904,17 +904,17 @@ this.UITour = {
 
       let document = aAnchorEl.ownerDocument;
       let tooltip = document.getElementById("UITourTooltip");
       let tooltipTitle = document.getElementById("UITourTooltipTitle");
       let tooltipDesc = document.getElementById("UITourTooltipDescription");
       let tooltipIcon = document.getElementById("UITourTooltipIcon");
       let tooltipButtons = document.getElementById("UITourTooltipButtons");
 
-      if (tooltip.state == "open") {
+      if (tooltip.state == "showing" || tooltip.state == "open") {
         tooltip.hidePopup();
       }
 
       tooltipTitle.textContent = aTitle || "";
       tooltipDesc.textContent = aDescription || "";
       tooltipIcon.src = aIconURL || "";
       tooltipIcon.hidden = !aIconURL;
 
--- a/browser/modules/test/browser.ini
+++ b/browser/modules/test/browser.ini
@@ -19,18 +19,18 @@ skip-if = os == "linux" || e10s # Interm
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly
 [browser_UITour3.js]
 skip-if = os == "linux" || e10s # Linux: Bug 986760, Bug 989101; e10s: Bug 941428 - UITour.jsm not e10s friendly
 [browser_UITour_availableTargets.js]
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly
 [browser_UITour_detach_tab.js]
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly
 [browser_UITour_annotation_size_attributes.js]
-skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly
+skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly.
 [browser_UITour_panel_close_annotation.js]
-skip-if = e10s || os == "win" # Bug 941428 - UITour.jsm not e10s friendly. Intermittent test failures on Windows (bug 1026310 & bug 1032137)
+skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly
 [browser_UITour_registerPageID.js]
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly
 [browser_UITour_sync.js]
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly
 [browser_taskbar_preview.js]
 run-if = os == "win"
 skip-if = e10s # Bug 666808 - AeroPeek support for e10s
--- a/browser/themes/shared/devtools/widgets.inc.css
+++ b/browser/themes/shared/devtools/widgets.inc.css
@@ -921,34 +921,31 @@
 .graph-widget-canvas[input=hovering-selection-contents] {
   cursor: grab;
 }
 
 .graph-widget-canvas[input=dragging-selection-contents] {
   cursor: grabbing;
 }
 
-.graph-widget-canvas ~ * {
-  pointer-events: none;
-}
-
 /* Line graph widget */
 
 .line-graph-widget-canvas {
   background: #0088cc;
 }
 
 .line-graph-widget-gutter {
   position: absolute;
   background: rgba(255,255,255,0.75);
   width: 10px;
   height: 100%;
   top: 0;
   left: 0;
   border-right: 1px solid rgba(255,255,255,0.25);
+  pointer-events: none;
 }
 
 .line-graph-widget-gutter-line {
   position: absolute;
   width: 100%;
   border-top: 1px solid;
   transform: translateY(-1px);
 }
@@ -971,16 +968,17 @@
   box-shadow: 0 2px 1px rgba(0,0,0,0.1);
   border-radius: 2px;
   line-height: 15px;
   -moz-padding-start: 6px;
   -moz-padding-end: 6px;
   transform: translateY(-50%);
   font-size: 80%;
   z-index: 1;
+  pointer-events: none;
 }
 
 .line-graph-widget-tooltip::before {
   content: "";
   position: absolute;
   border-top: 3px solid transparent;
   border-bottom: 3px solid transparent;
   top: calc(50% - 3px);
@@ -1048,16 +1046,17 @@
 }
 
 .bar-graph-widget-legend {
   position: absolute;
   top: 4px;
   left: 8px;
   color: #292e33;
   font-size: 80%;
+  pointer-events: none;
 }
 
 .bar-graph-widget-legend-item {
   float: left;
   -moz-margin-end: 8px;
 }
 
 .bar-graph-widget-legend-item > [view="color"],
@@ -1067,16 +1066,18 @@
 
 .bar-graph-widget-legend-item > [view="color"] {
   display: inline-block;
   width: 8px;
   height: 8px;
   border: 1px solid #fff;
   border-radius: 1px;
   -moz-margin-end: 4px;
+  pointer-events: all;
+  cursor: pointer;
 }
 
 .bar-graph-widget-legend-item > [view="label"] {
   text-shadow: 1px  0px rgba(255,255,255,0.8),
               -1px  0px rgba(255,255,255,0.8),
                0px -1px rgba(255,255,255,0.8),
                0px  1px rgba(255,255,255,0.8);
 }
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -11334,17 +11334,17 @@ class nsPointerLockPermissionRequest : p
                                        public nsIContentPermissionRequest
 {
 public:
   nsPointerLockPermissionRequest(Element* aElement, bool aUserInputOrChromeCaller)
   : mElement(do_GetWeakReference(aElement)),
     mDocument(do_GetWeakReference(aElement->OwnerDoc())),
     mUserInputOrChromeCaller(aUserInputOrChromeCaller) {}
 
-  NS_DECL_ISUPPORTS
+  NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSICONTENTPERMISSIONREQUEST
 
   NS_IMETHOD Run()
   {
     nsCOMPtr<Element> e = do_QueryReferent(mElement);
     nsCOMPtr<nsIDocument> d = do_QueryReferent(mDocument);
     if (!e || !d || gPendingPointerLockRequest != this ||
         e->GetCurrentDoc() != d) {
--- a/content/canvas/src/ImageEncoder.cpp
+++ b/content/canvas/src/ImageEncoder.cpp
@@ -14,17 +14,17 @@ using namespace mozilla::gfx;
 namespace mozilla {
 namespace dom {
 
 class EncodingCompleteEvent : public nsRunnable
 {
   virtual ~EncodingCompleteEvent() {}
 
 public:
-  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_ISUPPORTS_INHERITED
 
   EncodingCompleteEvent(nsIGlobalObject* aGlobal,
                         nsIThread* aEncoderThread,
                         FileCallback& aCallback)
     : mImgSize(0)
     , mType()
     , mImgData(nullptr)
     , mGlobal(aGlobal)
@@ -80,24 +80,24 @@ private:
   nsAutoString mType;
   void* mImgData;
   nsCOMPtr<nsIGlobalObject> mGlobal;
   nsCOMPtr<nsIThread> mEncoderThread;
   nsRefPtr<FileCallback> mCallback;
   bool mFailed;
 };
 
-NS_IMPL_ISUPPORTS(EncodingCompleteEvent, nsIRunnable);
+NS_IMPL_ISUPPORTS_INHERITED0(EncodingCompleteEvent, nsRunnable);
 
 class EncodingRunnable : public nsRunnable
 {
   virtual ~EncodingRunnable() {}
 
 public:
-  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_ISUPPORTS_INHERITED
 
   EncodingRunnable(const nsAString& aType,
                    const nsAString& aOptions,
                    uint8_t* aImageBuffer,
                    imgIEncoder* aEncoder,
                    EncodingCompleteEvent* aEncodingCompleteEvent,
                    int32_t aFormat,
                    const nsIntSize aSize,
@@ -175,17 +175,17 @@ private:
   nsAutoArrayPtr<uint8_t> mImageBuffer;
   nsCOMPtr<imgIEncoder> mEncoder;
   nsRefPtr<EncodingCompleteEvent> mEncodingCompleteEvent;
   int32_t mFormat;
   const nsIntSize mSize;
   bool mUsingCustomOptions;
 };
 
-NS_IMPL_ISUPPORTS(EncodingRunnable, nsIRunnable)
+NS_IMPL_ISUPPORTS_INHERITED0(EncodingRunnable, nsRunnable);
 
 /* static */
 nsresult
 ImageEncoder::ExtractData(nsAString& aType,
                           const nsAString& aOptions,
                           const nsIntSize aSize,
                           nsICanvasRenderingContextInternal* aContext,
                           nsIInputStream** aStream)
--- a/content/media/MediaDecoderStateMachineScheduler.cpp
+++ b/content/media/MediaDecoderStateMachineScheduler.cpp
@@ -9,17 +9,17 @@
 #include "mozilla/ReentrantMonitor.h"
 #include "nsITimer.h"
 #include "nsComponentManagerUtils.h"
 #include "VideoUtils.h"
 
 namespace {
 class TimerEvent : public nsITimerCallback, public nsRunnable {
   typedef mozilla::MediaDecoderStateMachineScheduler Scheduler;
-  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_ISUPPORTS_INHERITED
 public:
   TimerEvent(Scheduler* aScheduler, int aTimerId)
     : mScheduler(aScheduler), mTimerId(aTimerId) {}
 
   NS_IMETHOD Run() MOZ_OVERRIDE {
     return mScheduler->TimeoutExpired(mTimerId);
   }
 
@@ -27,17 +27,17 @@ public:
     return mScheduler->TimeoutExpired(mTimerId);
   }
 private:
   ~TimerEvent() {}
   Scheduler* const mScheduler;
   const int mTimerId;
 };
 
-NS_IMPL_ISUPPORTS(TimerEvent, nsITimerCallback, nsIRunnable);
+NS_IMPL_ISUPPORTS_INHERITED(TimerEvent, nsRunnable, nsITimerCallback);
 } // anonymous namespace
 
 static already_AddRefed<nsIEventTarget>
 CreateStateMachineThread()
 {
   using mozilla::SharedThreadPool;
   using mozilla::RefPtr;
   RefPtr<SharedThreadPool> threadPool(
--- a/content/media/mediasource/SourceBufferResource.cpp
+++ b/content/media/mediasource/SourceBufferResource.cpp
@@ -124,22 +124,24 @@ SourceBufferResource::ReadFromCache(char
     return rv;
   }
   return Read(aBuffer, aCount, nullptr);
 }
 
 bool
 SourceBufferResource::EvictData(uint32_t aThreshold)
 {
+  ReentrantMonitorAutoEnter mon(mMonitor);
   return mInputBuffer.Evict(mOffset, aThreshold);
 }
 
 void
 SourceBufferResource::EvictBefore(uint64_t aOffset)
 {
+  ReentrantMonitorAutoEnter mon(mMonitor);
   // If aOffset is past the current playback offset we don't evict.
   if (aOffset < mOffset) {
     mInputBuffer.Evict(aOffset, 0);
   }
 }
 
 void
 SourceBufferResource::AppendData(const uint8_t* aData, uint32_t aLength)
--- a/content/media/mediasource/SourceBufferResource.h
+++ b/content/media/mediasource/SourceBufferResource.h
@@ -67,81 +67,39 @@ private:
   class ResourceQueueDeallocator : public nsDequeFunctor {
     virtual void* operator() (void* anObject) {
       delete static_cast<ResourceItem*>(anObject);
       return nullptr;
     }
   };
 
   class ResourceQueue : private nsDeque {
-  private:
-    // Logical offset into the resource of the first element
-    // in the queue.
-    uint64_t mOffset;
-
   public:
     ResourceQueue() :
       nsDeque(new ResourceQueueDeallocator()),
       mOffset(0)
     {
     }
 
-    // Clears all items from the queue
-    inline void Clear() {
-      return nsDeque::Erase();
-    }
-
-    // Returns the number of items in the queue
-    inline uint32_t GetSize() {
-      return nsDeque::GetSize();
-    }
-
     // Returns the logical byte offset of the start of the data.
     inline uint64_t GetOffset() {
       return mOffset;
     }
 
-    inline ResourceItem* ResourceAt(uint32_t aIndex) {
-      return static_cast<ResourceItem*>(nsDeque::ObjectAt(aIndex));
-    }
-
     // Returns the length of all items in the queue plus the offset.
     // This is the logical length of the resource.
     inline uint64_t GetLength() {
       uint64_t s = mOffset;
       for (uint32_t i = 0; i < GetSize(); ++i) {
         ResourceItem* item = ResourceAt(i);
         s += item->mData.Length();
       }
       return s;
     }
 
-    // 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
-    // no such resource exists, returns GetSize() and aOffset is
-    // untouched.
-    inline uint32_t GetAtOffset(uint64_t aOffset, uint32_t *aResourceOffset) {
-      MOZ_ASSERT(aOffset >= mOffset);
-      uint64_t offset = mOffset;
-      for (uint32_t i = 0; i < GetSize(); ++i) {
-        ResourceItem* item = ResourceAt(i);
-        // If the item contains the start of the offset we want to
-        // break out of the loop.
-        if (item->mData.Length() + offset > aOffset) {
-          if (aResourceOffset) {
-            *aResourceOffset = aOffset - offset;
-          }
-          return i;
-        }
-        offset += item->mData.Length();
-      }
-      return GetSize();
-    }
-
     // Copies aCount bytes from aOffset in the queue into aDest.
     inline void CopyData(uint64_t aOffset, uint32_t aCount, char* aDest) {
       uint32_t offset = 0;
       uint32_t start = GetAtOffset(aOffset, &offset);
       uint32_t end = std::min(GetAtOffset(aOffset + aCount, nullptr) + 1, GetSize());
       for (uint32_t i = start; i < end; ++i) {
         ResourceItem* item = ResourceAt(i);
         uint32_t bytes = std::min(aCount, uint32_t(item->mData.Length() - offset));
@@ -153,28 +111,16 @@ private:
         }
       }
     }
 
     inline void PushBack(ResourceItem* aItem) {
       nsDeque::Push(aItem);
     }
 
-    inline void PushFront(ResourceItem* aItem) {
-      nsDeque::PushFront(aItem);
-    }
-
-    inline ResourceItem* PopBack() {
-      return static_cast<ResourceItem*>(nsDeque::Pop());
-    }
-
-    inline ResourceItem* PopFront() {
-      return static_cast<ResourceItem*>(nsDeque::PopFront());
-    }
-
     // Evict data in queue if the total queue size is greater than
     // aThreshold past the offset. Returns true if some data was
     // actually evicted.
     inline bool Evict(uint64_t aOffset, uint32_t aThreshold) {
       bool evicted = false;
       while (GetLength() - mOffset > aThreshold) {
         ResourceItem* item = ResourceAt(0);
         if (item->mData.Length() + mOffset > aOffset) {
@@ -195,16 +141,57 @@ private:
       for (int32_t i = 0; i < nsDeque::GetSize(); ++i) {
         const ResourceItem* item =
             static_cast<const ResourceItem*>(nsDeque::ObjectAt(i));
         size += item->SizeOfIncludingThis(aMallocSizeOf);
       }
 
       return size;
     }
+
+  private:
+    // Returns the number of items in the queue
+    inline uint32_t GetSize() {
+      return nsDeque::GetSize();
+    }
+
+    inline ResourceItem* ResourceAt(uint32_t aIndex) {
+      return static_cast<ResourceItem*>(nsDeque::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
+    // no such resource exists, returns GetSize() and aOffset is
+    // untouched.
+    inline uint32_t GetAtOffset(uint64_t aOffset, uint32_t *aResourceOffset) {
+      MOZ_ASSERT(aOffset >= mOffset);
+      uint64_t offset = mOffset;
+      for (uint32_t i = 0; i < GetSize(); ++i) {
+        ResourceItem* item = ResourceAt(i);
+        // If the item contains the start of the offset we want to
+        // break out of the loop.
+        if (item->mData.Length() + offset > aOffset) {
+          if (aResourceOffset) {
+            *aResourceOffset = aOffset - offset;
+          }
+          return i;
+        }
+        offset += item->mData.Length();
+      }
+      return GetSize();
+    }
+
+    inline ResourceItem* PopFront() {
+      return static_cast<ResourceItem*>(nsDeque::PopFront());
+    }
+
+    // Logical offset into the resource of the first element
+    // in the queue.
+    uint64_t mOffset;
   };
 
 public:
   SourceBufferResource(nsIPrincipal* aPrincipal,
                        const nsACString& aType);
   ~SourceBufferResource();
 
   virtual nsresult Close() MOZ_OVERRIDE;
--- a/content/media/plugins/MediaResourceServer.cpp
+++ b/content/media/plugins/MediaResourceServer.cpp
@@ -341,25 +341,25 @@ ServeResourceEvent::Shutdown()
 */
 class ResourceSocketListener : public nsIServerSocketListener
 {
 public:
   // The MediaResourceServer used to look up the MediaResource
   // on requests.
   nsRefPtr<MediaResourceServer> mServer;
 
-public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSISERVERSOCKETLISTENER
 
   ResourceSocketListener(MediaResourceServer* aServer) :
     mServer(aServer)
   {
   }
 
+private:
   virtual ~ResourceSocketListener() { }
 };
 
 NS_IMPL_ISUPPORTS(ResourceSocketListener, nsIServerSocketListener)
 
 NS_IMETHODIMP
 ResourceSocketListener::OnSocketAccepted(nsIServerSocket* aServ,
                                          nsISocketTransport* aTrans)
--- a/content/media/wmf/WMFByteStream.cpp
+++ b/content/media/wmf/WMFByteStream.cpp
@@ -112,16 +112,18 @@ WMFByteStream::QueryInterface(REFIID aII
 }
 
 NS_IMPL_ADDREF(WMFByteStream)
 NS_IMPL_RELEASE(WMFByteStream)
 
 
 // Stores data regarding an async read opreation.
 class ReadRequest MOZ_FINAL : public IUnknown {
+  ~ReadRequest() {}
+
 public:
   ReadRequest(int64_t aOffset, BYTE* aBuffer, ULONG aLength)
     : mOffset(aOffset),
       mBuffer(aBuffer),
       mBufferLength(aLength),
       mBytesRead(0)
   {}
 
@@ -389,27 +391,27 @@ WMFByteStream::IsEndOfStream(BOOL *aEndO
   WMF_BS_LOG("[%p] WMFByteStream::IsEndOfStream() %d", this, *aEndOfStream);
   return S_OK;
 }
 
 STDMETHODIMP
 WMFByteStream::Read(BYTE* aBuffer, ULONG aBufferLength, ULONG* aOutBytesRead)
 {
   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-  ReadRequest request(mOffset, aBuffer, aBufferLength);
-  if (NS_FAILED(Read(&request))) {
+  nsRefPtr<ReadRequest> request = new ReadRequest(mOffset, aBuffer, aBufferLength);
+  if (NS_FAILED(Read(request))) {
     WMF_BS_LOG("[%p] WMFByteStream::Read() offset=%lld failed!", this, mOffset);
     return E_FAIL;
   }
   if (aOutBytesRead) {
-    *aOutBytesRead = request.mBytesRead;
+    *aOutBytesRead = request->mBytesRead;
   }
   WMF_BS_LOG("[%p] WMFByteStream::Read() offset=%lld length=%u bytesRead=%u",
-             this, mOffset, aBufferLength, request.mBytesRead);
-  mOffset += request.mBytesRead;
+             this, mOffset, aBufferLength, request->mBytesRead);
+  mOffset += request->mBytesRead;
   return S_OK;
 }
 
 STDMETHODIMP
 WMFByteStream::Seek(MFBYTESTREAM_SEEK_ORIGIN aSeekOrigin,
                     LONGLONG aSeekOffset,
                     DWORD aSeekFlags,
                     QWORD *aCurrentPosition)
--- a/content/media/wmf/WMFByteStream.h
+++ b/content/media/wmf/WMFByteStream.h
@@ -31,19 +31,20 @@ class SharedThreadPool;
 // Note: This implementation attempts to be bug-compatible with Windows Media
 //       Foundation's implementation of IMFByteStream. The behaviour of WMF's
 //       IMFByteStream was determined by creating it and testing the edge cases.
 //       For details see the test code at:
 //       https://github.com/cpearce/IMFByteStreamBehaviour/
 class WMFByteStream MOZ_FINAL : public IMFByteStream
                               , public IMFAttributes
 {
+  ~WMFByteStream();
+
 public:
   WMFByteStream(MediaResource* aResource, WMFSourceReaderCallback* aCallback);
-  ~WMFByteStream();
 
   nsresult Init();
   nsresult Shutdown();
 
   // IUnknown Methods.
   STDMETHODIMP QueryInterface(REFIID aIId, LPVOID *aInterface);
   STDMETHODIMP_(ULONG) AddRef();
   STDMETHODIMP_(ULONG) Release();
--- a/content/media/wmf/WMFSourceReaderCallback.h
+++ b/content/media/wmf/WMFSourceReaderCallback.h
@@ -16,16 +16,18 @@ namespace mozilla {
 // A listener which we pass into the IMFSourceReader upon creation which is
 // notified when an asynchronous call to IMFSourceReader::ReadSample()
 // completes. This allows us to abort ReadSample() operations when the
 // WMFByteStream's underlying MediaResource is closed. This ensures that
 // the decode threads don't get stuck in a synchronous ReadSample() call
 // when the MediaResource is unexpectedly shutdown.
 class WMFSourceReaderCallback MOZ_FINAL : public IMFSourceReaderCallback
 {
+  ~WMFSourceReaderCallback() {}
+
 public:
   WMFSourceReaderCallback();
 
   // IUnknown Methods.
   STDMETHODIMP QueryInterface(REFIID aIId, LPVOID *aInterface);
   STDMETHODIMP_(ULONG) AddRef();
   STDMETHODIMP_(ULONG) Release();
 
--- a/content/svg/content/src/nsSVGElement.cpp
+++ b/content/svg/content/src/nsSVGElement.cpp
@@ -1208,17 +1208,17 @@ MappedAttrParser::ParseMappedAttrValue(n
 
 already_AddRefed<css::StyleRule>
 MappedAttrParser::CreateStyleRule()
 {
   if (!mDecl) {
     return nullptr; // No mapped attributes were parsed
   }
 
-  nsRefPtr<css::StyleRule> rule = new css::StyleRule(nullptr, mDecl);
+  nsRefPtr<css::StyleRule> rule = new css::StyleRule(nullptr, mDecl, 0, 0);
   mDecl = nullptr; // We no longer own the declaration -- drop our pointer to it
   return rule.forget();
 }
 
 } // anonymous namespace
 
 //----------------------------------------------------------------------
 // Implementation Helpers:
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -3394,22 +3394,30 @@ this.DOMApplicationRegistry = {
         }
         // Nothing else to do for an update... since the
         // origin can't change we don't need to move the
         // app nor can we have a duplicated origin
       } else {
         debug("Setting origin to " + uri.prePath +
               " for " + aOldApp.manifestURL);
         let newId = uri.prePath.substring(6); // "app://".length
-        if (newId in this.webapps) {
+        if (newId in this.webapps && this._isLaunchable(this.webapps[newId])) {
           throw "DUPLICATE_ORIGIN";
         }
         aOldApp.origin = uri.prePath;
         // Update the registry.
         let oldId = aOldApp.id;
+
+        if (oldId == newId) {
+          // This could happen when we have an app in the registry
+          // that is not launchable. Since the app already has
+          // the correct id, we don't need to change it.
+          return;
+        }
+
         aOldApp.id = newId;
         this.webapps[newId] = aOldApp;
         delete this.webapps[oldId];
         // Rename the directories where the files are installed.
         [DIRECTORY_NAME, "TmpD"].forEach(function(aDir) {
           let parent = FileUtils.getDir(aDir, ["webapps"], true, true);
           let dir = FileUtils.getDir(aDir, ["webapps", oldId], true, true);
           dir.moveTo(parent, newId);
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -2193,43 +2193,16 @@ Navigator::HasWakeLockSupport(JSContext*
   nsCOMPtr<nsIPowerManagerService> pmService =
     do_GetService(POWERMANAGERSERVICE_CONTRACTID);
   // No service means no wake lock support
   return !!pmService;
 }
 
 /* static */
 bool
-Navigator::HasMobileMessageSupport(JSContext* /* unused */, JSObject* aGlobal)
-{
-  nsCOMPtr<nsPIDOMWindow> win = GetWindowFromGlobal(aGlobal);
-
-#ifndef MOZ_WEBSMS_BACKEND
-  return false;
-#endif
-
-  // First of all, the general pref has to be turned on.
-  bool enabled = false;
-  Preferences::GetBool("dom.sms.enabled", &enabled);
-  if (!enabled) {
-    return false;
-  }
-
-  NS_ENSURE_TRUE(win, false);
-  NS_ENSURE_TRUE(win->GetDocShell(), false);
-
-  if (!CheckPermission(win, "sms")) {
-    return false;
-  }
-
-  return true;
-}
-
-/* static */
-bool
 Navigator::HasCameraSupport(JSContext* /* unused */, JSObject* aGlobal)
 {
   nsCOMPtr<nsPIDOMWindow> win = GetWindowFromGlobal(aGlobal);
   return win && nsDOMCameraManager::CheckPermission(win);
 }
 
 /* static */
 bool
@@ -2263,26 +2236,16 @@ Navigator::HasNFCSupport(JSContext* /* u
   }
 
   nsCOMPtr<nsPIDOMWindow> win = GetWindowFromGlobal(aGlobal);
   return win && (CheckPermission(win, "nfc-read") ||
                  CheckPermission(win, "nfc-write"));
 }
 #endif // MOZ_NFC
 
-#ifdef MOZ_TIME_MANAGER
-/* static */
-bool
-Navigator::HasTimeSupport(JSContext* /* unused */, JSObject* aGlobal)
-{
-  nsCOMPtr<nsPIDOMWindow> win = GetWindowFromGlobal(aGlobal);
-  return win && CheckPermission(win, "time");
-}
-#endif // MOZ_TIME_MANAGER
-
 #ifdef MOZ_MEDIA_NAVIGATOR
 /* static */
 bool
 Navigator::HasUserMediaSupport(JSContext* /* unused */,
                                JSObject* /* unused */)
 {
   // Make enabling peerconnection enable getUserMedia() as well
   return Preferences::GetBool("media.navigator.enabled", false) ||
@@ -2374,32 +2337,16 @@ Navigator::HasDataStoreSupport(JSContext
   nsIDocument* doc = win->GetExtantDoc();
   if (!doc || !doc->NodePrincipal()) {
     return false;
   }
 
   return HasDataStoreSupport(doc->NodePrincipal());
 }
 
-/* static */
-bool
-Navigator::HasNetworkStatsSupport(JSContext* /* unused */, JSObject* aGlobal)
-{
-  nsCOMPtr<nsPIDOMWindow> win = GetWindowFromGlobal(aGlobal);
-  return CheckPermission(win, "networkstats-manage");
-}
-
-/* static */
-bool
-Navigator::HasFeatureDetectionSupport(JSContext* /* unused */, JSObject* aGlobal)
-{
-  nsCOMPtr<nsPIDOMWindow> win = GetWindowFromGlobal(aGlobal);
-  return CheckPermission(win, "feature-detection");
-}
-
 #ifdef MOZ_B2G
 /* static */
 bool
 Navigator::HasMobileIdSupport(JSContext* aCx, JSObject* aGlobal)
 {
   nsCOMPtr<nsPIDOMWindow> win = GetWindowFromGlobal(aGlobal);
   if (!win) {
     return false;
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -263,43 +263,34 @@ public:
                     JS::MutableHandle<JSPropertyDescriptor> aDesc);
   void GetOwnPropertyNames(JSContext* aCx, nsTArray<nsString>& aNames,
                            ErrorResult& aRv);
   void GetLanguages(nsTArray<nsString>& aLanguages);
   void GetAcceptLanguages(nsTArray<nsString>& aLanguages);
 
   // WebIDL helper methods
   static bool HasWakeLockSupport(JSContext* /* unused*/, JSObject* /*unused */);
-  static bool HasMobileMessageSupport(JSContext* /* unused */,
-                                      JSObject* aGlobal);
   static bool HasCameraSupport(JSContext* /* unused */,
                                JSObject* aGlobal);
   static bool HasWifiManagerSupport(JSContext* /* unused */,
                                   JSObject* aGlobal);
 #ifdef MOZ_NFC
   static bool HasNFCSupport(JSContext* /* unused */, JSObject* aGlobal);
 #endif // MOZ_NFC
-#ifdef MOZ_TIME_MANAGER
-  static bool HasTimeSupport(JSContext* /* unused */, JSObject* aGlobal);
-#endif // MOZ_TIME_MANAGER
 #ifdef MOZ_MEDIA_NAVIGATOR
   static bool HasUserMediaSupport(JSContext* /* unused */,
                                   JSObject* /* unused */);
 #endif // MOZ_MEDIA_NAVIGATOR
 
   static bool HasInputMethodSupport(JSContext* /* unused */, JSObject* aGlobal);
 
   static bool HasDataStoreSupport(nsIPrincipal* aPrincipal);
 
   static bool HasDataStoreSupport(JSContext* cx, JSObject* aGlobal);
 
-  static bool HasNetworkStatsSupport(JSContext* aCx, JSObject* aGlobal);
-
-  static bool HasFeatureDetectionSupport(JSContext* aCx, JSObject* aGlobal);
-
 #ifdef MOZ_B2G
   static bool HasMobileIdSupport(JSContext* aCx, JSObject* aGlobal);
 #endif
 
   nsPIDOMWindow* GetParentObject() const
   {
     return GetWindow();
   }
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -365,20 +365,19 @@ AsyncErrorReporter::AsyncErrorReporter(J
   if (!aErrorReport->filename) {
     mFileName.SetIsVoid(true);
   } else {
     mFileName.AssignWithConversion(aErrorReport->filename);
   }
 
   const char16_t* m = static_cast<const char16_t*>(aErrorReport->ucmessage);
   if (m) {
-    const char16_t* n = static_cast<const char16_t*>
-      (js::GetErrorTypeName(aRuntime, aErrorReport->exnType));
-    if (n) {
-      mErrorMsg.Assign(n);
+    JSFlatString* name = js::GetErrorTypeName(aRuntime, aErrorReport->exnType);
+    if (name) {
+      AssignJSFlatString(mErrorMsg, name);
       mErrorMsg.AppendLiteral(": ");
     }
     mErrorMsg.Append(m);
   }
 
   if (mErrorMsg.IsEmpty() && aFallbackMessage) {
     mErrorMsg.AssignWithConversion(aFallbackMessage);
   }
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -360,31 +360,34 @@ InterfaceObjectToString(JSContext* cx, u
     return false;
   }
 
   JS::Value v = js::GetFunctionNativeReserved(callee,
                                               TOSTRING_CLASS_RESERVED_SLOT);
   const JSClass* clasp = static_cast<const JSClass*>(v.toPrivate());
 
   v = js::GetFunctionNativeReserved(callee, TOSTRING_NAME_RESERVED_SLOT);
-  JSString* jsname = static_cast<JSString*>(v.toString());
-  size_t length;
-  const jschar* name = JS_GetInternedStringCharsAndLength(jsname, &length);
+  JSString* jsname = v.toString();
+
+  nsAutoJSString name;
+  if (!name.init(cx, jsname)) {
+    return false;
+  }
 
   if (js::GetObjectJSClass(&args.thisv().toObject()) != clasp) {
     JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
                          JSMSG_INCOMPATIBLE_PROTO,
                          NS_ConvertUTF16toUTF8(name).get(), "toString",
                          "object");
     return false;
   }
 
   nsString str;
   str.AppendLiteral("function ");
-  str.Append(name, length);
+  str.Append(name);
   str.AppendLiteral("() {");
   str.Append('\n');
   str.AppendLiteral("    [native code]");
   str.Append('\n');
   str.Append('}');
 
   return xpc::NonVoidStringToJsval(cx, str, args.rval());
 }
--- a/dom/crypto/WebCryptoCommon.h
+++ b/dom/crypto/WebCryptoCommon.h
@@ -19,16 +19,17 @@
 #define WEBCRYPTO_ALG_SHA1          "SHA-1"
 #define WEBCRYPTO_ALG_SHA256        "SHA-256"
 #define WEBCRYPTO_ALG_SHA384        "SHA-384"
 #define WEBCRYPTO_ALG_SHA512        "SHA-512"
 #define WEBCRYPTO_ALG_HMAC          "HMAC"
 #define WEBCRYPTO_ALG_PBKDF2        "PBKDF2"
 #define WEBCRYPTO_ALG_RSAES_PKCS1   "RSAES-PKCS1-v1_5"
 #define WEBCRYPTO_ALG_RSASSA_PKCS1  "RSASSA-PKCS1-v1_5"
+#define WEBCRYPTO_ALG_RSA_OAEP      "RSA-OAEP"
 
 // WebCrypto key formats
 #define WEBCRYPTO_KEY_FORMAT_RAW    "raw"
 #define WEBCRYPTO_KEY_FORMAT_PKCS8  "pkcs8"
 #define WEBCRYPTO_KEY_FORMAT_SPKI   "spki"
 #define WEBCRYPTO_KEY_FORMAT_JWK    "jwk"
 
 // WebCrypto key types
@@ -141,16 +142,18 @@ MapAlgorithmNameToMechanism(const nsStri
   } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA512)) {
     mechanism = CKM_SHA512;
   } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_PBKDF2)) {
     mechanism = CKM_PKCS5_PBKD2;
   } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_RSAES_PKCS1)) {
     mechanism = CKM_RSA_PKCS;
   } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1)) {
     mechanism = CKM_RSA_PKCS;
+  } else if (aName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
+    mechanism = CKM_RSA_PKCS_OAEP;
   }
 
   return mechanism;
 }
 
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/crypto/WebCryptoTask.cpp
+++ b/dom/crypto/WebCryptoTask.cpp
@@ -298,17 +298,17 @@ private:
     mResultPromise->MaybeResolve(ret);
   }
 };
 
 class AesTask : public ReturnArrayBufferViewTask
 {
 public:
   AesTask(JSContext* aCx, const ObjectOrString& aAlgorithm,
-          mozilla::dom::CryptoKey& aKey, const CryptoOperationData& aData,
+          CryptoKey& aKey, const CryptoOperationData& aData,
           bool aEncrypt)
     : mSymKey(aKey.GetSymKey())
     , mEncrypt(aEncrypt)
   {
     ATTEMPT_BUFFER_INIT(mData, aData);
 
     nsString algName;
     mEarlyRv = GetAlgorithmName(aCx, aAlgorithm, algName);
@@ -475,17 +475,17 @@ private:
     return rv;
   }
 };
 
 class RsaesPkcs1Task : public ReturnArrayBufferViewTask
 {
 public:
   RsaesPkcs1Task(JSContext* aCx, const ObjectOrString& aAlgorithm,
-                 mozilla::dom::CryptoKey& aKey, const CryptoOperationData& aData,
+                 CryptoKey& aKey, const CryptoOperationData& aData,
                  bool aEncrypt)
     : mPrivKey(aKey.GetPrivateKey())
     , mPubKey(aKey.GetPublicKey())
     , mEncrypt(aEncrypt)
   {
     Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_RSAES_PKCS1);
 
     ATTEMPT_BUFFER_INIT(mData, aData);
@@ -544,21 +544,144 @@ private:
       mResult.SetLength(outLen);
     }
 
     NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
     return NS_OK;
   }
 };
 
+class RsaOaepTask : public ReturnArrayBufferViewTask
+{
+public:
+  RsaOaepTask(JSContext* aCx, const ObjectOrString& aAlgorithm,
+              CryptoKey& aKey, const CryptoOperationData& aData,
+              bool aEncrypt)
+    : mPrivKey(aKey.GetPrivateKey())
+    , mPubKey(aKey.GetPublicKey())
+    , mEncrypt(aEncrypt)
+  {
+    Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_RSA_OAEP);
+
+    ATTEMPT_BUFFER_INIT(mData, aData);
+
+    if (mEncrypt) {
+      if (!mPubKey) {
+        mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
+        return;
+      }
+      mStrength = SECKEY_PublicKeyStrength(mPubKey);
+    } else {
+      if (!mPrivKey) {
+        mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
+        return;
+      }
+      mStrength = PK11_GetPrivateModulusLen(mPrivKey);
+    }
+
+    RootedDictionary<RsaOaepParams> params(aCx);
+    mEarlyRv = Coerce(aCx, params, aAlgorithm);
+    if (NS_FAILED(mEarlyRv)) {
+      mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+      return;
+    }
+
+    if (params.mLabel.WasPassed() && !params.mLabel.Value().IsNull()) {
+      ATTEMPT_BUFFER_INIT(mLabel, params.mLabel.Value().Value());
+    }
+    // Otherwise mLabel remains the empty octet string, as intended
+
+    // Look up the MGF based on the KeyAlgorithm.
+    // static_cast is safe because we only get here if the algorithm name
+    // is RSA-OAEP, and that only happens if we've constructed
+    // an RsaHashedKeyAlgorithm.
+    // TODO: Add As* methods to KeyAlgorithm (Bug 1036734)
+    nsRefPtr<RsaHashedKeyAlgorithm> rsaAlg =
+      static_cast<RsaHashedKeyAlgorithm*>(aKey.Algorithm());
+    mHashMechanism = rsaAlg->Hash()->Mechanism();
+
+    switch (mHashMechanism) {
+      case CKM_SHA_1:
+        mMgfMechanism = CKG_MGF1_SHA1; break;
+      case CKM_SHA256:
+        mMgfMechanism = CKG_MGF1_SHA256; break;
+      case CKM_SHA384:
+        mMgfMechanism = CKG_MGF1_SHA384; break;
+      case CKM_SHA512:
+        mMgfMechanism = CKG_MGF1_SHA512; break;
+      default: {
+        mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+        return;
+      }
+    }
+  }
+
+private:
+  CK_MECHANISM_TYPE mHashMechanism;
+  CK_MECHANISM_TYPE mMgfMechanism;
+  ScopedSECKEYPrivateKey mPrivKey;
+  ScopedSECKEYPublicKey mPubKey;
+  CryptoBuffer mLabel;
+  CryptoBuffer mData;
+  uint32_t mStrength;
+  bool mEncrypt;
+
+  virtual nsresult DoCrypto() MOZ_OVERRIDE
+  {
+    nsresult rv;
+
+    // Ciphertext is an integer mod the modulus, so it will be
+    // no longer than mStrength octets
+    if (!mResult.SetLength(mStrength)) {
+      return NS_ERROR_DOM_UNKNOWN_ERR;
+    }
+
+    CK_RSA_PKCS_OAEP_PARAMS oaepParams;
+    oaepParams.source = CKZ_DATA_SPECIFIED;
+
+    oaepParams.pSourceData = mLabel.Length() ? mLabel.Elements() : nullptr;
+    oaepParams.ulSourceDataLen = mLabel.Length();
+
+    oaepParams.mgf = mMgfMechanism;
+    oaepParams.hashAlg = mHashMechanism;
+
+    SECItem param;
+    param.type = siBuffer;
+    param.data = (unsigned char*) &oaepParams;
+    param.len = sizeof(oaepParams);
+
+    uint32_t outLen;
+    if (mEncrypt) {
+      // PK11_PubEncrypt() checks the plaintext's length and fails if it is too
+      // long to encrypt, i.e. if it is longer than (k - 2hLen - 2) with 'k'
+      // being the length in octets of the RSA modulus n and 'hLen' being the
+      // output length in octets of the chosen hash function.
+      // <https://tools.ietf.org/html/rfc3447#section-7.1>
+      rv = MapSECStatus(PK11_PubEncrypt(
+             mPubKey.get(), CKM_RSA_PKCS_OAEP, &param,
+             mResult.Elements(), &outLen, mResult.Length(),
+             mData.Elements(), mData.Length(), nullptr));
+    } else {
+      rv = MapSECStatus(PK11_PrivDecrypt(
+             mPrivKey.get(), CKM_RSA_PKCS_OAEP, &param,
+             mResult.Elements(), &outLen, mResult.Length(),
+             mData.Elements(), mData.Length()));
+    }
+    mResult.SetLength(outLen);
+
+    NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
+    return NS_OK;
+  }
+};
+
 class HmacTask : public WebCryptoTask
 {
 public:
   HmacTask(JSContext* aCx, const ObjectOrString& aAlgorithm,
-           mozilla::dom::CryptoKey& aKey,
+           CryptoKey& aKey,
            const CryptoOperationData& aSignature,
            const CryptoOperationData& aData,
            bool aSign)
     : mMechanism(aKey.Algorithm()->Mechanism())
     , mSymKey(aKey.GetSymKey())
     , mSign(aSign)
   {
     ATTEMPT_BUFFER_INIT(mData, aData);
@@ -651,17 +774,17 @@ private:
     }
   }
 };
 
 class RsassaPkcs1Task : public WebCryptoTask
 {
 public:
   RsassaPkcs1Task(JSContext* aCx, const ObjectOrString& aAlgorithm,
-                  mozilla::dom::CryptoKey& aKey,
+                  CryptoKey& aKey,
                   const CryptoOperationData& aSignature,
                   const CryptoOperationData& aData,
                   bool aSign)
     : mOidTag(SEC_OID_UNKNOWN)
     , mPrivKey(aKey.GetPrivateKey())
     , mPubKey(aKey.GetPublicKey())
     , mSign(aSign)
     , mVerified(false)
@@ -1012,17 +1135,18 @@ public:
       mKeyData.Assign(aKeyData.GetAsArrayBuffer());
     } else {
       // TODO This will need to be changed for JWK (Bug 1005220)
       mEarlyRv = NS_ERROR_DOM_DATA_ERR;
       return;
     }
 
     // If this is RSA with a hash, cache the hash name
-    if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1)) {
+    if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
+        mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
       RootedDictionary<RsaHashedImportParams> params(aCx);
       mEarlyRv = Coerce(aCx, params, aAlgorithm);
       if (NS_FAILED(mEarlyRv) || !params.mHash.WasPassed()) {
         mEarlyRv = NS_ERROR_DOM_DATA_ERR;
         return;
       }
 
       mEarlyRv = GetAlgorithmName(aCx, params.mHash.Value(), mHashName);
@@ -1083,42 +1207,48 @@ private:
 
     return NS_OK;
   }
 
   virtual nsresult AfterCrypto() MOZ_OVERRIDE
   {
     // Construct an appropriate KeyAlgorithm
     nsIGlobalObject* global = mKey->GetParentObject();
-    if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSAES_PKCS1)) {
+    if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSAES_PKCS1) ||
+        mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
       if ((mKey->GetKeyType() == CryptoKey::PUBLIC &&
            mKey->HasUsageOtherThan(CryptoKey::ENCRYPT)) ||
           (mKey->GetKeyType() == CryptoKey::PRIVATE &&
            mKey->HasUsageOtherThan(CryptoKey::DECRYPT))) {
         return NS_ERROR_DOM_DATA_ERR;
       }
-
-      mKey->SetAlgorithm(new RsaKeyAlgorithm(global, mAlgName, mModulusLength, mPublicExponent));
     } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1)) {
       if ((mKey->GetKeyType() == CryptoKey::PUBLIC &&
            mKey->HasUsageOtherThan(CryptoKey::VERIFY)) ||
           (mKey->GetKeyType() == CryptoKey::PRIVATE &&
            mKey->HasUsageOtherThan(CryptoKey::SIGN))) {
         return NS_ERROR_DOM_DATA_ERR;
       }
+    }
 
-      nsRefPtr<RsaHashedKeyAlgorithm> algorithm = new RsaHashedKeyAlgorithm(
-                                                          global,
-                                                          mAlgName,
-                                                          mModulusLength,
-                                                          mPublicExponent,
-                                                          mHashName);
+    // Construct an appropriate KeyAlgorithm
+    if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSAES_PKCS1)) {
+      mKey->SetAlgorithm(new RsaKeyAlgorithm(global, mAlgName, mModulusLength, mPublicExponent));
+    } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
+               mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
+      nsRefPtr<RsaHashedKeyAlgorithm> algorithm =
+        new RsaHashedKeyAlgorithm(global, mAlgName,
+                                  mModulusLength, mPublicExponent, mHashName);
       if (algorithm->Mechanism() == UNKNOWN_CK_MECHANISM) {
         return NS_ERROR_DOM_SYNTAX_ERR;
       }
+
+      if (algorithm->Hash()->Mechanism() == UNKNOWN_CK_MECHANISM) {
+        return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+      }
       mKey->SetAlgorithm(algorithm);
     }
 
     return NS_OK;
   }
 };
 
 
@@ -1340,17 +1470,18 @@ public:
     if (NS_FAILED(mEarlyRv)) {
       mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
       return;
     }
 
     // Construct an appropriate KeyAlorithm
     KeyAlgorithm* algorithm;
     uint32_t privateAllowedUsages = 0, publicAllowedUsages = 0;
-    if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1)) {
+    if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
+        algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
       RootedDictionary<RsaHashedKeyGenParams> params(aCx);
       mEarlyRv = Coerce(aCx, params, aAlgorithm);
       if (NS_FAILED(mEarlyRv) || !params.mModulusLength.WasPassed() ||
           !params.mPublicExponent.WasPassed() ||
           !params.mHash.WasPassed()) {
         mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
         return;
       }
@@ -1375,19 +1506,16 @@ public:
 
       // Set up params struct
       mRsaParams.keySizeInBits = modulusLength;
       bool converted = publicExponent.GetBigIntValue(mRsaParams.pe);
       if (!converted) {
         mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
         return;
       }
-
-      privateAllowedUsages = CryptoKey::SIGN;
-      publicAllowedUsages = CryptoKey::VERIFY;
     } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSAES_PKCS1)) {
       RootedDictionary<RsaKeyGenParams> params(aCx);
       mEarlyRv = Coerce(aCx, params, aAlgorithm);
       if (NS_FAILED(mEarlyRv) || !params.mModulusLength.WasPassed() ||
           !params.mPublicExponent.WasPassed()) {
         mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
         return;
       }
@@ -1406,24 +1534,31 @@ public:
 
       // Set up params struct
       mRsaParams.keySizeInBits = modulusLength;
       bool converted = publicExponent.GetBigIntValue(mRsaParams.pe);
       if (!converted) {
         mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
         return;
       }
-
-      privateAllowedUsages = CryptoKey::DECRYPT | CryptoKey::UNWRAPKEY;
-      publicAllowedUsages = CryptoKey::ENCRYPT | CryptoKey::WRAPKEY;
     } else {
       mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
       return;
     }
 
+    // Set key usages.
+    if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1)) {
+      privateAllowedUsages = CryptoKey::SIGN;
+      publicAllowedUsages = CryptoKey::VERIFY;
+    } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSAES_PKCS1) ||
+               algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
+      privateAllowedUsages = CryptoKey::DECRYPT | CryptoKey::UNWRAPKEY;
+      publicAllowedUsages = CryptoKey::ENCRYPT | CryptoKey::WRAPKEY;
+    }
+
     mKeyPair->PrivateKey()->SetExtractable(aExtractable);
     mKeyPair->PrivateKey()->SetType(CryptoKey::PRIVATE);
 
     mKeyPair->PublicKey()->SetExtractable(true);
     mKeyPair->PublicKey()->SetType(CryptoKey::PUBLIC);
 
     mKeyPair->PrivateKey()->ClearUsages();
     mKeyPair->PublicKey()->ClearUsages();
@@ -1671,16 +1806,18 @@ WebCryptoTask::EncryptDecryptTask(JSCont
   }
 
   if (algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CBC) ||
       algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CTR) ||
       algName.EqualsLiteral(WEBCRYPTO_ALG_AES_GCM)) {
     return new AesTask(aCx, aAlgorithm, aKey, aData, aEncrypt);
   } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSAES_PKCS1)) {
     return new RsaesPkcs1Task(aCx, aAlgorithm, aKey, aData, aEncrypt);
+  } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
+    return new RsaOaepTask(aCx, aAlgorithm, aKey, aData, aEncrypt);
   }
 
   return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
 }
 
 WebCryptoTask*
 WebCryptoTask::SignVerifyTask(JSContext* aCx,
                               const ObjectOrString& aAlgorithm,
@@ -1743,17 +1880,18 @@ WebCryptoTask::ImportKeyTask(JSContext* 
   if (algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CBC) ||
       algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CTR) ||
       algName.EqualsLiteral(WEBCRYPTO_ALG_AES_GCM) ||
       algName.EqualsLiteral(WEBCRYPTO_ALG_PBKDF2) ||
       algName.EqualsLiteral(WEBCRYPTO_ALG_HMAC)) {
     return new ImportSymmetricKeyTask(aCx, aFormat, aKeyData, aAlgorithm,
                                       aExtractable, aKeyUsages);
   } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSAES_PKCS1) ||
-             algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1)) {
+             algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
+             algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
     return new ImportRsaKeyTask(aCx, aFormat, aKeyData, aAlgorithm,
                                 aExtractable, aKeyUsages);
   } else {
     return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
   }
 }
 
 WebCryptoTask*
@@ -1785,17 +1923,18 @@ WebCryptoTask::GenerateKeyTask(JSContext
   }
 
   if (algName.EqualsASCII(WEBCRYPTO_ALG_AES_CBC) ||
       algName.EqualsASCII(WEBCRYPTO_ALG_AES_CTR) ||
       algName.EqualsASCII(WEBCRYPTO_ALG_AES_GCM) ||
       algName.EqualsASCII(WEBCRYPTO_ALG_HMAC)) {
     return new GenerateSymmetricKeyTask(aCx, aAlgorithm, aExtractable, aKeyUsages);
   } else if (algName.EqualsASCII(WEBCRYPTO_ALG_RSAES_PKCS1) ||
-             algName.EqualsASCII(WEBCRYPTO_ALG_RSASSA_PKCS1)) {
+             algName.EqualsASCII(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
+             algName.EqualsASCII(WEBCRYPTO_ALG_RSA_OAEP)) {
     return new GenerateAsymmetricKeyTask(aCx, aAlgorithm, aExtractable, aKeyUsages);
   } else {
     return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
   }
 }
 
 WebCryptoTask*
 WebCryptoTask::DeriveKeyTask(JSContext* aCx,
--- a/dom/crypto/test/mochitest.ini
+++ b/dom/crypto/test/mochitest.ini
@@ -1,11 +1,11 @@
 [DEFAULT]
-# Bug 1010743 - Re-enable WebCrypto tests on b2g-desktop
-skip-if = (buildapp == 'b2g' && toolkit != 'gonk')
+# Bug 1010743 - Re-enable WebCrypto tests on b2g
+skip-if = (buildapp == 'b2g')
 support-files =
   test-array.js
   test-vectors.js
   test_WebCrypto.css
   tests.js
   util.js
 
 [test_WebCrypto.html]
--- a/dom/crypto/test/test-vectors.js
+++ b/dom/crypto/test/test-vectors.js
@@ -306,16 +306,60 @@ tv = {
     sig_fail: util.hex2abv(
       "8000000080e5c7b4b5e672929f664c4896e50c35134b6de4d5a934252a3a245f" +
       "f48340920e1034b7d5a5b524eb0e1cf12befef49b27b732d2c19e1c43217d6e1" +
       "417381111a1d36de6375cf455b3c9812639dbc27600c751994fb61799ecf7da6" +
       "bcf51540afd0174db4033188556675b1d763360af46feeca5b60f882829ee7b2"
     ),
   },
 
+  // RSA test vectors, oaep-vect.txt, Example 1.1: A 1024-bit RSA Key Pair
+  // <ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1-vec.zip>
+  rsaoaep: {
+    pkcs8: util.hex2abv(
+      "30820276020100300d06092a864886f70d0101010500048202603082025c0201" +
+      "0002818100a8b3b284af8eb50b387034a860f146c4919f318763cd6c5598c8ae" +
+      "4811a1e0abc4c7e0b082d693a5e7fced675cf4668512772c0cbc64a742c6c630" +
+      "f533c8cc72f62ae833c40bf25842e984bb78bdbf97c0107d55bdb662f5c4e0fa" +
+      "b9845cb5148ef7392dd3aaff93ae1e6b667bb3d4247616d4f5ba10d4cfd226de" +
+      "88d39f16fb020301000102818053339cfdb79fc8466a655c7316aca85c55fd8f" +
+      "6dd898fdaf119517ef4f52e8fd8e258df93fee180fa0e4ab29693cd83b152a55" +
+      "3d4ac4d1812b8b9fa5af0e7f55fe7304df41570926f3311f15c4d65a732c4831" +
+      "16ee3d3d2d0af3549ad9bf7cbfb78ad884f84d5beb04724dc7369b31def37d0c" +
+      "f539e9cfcdd3de653729ead5d1024100d32737e7267ffe1341b2d5c0d150a81b" +
+      "586fb3132bed2f8d5262864a9cb9f30af38be448598d413a172efb802c21acf1" +
+      "c11c520c2f26a471dcad212eac7ca39d024100cc8853d1d54da630fac004f471" +
+      "f281c7b8982d8224a490edbeb33d3e3d5cc93c4765703d1dd791642f1f116a0d" +
+      "d852be2419b2af72bfe9a030e860b0288b5d7702400e12bf1718e9cef5599ba1" +
+      "c3882fe8046a90874eefce8f2ccc20e4f2741fb0a33a3848aec9c9305fbecbd2" +
+      "d76819967d4671acc6431e4037968db37878e695c102410095297b0f95a2fa67" +
+      "d00707d609dfd4fc05c89dafc2ef6d6ea55bec771ea333734d9251e79082ecda" +
+      "866efef13c459e1a631386b7e354c899f5f112ca85d7158302404f456c502493" +
+      "bdc0ed2ab756a3a6ed4d67352a697d4216e93212b127a63d5411ce6fa98d5dbe" +
+      "fd73263e3728142743818166ed7dd63687dd2a8ca1d2f4fbd8e1"
+    ),
+    spki: util.hex2abv(
+      "30819f300d06092a864886f70d010101050003818d0030818902818100a8b3b2" +
+      "84af8eb50b387034a860f146c4919f318763cd6c5598c8ae4811a1e0abc4c7e0" +
+      "b082d693a5e7fced675cf4668512772c0cbc64a742c6c630f533c8cc72f62ae8" +
+      "33c40bf25842e984bb78bdbf97c0107d55bdb662f5c4e0fab9845cb5148ef739" +
+      "2dd3aaff93ae1e6b667bb3d4247616d4f5ba10d4cfd226de88d39f16fb020301" +
+      "0001"
+    ),
+    data: util.hex2abv(
+      "6628194e12073db03ba94cda9ef9532397d50dba79b987004afefe34"
+    ),
+    result: util.hex2abv(
+      "354fe67b4a126d5d35fe36c777791a3f7ba13def484e2d3908aff722fad468fb" +
+      "21696de95d0be911c2d3174f8afcc201035f7b6d8e69402de5451618c21a535f" +
+      "a9d7bfc5b8dd9fc243f8cf927db31322d6e881eaa91a996170e657a05a266426" +
+      "d98c88003f8477c1227094a0d9fa1e8c4024309ce1ecccb5210035d47ac72e8a"
+    ),
+  },
+
   // RFC 6070 <http://tools.ietf.org/html/rfc6070>
   pbkdf2_sha1: {
     password: new TextEncoder("utf-8").encode("passwordPASSWORDpassword"),
     salt: new TextEncoder("utf-8").encode("saltSALTsaltSALTsaltSALTsaltSALTsalt"),
     iterations: 4096,
     length: 25 * 8,
 
     derived: util.hex2abv(
--- a/dom/crypto/test/tests.js
+++ b/dom/crypto/test/tests.js
@@ -149,17 +149,17 @@ TestArray.addTest(
   }
 );
 
 // -----------------------------------------------------------------------------
 TestArray.addTest(
   "Import / export round-trip with 'pkcs8'",
   function() {
     var that = this;
-    var alg = { name: "RSASSA-PKCS1-v1_5", hash: "SHA1" };
+    var alg = { name: "RSASSA-PKCS1-v1_5", hash: "SHA-1" };
 
     function doExport(x) {
       if (!hasKeyFields(x)) {
         throw "Invalid key; missing field(s)";
       } else if ((x.algorithm.name != alg.name) ||
         (x.algorithm.hash.name != alg.hash) ||
         (x.algorithm.modulusLength != 512) ||
         (x.algorithm.publicExponent.byteLength != 3) ||
@@ -181,17 +181,17 @@ TestArray.addTest(
   }
 );
 
 // -----------------------------------------------------------------------------
 TestArray.addTest(
   "Import failure with format 'pkcs8'",
   function() {
     var that = this;
-    var alg = { name: "RSASSA-PKCS1-v1_5", hash: "SHA1" };
+    var alg = { name: "RSASSA-PKCS1-v1_5", hash: "SHA-1" };
 
     crypto.subtle.importKey("pkcs8", tv.negative_pkcs8, alg, true, ["encrypt"])
       .then(error(that), complete(that));
   }
 );
 
 // -----------------------------------------------------------------------------
 TestArray.addTest(
@@ -1145,8 +1145,132 @@ TestArray.addTest(
     }
     function fail(x) { console.log("failing"); error(that)(x); }
 
     crypto.subtle.importKey("raw", key, alg, false, ["deriveKey"])
       .then( doDerive, fail )
       .then( memcmp_complete(that, tv.pbkdf2_sha256.derived), fail );
   }
 );*/
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "RSA-OAEP encrypt/decrypt round-trip",
+  function () {
+    var that = this;
+    var privKey, pubKey;
+    var alg = {name: "RSA-OAEP", hash: "SHA-1"};
+
+    var privKey, pubKey;
+    function setPriv(x) { privKey = x; }
+    function setPub(x) { pubKey = x; }
+    function doEncrypt() {
+      return crypto.subtle.encrypt(alg, pubKey, tv.rsaoaep.data);
+    }
+    function doDecrypt(x) {
+      return crypto.subtle.decrypt(alg, privKey, x);
+    }
+
+    Promise.all([
+      crypto.subtle.importKey("pkcs8", tv.rsaoaep.pkcs8, alg, false, ['decrypt'])
+          .then(setPriv, error(that)),
+      crypto.subtle.importKey("spki", tv.rsaoaep.spki, alg, false, ['encrypt'])
+          .then(setPub, error(that))
+    ]).then(doEncrypt, error(that))
+      .then(doDecrypt, error(that))
+      .then(
+        memcmp_complete(that, tv.rsaoaep.data),
+        error(that)
+      );
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "RSA-OAEP key generation and encrypt/decrypt round-trip (SHA-256)",
+  function () {
+    var that = this;
+    var alg = {
+      name: "RSA-OAEP",
+      hash: "SHA-256",
+      modulusLength: 2048,
+      publicExponent: new Uint8Array([0x01, 0x00, 0x01])
+    };
+
+    var privKey, pubKey, data = crypto.getRandomValues(new Uint8Array(128));
+    function setKey(x) { pubKey = x.publicKey; privKey = x.privateKey; }
+    function doEncrypt() {
+      return crypto.subtle.encrypt(alg, pubKey, data);
+    }
+    function doDecrypt(x) {
+      return crypto.subtle.decrypt(alg, privKey, x);
+    }
+
+    crypto.subtle.generateKey(alg, false, ['encrypt', 'decrypt'])
+      .then(setKey, error(that))
+      .then(doEncrypt, error(that))
+      .then(doDecrypt, error(that))
+      .then(
+        memcmp_complete(that, data),
+        error(that)
+      );
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "RSA-OAEP decryption known answer",
+  function () {
+    var that = this;
+    var alg = {name: "RSA-OAEP", hash: "SHA-1"};
+
+    function doDecrypt(x) {
+      return crypto.subtle.decrypt(alg, x, tv.rsaoaep.result);
+    }
+    function fail() { error(that); }
+
+    crypto.subtle.importKey("pkcs8", tv.rsaoaep.pkcs8, alg, false, ['decrypt'])
+      .then( doDecrypt, fail )
+      .then( memcmp_complete(that, tv.rsaoaep.data), fail );
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "RSA-OAEP input data length checks (2048-bit key)",
+  function () {
+    var that = this;
+    var privKey, pubKey;
+    var alg = {
+      name: "RSA-OAEP",
+      hash: "SHA-1",
+      modulusLength: 2048,
+      publicExponent: new Uint8Array([0x01, 0x00, 0x01])
+    };
+
+    var privKey, pubKey;
+    function setKey(x) { pubKey = x.publicKey; privKey = x.privateKey; }
+    function doEncrypt(n) {
+      return function () {
+        return crypto.subtle.encrypt(alg, pubKey, new Uint8Array(n));
+      }
+    }
+
+    crypto.subtle.generateKey(alg, false, ['encrypt'])
+      .then(setKey, error(that))
+      .then(doEncrypt(214), error(that))
+      .then(doEncrypt(215), error(that))
+      .then(error(that), complete(that));
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "RSA-OAEP key import with invalid hash",
+  function () {
+    var that = this;
+    var alg = {name: "RSA-OAEP", hash: "SHA-123"};
+
+    crypto.subtle.importKey("pkcs8", tv.rsaoaep.pkcs8, alg, false, ['decrypt'])
+      .then(error(that), complete(that));
+  }
+);
+
--- a/dom/downloads/moz.build
+++ b/dom/downloads/moz.build
@@ -1,10 +1,10 @@
 # -*- 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/.
 
 if CONFIG["MOZ_B2G"]:
-	TEST_DIRS += ['tests']
+    TEST_DIRS += ['tests']
 
 PARALLEL_DIRS += ['src']
--- a/dom/events/FocusEvent.h
+++ b/dom/events/FocusEvent.h
@@ -12,17 +12,17 @@
 
 namespace mozilla {
 namespace dom {
 
 class FocusEvent : public UIEvent,
                    public nsIDOMFocusEvent
 {
 public:
-  NS_DECL_ISUPPORTS
+  NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIDOMFOCUSEVENT
 
   // Forward to base class
   NS_FORWARD_TO_UIEVENT
 
   virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE
   {
     return FocusEventBinding::Wrap(aCx, this);
--- a/dom/interfaces/css/nsIDOMCSSCharsetRule.idl
+++ b/dom/interfaces/css/nsIDOMCSSCharsetRule.idl
@@ -1,13 +1,13 @@
 /* -*- 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/. */
 
 #include "nsIDOMCSSRule.idl"
 
-[scriptable, uuid(19fe78cc-65ff-4b1d-a5d7-9ea89692cec6)]
+[scriptable, uuid(756c326c-eb38-4342-95e5-5eabea809174)]
 interface nsIDOMCSSCharsetRule : nsIDOMCSSRule
 {
   attribute DOMString        encoding;
                                         // raises(DOMException) on setting
 };
--- a/dom/interfaces/css/nsIDOMCSSConditionRule.idl
+++ b/dom/interfaces/css/nsIDOMCSSConditionRule.idl
@@ -4,13 +4,13 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsIDOMCSSGroupingRule.idl"
 
 /**
  * Interface in the CSS OM for at-rules that conditionally apply their
  * child rules.
  */
-[scriptable, uuid(942754f2-2c0e-461b-9c10-c0e929504fe1)]
+[scriptable, uuid(44da41b2-5660-415d-8692-eae805776103)]
 interface nsIDOMCSSConditionRule : nsIDOMCSSGroupingRule
 {
   attribute DOMString conditionText;
 };
--- a/dom/interfaces/css/nsIDOMCSSCounterStyleRule.idl
+++ b/dom/interfaces/css/nsIDOMCSSCounterStyleRule.idl
@@ -1,17 +1,17 @@
 /* -*- Mode: IDL; tab-width: 2; 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 "nsIDOMCSSRule.idl"
 
-[scriptable, uuid(5f9f2068-743b-42e3-becb-10ffa994d1e3)]
+[scriptable, uuid(9b5e48ce-d84c-4e31-aff5-34e9f4141313)]
 interface nsIDOMCSSCounterStyleRule : nsIDOMCSSRule
 {
   attribute DOMString name;
   attribute DOMString system;
   attribute DOMString symbols;
   attribute DOMString additiveSymbols;
   attribute DOMString negative;
   attribute DOMString prefix;
--- a/dom/interfaces/css/nsIDOMCSSFontFaceRule.idl
+++ b/dom/interfaces/css/nsIDOMCSSFontFaceRule.idl
@@ -1,12 +1,12 @@
 /* -*- 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/. */
 
 #include "nsIDOMCSSRule.idl"
 
-[scriptable, uuid(a6cf90bb-15b3-11d2-932e-00805f8add32)]
+[scriptable, uuid(db971017-fe0c-4529-972c-8217f2fee217)]
 interface nsIDOMCSSFontFaceRule : nsIDOMCSSRule
 {
   readonly attribute nsIDOMCSSStyleDeclaration  style;
 };
--- a/dom/interfaces/css/nsIDOMCSSFontFeatureValuesRule.idl
+++ b/dom/interfaces/css/nsIDOMCSSFontFeatureValuesRule.idl
@@ -1,16 +1,16 @@
 /* -*- 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/. */
 
 #include "nsIDOMCSSRule.idl"
 
-[scriptable, uuid(f4cb1776-389d-4f52-a4d8-68bea5bd00c1)]
+[scriptable, uuid(a343d27f-1da6-4fc3-9355-d4ca434f958e)]
 interface nsIDOMCSSFontFeatureValuesRule : nsIDOMCSSRule
 {
   attribute DOMString fontFamily;
                       // raises(DOMException) on setting
 
   attribute DOMString valueText;
                       // raises(DOMException) on setting
 };
--- a/dom/interfaces/css/nsIDOMCSSGroupingRule.idl
+++ b/dom/interfaces/css/nsIDOMCSSGroupingRule.idl
@@ -3,17 +3,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsIDOMCSSRule.idl"
 
 /**
  * Interface for at-rules that have child rules in the CSS OM.
  */
-[scriptable, uuid(ab013eed-fa21-4c6a-bba3-79e8780f583e)]
+[scriptable, uuid(a0e3324a-f911-4baf-9591-5322c76cbb0d)]
 interface nsIDOMCSSGroupingRule : nsIDOMCSSRule
 {
   readonly attribute nsIDOMCSSRuleList cssRules;
 
   unsigned long      insertRule(in DOMString rule,
                                 in unsigned long index)
                                         raises(DOMException);
   void               deleteRule(in unsigned long index)
--- a/dom/interfaces/css/nsIDOMCSSImportRule.idl
+++ b/dom/interfaces/css/nsIDOMCSSImportRule.idl
@@ -1,14 +1,14 @@
 /* -*- 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/. */
 
 #include "nsIDOMCSSRule.idl"
 
-[scriptable, uuid(a6cf90cf-15b3-11d2-932e-00805f8add32)]
+[scriptable, uuid(d3b2b914-01ef-4663-beda-a6475a26f491)]
 interface nsIDOMCSSImportRule : nsIDOMCSSRule
 {
   readonly attribute DOMString           href;
   readonly attribute nsIDOMMediaList     media;
   readonly attribute nsIDOMCSSStyleSheet styleSheet;
 };
--- a/dom/interfaces/css/nsIDOMCSSMediaRule.idl
+++ b/dom/interfaces/css/nsIDOMCSSMediaRule.idl
@@ -3,13 +3,13 @@
  * 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 "nsIDOMCSSConditionRule.idl"
 
 /**
  * Interface for @media rules in the CSS OM.
  */
-[scriptable, uuid(1f491b05-932b-4aa1-a1f1-466505d70898)]
+[scriptable, uuid(6cf9c5b2-fa0f-43c0-aa50-ef85b4756e3a)]
 interface nsIDOMCSSMediaRule : nsIDOMCSSConditionRule
 {
   readonly attribute nsIDOMMediaList   media;
 };
--- a/dom/interfaces/css/nsIDOMCSSMozDocumentRule.idl
+++ b/dom/interfaces/css/nsIDOMCSSMozDocumentRule.idl
@@ -3,13 +3,13 @@
  * 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 "nsIDOMCSSConditionRule.idl"
 
 /**
  * Interface for @-moz-document rules in the CSS OM.
  */
-[scriptable, uuid(f118a5a8-ac36-464f-b993-18cf6fe76fda)]
+[scriptable, uuid(2d0cef9d-c1b2-4c6c-9003-fa83040626d1)]
 interface nsIDOMCSSMozDocumentRule : nsIDOMCSSConditionRule
 {
   // XXX Add access to the URL list.
 };
--- a/dom/interfaces/css/nsIDOMCSSPageRule.idl
+++ b/dom/interfaces/css/nsIDOMCSSPageRule.idl
@@ -1,15 +1,15 @@
 /* -*- 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/. */
 
 #include "nsIDOMCSSRule.idl"
 
-[scriptable, uuid(6126024d-d716-4ad8-bc53-24dd6d5846b1)]
+[scriptable, uuid(c119072b-7d2f-4aeb-a90d-e2d6b606c32a)]
 interface nsIDOMCSSPageRule : nsIDOMCSSRule
 {
            //attribute DOMString        selectorText;
                                         // raises(DOMException) on setting
 
   readonly attribute nsIDOMCSSStyleDeclaration  style;
 };
--- a/dom/interfaces/css/nsIDOMCSSRule.idl
+++ b/dom/interfaces/css/nsIDOMCSSRule.idl
@@ -1,24 +1,34 @@
 /* -*- 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/. */
 
 #include "domstubs.idl"
 
+%{C++
+namespace mozilla {
+namespace css {
+class Rule;
+}
+}
+%}
+
+[ptr] native Rule(mozilla::css::Rule);
+
 /**
  * The nsIDOMCSSRule interface is a datatype for a CSS style rule in
  * the Document Object Model.
  *
  * For more information on this interface please see
  * http://www.w3.org/TR/DOM-Level-2-Style
  */
 
-[scriptable, uuid(2938307a-9d70-4b63-8afc-0197e82318ad)]
+[scriptable, uuid(4d6b3bad-f53c-4585-82f6-62982e27ede8)]
 interface nsIDOMCSSRule : nsISupports
 {
   // RuleType
   const unsigned short      UNKNOWN_RULE                   = 0;
   const unsigned short      STYLE_RULE                     = 1;
   const unsigned short      CHARSET_RULE                   = 2;
   const unsigned short      IMPORT_RULE                    = 3;
   const unsigned short      MEDIA_RULE                     = 4;
@@ -36,9 +46,11 @@ interface nsIDOMCSSRule : nsISupports
   const unsigned short      FONT_FEATURE_VALUES_RULE       = 14;
 
   readonly attribute unsigned short      type;
            attribute DOMString           cssText;
                                         // raises(DOMException) on setting
 
   readonly attribute nsIDOMCSSStyleSheet parentStyleSheet;
   readonly attribute nsIDOMCSSRule       parentRule;
+
+  [noscript, nostdcall, notxpcom] Rule getCSSRule();
 };
--- a/dom/interfaces/css/nsIDOMCSSStyleRule.idl
+++ b/dom/interfaces/css/nsIDOMCSSStyleRule.idl
@@ -1,15 +1,15 @@
 /* -*- 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/. */
 
 #include "nsIDOMCSSRule.idl"
 
-[scriptable, uuid(a6cf90bf-15b3-11d2-932e-00805f8add32)]
+[scriptable, uuid(b5e9af48-a7c2-4f88-aae3-58307af4b5a5)]
 interface nsIDOMCSSStyleRule : nsIDOMCSSRule
 {
            attribute DOMString        selectorText;
                                         // raises(DOMException) on setting
 
   readonly attribute nsIDOMCSSStyleDeclaration  style;
 };
--- a/dom/interfaces/css/nsIDOMCSSSupportsRule.idl
+++ b/dom/interfaces/css/nsIDOMCSSSupportsRule.idl
@@ -3,12 +3,12 @@
  * 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 "nsIDOMCSSConditionRule.idl"
 
 /**
  * Interface for @supports rules in the CSS OM.
  */
-[scriptable, uuid(5f409a4d-92f9-4a62-8e8a-cc1c02c32918)]
+[scriptable, uuid(0b9e63a1-1bd7-4caf-850e-148b762b14d2)]
 interface nsIDOMCSSSupportsRule : nsIDOMCSSConditionRule
 {
 };
--- a/dom/interfaces/css/nsIDOMCSSUnknownRule.idl
+++ b/dom/interfaces/css/nsIDOMCSSUnknownRule.idl
@@ -1,11 +1,11 @@
 /* -*- 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/. */
 
 #include "nsIDOMCSSRule.idl"
 
-[scriptable, uuid(a6cf90d0-15b3-11d2-932e-00805f8add32)]
+[scriptable, uuid(98f4c27b-fb35-4355-8fd9-546c4697d71a)]
 interface nsIDOMCSSUnknownRule : nsIDOMCSSRule
 {
 };
--- a/dom/interfaces/css/nsIDOMMozCSSKeyframeRule.idl
+++ b/dom/interfaces/css/nsIDOMMozCSSKeyframeRule.idl
@@ -1,13 +1,13 @@
 /* -*- 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/. */
 
 #include "nsIDOMCSSRule.idl"
 
-[scriptable, uuid(38a19612-dc58-414a-954c-233183808484)]
+[scriptable, uuid(a281a8b4-eaa2-49a8-8b97-acc2814a57c9)]
 interface nsIDOMMozCSSKeyframeRule : nsIDOMCSSRule
 {
            attribute DOMString                 keyText;
   readonly attribute nsIDOMCSSStyleDeclaration style;
 };
--- a/dom/interfaces/css/nsIDOMMozCSSKeyframesRule.idl
+++ b/dom/interfaces/css/nsIDOMMozCSSKeyframesRule.idl
@@ -1,16 +1,16 @@
 /* -*- 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/. */
 
 #include "nsIDOMCSSRule.idl"
 
-[scriptable, uuid(aa4ea11f-791b-4671-b192-b931e6539669)]
+[scriptable, uuid(400f4b70-ad0a-4047-aba4-ee8019f6b907)]
 interface nsIDOMMozCSSKeyframesRule : nsIDOMCSSRule
 {
            attribute DOMString         name;
   readonly attribute nsIDOMCSSRuleList cssRules;
 
   void                     appendRule(in DOMString rule);
   void                     deleteRule(in DOMString key);
   nsIDOMMozCSSKeyframeRule findRule(in DOMString key);
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -54,16 +54,17 @@
 #include "nsICycleCollectorListener.h"
 #include "nsIIPCBackgroundChildCreateCallback.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIMemoryReporter.h"
 #include "nsIMemoryInfoDumper.h"
 #include "nsIMutable.h"
 #include "nsIObserverService.h"
 #include "nsIScriptSecurityManager.h"
+#include "nsScreenManagerProxy.h"
 #include "nsMemoryInfoDumper.h"
 #include "nsServiceManagerUtils.h"
 #include "nsStyleSheetService.h"
 #include "nsXULAppAPI.h"
 #include "nsIScriptError.h"
 #include "nsIConsoleService.h"
 #include "nsJSEnvironment.h"
 #include "SandboxHal.h"
@@ -146,16 +147,17 @@
 #include "nsIPrincipal.h"
 #include "nsDeviceStorage.h"
 #include "AudioChannelService.h"
 #include "JavaScriptChild.h"
 #include "mozilla/dom/DataStoreService.h"
 #include "mozilla/dom/telephony/PTelephonyChild.h"
 #include "mozilla/dom/time/DateCacheCleaner.h"
 #include "mozilla/net/NeckoMessageUtils.h"
+#include "mozilla/RemoteSpellCheckEngineChild.h"
 
 using namespace base;
 using namespace mozilla;
 using namespace mozilla::docshell;
 using namespace mozilla::dom::bluetooth;
 using namespace mozilla::dom::devicestorage;
 using namespace mozilla::dom::ipc;
 using namespace mozilla::dom::mobilemessage;
@@ -164,16 +166,17 @@ using namespace mozilla::dom::telephony;
 using namespace mozilla::hal_sandbox;
 using namespace mozilla::ipc;
 using namespace mozilla::layers;
 using namespace mozilla::net;
 using namespace mozilla::jsipc;
 #if defined(MOZ_WIDGET_GONK)
 using namespace mozilla::system;
 #endif
+using namespace mozilla::widget;
 
 #ifdef MOZ_NUWA_PROCESS
 static bool sNuwaForking = false;
 
 // The size of the reserved stack (in unsigned ints). It's used to reserve space
 // to push sigsetjmp() in NuwaCheckpointCurrentThread() to higher in the stack
 // so that after it returns and do other work we don't garble the stack we want
 // to preserve in NuwaCheckpointCurrentThread().
@@ -1047,16 +1050,30 @@ ContentChild::DeallocPBrowserChild(PBrow
 }
 
 PBlobChild*
 ContentChild::AllocPBlobChild(const BlobConstructorParams& aParams)
 {
     return nsIContentChild::AllocPBlobChild(aParams);
 }
 
+mozilla::PRemoteSpellcheckEngineChild *
+ContentChild::AllocPRemoteSpellcheckEngineChild()
+{
+    NS_NOTREACHED("Default Constructor for PRemoteSpellcheckEngineChilf should never be called");
+    return nullptr;
+}
+
+bool
+ContentChild::DeallocPRemoteSpellcheckEngineChild(PRemoteSpellcheckEngineChild *child)
+{
+    delete child;
+    return true;
+}
+
 bool
 ContentChild::DeallocPBlobChild(PBlobChild* aActor)
 {
     return nsIContentChild::DeallocPBlobChild(aActor);
 }
 
 PBlobChild*
 ContentChild::SendPBlobConstructor(PBlobChild* aActor,
@@ -1195,16 +1212,38 @@ ContentChild::AllocPNeckoChild()
 
 bool
 ContentChild::DeallocPNeckoChild(PNeckoChild* necko)
 {
     delete necko;
     return true;
 }
 
+PScreenManagerChild*
+ContentChild::AllocPScreenManagerChild(uint32_t* aNumberOfScreens,
+                                       float* aSystemDefaultScale,
+                                       bool* aSuccess)
+{
+    // The ContentParent should never attempt to allocate the
+    // nsScreenManagerProxy. Instead, the nsScreenManagerProxy
+    // service is requested and instantiated via XPCOM, and the
+    // constructor of nsScreenManagerProxy sets up the IPC connection.
+    NS_NOTREACHED("Should never get here!");
+    return nullptr;
+}
+
+bool
+ContentChild::DeallocPScreenManagerChild(PScreenManagerChild* aService)
+{
+    // nsScreenManagerProxy is AddRef'd in its constructor.
+    nsScreenManagerProxy *child = static_cast<nsScreenManagerProxy*>(aService);
+    child->Release();
+    return true;
+}
+
 PExternalHelperAppChild*
 ContentChild::AllocPExternalHelperAppChild(const OptionalURIParams& uri,
                                            const nsCString& aMimeContentType,
                                            const nsCString& aContentDisposition,
                                            const uint32_t& aContentDispositionHint,
                                            const nsString& aContentDispositionFilename,
                                            const bool& aForceSave,
                                            const int64_t& aContentLength,
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -21,16 +21,17 @@
 
 struct ChromePackage;
 class nsIDOMBlob;
 class nsIObserver;
 struct ResourceMapping;
 struct OverrideMapping;
 
 namespace mozilla {
+class RemoteSpellcheckEngineChild;
 
 namespace ipc {
 class OptionalURIParams;
 class URIParams;
 }// namespace ipc
 
 namespace jsipc {
 class JavaScriptChild;
@@ -188,16 +189,22 @@ public:
     virtual PTestShellChild* AllocPTestShellChild() MOZ_OVERRIDE;
     virtual bool DeallocPTestShellChild(PTestShellChild*) MOZ_OVERRIDE;
     virtual bool RecvPTestShellConstructor(PTestShellChild*) MOZ_OVERRIDE;
     jsipc::JavaScriptChild *GetCPOWManager();
 
     virtual PNeckoChild* AllocPNeckoChild() MOZ_OVERRIDE;
     virtual bool DeallocPNeckoChild(PNeckoChild*) MOZ_OVERRIDE;
 
+    virtual PScreenManagerChild*
+    AllocPScreenManagerChild(uint32_t* aNumberOfScreens,
+                             float* aSystemDefaultScale,
+                             bool* aSuccess) MOZ_OVERRIDE;
+    virtual bool DeallocPScreenManagerChild(PScreenManagerChild*) MOZ_OVERRIDE;
+
     virtual PExternalHelperAppChild *AllocPExternalHelperAppChild(
             const OptionalURIParams& uri,
             const nsCString& aMimeContentType,
             const nsCString& aContentDisposition,
             const uint32_t& aContentDispositionHint,
             const nsString& aContentDispositionFilename,
             const bool& aForceSave,
             const int64_t& aContentLength,
@@ -232,16 +239,18 @@ public:
 
     virtual bool RecvRegisterChrome(const InfallibleTArray<ChromePackage>& packages,
                                     const InfallibleTArray<ResourceMapping>& resources,
                                     const InfallibleTArray<OverrideMapping>& overrides,
                                     const nsCString& locale) MOZ_OVERRIDE;
 
     virtual mozilla::jsipc::PJavaScriptChild* AllocPJavaScriptChild() MOZ_OVERRIDE;
     virtual bool DeallocPJavaScriptChild(mozilla::jsipc::PJavaScriptChild*) MOZ_OVERRIDE;
+    virtual PRemoteSpellcheckEngineChild* AllocPRemoteSpellcheckEngineChild() MOZ_OVERRIDE;
+    virtual bool DeallocPRemoteSpellcheckEngineChild(PRemoteSpellcheckEngineChild*) MOZ_OVERRIDE;
 
     virtual bool RecvSetOffline(const bool& offline) MOZ_OVERRIDE;
 
     virtual bool RecvSpeakerManagerNotify() MOZ_OVERRIDE;
 
     virtual bool RecvNotifyVisited(const URIParams& aURI) MOZ_OVERRIDE;
     // auto remove when alertfinished is received.
     nsresult AddRemoteAlertObserver(const nsString& aData, nsIObserver* aObserver);
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -108,16 +108,17 @@
 #include "nsServiceManagerUtils.h"
 #include "nsStyleSheetService.h"
 #include "nsThreadUtils.h"
 #include "nsToolkitCompsCID.h"
 #include "nsWidgetsCID.h"
 #include "PreallocatedProcessManager.h"
 #include "ProcessPriorityManager.h"
 #include "SandboxHal.h"
+#include "ScreenManagerParent.h"
 #include "StructuredCloneUtils.h"
 #include "TabParent.h"
 #include "URIUtils.h"
 #include "nsIWebBrowserChrome.h"
 #include "nsIDocShell.h"
 #include "mozilla/net/NeckoMessageUtils.h"
 #include "gfxPrefs.h"
 #include "prio.h"
@@ -152,16 +153,18 @@ using namespace mozilla::system;
 
 #ifdef MOZ_B2G_BT
 #include "BluetoothParent.h"
 #include "BluetoothService.h"
 #endif
 
 #include "JavaScriptParent.h"
 
+#include "mozilla/RemoteSpellCheckEngineParent.h"
+
 #ifdef MOZ_B2G_FM
 #include "mozilla/dom/FMRadioParent.h"
 #endif
 
 #include "Crypto.h"
 
 #ifdef MOZ_WEBSPEECH
 #include "mozilla/dom/SpeechSynthesisParent.h"
@@ -184,16 +187,17 @@ using namespace mozilla::dom::indexedDB;
 using namespace mozilla::dom::power;
 using namespace mozilla::dom::mobilemessage;
 using namespace mozilla::dom::telephony;
 using namespace mozilla::hal;
 using namespace mozilla::ipc;
 using namespace mozilla::layers;
 using namespace mozilla::net;
 using namespace mozilla::jsipc;
+using namespace mozilla::widget;
 
 #ifdef ENABLE_TESTS
 
 class BackgroundTester MOZ_FINAL : public nsIIPCBackgroundChildCreateCallback,
                                    public nsIObserver
 {
     static uint32_t sCallbackCount;
 
@@ -2688,16 +2692,30 @@ ContentParent::AllocPBlobParent(const Bl
 
 bool
 ContentParent::DeallocPBlobParent(PBlobParent* aActor)
 {
     delete aActor;
     return true;
 }
 
+mozilla::PRemoteSpellcheckEngineParent *
+ContentParent::AllocPRemoteSpellcheckEngineParent()
+{
+    mozilla::RemoteSpellcheckEngineParent *parent = new mozilla::RemoteSpellcheckEngineParent();
+    return parent;
+}
+
+bool
+ContentParent::DeallocPRemoteSpellcheckEngineParent(PRemoteSpellcheckEngineParent *parent)
+{
+    delete parent;
+    return true;
+}
+
 void
 ContentParent::KillHard()
 {
     // On Windows, calling KillHard multiple times causes problems - the
     // process handle becomes invalid on the first call, causing a second call
     // to crash our process - more details in bug 890840.
     if (mCalledKillHard) {
         return;
@@ -2898,16 +2916,31 @@ ContentParent::AllocPNeckoParent()
 
 bool
 ContentParent::DeallocPNeckoParent(PNeckoParent* necko)
 {
     delete necko;
     return true;
 }
 
+PScreenManagerParent*
+ContentParent::AllocPScreenManagerParent(uint32_t* aNumberOfScreens,
+                                         float* aSystemDefaultScale,
+                                         bool* aSuccess)
+{
+    return new ScreenManagerParent(aNumberOfScreens, aSystemDefaultScale, aSuccess);
+}
+
+bool
+ContentParent::DeallocPScreenManagerParent(PScreenManagerParent* aActor)
+{
+    delete aActor;
+    return true;
+}
+
 PExternalHelperAppParent*
 ContentParent::AllocPExternalHelperAppParent(const OptionalURIParams& uri,
                                              const nsCString& aMimeContentType,
                                              const nsCString& aContentDisposition,
                                              const uint32_t& aContentDispositionHint,
                                              const nsString& aContentDispositionFilename,
                                              const bool& aForceSave,
                                              const int64_t& aContentLength,
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -31,16 +31,17 @@ class mozIApplication;
 class nsConsoleService;
 class nsICycleCollectorLogSink;
 class nsIDOMBlob;
 class nsIDumpGCAndCCLogsCallback;
 class nsIMemoryReporter;
 class ParentIdleListener;
 
 namespace mozilla {
+class PRemoteSpellcheckEngineParent;
 
 namespace ipc {
 class OptionalURIParams;
 class URIParams;
 class TestShellParent;
 } // namespace ipc
 
 namespace jsipc {
@@ -225,32 +226,39 @@ public:
                                   const NativeThreadId& tid,
                                   const uint32_t& processType) MOZ_OVERRIDE;
 
     virtual PNeckoParent* AllocPNeckoParent() MOZ_OVERRIDE;
     virtual bool RecvPNeckoConstructor(PNeckoParent* aActor) MOZ_OVERRIDE {
         return PContentParent::RecvPNeckoConstructor(aActor);
     }
 
+    virtual PScreenManagerParent*
+    AllocPScreenManagerParent(uint32_t* aNumberOfScreens,
+                              float* aSystemDefaultScale,
+                              bool* aSuccess) MOZ_OVERRIDE;
+    virtual bool DeallocPScreenManagerParent(PScreenManagerParent* aActor) MOZ_OVERRIDE;
+
     virtual PHalParent* AllocPHalParent() MOZ_OVERRIDE;
     virtual bool RecvPHalConstructor(PHalParent* aActor) MOZ_OVERRIDE {
         return PContentParent::RecvPHalConstructor(aActor);
     }
 
     virtual PStorageParent* AllocPStorageParent() MOZ_OVERRIDE;
     virtual bool RecvPStorageConstructor(PStorageParent* aActor) MOZ_OVERRIDE {
         return PContentParent::RecvPStorageConstructor(aActor);
     }
 
     virtual PJavaScriptParent*
     AllocPJavaScriptParent() MOZ_OVERRIDE;
     virtual bool
     RecvPJavaScriptConstructor(PJavaScriptParent* aActor) MOZ_OVERRIDE {
         return PContentParent::RecvPJavaScriptConstructor(aActor);
     }
+    virtual PRemoteSpellcheckEngineParent* AllocPRemoteSpellcheckEngineParent() MOZ_OVERRIDE;
 
     virtual bool RecvRecordingDeviceEvents(const nsString& aRecordingStatus,
                                            const nsString& aPageURL,
                                            const bool& aIsAudio,
                                            const bool& aIsVideo) MOZ_OVERRIDE;
 
     bool CycleCollectWithLogs(bool aDumpAllTraces,
                               nsICycleCollectorLogSink* aSink,
@@ -384,16 +392,17 @@ private:
 
     virtual bool RecvGetProcessAttributes(uint64_t* aId,
                                           bool* aIsForApp,
                                           bool* aIsForBrowser) MOZ_OVERRIDE;
     virtual bool RecvGetXPCOMProcessAttributes(bool* aIsOffline) MOZ_OVERRIDE;
 
     virtual bool DeallocPJavaScriptParent(mozilla::jsipc::PJavaScriptParent*) MOZ_OVERRIDE;
 
+    virtual bool DeallocPRemoteSpellcheckEngineParent(PRemoteSpellcheckEngineParent*) MOZ_OVERRIDE;
     virtual PBrowserParent* AllocPBrowserParent(const IPCTabContext& aContext,
                                                 const uint32_t& aChromeFlags,
                                                 const uint64_t& aId,
                                                 const bool& aIsForApp,
                                                 const bool& aIsForBrowser) MOZ_OVERRIDE;
     virtual bool DeallocPBrowserParent(PBrowserParent* frame) MOZ_OVERRIDE;
 
     virtual PDeviceStorageRequestParent*
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -18,23 +18,25 @@ include protocol PDeviceStorageRequest;
 include protocol PFileDescriptorSet;
 include protocol PFMRadio;
 include protocol PFileSystemRequest;
 include protocol PHal;
 include protocol PImageBridge;
 include protocol PIndexedDB;
 include protocol PMemoryReportRequest;
 include protocol PNecko;
+include protocol PScreenManager;
 include protocol PSharedBufferManager;
 include protocol PSms;
 include protocol PSpeechSynthesis;
 include protocol PStorage;
 include protocol PTelephony;
 include protocol PTestShell;
 include protocol PJavaScript;
+include protocol PRemoteSpellcheckEngine;
 include DOMTypes;
 include JavaScriptTypes;
 include InputStreamParams;
 include PTabContext;
 include URIParams;
 include ProtocolTypes;
 
 using GeoPosition from "nsGeoPositionIPCSerialiser.h";
@@ -298,22 +300,24 @@ intr protocol PContent
     manages PFileSystemRequest;
     manages PExternalHelperApp;
     manages PFileDescriptorSet;
     manages PFMRadio;
     manages PHal;
     manages PIndexedDB;
     manages PMemoryReportRequest;
     manages PNecko;
+    manages PScreenManager;
     manages PSms;
     manages PSpeechSynthesis;
     manages PStorage;
     manages PTelephony;
     manages PTestShell;
     manages PJavaScript;
+    manages PRemoteSpellcheckEngine;
 
 both:
     // Depending on exactly how the new browser is being created, it might be
     // created from either the child or parent process!
     //
     // The child creates the PBrowser as part of
     // TabChild::BrowserFrameProvideWindow (which happens when the child's
     // content calls window.open()), and the parent creates the PBrowser as part
@@ -457,16 +461,17 @@ parent:
 
     sync CreateChildProcess(IPCTabContext context,
                             ProcessPriority priority)
         returns (uint64_t id, bool isForApp, bool isForBrowser);
     intr BridgeToChildProcess(uint64_t id);
 
     async PJavaScript();
 
+    sync PRemoteSpellcheckEngine();
     PDeviceStorageRequest(DeviceStorageParams params);
 
     PFileSystemRequest(FileSystemParams params);
 
     sync PCrashReporter(NativeThreadId tid, uint32_t processType);
 
     sync GetRandomValues(uint32_t length)
         returns (uint8_t[] randomValues);
@@ -474,16 +479,21 @@ parent:
     async GetSystemMemory(uint64_t getterId);
 
     PHal();
 
     PIndexedDB();
 
     PNecko();
 
+    sync PScreenManager()
+        returns (uint32_t numberOfScreens,
+                 float systemDefaultScale,
+                 bool success);
+
     PSms();
 
     PSpeechSynthesis();
 
     PStorage();
 
     PTelephony();
 
new file mode 100644
--- /dev/null
+++ b/dom/ipc/PScreenManager.ipdl
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
+/* 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 protocol PBrowser;
+include protocol PContent;
+
+using struct nsIntRect from "nsRect.h";
+
+namespace mozilla {
+namespace dom {
+
+struct ScreenDetails {
+  uint32_t id;
+  nsIntRect rect;
+  nsIntRect availRect;
+  int32_t pixelDepth;
+  int32_t colorDepth;
+  double contentsScaleFactor;
+};
+
+sync protocol PScreenManager
+{
+  manager PContent;
+
+parent:
+    sync Refresh()
+      returns (uint32_t numberOfScreens,
+               float systemDefaultScale,
+               bool success);
+
+    sync ScreenRefresh(uint32_t aId)
+      returns (ScreenDetails screen,
+               bool success);
+
+    sync GetPrimaryScreen()
+      returns (ScreenDetails screen,
+               bool success);
+
+    sync ScreenForRect(int32_t aLeft,
+                       int32_t aTop,
+                       int32_t aWidth,
+                       int32_t aHeight)
+      returns (ScreenDetails screen,
+               bool success);
+
+    sync ScreenForBrowser(PBrowser aBrowser)
+      returns (ScreenDetails screen,
+               bool success);
+
+child:
+    __delete__();
+};
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/ipc/ScreenManagerParent.cpp
@@ -0,0 +1,195 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/TabParent.h"
+#include "mozilla/unused.h"
+#include "nsIWidget.h"
+#include "nsServiceManagerUtils.h"
+#include "ScreenManagerParent.h"
+
+namespace mozilla {
+namespace dom {
+
+static const char *sScreenManagerContractID = "@mozilla.org/gfx/screenmanager;1";
+
+ScreenManagerParent::ScreenManagerParent(uint32_t* aNumberOfScreens,
+                                         float* aSystemDefaultScale,
+                                         bool* aSuccess)
+{
+  mScreenMgr = do_GetService(sScreenManagerContractID);
+  if (!mScreenMgr) {
+    MOZ_CRASH("Couldn't get nsIScreenManager from ScreenManagerParent.");
+  }
+
+  unused << RecvRefresh(aNumberOfScreens, aSystemDefaultScale, aSuccess);
+}
+
+bool
+ScreenManagerParent::RecvRefresh(uint32_t* aNumberOfScreens,
+                                 float* aSystemDefaultScale,
+                                 bool* aSuccess)
+{
+  *aSuccess = false;
+
+  nsresult rv = mScreenMgr->GetNumberOfScreens(aNumberOfScreens);
+  if (NS_FAILED(rv)) {
+    return true;
+  }
+
+  rv = mScreenMgr->GetSystemDefaultScale(aSystemDefaultScale);
+  if (NS_FAILED(rv)) {
+    return true;
+  }
+
+  *aSuccess = true;
+  return true;
+}
+
+bool
+ScreenManagerParent::RecvScreenRefresh(const uint32_t& aId,
+                                       ScreenDetails* aRetVal,
+                                       bool* aSuccess)
+{
+  *aSuccess = false;
+
+  nsCOMPtr<nsIScreen> screen;
+  nsresult rv = mScreenMgr->ScreenForId(aId, getter_AddRefs(screen));
+  if (NS_FAILED(rv)) {
+    return true;
+  }
+
+  ScreenDetails details;
+  unused << ExtractScreenDetails(screen, details);
+
+  *aRetVal = details;
+  *aSuccess = true;
+  return true;
+}
+
+bool
+ScreenManagerParent::RecvGetPrimaryScreen(ScreenDetails* aRetVal,
+                                          bool* aSuccess)
+{
+  *aSuccess = false;
+
+  nsCOMPtr<nsIScreen> screen;
+  nsresult rv = mScreenMgr->GetPrimaryScreen(getter_AddRefs(screen));
+
+  NS_ENSURE_SUCCESS(rv, true);
+
+  ScreenDetails details;
+  if (!ExtractScreenDetails(screen, details)) {
+    return true;
+  }
+
+  *aRetVal = details;
+  *aSuccess = true;
+  return true;
+}
+
+bool
+ScreenManagerParent::RecvScreenForRect(const int32_t& aLeft,
+                                       const int32_t& aTop,
+                                       const int32_t& aWidth,
+                                       const int32_t& aHeight,
+                                       ScreenDetails* aRetVal,
+                                       bool* aSuccess)
+{
+  *aSuccess = false;
+
+  nsCOMPtr<nsIScreen> screen;
+  nsresult rv = mScreenMgr->ScreenForRect(aLeft, aTop, aWidth, aHeight, getter_AddRefs(screen));
+
+  NS_ENSURE_SUCCESS(rv, true);
+
+  ScreenDetails details;
+  if (!ExtractScreenDetails(screen, details)) {
+    return true;
+  }
+
+  *aRetVal = details;
+  *aSuccess = true;
+  return true;
+}
+
+bool
+ScreenManagerParent::RecvScreenForBrowser(PBrowserParent* aBrowser,
+                                          ScreenDetails* aRetVal,
+                                          bool* aSuccess)
+{
+  *aSuccess = false;
+
+  // Find the mWidget associated with the tabparent, and then return
+  // the nsIScreen it's on.
+  TabParent* tabParent = static_cast<TabParent*>(aBrowser);
+  nsCOMPtr<nsIWidget> widget = tabParent->GetWidget();
+  if (!widget) {
+    return true;
+  }
+
+  nsCOMPtr<nsIScreen> screen;
+  if (widget->GetNativeData(NS_NATIVE_WINDOW)) {
+    mScreenMgr->ScreenForNativeWidget(widget->GetNativeData(NS_NATIVE_WINDOW),
+                                      getter_AddRefs(screen));
+  }
+
+  NS_ENSURE_TRUE(screen, true);
+
+  ScreenDetails details;
+  if (!ExtractScreenDetails(screen, details)) {
+    return true;
+  }
+
+  *aRetVal = details;
+  *aSuccess = true;
+  return true;
+}
+
+bool
+ScreenManagerParent::ExtractScreenDetails(nsIScreen* aScreen, ScreenDetails &aDetails)
+{
+  uint32_t id;
+  nsresult rv = aScreen->GetId(&id);
+  NS_ENSURE_SUCCESS(rv, false);
+  aDetails.id() = id;
+
+  nsIntRect rect;
+  rv = aScreen->GetRect(&rect.x, &rect.y, &rect.width, &rect.height);
+  NS_ENSURE_SUCCESS(rv, false);
+  aDetails.rect() = rect;
+
+  nsIntRect availRect;
+  rv = aScreen->GetAvailRect(&availRect.x, &availRect.y, &availRect.width,
+                             &availRect.height);
+  NS_ENSURE_SUCCESS(rv, false);
+  aDetails.availRect() = availRect;
+
+  int32_t pixelDepth = 0;
+  rv = aScreen->GetPixelDepth(&pixelDepth);
+  NS_ENSURE_SUCCESS(rv, false);
+  aDetails.pixelDepth() = pixelDepth;
+
+  int32_t colorDepth = 0;
+  rv = aScreen->GetColorDepth(&colorDepth);
+  NS_ENSURE_SUCCESS(rv, false);
+  aDetails.colorDepth() = colorDepth;
+
+  double contentsScaleFactor = 1.0;
+  rv = aScreen->GetContentsScaleFactor(&contentsScaleFactor);
+  NS_ENSURE_SUCCESS(rv, false);
+  aDetails.contentsScaleFactor() = contentsScaleFactor;
+
+  return true;
+}
+
+void
+ScreenManagerParent::ActorDestroy(ActorDestroyReason why)
+{
+}
+
+} // namespace dom
+} // namespace mozilla
+
new file mode 100644
--- /dev/null
+++ b/dom/ipc/ScreenManagerParent.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ScreenManagerParent_h
+#define mozilla_dom_ScreenManagerParent_h
+
+#include "mozilla/dom/PScreenManagerParent.h"
+#include "nsIScreenManager.h"
+
+namespace mozilla {
+namespace dom {
+
+class ScreenManagerParent : public PScreenManagerParent
+{
+ public:
+  ScreenManagerParent(uint32_t* aNumberOfScreens,
+                      float* aSystemDefaultScale,
+                      bool* aSuccess);
+  ~ScreenManagerParent() {};
+
+  virtual bool RecvRefresh(uint32_t* aNumberOfScreens,
+                           float* aSystemDefaultScale,
+                           bool* aSuccess) MOZ_OVERRIDE;
+
+  virtual bool RecvScreenRefresh(const uint32_t& aId,
+                                 ScreenDetails* aRetVal,
+                                 bool* aSuccess) MOZ_OVERRIDE;
+
+  virtual void ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE;
+
+  virtual bool RecvGetPrimaryScreen(ScreenDetails* aRetVal,
+                                    bool* aSuccess) MOZ_OVERRIDE;
+
+  virtual bool RecvScreenForRect(const int32_t& aLeft,
+                                 const int32_t& aTop,
+                                 const int32_t& aWidth,
+                                 const int32_t& aHeight,
+                                 ScreenDetails* aRetVal,
+                                 bool* aSuccess) MOZ_OVERRIDE;
+
+  virtual bool RecvScreenForBrowser(PBrowserParent* aBrowser,
+                                    ScreenDetails* aRetVal,
+                                    bool* aSuccess);
+
+ private:
+  bool ExtractScreenDetails(nsIScreen* aScreen, ScreenDetails &aDetails);
+  nsCOMPtr<nsIScreenManager> mScreenMgr;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ScreenManagerParent_h
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -304,16 +304,18 @@ public:
     nsIContentParent* Manager() { return mManager; }
 
     /**
      * Let managees query if Destroy() is already called so they don't send out
      * messages when the PBrowser actor is being destroyed.
      */
     bool IsDestroyed() const { return mIsDestroyed; }
 
+    already_AddRefed<nsIWidget> GetWidget() const;
+
 protected:
     bool ReceiveMessage(const nsString& aMessage,
                         bool aSync,
                         const StructuredCloneData* aCloneData,
                         CpowHolder* aCpows,
                         nsIPrincipal* aPrincipal,
                         InfallibleTArray<nsString>* aJSONRetVal = nullptr);
 
@@ -375,17 +377,16 @@ protected:
     ScreenOrientation mOrientation;
     float mDPI;
     CSSToLayoutDeviceScale mDefaultScale;
     bool mShown;
     bool mUpdatedDimensions;
 
 private:
     already_AddRefed<nsFrameLoader> GetFrameLoader() const;
-    already_AddRefed<nsIWidget> GetWidget() const;
     layout::RenderFrameParent* GetRenderFrame();
     nsRefPtr<nsIContentParent> mManager;
     void TryCacheDPIAndScale();
 
     CSSPoint AdjustTapToChildWidget(const CSSPoint& aPoint);
 
     // When true, we create a pan/zoom controller for our frame and
     // notify it of input events targeting us.
--- a/dom/ipc/moz.build
+++ b/dom/ipc/moz.build
@@ -54,16 +54,17 @@ UNIFIED_SOURCES += [
     'FileDescriptorSetChild.cpp',
     'FileDescriptorSetParent.cpp',
     'FilePickerParent.cpp',
     'nsIContentChild.cpp',
     'nsIContentParent.cpp',
     'PermissionMessageUtils.cpp',
     'PreallocatedProcessManager.cpp',
     'ProcessPriorityManager.cpp',
+    'ScreenManagerParent.cpp',
     'StructuredCloneUtils.cpp',
     'TabChild.cpp',
     'TabContext.cpp',
     'TabMessageUtils.cpp',
     'TabParent.cpp',
 ]
 
 # Blob.cpp cannot be compiled in unified mode because it triggers a fatal gcc warning.
@@ -87,16 +88,17 @@ IPDL_SOURCES += [
     'PContentPermission.ipdlh',
     'PContentPermissionRequest.ipdl',
     'PCrashReporter.ipdl',
     'PCycleCollectWithLogs.ipdl',
     'PDocumentRenderer.ipdl',
     'PFileDescriptorSet.ipdl',
     'PFilePicker.ipdl',
     'PMemoryReportRequest.ipdl',
+    'PScreenManager.ipdl',
     'PTabContext.ipdlh',
 ]
 
 FAIL_ON_WARNINGS = True
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
--- a/dom/media/tests/mochitest/moz.build
+++ b/dom/media/tests/mochitest/moz.build
@@ -1,10 +1,10 @@
 # -*- 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/.
 
 if CONFIG ['MOZ_WEBRTC']:
-	MOCHITEST_MANIFESTS += ['mochitest.ini']
-	WEBRTC_SIGNALLING_TEST_MANIFESTS += ['steeplechase.ini']
+    MOCHITEST_MANIFESTS += ['mochitest.ini']
+    WEBRTC_SIGNALLING_TEST_MANIFESTS += ['steeplechase.ini']
 
--- a/dom/src/notification/DesktopNotification.cpp
+++ b/dom/src/notification/DesktopNotification.cpp
@@ -29,17 +29,17 @@ class DesktopNotificationRequest : publi
                                    public PCOMContentPermissionRequestChild
 
 {
   ~DesktopNotificationRequest()
   {
   }
 
 public:
-  NS_DECL_ISUPPORTS
+  NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSICONTENTPERMISSIONREQUEST
 
   DesktopNotificationRequest(DesktopNotification* notification)
     : mDesktopNotification(notification) {}
 
   NS_IMETHOD Run() MOZ_OVERRIDE
   {
     nsCOMPtr<nsIContentPermissionPrompt> prompt =
@@ -304,19 +304,18 @@ DesktopNotificationCenter::WrapObject(JS
 {
   return DesktopNotificationCenterBinding::Wrap(aCx, this);
 }
 
 /* ------------------------------------------------------------------------ */
 /* DesktopNotificationRequest                                               */
 /* ------------------------------------------------------------------------ */
 
-NS_IMPL_ISUPPORTS(DesktopNotificationRequest,
-                  nsIContentPermissionRequest,
-                  nsIRunnable)
+NS_IMPL_ISUPPORTS_INHERITED(DesktopNotificationRequest, nsRunnable,
+			    nsIContentPermissionRequest)
 
 NS_IMETHODIMP
 DesktopNotificationRequest::GetPrincipal(nsIPrincipal * *aRequestingPrincipal)
 {
   if (!mDesktopNotification) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
--- a/dom/webidl/MozNetworkStats.webidl
+++ b/dom/webidl/MozNetworkStats.webidl
@@ -21,18 +21,18 @@ dictionary NetworkStatsGetOptions
 dictionary NetworkStatsAlarmOptions
 {
   Date startTime;
   Date data;
 };
 
 [JSImplementation="@mozilla.org/networkstats;1",
  ChromeOnly,
- Pref="dom.mozNetworkStats.enabled",
- Func="Navigator::HasNetworkStatsSupport"]
+ CheckPermissions="networkstats-manage",
+ Pref="dom.mozNetworkStats.enabled"]
 interface MozNetworkStats {
   /**
    * App manifest URL of an application for specifying the per-app stats of the
    * specified app.
    */
   readonly attribute DOMString    appManifestURL;
 
   /**
--- a/dom/webidl/MozNetworkStatsAlarm.webidl
+++ b/dom/webidl/MozNetworkStatsAlarm.webidl
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 [JSImplementation="@mozilla.org/networkstatsalarm;1",
  ChromeOnly,
- Pref="dom.mozNetworkStats.enabled",
- Func="Navigator::HasNetworkStatsSupport"]
+ CheckPermissions="networkstats-manage",
+ Pref="dom.mozNetworkStats.enabled"]
 interface MozNetworkStatsAlarm {
   readonly attribute unsigned long alarmId;
   readonly attribute MozNetworkStatsInterface network;
   readonly attribute long threshold;
   readonly attribute any data;
 };
--- a/dom/webidl/MozNetworkStatsData.webidl
+++ b/dom/webidl/MozNetworkStatsData.webidl
@@ -1,13 +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/. */
 
 [JSImplementation="@mozilla.org/networkStatsdata;1",
  ChromeOnly,
- Pref="dom.mozNetworkStats.enabled",
- Func="Navigator::HasNetworkStatsSupport"]
+ CheckPermissions="networkstats-manage",
+ Pref="dom.mozNetworkStats.enabled"]
 interface MozNetworkStatsData {
   readonly attribute unsigned long   rxBytes;   // Received bytes.
   readonly attribute unsigned long   txBytes;   // Sent bytes.
   readonly attribute Date            date;      // Date.
 };
--- a/dom/webidl/MozNetworkStatsInterface.webidl
+++ b/dom/webidl/MozNetworkStatsInterface.webidl
@@ -7,18 +7,18 @@ dictionary NetworkInterface {
   DOMString id;
 };
 
 /**
  * Represents a data interface for which the manager is recording statistics.
  */
 [Constructor(optional NetworkInterface networkinterface),
  JSImplementation="@mozilla.org/networkstatsinterface;1",
- Pref="dom.mozNetworkStats.enabled",
- Func="Navigator::HasNetworkStatsSupport"]
+ CheckPermissions="networkstats-manage",
+ Pref="dom.mozNetworkStats.enabled"]
 interface MozNetworkStatsInterface {
   readonly attribute long type;
 
   /**
    * Id value is '0' for wifi or the iccid for mobile (SIM).
    */
   readonly attribute DOMString id;
 
--- a/dom/webidl/Navigator.webidl
+++ b/dom/webidl/Navigator.webidl
@@ -78,17 +78,17 @@ interface NavigatorContentUtils {
 [NoInterfaceObject]
 interface NavigatorStorageUtils {
   // NOT IMPLEMENTED
   //void yieldForStorageUpdates();
 };
 
 [NoInterfaceObject]
 interface NavigatorFeatures {
-  [Func="Navigator::HasFeatureDetectionSupport"]
+  [CheckPermissions="feature-detection"]
   Promise getFeature(DOMString name);
 };
 
 // Things that definitely need to be in the spec and and are not for some
 // reason.  See https://www.w3.org/Bugs/Public/show_bug.cgi?id=22406
 partial interface Navigator {
   [Throws]
   readonly attribute MimeTypeArray mimeTypes;
@@ -247,20 +247,22 @@ partial interface Navigator {
 };
 
 // nsIDOMClientInformation
 partial interface Navigator {
   [Throws]
   boolean mozIsLocallyAvailable(DOMString uri, boolean whenOffline);
 };
 
+#ifdef MOZ_WEBSMS_BACKEND
 partial interface Navigator {
-  [Func="Navigator::HasMobileMessageSupport"]
+  [CheckPermissions="sms", Pref="dom.sms.enabled"]
   readonly attribute MozMobileMessageManager? mozMobileMessage;
 };
+#endif
 
 // NetworkInformation
 partial interface Navigator {
   [Throws, Pref="dom.netinfo.enabled"]
   readonly attribute NetworkInformation connection;
 };
 
 // nsIDOMNavigatorCamera
@@ -326,17 +328,17 @@ partial interface Navigator {
   [Throws, CheckPermissions="fmradio"]
   readonly attribute FMRadio mozFMRadio;
 };
 #endif // MOZ_B2G_FM
 
 #ifdef MOZ_TIME_MANAGER
 // nsIDOMMozNavigatorTime
 partial interface Navigator {
-  [Throws, Func="Navigator::HasTimeSupport"]
+  [Throws, CheckPermissions="time"]
   readonly attribute MozTimeManager mozTime;
 };
 #endif // MOZ_TIME_MANAGER
 
 #ifdef MOZ_AUDIO_CHANNEL_MANAGER
 // nsIMozNavigatorAudioChannelManager
 partial interface Navigator {
   [Throws]
--- a/dom/webidl/SubtleCrypto.webidl
+++ b/dom/webidl/SubtleCrypto.webidl
@@ -90,16 +90,20 @@ dictionary RsaKeyGenParams : Algorithm {
   [EnforceRange] unsigned long modulusLength;
   BigInteger publicExponent;
 };
 
 dictionary RsaHashedKeyGenParams : RsaKeyGenParams {
   AlgorithmIdentifier hash;
 };
 
+dictionary RsaOaepParams : Algorithm {
+  CryptoOperationData? label;
+};
+
 dictionary DhKeyGenParams : Algorithm {
   BigInteger prime;
   BigInteger generator;
 };
 
 typedef DOMString NamedCurve;
 dictionary EcKeyGenParams : Algorithm {
   NamedCurve namedCurve;
--- a/editor/composer/nsEditorSpellCheck.cpp
+++ b/editor/composer/nsEditorSpellCheck.cpp
@@ -37,16 +37,17 @@
 #include "nsIVariant.h"                 // for nsIWritableVariant, etc
 #include "nsLiteralString.h"            // for NS_LITERAL_STRING, etc
 #include "nsMemory.h"                   // for nsMemory
 #include "nsReadableUtils.h"            // for ToNewUnicode, EmptyString, etc
 #include "nsServiceManagerUtils.h"      // for do_GetService
 #include "nsString.h"                   // for nsAutoString, nsString, etc
 #include "nsStringFwd.h"                // for nsAFlatString
 #include "nsStyleUtil.h"                // for nsStyleUtil
+#include "nsXULAppAPI.h"                // for XRE_GetProcessType
 
 using namespace mozilla;
 
 class UpdateDictionnaryHolder {
   private:
     nsEditorSpellCheck* mSpellCheck;
   public:
     UpdateDictionnaryHolder(nsEditorSpellCheck* esc): mSpellCheck(esc) {
@@ -700,18 +701,26 @@ nsEditorSpellCheck::UpdateCurrentDiction
 
   DictionaryFetcher* fetcher = new DictionaryFetcher(this, aCallback,
                                                      mDictionaryFetcherGroup);
   rootContent->GetLang(fetcher->mRootContentLang);
   nsCOMPtr<nsIDocument> doc = rootContent->GetCurrentDoc();
   NS_ENSURE_STATE(doc);
   doc->GetContentLanguage(fetcher->mRootDocContentLang);
 
-  rv = fetcher->Fetch(mEditor);
-  NS_ENSURE_SUCCESS(rv, rv);
+  if (XRE_GetProcessType() == GeckoProcessType_Content) {
+    // Content prefs don't work in E10S (Bug 1027898) pretend that we
+    // didn't have any & trigger the asynchrous completion.
+    nsCOMPtr<nsIRunnable> runnable =
+      NS_NewRunnableMethodWithArg<uint16_t>(fetcher, &DictionaryFetcher::HandleCompletion, 0);
+    NS_DispatchToMainThread(runnable);
+  } else {
+    rv = fetcher->Fetch(mEditor);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
 
   return NS_OK;
 }
 
 nsresult
 nsEditorSpellCheck::DictionaryFetched(DictionaryFetcher* aFetcher)
 {
   nsRefPtr<nsEditorSpellCheck> kungFuDeathGrip = this;
new file mode 100644
--- /dev/null
+++ b/extensions/spellcheck/hunspell/src/PRemoteSpellcheckEngine.ipdl
@@ -0,0 +1,20 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PContent;
+
+namespace mozilla {
+
+sync protocol PRemoteSpellcheckEngine {
+  manager PContent;
+
+parent:
+  __delete__();
+
+  sync CheckForMisspelling(nsString aWord) returns (bool isMisspelled);
+
+  sync SetDictionary(nsString aDictionary) returns (bool success);
+};
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/extensions/spellcheck/hunspell/src/RemoteSpellCheckEngineChild.cpp
@@ -0,0 +1,21 @@
+/* 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 "RemoteSpellCheckEngineChild.h"
+
+namespace mozilla {
+RemoteSpellcheckEngineChild::RemoteSpellcheckEngineChild(mozSpellChecker *aOwner)
+  :mOwner(aOwner)
+{
+}
+
+RemoteSpellcheckEngineChild::~RemoteSpellcheckEngineChild()
+{
+  // null out the owner's SpellcheckEngineChild to prevent state corruption
+  // during shutdown
+  mOwner->DeleteRemoteEngine();
+
+}
+
+} //namespace mozilla
new file mode 100644
--- /dev/null
+++ b/extensions/spellcheck/hunspell/src/RemoteSpellCheckEngineChild.h
@@ -0,0 +1,26 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef RemoteSpellcheckEngineChild_h_
+#define RemoteSpellcheckEngineChild_h_
+
+#include "mozilla/PRemoteSpellcheckEngineChild.h"
+#include "mozSpellChecker.h"
+
+class mozSpellChecker;
+
+namespace mozilla {
+class RemoteSpellcheckEngineChild : public mozilla::PRemoteSpellcheckEngineChild
+{
+public:
+  RemoteSpellcheckEngineChild(mozSpellChecker *aOwner);
+  ~RemoteSpellcheckEngineChild();
+
+private:
+  mozSpellChecker *mOwner;
+};
+
+} //namespace mozilla
+
+#endif // RemoteSpellcheckEngineChild_h_
new file mode 100644
--- /dev/null
+++ b/extensions/spellcheck/hunspell/src/RemoteSpellCheckEngineParent.cpp
@@ -0,0 +1,49 @@
+/* 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 "RemoteSpellCheckEngineParent.h"
+#include "mozISpellCheckingEngine.h"
+#include "nsServiceManagerUtils.h"
+
+#define DEFAULT_SPELL_CHECKER "@mozilla.org/spellchecker/engine;1"
+
+namespace mozilla {
+
+RemoteSpellcheckEngineParent::RemoteSpellcheckEngineParent()
+{
+  mEngine = do_GetService(DEFAULT_SPELL_CHECKER);
+}
+
+RemoteSpellcheckEngineParent::~RemoteSpellcheckEngineParent()
+{
+}
+
+bool
+RemoteSpellcheckEngineParent::RecvSetDictionary(
+  const nsString& aDictionary,
+  bool* success)
+{
+  nsresult rv = mEngine->SetDictionary(aDictionary.get());
+  *success = NS_SUCCEEDED(rv);
+  return true;
+}
+
+bool
+RemoteSpellcheckEngineParent::RecvCheckForMisspelling(
+  const nsString& aWord,
+  bool* isMisspelled)
+{
+  bool isCorrect = false;
+  mEngine->Check(aWord.get(), &isCorrect);
+  *isMisspelled = !isCorrect;
+  return true;
+}
+
+void
+RemoteSpellcheckEngineParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+}
+
+} // namespace mozilla
+
new file mode 100644
--- /dev/null
+++ b/extensions/spellcheck/hunspell/src/RemoteSpellCheckEngineParent.h
@@ -0,0 +1,31 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef RemoteSpellcheckEngineParent_h_
+#define RemoteSpellcheckEngineParent_h_
+
+#include "mozISpellCheckingEngine.h"
+#include "mozilla/PRemoteSpellcheckEngineParent.h"
+#include "nsCOMPtr.h"
+
+namespace mozilla {
+
+class RemoteSpellcheckEngineParent : public mozilla::PRemoteSpellcheckEngineParent {
+
+public:
+  RemoteSpellcheckEngineParent();
+
+  ~RemoteSpellcheckEngineParent();
+
+  virtual void ActorDestroy(ActorDestroyReason aWhy);
+
+  bool RecvSetDictionary(const nsString& aDictionary, bool* success);
+
+  bool RecvCheckForMisspelling( const nsString& aWord, bool* isMisspelled);
+
+private:
+  nsCOMPtr<mozISpellCheckingEngine> mEngine;
+};
+
+}
+#endif
--- a/extensions/spellcheck/hunspell/src/moz.build
+++ b/extensions/spellcheck/hunspell/src/moz.build
@@ -1,17 +1,19 @@
 # -*- 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/.
 
-UNIFIED_SOURCES += [
+SOURCES += [
     'mozHunspell.cpp',
     'mozHunspellDirProvider.cpp',
+    'RemoteSpellCheckEngineChild.cpp',
+    'RemoteSpellCheckEngineParent.cpp',
 ]
 
 if not CONFIG['MOZ_NATIVE_HUNSPELL']:
     SOURCES += [
         'affentry.cxx',
         'affixmgr.cxx',
         'csutil.cxx',
         'dictmgr.cxx',
@@ -35,8 +37,19 @@ LOCAL_INCLUDES += [
 
 LOCAL_INCLUDES += [
     '/editor/libeditor/base',
 ]
 
 # Suppress warnings in third-party code.
 if CONFIG['CLANG_CXX']:
     CXXFLAGS += ['-Wno-unused-private-field']
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+IPDL_SOURCES = [
+    'PRemoteSpellcheckEngine.ipdl',
+]
+
+EXPORTS.mozilla += [
+     'RemoteSpellCheckEngineChild.h',
+     'RemoteSpellCheckEngineParent.h',
+]
--- a/extensions/spellcheck/src/moz.build
+++ b/extensions/spellcheck/src/moz.build
@@ -1,15 +1,16 @@
 # -*- 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/.
 
-UNIFIED_SOURCES += [
+include('/ipc/chromium/chromium-config.mozbuild')
+SOURCES += [
     'mozEnglishWordUtils.cpp',
     'mozGenericWordUtils.cpp',
     'mozInlineSpellChecker.cpp',
     'mozInlineSpellWordUtil.cpp',
     'mozPersonalDictionary.cpp',
     'mozSpellChecker.cpp',
     'mozSpellCheckerFactory.cpp',
     'mozSpellI18NManager.cpp',
@@ -19,10 +20,13 @@ LIBRARY_NAME = 'spellchecker'
 
 FINAL_LIBRARY = 'xul'
 
 LOCAL_INCLUDES += [
     '../hunspell/src',
     '/content/base/src',
     '/editor/libeditor/base',
 ]
+EXPORTS.mozilla += [
+     'mozSpellChecker.h',
+]
 
 FAIL_ON_WARNINGS = True
--- a/extensions/spellcheck/src/mozSpellChecker.cpp
+++ b/extensions/spellcheck/src/mozSpellChecker.cpp
@@ -5,16 +5,23 @@
 
 #include "mozSpellChecker.h"
 #include "nsIServiceManager.h"
 #include "mozISpellI18NManager.h"
 #include "nsIStringEnumerator.h"
 #include "nsICategoryManager.h"
 #include "nsISupportsPrimitives.h"
 #include "nsISimpleEnumerator.h"
+#include "mozilla/PRemoteSpellcheckEngineChild.h"
+#include "mozilla/dom/ContentChild.h"
+#include "nsXULAppAPI.h"
+
+using mozilla::dom::ContentChild;
+using mozilla::PRemoteSpellcheckEngineChild;
+using mozilla::RemoteSpellcheckEngineChild;
 
 #define DEFAULT_SPELL_CHECKER "@mozilla.org/spellchecker/engine;1"
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(mozSpellChecker)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(mozSpellChecker)
 
 NS_INTERFACE_MAP_BEGIN(mozSpellChecker)
   NS_INTERFACE_MAP_ENTRY(nsISpellChecker)
@@ -33,24 +40,34 @@ mozSpellChecker::mozSpellChecker()
 mozSpellChecker::~mozSpellChecker()
 {
   if(mPersonalDictionary){
     //    mPersonalDictionary->Save();
     mPersonalDictionary->EndSession();
   }
   mSpellCheckingEngine = nullptr;
   mPersonalDictionary = nullptr;
+
+  if(XRE_GetProcessType() == GeckoProcessType_Content) {
+    mEngine->Send__delete__(mEngine);
+  }
 }
 
 nsresult 
 mozSpellChecker::Init()
 {
   mPersonalDictionary = do_GetService("@mozilla.org/spellchecker/personaldictionary;1");
   
   mSpellCheckingEngine = nullptr;
+  if (XRE_GetProcessType() == GeckoProcessType_Content) {
+    mozilla::dom::ContentChild* contentChild = mozilla::dom::ContentChild::GetSingleton();
+    MOZ_ASSERT(contentChild);
+    mEngine = new RemoteSpellcheckEngineChild(this);
+    contentChild->SendPRemoteSpellcheckEngineConstructor(mEngine);
+  }
 
   return NS_OK;
 } 
 
 NS_IMETHODIMP 
 mozSpellChecker::SetDocument(nsITextServicesDocument *aDoc, bool aFromStartofDoc)
 {
   mTsDoc = aDoc;
@@ -103,19 +120,26 @@ mozSpellChecker::NextMisspelledWord(nsAS
   return NS_OK;
 }
 
 NS_IMETHODIMP 
 mozSpellChecker::CheckWord(const nsAString &aWord, bool *aIsMisspelled, nsTArray<nsString> *aSuggestions)
 {
   nsresult result;
   bool correct;
-  if(!mSpellCheckingEngine)
+
+  if (XRE_GetProcessType() == GeckoProcessType_Content) {
+    nsString wordwrapped = nsString(aWord);
+    bool rv = mEngine->SendCheckForMisspelling(wordwrapped, aIsMisspelled);
+    return rv ? NS_OK : NS_ERROR_NOT_AVAILABLE;
+  }
+
+  if(!mSpellCheckingEngine) {
     return NS_ERROR_NULL_POINTER;
-
+  }
   *aIsMisspelled = false;
   result = mSpellCheckingEngine->Check(PromiseFlatString(aWord).get(), &correct);
   NS_ENSURE_SUCCESS(result, result);
   if(!correct){
     if(aSuggestions){
       uint32_t count,i;
       char16_t **words;
       
@@ -327,16 +351,23 @@ mozSpellChecker::GetCurrentDictionary(ns
   mSpellCheckingEngine->GetDictionary(getter_Copies(dictname));
   aDictionary = dictname;
   return NS_OK;
 }
 
 NS_IMETHODIMP 
 mozSpellChecker::SetCurrentDictionary(const nsAString &aDictionary)
 {
+  if (XRE_GetProcessType() == GeckoProcessType_Content) {
+    nsString wrappedDict = nsString(aDictionary);
+    bool isSuccess;
+    mEngine->SendSetDictionary(wrappedDict, &isSuccess);
+    return isSuccess ? NS_OK : NS_ERROR_NOT_AVAILABLE;
+  }
+
   // Calls to mozISpellCheckingEngine::SetDictionary might destroy us
   nsRefPtr<mozSpellChecker> kungFuDeathGrip = this;
 
   mSpellCheckingEngine = nullptr;
 
   if (aDictionary.IsEmpty()) {
     return NS_OK;
   }
@@ -523,8 +554,12 @@ mozSpellChecker::GetEngineList(nsCOMArra
     // Fail if not succeeded to load HunSpell. Ignore errors
     // for external spellcheck engines.
     return rv;
   }
   aSpellCheckingEngines->AppendObject(engine);
 
   return NS_OK;
 }
+
+void mozSpellChecker::DeleteRemoteEngine() {
+  mEngine = nullptr;
+}
\ No newline at end of file
--- a/extensions/spellcheck/src/mozSpellChecker.h
+++ b/extensions/spellcheck/src/mozSpellChecker.h
@@ -12,16 +12,22 @@
 #include "nsString.h"
 #include "nsITextServicesDocument.h"
 #include "mozIPersonalDictionary.h"
 #include "mozISpellCheckingEngine.h"
 #include "nsClassHashtable.h"
 #include "nsTArray.h"
 #include "mozISpellI18NUtil.h"
 #include "nsCycleCollectionParticipant.h"
+#include "RemoteSpellCheckEngineChild.h"
+
+namespace mozilla {
+class PRemoteSpellcheckEngineChild;
+class RemoteSpellcheckEngineChild;
+}
 
 class mozSpellChecker : public nsISpellChecker
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS(mozSpellChecker)
 
   mozSpellChecker();
@@ -38,26 +44,29 @@ public:
   NS_IMETHOD AddWordToPersonalDictionary(const nsAString &aWord);
   NS_IMETHOD RemoveWordFromPersonalDictionary(const nsAString &aWord);
   NS_IMETHOD GetPersonalDictionary(nsTArray<nsString> *aWordList);
 
   NS_IMETHOD GetDictionaryList(nsTArray<nsString> *aDictionaryList);
   NS_IMETHOD GetCurrentDictionary(nsAString &aDictionary);
   NS_IMETHOD SetCurrentDictionary(const nsAString &aDictionary);
   NS_IMETHOD CheckCurrentDictionary();
+  void DeleteRemoteEngine();
 
 protected:
   virtual ~mozSpellChecker();
 
   nsCOMPtr<mozISpellI18NUtil> mConverter;
   nsCOMPtr<nsITextServicesDocument> mTsDoc;
   nsCOMPtr<mozIPersonalDictionary> mPersonalDictionary;
 
   nsCOMPtr<mozISpellCheckingEngine>  mSpellCheckingEngine;
   bool mFromStart;
 
   nsresult SetupDoc(int32_t *outBlockOffset);
 
   nsresult GetCurrentBlockIndex(nsITextServicesDocument *aDoc, int32_t *outBlockIndex);
 
   nsresult GetEngineList(nsCOMArray<mozISpellCheckingEngine> *aDictionaryList);
+
+  mozilla::PRemoteSpellcheckEngineChild *mEngine;
 };
 #endif // mozSpellChecker_h__
--- a/gfx/layers/CompositorTypes.h
+++ b/gfx/layers/CompositorTypes.h
@@ -199,17 +199,17 @@ struct TextureFactoryIdentifier
   GeckoProcessType mParentProcessId;
   EnumSet<gfx::CompositionOp> mSupportedBlendModes;
   int32_t mMaxTextureSize;
   bool mSupportsTextureBlitting;
   bool mSupportsPartialUploads;
 
   TextureFactoryIdentifier(LayersBackend aLayersBackend = LayersBackend::LAYERS_NONE,
                            GeckoProcessType aParentProcessId = GeckoProcessType_Default,
-                           int32_t aMaxTextureSize = 0,
+                           int32_t aMaxTextureSize = 4096,
                            bool aSupportsTextureBlitting = false,
                            bool aSupportsPartialUploads = false)
     : mParentBackend(aLayersBackend)
     , mParentProcessId(aParentProcessId)
     , mSupportedBlendModes(gfx::CompositionOp::OP_OVER)
     , mMaxTextureSize(aMaxTextureSize)
     , mSupportsTextureBlitting(aSupportsTextureBlitting)
     , mSupportsPartialUploads(aSupportsPartialUploads)
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -1405,16 +1405,18 @@ public:
   virtual LayerRenderState GetRenderState() { return LayerRenderState(); }
 
 
   void Mutated()
   {
     mManager->Mutated(this);
   }
 
+  virtual int32_t GetMaxLayerSize() { return Manager()->GetMaxTextureSize(); }
+
 protected:
   Layer(LayerManager* aManager, void* aImplData);
 
   // Protected destructor, to discourage deletion outside of Release():
   virtual ~Layer();
 
   /**
    * We can snap layer transforms for two reasons:
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -407,44 +407,23 @@ static uint32_t sAsyncPanZoomControllerC
 static TimeStamp
 GetFrameTime() {
   if (sFrameTime.IsNull()) {
     return TimeStamp::Now();
   }
   return sFrameTime;
 }
 
-// This is a base class for animations that can deal with
-// overscroll states. In particular, it ensures that overscroll
-// states are cleared when the animation is cancelled.
-class OverscrollableAnimation: public AsyncPanZoomAnimation {
-public:
-  OverscrollableAnimation(AsyncPanZoomController& aApzc,
-                          const TimeDuration& aRepaintInterval = TimeDuration::Forever())
-    : AsyncPanZoomAnimation(aRepaintInterval)
-    , mApzc(aApzc)
-  {
-  }
-
-  virtual void Cancel() MOZ_OVERRIDE
-  {
-    mApzc.mX.ClearOverscroll();
-    mApzc.mY.ClearOverscroll();
-  }
-
-protected:
-  AsyncPanZoomController& mApzc;
-};
-
-class FlingAnimation: public OverscrollableAnimation {
+class FlingAnimation: public AsyncPanZoomAnimation {
 public:
   FlingAnimation(AsyncPanZoomController& aApzc,
                  bool aApplyAcceleration,
                  bool aAllowOverscroll)
-    : OverscrollableAnimation(aApzc, TimeDuration::FromMilliseconds(gfxPrefs::APZFlingRepaintInterval()))
+    : AsyncPanZoomAnimation(TimeDuration::FromMilliseconds(gfxPrefs::APZFlingRepaintInterval()))
+    , mApzc(aApzc)
     , mAllowOverscroll(aAllowOverscroll)
   {
     TimeStamp now = GetFrameTime();
     ScreenPoint velocity(mApzc.mX.GetVelocity(), mApzc.mY.GetVelocity());
 
     // If the last fling was very recent and in the same direction as this one,
     // boost the velocity to be the sum of the two. Check separate axes separately
     // because we could have two vertical flings with small horizontal components
@@ -589,16 +568,17 @@ private:
   }
 
   static float Accelerate(float aBase, float aSupplemental)
   {
     return (aBase * gfxPrefs::APZFlingAccelBaseMultiplier())
          + (aSupplemental * gfxPrefs::APZFlingAccelSupplementalMultiplier());
   }
 
+  AsyncPanZoomController& mApzc;
   bool mAllowOverscroll;
 };
 
 class ZoomAnimation: public AsyncPanZoomAnimation {
 public:
   ZoomAnimation(CSSPoint aStartOffset, CSSToScreenScale aStartZoom,
                 CSSPoint aEndOffset, CSSToScreenScale aEndZoom)
     : mTotalDuration(TimeDuration::FromMilliseconds(gfxPrefs::APZZoomAnimationDuration()))
@@ -651,31 +631,33 @@ private:
 
   // Target metrics for a zoom to animation. This is only valid when we are in
   // the "ANIMATED_ZOOM" state. We only use the |mViewportScrollOffset| and
   // |mResolution| fields on this.
   CSSPoint mEndOffset;
   CSSToScreenScale mEndZoom;
 };
 
-class OverscrollSnapBackAnimation: public OverscrollableAnimation {
+class OverscrollSnapBackAnimation: public AsyncPanZoomAnimation {
 public:
   OverscrollSnapBackAnimation(AsyncPanZoomController& aApzc)
-    : OverscrollableAnimation(aApzc)
+    : mApzc(aApzc)
   {
   }
 
   virtual bool Sample(FrameMetrics& aFrameMetrics,
                       const TimeDuration& aDelta) MOZ_OVERRIDE
   {
     // Can't inline these variables due to short-circuit evaluation.
     bool continueX = mApzc.mX.SampleSnapBack(aDelta);
     bool continueY = mApzc.mY.SampleSnapBack(aDelta);
     return continueX || continueY;
   }
+private:
+  AsyncPanZoomController& mApzc;
 };
 
 void
 AsyncPanZoomController::SetFrameTime(const TimeStamp& aTime) {
   sFrameTime = aTime;
 }
 
 void
@@ -1766,20 +1748,23 @@ void AsyncPanZoomController::StartAnimat
   mLastSampleTime = GetFrameTime();
   ScheduleComposite();
 }
 
 void AsyncPanZoomController::CancelAnimation() {
   ReentrantMonitorAutoEnter lock(mMonitor);
   APZC_LOG("%p running CancelAnimation in state %d\n", this, mState);
   SetState(NOTHING);
-  if (mAnimation) {
-    mAnimation->Cancel();
-    mAnimation = nullptr;
-    // mAnimation->Cancel() may have done something that requires a repaint.
+  mAnimation = nullptr;
+  // Setting the state to nothing and cancelling the animation can
+  // preempt normal mechanisms for relieving overscroll, so we need to clear
+  // overscroll here.
+  if (mX.IsOverscrolled() || mY.IsOverscrolled()) {
+    mX.ClearOverscroll();
+    mY.ClearOverscroll();
     RequestContentRepaint();
     ScheduleComposite();
     UpdateSharedCompositorFrameMetrics();
   }
 }
 
 void AsyncPanZoomController::SetCompositorParent(CompositorParent* aCompositorParent) {
   mCompositorParent = aCompositorParent;
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -795,17 +795,16 @@ public:
    * during overscroll handoff for a fling. If we are not pannable, calls
    * mTreeManager->HandOffFling() to hand the fling off further.
    * Returns true iff. any APZC (whether this one or one further in the handoff
    * chain accepted the fling).
    */
   bool TakeOverFling(ScreenPoint aVelocity);
 
 private:
-  friend class OverscrollableAnimation;
   friend class FlingAnimation;
   friend class OverscrollSnapBackAnimation;
   // The initial velocity of the most recent fling.
   ScreenPoint mLastFlingVelocity;
   // The time at which the most recent fling started.
   TimeStamp mLastFlingTime;
 
   // Deal with overscroll resulting from a fling animation. This is only ever
@@ -1034,19 +1033,16 @@ public:
   AsyncPanZoomAnimation(const TimeDuration& aRepaintInterval =
                         TimeDuration::Forever())
     : mRepaintInterval(aRepaintInterval)
   { }
 
   virtual bool Sample(FrameMetrics& aFrameMetrics,
                       const TimeDuration& aDelta) = 0;
 
-  // Called if the animation is cancelled before it ends.
-  virtual void Cancel() {}
-
   /**
    * Get the deferred tasks in |mDeferredTasks|. See |mDeferredTasks|
    * for more information.
    * Clears |mDeferredTasks|.
    */
   Vector<Task*> TakeDeferredTasks() {
     Vector<Task*> result;
     mDeferredTasks.swap(result);
--- a/gfx/layers/basic/BasicContainerLayer.h
+++ b/gfx/layers/basic/BasicContainerLayer.h
@@ -81,16 +81,22 @@ public:
   void ForceIntermediateSurface() { mUseIntermediateSurface = true; }
 
   void SetSupportsComponentAlphaChildren(bool aSupports) { mSupportsComponentAlphaChildren = aSupports; }
 
   virtual void Validate(LayerManager::DrawThebesLayerCallback aCallback,
                         void* aCallbackData,
                         ReadbackProcessor* aReadback) MOZ_OVERRIDE;
 
+  /**
+   * We don't really have a hard restriction for max layer size, but we pick
+   * 4096 to avoid excessive memory usage.
+   */
+  virtual int32_t GetMaxLayerSize() MOZ_OVERRIDE { return 4096; }
+
 protected:
   BasicLayerManager* BasicManager()
   {
     return static_cast<BasicLayerManager*>(mManager);
   }
 };
 }
 }
--- a/gfx/src/nsDeviceContext.cpp
+++ b/gfx/src/nsDeviceContext.cpp
@@ -626,21 +626,27 @@ nsDeviceContext::ComputeFullAreaUsingScr
 //
 // FindScreen
 //
 // Determines which screen intersects the largest area of the given surface.
 //
 void
 nsDeviceContext::FindScreen(nsIScreen** outScreen)
 {
-    if (mWidget && mWidget->GetNativeData(NS_NATIVE_WINDOW))
+    if (mWidget && mWidget->GetOwningTabChild()) {
+        mScreenManager->ScreenForNativeWidget((void *)mWidget->GetOwningTabChild(),
+                                              outScreen);
+    }
+    else if (mWidget && mWidget->GetNativeData(NS_NATIVE_WINDOW)) {
         mScreenManager->ScreenForNativeWidget(mWidget->GetNativeData(NS_NATIVE_WINDOW),
                                               outScreen);
-    else
+    }
+    else {
         mScreenManager->GetPrimaryScreen(outScreen);
+    }
 }
 
 void
 nsDeviceContext::CalcPrintingSize()
 {
     if (!mPrintingSurface)
         return;
 
--- a/gfx/tests/gtest/TestAsyncPanZoomController.cpp
+++ b/gfx/tests/gtest/TestAsyncPanZoomController.cpp
@@ -4,21 +4,19 @@
  */
 
 #include "gtest/gtest.h"
 #include "gmock/gmock.h"
 
 #include "mozilla/Attributes.h"
 #include "mozilla/layers/AsyncCompositionManager.h" // for ViewTransform
 #include "mozilla/layers/AsyncPanZoomController.h"
-#include "mozilla/layers/LayerManagerComposite.h"
 #include "mozilla/layers/GeckoContentController.h"
 #include "mozilla/layers/CompositorParent.h"
 #include "mozilla/layers/APZCTreeManager.h"
-#include "mozilla/Preferences.h"
 #include "base/task.h"
 #include "Layers.h"
 #include "TestLayers.h"
 #include "gfxPrefs.h"
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 using namespace mozilla::layers;
@@ -26,75 +24,61 @@ using ::testing::_;
 using ::testing::NiceMock;
 using ::testing::AtLeast;
 using ::testing::AtMost;
 using ::testing::MockFunction;
 using ::testing::InSequence;
 
 class Task;
 
-class AsyncPanZoomControllerTester : public ::testing::Test {
-protected:
-  virtual void SetUp() {
-    gfxPrefs::GetSingleton();
-    AsyncPanZoomController::SetThreadAssertionsEnabled(false);
-  }
-};
-
 class APZCTreeManagerTester : public ::testing::Test {
 protected:
   virtual void SetUp() {
     gfxPrefs::GetSingleton();
     AsyncPanZoomController::SetThreadAssertionsEnabled(false);
   }
 };
 
-class TouchActionEnabledTester : public AsyncPanZoomControllerTester {
-protected:
-  virtual void SetUp() {
-    AsyncPanZoomControllerTester::SetUp();
-    gfxPrefs::SetTouchActionEnabled(true);
+template<class T>
+class ScopedGfxPref {
+public:
+  ScopedGfxPref(T (*aGetPrefFunc)(void), void (*aSetPrefFunc)(T), T aVal)
+    : mSetPrefFunc(aSetPrefFunc)
+  {
+    mOldVal = aGetPrefFunc();
+    aSetPrefFunc(aVal);
   }
 
-  virtual void TearDown() {
-    gfxPrefs::SetTouchActionEnabled(false);
-    AsyncPanZoomControllerTester::TearDown();
+  ~ScopedGfxPref() {
+    mSetPrefFunc(mOldVal);
   }
+
+private:
+  void (*mSetPrefFunc)(T);
+  T mOldVal;
 };
 
+#define SCOPED_GFX_PREF(prefBase, prefType, prefValue) \
+  ScopedGfxPref<prefType> pref_##prefBase( \
+    &(gfxPrefs::prefBase), \
+    &(gfxPrefs::Set##prefBase), \
+    prefValue)
+
 class MockContentController : public GeckoContentController {
 public:
   MOCK_METHOD1(RequestContentRepaint, void(const FrameMetrics&));
   MOCK_METHOD2(AcknowledgeScrollUpdate, void(const FrameMetrics::ViewID&, const uint32_t& aScrollGeneration));
   MOCK_METHOD3(HandleDoubleTap, void(const CSSPoint&, int32_t, const ScrollableLayerGuid&));
   MOCK_METHOD3(HandleSingleTap, void(const CSSPoint&, int32_t, const ScrollableLayerGuid&));
   MOCK_METHOD3(HandleLongTap, void(const CSSPoint&, int32_t, const ScrollableLayerGuid&));
   MOCK_METHOD3(HandleLongTapUp, void(const CSSPoint&, int32_t, const ScrollableLayerGuid&));
   MOCK_METHOD3(SendAsyncScrollDOMEvent, void(bool aIsRoot, const CSSRect &aContentRect, const CSSSize &aScrollableSize));
   MOCK_METHOD2(PostDelayedTask, void(Task* aTask, int aDelayMs));
 };
 
-class TestScopedBoolPref {
-public:
-  TestScopedBoolPref(const char* aPref, bool aVal)
-    : mPref(aPref)
-  {
-    mOldVal = Preferences::GetBool(aPref);
-    Preferences::SetBool(aPref, aVal);
-  }
-
-  ~TestScopedBoolPref() {
-    Preferences::SetBool(mPref, mOldVal);
-  }
-
-private:
-  const char* mPref;
-  bool mOldVal;
-};
-
 class MockContentControllerDelayed : public MockContentController {
 public:
   MockContentControllerDelayed()
   {
   }
 
   void PostDelayedTask(Task* aTask, int aDelayMs) {
     mTaskQueue.AppendElement(aTask);
@@ -135,28 +119,16 @@ public:
     }
     return numTasks;
   }
 
 private:
   nsTArray<Task*> mTaskQueue;
 };
 
-
-class TestAPZCContainerLayer : public ContainerLayer {
-  public:
-    TestAPZCContainerLayer()
-      : ContainerLayer(nullptr, nullptr)
-    {}
-  bool RemoveChild(Layer* aChild) { return true; }
-  bool InsertAfter(Layer* aChild, Layer* aAfter) { return true; }
-  void ComputeEffectiveTransforms(const Matrix4x4& aTransformToSurface) {}
-  bool RepositionChild(Layer* aChild, Layer* aAfter) { return true; }
-};
-
 class TestAsyncPanZoomController : public AsyncPanZoomController {
 public:
   TestAsyncPanZoomController(uint64_t aLayersId, MockContentController* aMcc,
                              APZCTreeManager* aTreeManager = nullptr,
                              GestureBehavior aBehavior = DEFAULT_GESTURES)
     : AsyncPanZoomController(aLayersId, aTreeManager, aMcc, aBehavior)
   {}
 
@@ -182,467 +154,513 @@ public:
   void BuildOverscrollHandoffChain(AsyncPanZoomController* aApzc) {
     APZCTreeManager::BuildOverscrollHandoffChain(aApzc);
   }
   void ClearOverscrollHandoffChain() {
     APZCTreeManager::ClearOverscrollHandoffChain();
   }
 };
 
-static
-FrameMetrics TestFrameMetrics() {
+static FrameMetrics
+TestFrameMetrics()
+{
   FrameMetrics fm;
 
   fm.mDisplayPort = CSSRect(0, 0, 10, 10);
   fm.mCompositionBounds = ParentLayerRect(0, 0, 10, 10);
   fm.mCriticalDisplayPort = CSSRect(0, 0, 10, 10);
   fm.mScrollableRect = CSSRect(0, 0, 100, 100);
   fm.mViewport = CSSRect(0, 0, 10, 10);
 
   return fm;
 }
 
-/*
- * Dispatches mock touch events to the apzc and checks whether apzc properly
- * consumed them and triggered scrolling behavior.
- */
-static
-void ApzcPan(AsyncPanZoomController* apzc,
-             TestAPZCTreeManager* aTreeManager,
-             int& aTime,
-             int aTouchStartY,
-             int aTouchEndY,
-             bool expectIgnoredPan = false,
-             bool hasTouchListeners = false,
-             nsTArray<uint32_t>* aAllowedTouchBehaviors = nullptr) {
-
-  const int TIME_BETWEEN_TOUCH_EVENT = 100;
-  const int OVERCOME_TOUCH_TOLERANCE = 100;
-  MultiTouchInput mti;
-  nsEventStatus status;
-
-  // Since we're passing inputs directly to the APZC instead of going through
-  // the tree manager, we need to build the overscroll handoff chain explicitly
-  // for panning to work correctly.
-  aTreeManager->BuildOverscrollHandoffChain(apzc);
-
-  nsEventStatus touchStartStatus;
-  if (hasTouchListeners) {
-    // APZC shouldn't consume the start event now, instead queueing it up
-    // waiting for content's response.
-    touchStartStatus = nsEventStatus_eIgnore;
-  } else {
-    // APZC should go into the touching state and therefore consume the event.
-    touchStartStatus = nsEventStatus_eConsumeNoDefault;
-  }
-
-  mti =
-    MultiTouchInput(MultiTouchInput::MULTITOUCH_START, aTime, TimeStamp(), 0);
-  aTime += TIME_BETWEEN_TOUCH_EVENT;
-  // Make sure the move is large enough to not be handled as a tap
-  mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, aTouchStartY+OVERCOME_TOUCH_TOLERANCE), ScreenSize(0, 0), 0, 0));
-  status = apzc->ReceiveInputEvent(mti);
-  EXPECT_EQ(touchStartStatus, status);
-  // APZC should be in TOUCHING state
-
-  // Allowed touch behaviours must be set after sending touch-start.
-  if (aAllowedTouchBehaviors) {
-    apzc->SetAllowedTouchBehavior(*aAllowedTouchBehaviors);
-  }
-
-  nsEventStatus touchMoveStatus;
-  if (expectIgnoredPan) {
-    // APZC should ignore panning, be in TOUCHING state and therefore return eIgnore.
-    // The same applies to all consequent touch move events.
-    touchMoveStatus = nsEventStatus_eIgnore;
-  } else {
-    // APZC should go into the panning state and therefore consume the event.
-    touchMoveStatus = nsEventStatus_eConsumeNoDefault;
+class APZCBasicTester : public ::testing::Test {
+public:
+  APZCBasicTester(AsyncPanZoomController::GestureBehavior aGestureBehavior = AsyncPanZoomController::DEFAULT_GESTURES)
+    : mGestureBehavior(aGestureBehavior)
+  {
   }
 
-  mti =
-    MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, aTime, TimeStamp(), 0);
-  aTime += TIME_BETWEEN_TOUCH_EVENT;
-  mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, aTouchStartY), ScreenSize(0, 0), 0, 0));
-  status = apzc->ReceiveInputEvent(mti);
-  EXPECT_EQ(touchMoveStatus, status);
-
-  mti =
-    MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, aTime, TimeStamp(), 0);
-  aTime += TIME_BETWEEN_TOUCH_EVENT;
-  mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, aTouchEndY), ScreenSize(0, 0), 0, 0));
-  status = apzc->ReceiveInputEvent(mti);
-  EXPECT_EQ(touchMoveStatus, status);
-
-  mti =
-    MultiTouchInput(MultiTouchInput::MULTITOUCH_END, aTime, TimeStamp(), 0);
-  aTime += TIME_BETWEEN_TOUCH_EVENT;
-  mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, aTouchEndY), ScreenSize(0, 0), 0, 0));
-  status = apzc->ReceiveInputEvent(mti);
+protected:
+  virtual void SetUp()
+  {
+    gfxPrefs::GetSingleton();
+    AsyncPanZoomController::SetThreadAssertionsEnabled(false);
 
-  // Since we've explicitly built the overscroll handoff chain before
-  // touch-start, we need to explicitly clear it after touch-end.
-  aTreeManager->ClearOverscrollHandoffChain();
-}
-
-static
-void DoPanTest(bool aShouldTriggerScroll, uint32_t aBehavior)
-{
-  TimeStamp testStartTime = TimeStamp::Now();
-  AsyncPanZoomController::SetFrameTime(testStartTime);
+    testStartTime = TimeStamp::Now();
+    AsyncPanZoomController::SetFrameTime(testStartTime);
 
-  nsRefPtr<MockContentController> mcc = new NiceMock<MockContentController>();
-  nsRefPtr<TestAPZCTreeManager> tm = new TestAPZCTreeManager();
-  nsRefPtr<TestAsyncPanZoomController> apzc = new TestAsyncPanZoomController(0, mcc, tm);
-
-  apzc->SetFrameMetrics(TestFrameMetrics());
-  apzc->NotifyLayersUpdated(TestFrameMetrics(), true);
-
-  if (aShouldTriggerScroll) {
-    EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(AtLeast(1));
-    EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1);
-  } else {
-    EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(0);
-    EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0);
+    mcc = new NiceMock<MockContentControllerDelayed>();
+    tm = new TestAPZCTreeManager();
+    apzc = new TestAsyncPanZoomController(0, mcc, tm, mGestureBehavior);
+    apzc->SetFrameMetrics(TestFrameMetrics());
   }
 
-  int time = 0;
-  int touchStart = 50;
-  int touchEnd = 10;
-  ScreenPoint pointOut;
-  ViewTransform viewTransformOut;
-
-  nsTArray<uint32_t> allowedTouchBehaviors;
-  allowedTouchBehaviors.AppendElement(aBehavior);
-
-  // Pan down
-  ApzcPan(apzc, tm, time, touchStart, touchEnd, !aShouldTriggerScroll, false, &allowedTouchBehaviors);
-  apzc->SampleContentTransformForFrame(testStartTime, &viewTransformOut, pointOut);
-
-  if (aShouldTriggerScroll) {
-    EXPECT_EQ(ScreenPoint(0, -(touchEnd-touchStart)), pointOut);
-    EXPECT_NE(ViewTransform(), viewTransformOut);
-  } else {
-    EXPECT_EQ(ScreenPoint(), pointOut);
-    EXPECT_EQ(ViewTransform(), viewTransformOut);
-  }
-
-  // Pan back
-  ApzcPan(apzc, tm, time, touchEnd, touchStart, !aShouldTriggerScroll, false, &allowedTouchBehaviors);
-  apzc->SampleContentTransformForFrame(testStartTime, &viewTransformOut, pointOut);
-
-  EXPECT_EQ(ScreenPoint(), pointOut);
-  EXPECT_EQ(ViewTransform(), viewTransformOut);
-
-  apzc->Destroy();
-}
-
-static void ApzcPinchWithPinchInput(AsyncPanZoomController* aApzc,
-                                    int aFocusX,
-                                    int aFocusY,
-                                    float aScale,
-                                    bool aShouldTriggerPinch,
-                                    nsTArray<uint32_t>* aAllowedTouchBehaviors = nullptr) {
-  if (aAllowedTouchBehaviors) {
-    aApzc->SetAllowedTouchBehavior(*aAllowedTouchBehaviors);
+  virtual void TearDown()
+  {
+    apzc->Destroy();
   }
 
-  nsEventStatus expectedStatus = aShouldTriggerPinch
-    ? nsEventStatus_eConsumeNoDefault
-    : nsEventStatus_eIgnore;
-  nsEventStatus actualStatus;
+  void UseTouchListenerMetrics()
+  {
+    FrameMetrics frameMetrics(TestFrameMetrics());
+    frameMetrics.mMayHaveTouchListeners = true;
+    apzc->SetFrameMetrics(frameMetrics);
+  }
 
-  actualStatus = aApzc->HandleGestureEvent(PinchGestureInput(PinchGestureInput::PINCHGESTURE_START,
-                                            0,
-                                            TimeStamp(),
-                                            ScreenPoint(aFocusX, aFocusY),
-                                            10.0,
-                                            10.0,
-                                            0));
-  EXPECT_EQ(actualStatus, expectedStatus);
-  actualStatus = aApzc->HandleGestureEvent(PinchGestureInput(PinchGestureInput::PINCHGESTURE_SCALE,
-                                            0,
-                                            TimeStamp(),
-                                            ScreenPoint(aFocusX, aFocusY),
-                                            10.0 * aScale,
-                                            10.0,
-                                            0));
-  EXPECT_EQ(actualStatus, expectedStatus);
-  aApzc->HandleGestureEvent(PinchGestureInput(PinchGestureInput::PINCHGESTURE_END,
-                                            0,
-                                            TimeStamp(),
-                                            ScreenPoint(aFocusX, aFocusY),
-                                            // note: negative values here tell APZC
-                                            //       not to turn the pinch into a pan
-                                            -1.0,
-                                            -1.0,
-                                            0));
-}
+  void MakeApzcZoomable()
+  {
+    apzc->UpdateZoomConstraints(ZoomConstraints(true, true, CSSToScreenScale(0.25f), CSSToScreenScale(4.0f)));
+  }
 
-static void ApzcPinchWithTouchMoveInput(AsyncPanZoomController* aApzc,
-                                        int aFocusX,
-                                        int aFocusY,
-                                        float aScale,
-                                        int& inputId,
-                                        bool aShouldTriggerPinch,
-                                        nsTArray<uint32_t>* aAllowedTouchBehaviors = nullptr) {
-  // Having pinch coordinates in float type may cause problems with high-precision scale values
-  // since SingleTouchData accepts integer value. But for trivial tests it should be ok.
-  float pinchLength = 100.0;
-  float pinchLengthScaled = pinchLength * aScale;
-
-  nsEventStatus expectedStatus = aShouldTriggerPinch
-    ? nsEventStatus_eConsumeNoDefault
-    : nsEventStatus_eIgnore;
-  nsEventStatus actualStatus;
-
-  MultiTouchInput mtiStart =
-    MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0);
-  mtiStart.mTouches.AppendElement(SingleTouchData(inputId, ScreenIntPoint(aFocusX, aFocusY), ScreenSize(0, 0), 0, 0));
-  mtiStart.mTouches.AppendElement(SingleTouchData(inputId + 1, ScreenIntPoint(aFocusX, aFocusY), ScreenSize(0, 0), 0, 0));
-  aApzc->ReceiveInputEvent(mtiStart);
-
-  if (aAllowedTouchBehaviors) {
-    aApzc->SetAllowedTouchBehavior(*aAllowedTouchBehaviors);
+  void MakeApzcUnzoomable()
+  {
+    apzc->UpdateZoomConstraints(ZoomConstraints(false, false, CSSToScreenScale(1.0f), CSSToScreenScale(1.0f)));
   }
 
-  MultiTouchInput mtiMove1 =
-    MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
-  mtiMove1.mTouches.AppendElement(SingleTouchData(inputId, ScreenIntPoint(aFocusX - pinchLength, aFocusY), ScreenSize(0, 0), 0, 0));
-  mtiMove1.mTouches.AppendElement(SingleTouchData(inputId + 1, ScreenIntPoint(aFocusX + pinchLength, aFocusY), ScreenSize(0, 0), 0, 0));
-  actualStatus = aApzc->ReceiveInputEvent(mtiMove1);
-  EXPECT_EQ(actualStatus, expectedStatus);
-
-  MultiTouchInput mtiMove2 =
-    MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
-  mtiMove2.mTouches.AppendElement(SingleTouchData(inputId, ScreenIntPoint(aFocusX - pinchLengthScaled, aFocusY), ScreenSize(0, 0), 0, 0));
-  mtiMove2.mTouches.AppendElement(SingleTouchData(inputId + 1, ScreenIntPoint(aFocusX + pinchLengthScaled, aFocusY), ScreenSize(0, 0), 0, 0));
-  actualStatus = aApzc->ReceiveInputEvent(mtiMove2);
-  EXPECT_EQ(actualStatus, expectedStatus);
-
-  MultiTouchInput mtiEnd =
-    MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, TimeStamp(), 0);
-  mtiEnd.mTouches.AppendElement(SingleTouchData(inputId, ScreenIntPoint(aFocusX - pinchLengthScaled, aFocusY), ScreenSize(0, 0), 0, 0));
-  mtiEnd.mTouches.AppendElement(SingleTouchData(inputId + 1, ScreenIntPoint(aFocusX + pinchLengthScaled, aFocusY), ScreenSize(0, 0), 0, 0));
-  aApzc->ReceiveInputEvent(mtiEnd);
-  inputId += 2;
-}
-
-static
-void DoPinchTest(bool aUseGestureRecognizer, bool aShouldTriggerPinch,
-                 nsTArray<uint32_t> *aAllowedTouchBehaviors = nullptr) {
-  nsRefPtr<MockContentController> mcc = new NiceMock<MockContentController>();
-  nsRefPtr<TestAPZCTreeManager> tm = new TestAPZCTreeManager();
-  nsRefPtr<TestAsyncPanZoomController> apzc = new TestAsyncPanZoomController(0, mcc, tm,
-    aUseGestureRecognizer
-      ? AsyncPanZoomController::USE_GESTURE_DETECTOR
-      : AsyncPanZoomController::DEFAULT_GESTURES);
-
-  FrameMetrics fm;
-  fm.mViewport = CSSRect(0, 0, 980, 480);
-  fm.mCompositionBounds = ParentLayerRect(200, 200, 100, 200);
-  fm.mScrollableRect = CSSRect(0, 0, 980, 1000);
-  fm.SetScrollOffset(CSSPoint(300, 300));
-  fm.SetZoom(CSSToScreenScale(2.0));
-  apzc->SetFrameMetrics(fm);
-  apzc->UpdateZoomConstraints(ZoomConstraints(true, true, CSSToScreenScale(0.25), CSSToScreenScale(4.0)));
-  // the visible area of the document in CSS pixels is x=300 y=300 w=50 h=100
-
-  if (aShouldTriggerPinch) {
-    EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(AtLeast(1));
-    EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1);
-  } else {
-    EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(AtMost(2));
-    EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0);
-  }
+  AsyncPanZoomController::GestureBehavior mGestureBehavior;
+  TimeStamp testStartTime;
+  nsRefPtr<MockContentControllerDelayed> mcc;
+  nsRefPtr<TestAPZCTreeManager> tm;
+  nsRefPtr<TestAsyncPanZoomController> apzc;
+};
 
-  int touchInputId = 0;
-  if (aUseGestureRecognizer) {
-    ApzcPinchWithTouchMoveInput(apzc, 250, 300, 1.25, touchInputId, aShouldTriggerPinch, aAllowedTouchBehaviors);
-  } else {
-    ApzcPinchWithPinchInput(apzc, 250, 300, 1.25, aShouldTriggerPinch, aAllowedTouchBehaviors);
-  }
-
-  fm = apzc->GetFrameMetrics();
-
-  if (aShouldTriggerPinch) {
-    // the visible area of the document in CSS pixels is now x=305 y=310 w=40 h=80
-    EXPECT_EQ(2.5f, fm.GetZoom().scale);
-    EXPECT_EQ(305, fm.GetScrollOffset().x);
-    EXPECT_EQ(310, fm.GetScrollOffset().y);
-  } else {
-    // The frame metrics should stay the same since touch-action:none makes
-    // apzc ignore pinch gestures.
-    EXPECT_EQ(2.0f, fm.GetZoom().scale);
-    EXPECT_EQ(300, fm.GetScrollOffset().x);
-    EXPECT_EQ(300, fm.GetScrollOffset().y);
+class APZCGestureDetectorTester : public APZCBasicTester {
+public:
+  APZCGestureDetectorTester()
+    : APZCBasicTester(AsyncPanZoomController::USE_GESTURE_DETECTOR)
+  {
   }
-
-  // part 2 of the test, move to the top-right corner of the page and pinch and
-  // make sure we stay in the correct spot
-  fm.SetZoom(CSSToScreenScale(2.0));
-  fm.SetScrollOffset(CSSPoint(930, 5));
-  apzc->SetFrameMetrics(fm);
-  // the visible area of the document in CSS pixels is x=930 y=5 w=50 h=100
-
-  if (aUseGestureRecognizer) {
-    ApzcPinchWithTouchMoveInput(apzc, 250, 300, 0.5, touchInputId, aShouldTriggerPinch, aAllowedTouchBehaviors);
-  } else {
-    ApzcPinchWithPinchInput(apzc, 250, 300, 0.5, aShouldTriggerPinch, aAllowedTouchBehaviors);
-  }
-
-  fm = apzc->GetFrameMetrics();
-
-  if (aShouldTriggerPinch) {
-    // the visible area of the document in CSS pixels is now x=880 y=0 w=100 h=200
-    EXPECT_EQ(1.0f, fm.GetZoom().scale);
-    EXPECT_EQ(880, fm.GetScrollOffset().x);
-    EXPECT_EQ(0, fm.GetScrollOffset().y);
-  } else {
-    EXPECT_EQ(2.0f, fm.GetZoom().scale);
-    EXPECT_EQ(930, fm.GetScrollOffset().x);
-    EXPECT_EQ(5, fm.GetScrollOffset().y);
-  }
-
-  apzc->Destroy();
-}
+};
 
 static nsEventStatus
-ApzcDown(AsyncPanZoomController* apzc, int aX, int aY, int& aTime) {
-  MultiTouchInput mti =
-    MultiTouchInput(MultiTouchInput::MULTITOUCH_START, aTime, TimeStamp(), 0);
+ApzcDown(AsyncPanZoomController* apzc, int aX, int aY, int& aTime)
+{
+  MultiTouchInput mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, aTime, TimeStamp(), 0);
   mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(aX, aY), ScreenSize(0, 0), 0, 0));
   return apzc->ReceiveInputEvent(mti);
 }
 
 static nsEventStatus
-ApzcUp(AsyncPanZoomController* apzc, int aX, int aY, int& aTime) {
-  MultiTouchInput mti =
-    MultiTouchInput(MultiTouchInput::MULTITOUCH_END, aTime, TimeStamp(), 0);
+ApzcUp(AsyncPanZoomController* apzc, int aX, int aY, int& aTime)
+{
+  MultiTouchInput mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, aTime, TimeStamp(), 0);
   mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(aX, aY), ScreenSize(0, 0), 0, 0));
   return apzc->ReceiveInputEvent(mti);
 }
 
 static nsEventStatus
-ApzcTap(AsyncPanZoomController* apzc, int aX, int aY, int& aTime, int aTapLength, MockContentControllerDelayed* mcc = nullptr) {
+ApzcTap(AsyncPanZoomController* apzc, int aX, int aY, int& aTime,
+        int aTapLength, MockContentControllerDelayed* mcc = nullptr)
+{
   nsEventStatus status = ApzcDown(apzc, aX, aY, aTime);
   if (mcc != nullptr) {
     // There will be delayed tasks posted for the long-tap and MAX_TAP timeouts, but
     // if we were provided a non-null mcc we want to clear them.
     mcc->CheckHasDelayedTask();
     mcc->ClearDelayedTask();
     mcc->CheckHasDelayedTask();
     mcc->ClearDelayedTask();
   }
   EXPECT_EQ(nsEventStatus_eConsumeNoDefault, status);
   aTime += aTapLength;
   return ApzcUp(apzc, aX, aY, aTime);
 }
 
-TEST_F(AsyncPanZoomControllerTester, Constructor) {
-  // RefCounted class can't live in the stack
-  nsRefPtr<MockContentController> mcc = new NiceMock<MockContentController>();
-  nsRefPtr<TestAsyncPanZoomController> apzc = new TestAsyncPanZoomController(0, mcc);
-  apzc->SetFrameMetrics(TestFrameMetrics());
+static void
+ApzcPan(AsyncPanZoomController* aApzc,
+        TestAPZCTreeManager* aTreeManager,
+        int& aTime,
+        int aTouchStartY,
+        int aTouchEndY,
+        bool aKeepFingerDown = false,
+        nsTArray<uint32_t>* aAllowedTouchBehaviors = nullptr,
+        nsEventStatus (*aOutEventStatuses)[4] = nullptr)
+{
+  const int TIME_BETWEEN_TOUCH_EVENT = 100;
+  const int OVERCOME_TOUCH_TOLERANCE = 100;
+
+  // Since we're passing inputs directly to the APZC instead of going through
+  // the tree manager, we need to build the overscroll handoff chain explicitly
+  // for panning to work correctly.
+  aTreeManager->BuildOverscrollHandoffChain(aApzc);
+
+  // Make sure the move is large enough to not be handled as a tap
+  nsEventStatus status = ApzcDown(aApzc, 10, aTouchStartY + OVERCOME_TOUCH_TOLERANCE, aTime);
+  if (aOutEventStatuses) {
+    (*aOutEventStatuses)[0] = status;
+  }
+
+  aTime += TIME_BETWEEN_TOUCH_EVENT;
+
+  // Allowed touch behaviours must be set after sending touch-start.
+  if (gfxPrefs::TouchActionEnabled() && aAllowedTouchBehaviors) {
+    aApzc->SetAllowedTouchBehavior(*aAllowedTouchBehaviors);
+  }
+
+  MultiTouchInput mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, aTime, TimeStamp(), 0);
+  mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, aTouchStartY), ScreenSize(0, 0), 0, 0));
+  status = aApzc->ReceiveInputEvent(mti);
+  if (aOutEventStatuses) {
+    (*aOutEventStatuses)[1] = status;
+  }
+
+  aTime += TIME_BETWEEN_TOUCH_EVENT;
+
+  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, aTime, TimeStamp(), 0);
+  mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, aTouchEndY), ScreenSize(0, 0), 0, 0));
+  status = aApzc->ReceiveInputEvent(mti);
+  if (aOutEventStatuses) {
+    (*aOutEventStatuses)[2] = status;
+  }
+
+  aTime += TIME_BETWEEN_TOUCH_EVENT;
+
+  if (!aKeepFingerDown) {
+    status = ApzcUp(aApzc, 10, aTouchEndY, aTime);
+  } else {
+    status = (nsEventStatus)-1;
+  }
+  if (aOutEventStatuses) {
+    (*aOutEventStatuses)[3] = status;
+  }
+
+  aTime += TIME_BETWEEN_TOUCH_EVENT;
+
+  // Since we've explicitly built the overscroll handoff chain before
+  // touch-start, we need to explicitly clear it after touch-end.
+  aTreeManager->ClearOverscrollHandoffChain();
+}
+
+/*
+ * Dispatches mock touch events to the apzc and checks whether apzc properly
+ * consumed them and triggered scrolling behavior.
+ */
+static void
+ApzcPanAndCheckStatus(AsyncPanZoomController* aApzc,
+                      TestAPZCTreeManager* aTreeManager,
+                      int& aTime,
+                      int aTouchStartY,
+                      int aTouchEndY,
+                      bool expectIgnoredPan,
+                      bool hasTouchListeners,
+                      nsTArray<uint32_t>* aAllowedTouchBehaviors)
+{
+  nsEventStatus statuses[4]; // down, move, move, up
+  ApzcPan(aApzc, aTreeManager, aTime, aTouchStartY, aTouchEndY, false, aAllowedTouchBehaviors, &statuses);
+
+  nsEventStatus touchStartStatus;
+  if (hasTouchListeners) {
+    // APZC shouldn't consume the start event now, instead queueing it up
+    // waiting for content's response.
+    touchStartStatus = nsEventStatus_eIgnore;
+  } else {
+    // APZC should go into the touching state and therefore consume the event.
+    touchStartStatus = nsEventStatus_eConsumeNoDefault;
+  }
+  EXPECT_EQ(touchStartStatus, statuses[0]);
+
+  nsEventStatus touchMoveStatus;
+  if (expectIgnoredPan) {
+    // APZC should ignore panning, be in TOUCHING state and therefore return eIgnore.
+    // The same applies to all consequent touch move events.
+    touchMoveStatus = nsEventStatus_eIgnore;
+  } else {
+    // APZC should go into the panning state and therefore consume the event.
+    touchMoveStatus = nsEventStatus_eConsumeNoDefault;
+  }
+  EXPECT_EQ(touchMoveStatus, statuses[1]);
+  EXPECT_EQ(touchMoveStatus, statuses[2]);
+}
+
+static void
+ApzcPinchWithPinchInput(AsyncPanZoomController* aApzc,
+                        int aFocusX, int aFocusY, float aScale,
+                        nsEventStatus (*aOutEventStatuses)[3] = nullptr)
+{
+  nsEventStatus actualStatus = aApzc->HandleGestureEvent(
+    PinchGestureInput(PinchGestureInput::PINCHGESTURE_START,
+                      0, TimeStamp(), ScreenPoint(aFocusX, aFocusY),
+                      10.0, 10.0, 0));
+  if (aOutEventStatuses) {
+    (*aOutEventStatuses)[0] = actualStatus;
+  }
+  actualStatus = aApzc->HandleGestureEvent(
+    PinchGestureInput(PinchGestureInput::PINCHGESTURE_SCALE,
+                      0, TimeStamp(), ScreenPoint(aFocusX, aFocusY),
+                      10.0 * aScale, 10.0, 0));
+  if (aOutEventStatuses) {
+    (*aOutEventStatuses)[1] = actualStatus;
+  }
+  actualStatus = aApzc->HandleGestureEvent(
+    PinchGestureInput(PinchGestureInput::PINCHGESTURE_END,
+                      0, TimeStamp(), ScreenPoint(aFocusX, aFocusY),
+                      // note: negative values here tell APZC
+                      //       not to turn the pinch into a pan
+                      -1.0, -1.0, 0));
+  if (aOutEventStatuses) {
+    (*aOutEventStatuses)[2] = actualStatus;
+  }
+}
+
+static void
+ApzcPinchWithPinchInputAndCheckStatus(AsyncPanZoomController* aApzc,
+                                      int aFocusX, int aFocusY, float aScale,
+                                      bool aShouldTriggerPinch)
+{
+  nsEventStatus statuses[3];  // scalebegin, scale, scaleend
+  ApzcPinchWithPinchInput(aApzc, aFocusX, aFocusY, aScale, &statuses);
+
+  nsEventStatus expectedStatus = aShouldTriggerPinch
+      ? nsEventStatus_eConsumeNoDefault
+      : nsEventStatus_eIgnore;
+  EXPECT_EQ(expectedStatus, statuses[0]);
+  EXPECT_EQ(expectedStatus, statuses[1]);
 }
 
-TEST_F(AsyncPanZoomControllerTester, Pinch_DefaultGestures_NoTouchAction) {
-  DoPinchTest(false, true);
+static void
+ApzcPinchWithTouchInput(AsyncPanZoomController* aApzc,
+                        int aFocusX, int aFocusY, float aScale,
+                        int& inputId,
+                        nsTArray<uint32_t>* aAllowedTouchBehaviors = nullptr,
+                        nsEventStatus (*aOutEventStatuses)[4] = nullptr)
+{
+  // Having pinch coordinates in float type may cause problems with high-precision scale values
+  // since SingleTouchData accepts integer value. But for trivial tests it should be ok.
+  float pinchLength = 100.0;
+  float pinchLengthScaled = pinchLength * aScale;
+
+  MultiTouchInput mtiStart = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0);
+  mtiStart.mTouches.AppendElement(SingleTouchData(inputId, ScreenIntPoint(aFocusX, aFocusY), ScreenSize(0, 0), 0, 0));
+  mtiStart.mTouches.AppendElement(SingleTouchData(inputId + 1, ScreenIntPoint(aFocusX, aFocusY), ScreenSize(0, 0), 0, 0));
+  nsEventStatus status = aApzc->ReceiveInputEvent(mtiStart);
+  if (aOutEventStatuses) {
+    (*aOutEventStatuses)[0] = status;
+  }
+
+  if (gfxPrefs::TouchActionEnabled() && aAllowedTouchBehaviors) {
+    aApzc->SetAllowedTouchBehavior(*aAllowedTouchBehaviors);
+  }
+
+  MultiTouchInput mtiMove1 = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
+  mtiMove1.mTouches.AppendElement(SingleTouchData(inputId, ScreenIntPoint(aFocusX - pinchLength, aFocusY), ScreenSize(0, 0), 0, 0));
+  mtiMove1.mTouches.AppendElement(SingleTouchData(inputId + 1, ScreenIntPoint(aFocusX + pinchLength, aFocusY), ScreenSize(0, 0), 0, 0));
+  status = aApzc->ReceiveInputEvent(mtiMove1);
+  if (aOutEventStatuses) {
+    (*aOutEventStatuses)[1] = status;
+  }
+
+  MultiTouchInput mtiMove2 = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
+  mtiMove2.mTouches.AppendElement(SingleTouchData(inputId, ScreenIntPoint(aFocusX - pinchLengthScaled, aFocusY), ScreenSize(0, 0), 0, 0));
+  mtiMove2.mTouches.AppendElement(SingleTouchData(inputId + 1, ScreenIntPoint(aFocusX + pinchLengthScaled, aFocusY), ScreenSize(0, 0), 0, 0));
+  status = aApzc->ReceiveInputEvent(mtiMove2);
+  if (aOutEventStatuses) {
+    (*aOutEventStatuses)[2] = status;
+  }
+
+  MultiTouchInput mtiEnd = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, TimeStamp(), 0);
+  mtiEnd.mTouches.AppendElement(SingleTouchData(inputId, ScreenIntPoint(aFocusX - pinchLengthScaled, aFocusY), ScreenSize(0, 0), 0, 0));
+  mtiEnd.mTouches.AppendElement(SingleTouchData(inputId + 1, ScreenIntPoint(aFocusX + pinchLengthScaled, aFocusY), ScreenSize(0, 0), 0, 0));
+  status = aApzc->ReceiveInputEvent(mtiEnd);
+  if (aOutEventStatuses) {
+    (*aOutEventStatuses)[3] = status;
+  }
+
+  inputId += 2;
+}
+
+static void
+ApzcPinchWithTouchInputAndCheckStatus(AsyncPanZoomController* aApzc,
+                                      int aFocusX, int aFocusY, float aScale,
+                                      int& inputId, bool aShouldTriggerPinch,
+                                      nsTArray<uint32_t>* aAllowedTouchBehaviors)
+{
+  nsEventStatus statuses[4];  // down, move, move, up
+  ApzcPinchWithTouchInput(aApzc, aFocusX, aFocusY, aScale, inputId, aAllowedTouchBehaviors, &statuses);
+
+  nsEventStatus expectedStatus = aShouldTriggerPinch
+      ? nsEventStatus_eConsumeNoDefault
+      : nsEventStatus_eIgnore;
+  EXPECT_EQ(statuses[1], expectedStatus);
+  EXPECT_EQ(statuses[2], expectedStatus);
 }
 
-TEST_F(AsyncPanZoomControllerTester, Pinch_UseGestureDetector_NoTouchAction) {
-  DoPinchTest(true, true);
+class APZCPinchTester : public APZCBasicTester {
+public:
+  APZCPinchTester(AsyncPanZoomController::GestureBehavior aGestureBehavior = AsyncPanZoomController::DEFAULT_GESTURES)
+    : APZCBasicTester(aGestureBehavior)
+  {
+  }
+
+protected:
+  void DoPinchTest(bool aShouldTriggerPinch,
+                   nsTArray<uint32_t> *aAllowedTouchBehaviors = nullptr)
+  {
+    FrameMetrics fm;
+    fm.mViewport = CSSRect(0, 0, 980, 480);
+    fm.mCompositionBounds = ParentLayerRect(200, 200, 100, 200);
+    fm.mScrollableRect = CSSRect(0, 0, 980, 1000);
+    fm.SetScrollOffset(CSSPoint(300, 300));
+    fm.SetZoom(CSSToScreenScale(2.0));
+    apzc->SetFrameMetrics(fm);
+    // the visible area of the document in CSS pixels is x=300 y=300 w=50 h=100
+
+    MakeApzcZoomable();
+
+    if (aShouldTriggerPinch) {
+      EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(AtLeast(1));
+      EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1);
+    } else {
+      EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(AtMost(2));
+      EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0);
+    }
+
+    int touchInputId = 0;
+    if (mGestureBehavior == AsyncPanZoomController::USE_GESTURE_DETECTOR) {
+      ApzcPinchWithTouchInputAndCheckStatus(apzc, 250, 300, 1.25, touchInputId, aShouldTriggerPinch, aAllowedTouchBehaviors);
+    } else {
+      ApzcPinchWithPinchInputAndCheckStatus(apzc, 250, 300, 1.25, aShouldTriggerPinch);
+    }
+
+    fm = apzc->GetFrameMetrics();
+
+    if (aShouldTriggerPinch) {
+      // the visible area of the document in CSS pixels is now x=305 y=310 w=40 h=80
+      EXPECT_EQ(2.5f, fm.GetZoom().scale);
+      EXPECT_EQ(305, fm.GetScrollOffset().x);
+      EXPECT_EQ(310, fm.GetScrollOffset().y);
+    } else {
+      // The frame metrics should stay the same since touch-action:none makes
+      // apzc ignore pinch gestures.
+      EXPECT_EQ(2.0f, fm.GetZoom().scale);
+      EXPECT_EQ(300, fm.GetScrollOffset().x);
+      EXPECT_EQ(300, fm.GetScrollOffset().y);
+    }
+
+    // part 2 of the test, move to the top-right corner of the page and pinch and
+    // make sure we stay in the correct spot
+    fm.SetZoom(CSSToScreenScale(2.0));
+    fm.SetScrollOffset(CSSPoint(930, 5));
+    apzc->SetFrameMetrics(fm);
+    // the visible area of the document in CSS pixels is x=930 y=5 w=50 h=100
+
+    if (mGestureBehavior == AsyncPanZoomController::USE_GESTURE_DETECTOR) {
+      ApzcPinchWithTouchInputAndCheckStatus(apzc, 250, 300, 0.5, touchInputId, aShouldTriggerPinch, aAllowedTouchBehaviors);
+    } else {
+      ApzcPinchWithPinchInputAndCheckStatus(apzc, 250, 300, 0.5, aShouldTriggerPinch);
+    }
+
+    fm = apzc->GetFrameMetrics();
+
+    if (aShouldTriggerPinch) {
+      // the visible area of the document in CSS pixels is now x=880 y=0 w=100 h=200
+      EXPECT_EQ(1.0f, fm.GetZoom().scale);
+      EXPECT_EQ(880, fm.GetScrollOffset().x);
+      EXPECT_EQ(0, fm.GetScrollOffset().y);
+    } else {
+      EXPECT_EQ(2.0f, fm.GetZoom().scale);
+      EXPECT_EQ(930, fm.GetScrollOffset().x);
+      EXPECT_EQ(5, fm.GetScrollOffset().y);
+    }
+  }
+};
+
+class APZCPinchGestureDetectorTester : public APZCPinchTester {
+public:
+  APZCPinchGestureDetectorTester()
+    : APZCPinchTester(AsyncPanZoomController::USE_GESTURE_DETECTOR)
+  {
+  }
+};
+
+TEST_F(APZCPinchTester, Pinch_DefaultGestures_NoTouchAction) {
+  DoPinchTest(true);
 }
 
-TEST_F(TouchActionEnabledTester, Pinch_UseGestureDetector_TouchActionNone) {
+TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_NoTouchAction) {
+  DoPinchTest(true);
+}
+
+TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_TouchActionNone) {
+  SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
   nsTArray<uint32_t> behaviors;
   behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::NONE);
   behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::NONE);
-  DoPinchTest(true, false, &behaviors);
+  DoPinchTest(false, &behaviors);
 }
 
-TEST_F(TouchActionEnabledTester, Pinch_UseGestureDetector_TouchActionZoom) {
+TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_TouchActionZoom) {
+  SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
   nsTArray<uint32_t> behaviors;
   behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM);
   behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM);
-  DoPinchTest(true, true, &behaviors);
+  DoPinchTest(true, &behaviors);
 }
 
-TEST_F(TouchActionEnabledTester, Pinch_UseGestureDetector_TouchActionNotAllowZoom) {
+TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_TouchActionNotAllowZoom) {
+  SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
   nsTArray<uint32_t> behaviors;
   behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN);
   behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM);
-  DoPinchTest(true, false, &behaviors);
+  DoPinchTest(false, &behaviors);
 }
 
-TEST_F(AsyncPanZoomControllerTester, Overzoom) {
-  nsRefPtr<MockContentController> mcc = new NiceMock<MockContentController>();
-  nsRefPtr<TestAsyncPanZoomController> apzc = new TestAsyncPanZoomController(0, mcc);
-
+TEST_F(APZCBasicTester, Overzoom) {
+  // the visible area of the document in CSS pixels is x=10 y=0 w=100 h=100
   FrameMetrics fm;
   fm.mViewport = CSSRect(0, 0, 100, 100);
   fm.mCompositionBounds = ParentLayerRect(0, 0, 100, 100);
   fm.mScrollableRect = CSSRect(0, 0, 125, 150);
   fm.SetScrollOffset(CSSPoint(10, 0));
   fm.SetZoom(CSSToScreenScale(1.0));
   apzc->SetFrameMetrics(fm);
-  apzc->UpdateZoomConstraints(ZoomConstraints(true, true, CSSToScreenScale(0.25), CSSToScreenScale(4.0)));
-  // the visible area of the document in CSS pixels is x=10 y=0 w=100 h=100
+
+  MakeApzcZoomable();
 
   EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(AtLeast(1));
   EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1);
 
-  ApzcPinchWithPinchInput(apzc, 50, 50, 0.5, true);
+  ApzcPinchWithPinchInputAndCheckStatus(apzc, 50, 50, 0.5, true);
 
   fm = apzc->GetFrameMetrics();
   EXPECT_EQ(0.8f, fm.GetZoom().scale);
   // bug 936721 - PGO builds introduce rounding error so
   // use a fuzzy match instead
   EXPECT_LT(abs(fm.GetScrollOffset().x), 1e-5);
   EXPECT_LT(abs(fm.GetScrollOffset().y), 1e-5);
 }
 
-TEST_F(AsyncPanZoomControllerTester, SimpleTransform) {
-  TimeStamp testStartTime = TimeStamp::Now();
-  // RefCounted class can't live in the stack
-  nsRefPtr<MockContentController> mcc = new NiceMock<MockContentController>();
-  nsRefPtr<TestAsyncPanZoomController> apzc = new TestAsyncPanZoomController(0, mcc);
-  apzc->SetFrameMetrics(TestFrameMetrics());
-
+TEST_F(APZCBasicTester, SimpleTransform) {
   ScreenPoint pointOut;
   ViewTransform viewTransformOut;
   apzc->SampleContentTransformForFrame(testStartTime, &viewTransformOut, pointOut);
 
   EXPECT_EQ(ScreenPoint(), pointOut);
   EXPECT_EQ(ViewTransform(), viewTransformOut);
 }
 
 
-TEST_F(AsyncPanZoomControllerTester, ComplexTransform) {
-  TimeStamp testStartTime = TimeStamp::Now();
-  AsyncPanZoomController::SetFrameTime(testStartTime);
-
+TEST_F(APZCBasicTester, ComplexTransform) {
   // This test assumes there is a page that gets rendered to
   // two layers. In CSS pixels, the first layer is 50x50 and
   // the second layer is 25x50. The widget scale factor is 3.0
   // and the presShell resolution is 2.0. Therefore, these layers
   // end up being 300x300 and 150x300 in layer pixels.
   //
   // The second (child) layer has an additional CSS transform that
   // stretches it by 2.0 on the x-axis. Therefore, after applying
   // CSS transforms, the two layers are the same size in screen
   // pixels.
   //
   // The screen itself is 24x24 in screen pixels (therefore 4x4 in
   // CSS pixels). The displayport is 1 extra CSS pixel on all
   // sides.
 
-  nsRefPtr<MockContentController> mcc = new NiceMock<MockContentController>();
-  nsRefPtr<TestAsyncPanZoomController> apzc = new TestAsyncPanZoomController(0, mcc);
-  nsRefPtr<TestAsyncPanZoomController> childApzc = new TestAsyncPanZoomController(0, mcc);
+  nsRefPtr<TestAsyncPanZoomController> childApzc = new TestAsyncPanZoomController(0, mcc, tm);
 
   const char* layerTreeSyntax = "c(c)";
   // LayerID                     0 1
   nsIntRegion layerVisibleRegion[] = {
     nsIntRegion(nsIntRect(0, 0, 300, 300)),
     nsIntRegion(nsIntRect(0, 0, 150, 300)),
   };
   gfx3DMatrix transforms[] = {
@@ -715,89 +733,113 @@ TEST_F(AsyncPanZoomControllerTester, Com
 
   childMetrics.ZoomBy(1.5f);
   childApzc->SetFrameMetrics(childMetrics);
   childApzc->SampleContentTransformForFrame(testStartTime, &viewTransformOut, pointOut);
   EXPECT_EQ(ViewTransform(LayerPoint(-30, 0), ParentLayerToScreenScale(3)), viewTransformOut);
   EXPECT_EQ(ScreenPoint(135, 90), pointOut);
 }
 
-TEST_F(AsyncPanZoomControllerTester, Pan) {
+class APZCPanningTester : public APZCBasicTester {
+protected:
+  void DoPanTest(bool aShouldTriggerScroll, uint32_t aBehavior)
+  {
+    if (aShouldTriggerScroll) {
+      EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(AtLeast(1));
+      EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1);
+    } else {
+      EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(0);
+      EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0);
+    }
+
+    int time = 0;
+    int touchStart = 50;
+    int touchEnd = 10;
+    ScreenPoint pointOut;
+    ViewTransform viewTransformOut;
+
+    nsTArray<uint32_t> allowedTouchBehaviors;
+    allowedTouchBehaviors.AppendElement(aBehavior);
+
+    // Pan down
+    ApzcPanAndCheckStatus(apzc, tm, time, touchStart, touchEnd, !aShouldTriggerScroll, false, &allowedTouchBehaviors);
+    apzc->SampleContentTransformForFrame(testStartTime, &viewTransformOut, pointOut);
+
+    if (aShouldTriggerScroll) {
+      EXPECT_EQ(ScreenPoint(0, -(touchEnd-touchStart)), pointOut);
+      EXPECT_NE(ViewTransform(), viewTransformOut);
+    } else {
+      EXPECT_EQ(ScreenPoint(), pointOut);
+      EXPECT_EQ(ViewTransform(), viewTransformOut);
+    }
+
+    // Pan back
+    ApzcPanAndCheckStatus(apzc, tm, time, touchEnd, touchStart, !aShouldTriggerScroll, false, &allowedTouchBehaviors);
+    apzc->SampleContentTransformForFrame(testStartTime, &viewTransformOut, pointOut);
+
+    EXPECT_EQ(ScreenPoint(), pointOut);
+    EXPECT_EQ(ViewTransform(), viewTransformOut);
+  }
+};
+
+TEST_F(APZCPanningTester, Pan) {
   DoPanTest(true, mozilla::layers::AllowedTouchBehavior::NONE);
 }
 
 // In the each of the following 4 pan tests we are performing two pan gestures: vertical pan from top
 // to bottom and back - from bottom to top.
 // According to the pointer-events/touch-action spec AUTO and PAN_Y touch-action values allow vertical
 // scrolling while NONE and PAN_X forbid it. The first parameter of DoPanTest method specifies this
 // behavior.
-TEST_F(TouchActionEnabledTester, PanWithTouchActionAuto) {
+TEST_F(APZCPanningTester, PanWithTouchActionAuto) {
+  SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
   DoPanTest(true, mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN
                   | mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN);
 }
 
-TEST_F(TouchActionEnabledTester, PanWithTouchActionNone) {
+TEST_F(APZCPanningTester, PanWithTouchActionNone) {
+  SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
   DoPanTest(false, 0);
 }
 
-TEST_F(TouchActionEnabledTester, PanWithTouchActionPanX) {
+TEST_F(APZCPanningTester, PanWithTouchActionPanX) {
+  SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
   DoPanTest(false, mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN);
 }
 
-TEST_F(TouchActionEnabledTester, PanWithTouchActionPanY) {
+TEST_F(APZCPanningTester, PanWithTouchActionPanY) {
+  SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
   DoPanTest(true, mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN);
 }
 
-TEST_F(TouchActionEnabledTester, PanWithPreventDefault) {
-  TimeStamp testStartTime = TimeStamp::Now();
-  AsyncPanZoomController::SetFrameTime(testStartTime);
-
-  nsRefPtr<MockContentController> mcc = new NiceMock<MockContentController>();
-  nsRefPtr<TestAPZCTreeManager> tm = new TestAPZCTreeManager();
-  nsRefPtr<TestAsyncPanZoomController> apzc = new TestAsyncPanZoomController(0, mcc, tm);
-
-  FrameMetrics frameMetrics(TestFrameMetrics());
-  frameMetrics.mMayHaveTouchListeners = true;
-
-  apzc->SetFrameMetrics(frameMetrics);
-  apzc->NotifyLayersUpdated(frameMetrics, true);
+TEST_F(APZCBasicTester, PanWithPreventDefault) {
+  SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
+  UseTouchListenerMetrics();
 
   int time = 0;
   int touchStart = 50;
   int touchEnd = 10;
   ScreenPoint pointOut;
   ViewTransform viewTransformOut;
 
   // Pan down
   nsTArray<uint32_t> allowedTouchBehaviors;
   allowedTouchBehaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN);
-  ApzcPan(apzc, tm, time, touchStart, touchEnd, true, true, &allowedTouchBehaviors);
+  ApzcPanAndCheckStatus(apzc, tm, time, touchStart, touchEnd, true, true, &allowedTouchBehaviors);
 
   // Send the signal that content has handled and preventDefaulted the touch
   // events. This flushes the event queue.
   apzc->ContentReceivedTouch(true);
 
   apzc->SampleContentTransformForFrame(testStartTime, &viewTransformOut, pointOut);
   EXPECT_EQ(ScreenPoint(), pointOut);
   EXPECT_EQ(ViewTransform(), viewTransformOut);
-
-  apzc->Destroy();
 }
 
-TEST_F(AsyncPanZoomControllerTester, Fling) {
-  TimeStamp testStartTime = TimeStamp::Now();
-  AsyncPanZoomController::SetFrameTime(testStartTime);
-
-  nsRefPtr<MockContentController> mcc = new NiceMock<MockContentController>();
-  nsRefPtr<TestAPZCTreeManager> tm = new TestAPZCTreeManager();
-  nsRefPtr<TestAsyncPanZoomController> apzc = new TestAsyncPanZoomController(0, mcc, tm);
-
-  apzc->SetFrameMetrics(TestFrameMetrics());
-  apzc->NotifyLayersUpdated(TestFrameMetrics(), true);
-
+TEST_F(APZCBasicTester, Fling) {
   EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(AtLeast(1));
   EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1);
 
   int time = 0;
   int touchStart = 50;
   int touchEnd = 10;
   ScreenPoint pointOut;
   ViewTransform viewTransformOut;
@@ -805,95 +847,74 @@ TEST_F(AsyncPanZoomControllerTester, Fli
   // Fling down. Each step scroll further down
   ApzcPan(apzc, tm, time, touchStart, touchEnd);
   ScreenPoint lastPoint;
   for (int i = 1; i < 50; i+=1) {
     apzc->SampleContentTransformForFrame(testStartTime+TimeDuration::FromMilliseconds(i), &viewTransformOut, pointOut);
     EXPECT_GT(pointOut.y, lastPoint.y);
     lastPoint = pointOut;
   }
-
-  apzc->Destroy();
 }
 
-// Start a fling, and then tap while the fling is ongoing. When
-// aSlow is false, the tap will happen while the fling is at a
-// high velocity, and we check that the tap doesn't trigger sending a tap
-// to content. If aSlow is true, the tap will happen while the fling
-// is at a slow velocity, and we check that the tap does trigger sending
-// a tap to content. See bug 1022956.
-void
-DoFlingStopTest(bool aSlow) {
-  TimeStamp testStartTime = TimeStamp::Now();
-  AsyncPanZoomController::SetFrameTime(testStartTime);
+class APZCFlingStopTester : public APZCGestureDetectorTester {
+protected:
+  // Start a fling, and then tap while the fling is ongoing. When
+  // aSlow is false, the tap will happen while the fling is at a
+  // high velocity, and we check that the tap doesn't trigger sending a tap
+  // to content. If aSlow is true, the tap will happen while the fling
+  // is at a slow velocity, and we check that the tap does trigger sending
+  // a tap to content. See bug 1022956.
+  void DoFlingStopTest(bool aSlow) {
+    int time = 0;
+    int touchStart = 50;
+    int touchEnd = 10;
 
-  nsRefPtr<MockContentControllerDelayed> mcc = new NiceMock<MockContentControllerDelayed>();
-  nsRefPtr<TestAPZCTreeManager> tm = new TestAPZCTreeManager();
-  nsRefPtr<TestAsyncPanZoomController> apzc = new TestAsyncPanZoomController(0, mcc, tm, AsyncPanZoomController::USE_GESTURE_DETECTOR);
-
-  apzc->SetFrameMetrics(TestFrameMetrics());
-  apzc->NotifyLayersUpdated(TestFrameMetrics(), true);
-
-  int time = 0;
-  int touchStart = 50;
-  int touchEnd = 10;
-
-  // Start the fling down.
-  ApzcPan(apzc, tm, time, touchStart, touchEnd);
-  // The touchstart from the pan will leave some cancelled tasks in the queue, clear them out
-  EXPECT_EQ(2, mcc->RunThroughDelayedTasks());
+    // Start the fling down.
+    ApzcPan(apzc, tm, time, touchStart, touchEnd);
+    // The touchstart from the pan will leave some cancelled tasks in the queue, clear them out
+    EXPECT_EQ(2, mcc->RunThroughDelayedTasks());
 
-  // If we want to tap while the fling is fast, let the fling advance for 10ms only. If we want
-  // the fling to slow down more, advance to 2000ms. These numbers may need adjusting if our
-  // friction and threshold values change, but they should be deterministic at least.
-  int timeDelta = aSlow ? 2000 : 10;
-  int tapCallsExpected = aSlow ? 1 : 0;
-  int delayedTasksExpected = aSlow ? 3 : 2;
+    // If we want to tap while the fling is fast, let the fling advance for 10ms only. If we want
+    // the fling to slow down more, advance to 2000ms. These numbers may need adjusting if our
+    // friction and threshold values change, but they should be deterministic at least.
+    int timeDelta = aSlow ? 2000 : 10;
+    int tapCallsExpected = aSlow ? 1 : 0;
+    int delayedTasksExpected = aSlow ? 3 : 2;
 
-  // Advance the fling animation by timeDelta milliseconds.
-  ScreenPoint pointOut;
-  ViewTransform viewTransformOut;
-  apzc->SampleContentTransformForFrame(testStartTime + TimeDuration::FromMilliseconds(timeDelta), &viewTransformOut, pointOut);
+    // Advance the fling animation by timeDelta milliseconds.
+    ScreenPoint pointOut;
+    ViewTransform viewTransformOut;
+    apzc->SampleContentTransformForFrame(testStartTime + TimeDuration::FromMilliseconds(timeDelta), &viewTransformOut, pointOut);
 
-  // Deliver a tap to abort the fling. Ensure that we get a HandleSingleTap
-  // call out of it if and only if the fling is slow.
-  EXPECT_CALL(*mcc, HandleSingleTap(_, 0, apzc->GetGuid())).Times(tapCallsExpected);
-  ApzcTap(apzc, 10, 10, time, 0, nullptr);
-  EXPECT_EQ(delayedTasksExpected, mcc->RunThroughDelayedTasks());
+    // Deliver a tap to abort the fling. Ensure that we get a HandleSingleTap
+    // call out of it if and only if the fling is slow.
+    EXPECT_CALL(*mcc, HandleSingleTap(_, 0, apzc->GetGuid())).Times(tapCallsExpected);
+    ApzcTap(apzc, 10, 10, time, 0, nullptr);
+    EXPECT_EQ(delayedTasksExpected, mcc->RunThroughDelayedTasks());
 
-  // Verify that we didn't advance any further after the fling was aborted, in either case.
-  ScreenPoint finalPointOut;
-  apzc->SampleContentTransformForFrame(testStartTime + TimeDuration::FromMilliseconds(timeDelta + 1000), &viewTransformOut, finalPointOut);
-  EXPECT_EQ(pointOut.x, finalPointOut.x);
-  EXPECT_EQ(pointOut.y, finalPointOut.y);
+    // Verify that we didn't advance any further after the fling was aborted, in either case.
+    ScreenPoint finalPointOut;
+    apzc->SampleContentTransformForFrame(testStartTime + TimeDuration::FromMilliseconds(timeDelta + 1000), &viewTransformOut, finalPointOut);
+    EXPECT_EQ(pointOut.x, finalPointOut.x);
+    EXPECT_EQ(pointOut.y, finalPointOut.y);
 
-  apzc->AssertStateIsReset();
-  apzc->Destroy();
-}
+    apzc->AssertStateIsReset();
+  }
+};
 
-TEST_F(AsyncPanZoomControllerTester, FlingStop) {
+TEST_F(APZCFlingStopTester, FlingStop) {
   DoFlingStopTest(false);
 }
 
-TEST_F(AsyncPanZoomControllerTester, FlingStopTap) {
+TEST_F(APZCFlingStopTester, FlingStopTap) {
   DoFlingStopTest(true);
 }
 
-TEST_F(AsyncPanZoomControllerTester, OverScrollPanning) {
-  TestScopedBoolPref overscrollEnabledPref("apz.overscroll.enabled", true);
-
-  TimeStamp testStartTime = TimeStamp::Now();
-  AsyncPanZoomController::SetFrameTime(testStartTime);
-
-  nsRefPtr<MockContentController> mcc = new NiceMock<MockContentController>();
-  nsRefPtr<TestAPZCTreeManager> tm = new TestAPZCTreeManager();
-  nsRefPtr<TestAsyncPanZoomController> apzc = new TestAsyncPanZoomController(0, mcc, tm);
-
-  apzc->SetFrameMetrics(TestFrameMetrics());
-  apzc->NotifyLayersUpdated(TestFrameMetrics(), true);
+TEST_F(APZCBasicTester, OverScrollPanning) {
+  SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
 
   // Pan sufficiently to hit overscroll behavior
   int time = 0;
   int touchStart = 500;
   int touchEnd = 10;
   ApzcPan(apzc, tm, time, touchStart, touchEnd);
   EXPECT_TRUE(apzc->IsOverscrolled());
 
@@ -920,31 +941,20 @@ TEST_F(AsyncPanZoomControllerTester, Ove
   EXPECT_TRUE(apzc->IsOverscrolled());
 
   // This sample will run to the end of the snapback animation and reset the state.
   apzc->SampleContentTransformForFrame(testStartTime + TimeDuration::FromMilliseconds(30000), &viewTransformOut, pointOut);
   EXPECT_EQ(ScreenPoint(0, 90), pointOut);
   EXPECT_FALSE(apzc->IsOverscrolled());
 
   apzc->AssertStateIsReset();
-  apzc->Destroy();
 }
 
-TEST_F(AsyncPanZoomControllerTester, OverScrollAbort) {
-  TestScopedBoolPref overscrollEnabledPref("apz.overscroll.enabled", true);
-
-  TimeStamp testStartTime = TimeStamp::Now();
-  AsyncPanZoomController::SetFrameTime(testStartTime);
-
-  nsRefPtr<MockContentController> mcc = new NiceMock<MockContentController>();
-  nsRefPtr<TestAPZCTreeManager> tm = new TestAPZCTreeManager();
-  nsRefPtr<TestAsyncPanZoomController> apzc = new TestAsyncPanZoomController(0, mcc, tm);
-
-  apzc->SetFrameMetrics(TestFrameMetrics());
-  apzc->NotifyLayersUpdated(TestFrameMetrics(), true);
+TEST_F(APZCBasicTester, OverScrollAbort) {
+  SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
 
   // Pan sufficiently to hit overscroll behavior
   int time = 0;
   int touchStart = 500;
   int touchEnd = 10;
   ApzcPan(apzc, tm, time, touchStart, touchEnd);
   EXPECT_TRUE(apzc->IsOverscrolled());
 
@@ -957,239 +967,221 @@ TEST_F(AsyncPanZoomControllerTester, Ove
   apzc->SampleContentTransformForFrame(testStartTime + TimeDuration::FromMilliseconds(10000), &viewTransformOut, pointOut);
   EXPECT_TRUE(apzc->IsOverscrolled());
 
   // At this point, we have an active overscrolling fling animation.
   // Check that cancelling the animation clears the overscroll.
   apzc->CancelAnimation();
   EXPECT_FALSE(apzc->IsOverscrolled());
   apzc->AssertStateIsReset();
-
-  apzc->Destroy();
 }
 
-TEST_F(AsyncPanZoomControllerTester, ShortPress) {
-  nsRefPtr<MockContentControllerDelayed> mcc = new NiceMock<MockContentControllerDelayed>();
-  nsRefPtr<TestAPZCTreeManager> tm = new TestAPZCTreeManager();
-  nsRefPtr<TestAsyncPanZoomController> apzc = new TestAsyncPanZoomController(
-    0, mcc, tm, AsyncPanZoomController::USE_GESTURE_DETECTOR);
+TEST_F(APZCBasicTester, OverScrollPanningAbort) {
+  SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
 
-  apzc->SetFrameMetrics(TestFrameMetrics());
-  apzc->NotifyLayersUpdated(TestFrameMetrics(), true);
-  apzc->UpdateZoomConstraints(ZoomConstraints(false, false, CSSToScreenScale(1.0), CSSToScreenScale(1.0)));
+  // Pan sufficiently to hit overscroll behaviour. Keep the finger down so
+  // the pan does not end.
+  int time = 0;
+  int touchStart = 500;
+  int touchEnd = 10;
+  ApzcPan(apzc, tm, time, touchStart, touchEnd,
+          true);                   // keep finger down
+  EXPECT_TRUE(apzc->IsOverscrolled());
+
+  // Check that calling CancelAnimation() while the user is still panning
+  // (and thus no fling or snap-back animation has had a chance to start)
+  // clears the overscroll.
+  apzc->CancelAnimation();
+  EXPECT_FALSE(apzc->IsOverscrolled());
+  apzc->AssertStateIsReset();
+}
+
+TEST_F(APZCGestureDetectorTester, ShortPress) {
+  MakeApzcUnzoomable();
 
   int time = 0;
   nsEventStatus status = ApzcTap(apzc, 10, 10, time, 100, mcc.get());
   EXPECT_EQ(nsEventStatus_eIgnore, status);
 
   // This verifies that the single tap notification is sent after the
   // touchdown is fully processed. The ordering here is important.
   mcc->CheckHasDelayedTask();
 
   EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1);
   mcc->RunDelayedTask();
 
   apzc->AssertStateIsReset();
-  apzc->Destroy();
 }
 
-TEST_F(AsyncPanZoomControllerTester, MediumPress) {
-  nsRefPtr<MockContentControllerDelayed> mcc = new NiceMock<MockContentControllerDelayed>();
-  nsRefPtr<TestAPZCTreeManager> tm = new TestAPZCTreeManager();
-  nsRefPtr<TestAsyncPanZoomController> apzc = new TestAsyncPanZoomController(
-    0, mcc, tm, AsyncPanZoomController::USE_GESTURE_DETECTOR);
-
-  apzc->SetFrameMetrics(TestFrameMetrics());
-  apzc->NotifyLayersUpdated(TestFrameMetrics(), true);
-  apzc->UpdateZoomConstraints(ZoomConstraints(false, false, CSSToScreenScale(1.0), CSSToScreenScale(1.0)));
+TEST_F(APZCGestureDetectorTester, MediumPress) {
+  MakeApzcUnzoomable();
 
   int time = 0;
   nsEventStatus status = ApzcTap(apzc, 10, 10, time, 400, mcc.get());
   EXPECT_EQ(nsEventStatus_eIgnore, status);
 
   // This verifies that the single tap notification is sent after the
   // touchdown is fully processed. The ordering here is important.
   mcc->CheckHasDelayedTask();
 
   EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1);
   mcc->RunDelayedTask();
 
   apzc->AssertStateIsReset();
-  apzc->Destroy();
-}
-
-void
-DoLongPressTest(uint32_t aBehavior) {
-  nsRefPtr<MockContentControllerDelayed> mcc = new MockContentControllerDelayed();
-  nsRefPtr<TestAPZCTreeManager> tm = new TestAPZCTreeManager();
-  nsRefPtr<TestAsyncPanZoomController> apzc = new TestAsyncPanZoomController(
-    0, mcc, tm, AsyncPanZoomController::USE_GESTURE_DETECTOR);
-
-  apzc->SetFrameMetrics(TestFrameMetrics());
-  apzc->NotifyLayersUpdated(TestFrameMetrics(), true);
-  apzc->UpdateZoomConstraints(ZoomConstraints(false, false, CSSToScreenScale(1.0), CSSToScreenScale(1.0)));
-
-  int time = 0;
-
-  nsEventStatus status = ApzcDown(apzc, 10, 10, time);
-  EXPECT_EQ(nsEventStatus_eConsumeNoDefault, status);
-
-  // SetAllowedTouchBehavior() must be called after sending touch-start.
-  nsTArray<uint32_t> allowedTouchBehaviors;
-  allowedTouchBehaviors.AppendElement(aBehavior);
-  apzc->SetAllowedTouchBehavior(allowedTouchBehaviors);
-
-  MockFunction<void(std::string checkPointName)> check;
-
-  {
-    InSequence s;
-
-    EXPECT_CALL(check, Call("preHandleLongTap"));
-    EXPECT_CALL(*mcc, HandleLongTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1);
-    EXPECT_CALL(check, Call("postHandleLongTap"));
-
-    EXPECT_CALL(check, Call("preHandleLongTapUp"));
-    EXPECT_CALL(*mcc, HandleLongTapUp(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1);
-    EXPECT_CALL(check, Call("postHandleLongTapUp"));
-  }
-
-  mcc->CheckHasDelayedTask();
-
-  // Manually invoke the longpress while the touch is currently down.
-  check.Call("preHandleLongTap");
-  mcc->RunDelayedTask();
-  check.Call("postHandleLongTap");
-
-  // Destroy pending MAX_TAP timeout task
-  mcc->DestroyOldestTask();
-  // There should be a TimeoutContentResponse task in the queue still
-  // Clear the waiting-for-content timeout task, then send the signal that
-  // content has handled this long tap. This takes the place of the
-  // "contextmenu" event.
-  mcc->CheckHasDelayedTask();
-  mcc->ClearDelayedTask();
-  apzc->ContentReceivedTouch(true);
-
-  time += 1000;
-
-  status = ApzcUp(apzc, 10, 10, time);
-  EXPECT_EQ(nsEventStatus_eIgnore, status);
-
-  // To get a LongTapUp event, we must kick APZC to flush its event queue. This
-  // would normally happen if we had a (Tab|RenderFrame)(Parent|Child)
-  // mechanism.
-  check.Call("preHandleLongTapUp");
-  apzc->ContentReceivedTouch(false);
-  check.Call("postHandleLongTapUp");
-
-  apzc->AssertStateIsReset();
-  apzc->Destroy();
 }
 
-void
-DoLongPressPreventDefaultTest(uint32_t aBehavior) {
-  // We have to initialize both an integer time and TimeStamp time because
-  // TimeStamp doesn't have any ToXXX() functions for converting back to
-  // primitives.
-  TimeStamp testStartTime = TimeStamp::Now();
-  int time = 0;
-  AsyncPanZoomController::SetFrameTime(testStartTime);
+class APZCLongPressTester : public APZCGestureDetectorTester {
+protected:
+  void DoLongPressTest(uint32_t aBehavior) {
+    MakeApzcUnzoomable();
+
+    int time = 0;
+
+    nsEventStatus status = ApzcDown(apzc, 10, 10, time);
+    EXPECT_EQ(nsEventStatus_eConsumeNoDefault, status);
 
-  nsRefPtr<MockContentControllerDelayed> mcc = new MockContentControllerDelayed();
-  nsRefPtr<TestAPZCTreeManager> tm = new TestAPZCTreeManager();
-  nsRefPtr<TestAsyncPanZoomController> apzc = new TestAsyncPanZoomController(
-    0, mcc, tm, AsyncPanZoomController::USE_GESTURE_DETECTOR);
+    // SetAllowedTouchBehavior() must be called after sending touch-start.
+    nsTArray<uint32_t> allowedTouchBehaviors;
+    allowedTouchBehaviors.AppendElement(aBehavior);
+    apzc->SetAllowedTouchBehavior(allowedTouchBehaviors);
+
+    MockFunction<void(std::string checkPointName)> check;
 
-  apzc->SetFrameMetrics(TestFrameMetrics());
-  apzc->NotifyLayersUpdated(TestFrameMetrics(), true);
-  apzc->UpdateZoomConstraints(ZoomConstraints(false, false, CSSToScreenScale(1.0), CSSToScreenScale(1.0)));
+    {
+      InSequence s;
 
-  EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(0);
-  EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0);
+      EXPECT_CALL(check, Call("preHandleLongTap"));
+      EXPECT_CALL(*mcc, HandleLongTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1);
+      EXPECT_CALL(check, Call("postHandleLongTap"));
+
+      EXPECT_CALL(check, Call("preHandleLongTapUp"));
+      EXPECT_CALL(*mcc, HandleLongTapUp(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1);
+      EXPECT_CALL(check, Call("postHandleLongTapUp"));
+    }
 
-  int touchX = 10,
-      touchStartY = 10,
-      touchEndY = 50;
+    mcc->CheckHasDelayedTask();
 
-  nsEventStatus status = ApzcDown(apzc, touchX, touchStartY, time);
-  EXPECT_EQ(nsEventStatus_eConsumeNoDefault, status);
+    // Manually invoke the longpress while the touch is currently down.
+    check.Call("preHandleLongTap");
+    mcc->RunDelayedTask();
+    check.Call("postHandleLongTap");
 
-  // SetAllowedTouchBehavior() must be called after sending touch-start.
-  nsTArray<uint32_t> allowedTouchBehaviors;
-  allowedTouchBehaviors.AppendElement(aBehavior);
-  apzc->SetAllowedTouchBehavior(allowedTouchBehaviors);
+    // Destroy pending MAX_TAP timeout task
+    mcc->DestroyOldestTask();
+    // There should be a TimeoutContentResponse task in the queue still
+    // Clear the waiting-for-content timeout task, then send the signal that
+    // content has handled this long tap. This takes the place of the
+    // "contextmenu" event.
+    mcc->CheckHasDelayedTask();
+    mcc->ClearDelayedTask();
+    apzc->ContentReceivedTouch(true);
 
-  MockFunction<void(std::string checkPointName)> check;
+    time += 1000;
 
-  {
-    InSequence s;
+    status = ApzcUp(apzc, 10, 10, time);
+    EXPECT_EQ(nsEventStatus_eIgnore, status);
 
-    EXPECT_CALL(check, Call("preHandleLongTap"));
-    EXPECT_CALL(*mcc, HandleLongTap(CSSPoint(touchX, touchStartY), 0, apzc->GetGuid())).Times(1);
-    EXPECT_CALL(check, Call("postHandleLongTap"));
+    // To get a LongTapUp event, we must kick APZC to flush its event queue. This
+    // would normally happen if we had a (Tab|RenderFrame)(Parent|Child)
+    // mechanism.
+    check.Call("preHandleLongTapUp");
+    apzc->ContentReceivedTouch(false);
+    check.Call("postHandleLongTapUp");
+
+    apzc->AssertStateIsReset();
   }
 
-  mcc->CheckHasDelayedTask();
+  void DoLongPressPreventDefaultTest(uint32_t aBehavior) {
+    MakeApzcUnzoomable();
+
+    EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(0);
+    EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0);
 
-  // Manually invoke the longpress while the touch is currently down.
-  check.Call("preHandleLongTap");
-  mcc->RunDelayedTask();
-  check.Call("postHandleLongTap");
+    int touchX = 10,
+        touchStartY = 10,
+        touchEndY = 50;
+
+    int time = 0;
+    nsEventStatus status = ApzcDown(apzc, touchX, touchStartY, time);
+    EXPECT_EQ(nsEventStatus_eConsumeNoDefault, status);
 
-  // Destroy pending MAX_TAP timeout task
-  mcc->DestroyOldestTask();
-  // Clear the waiting-for-content timeout task, then send the signal that
-  // content has handled this long tap. This takes the place of the
-  // "contextmenu" event.
-  mcc->ClearDelayedTask();
-  apzc->ContentReceivedTouch(true);
+    // SetAllowedTouchBehavior() must be called after sending touch-start.
+    nsTArray<uint32_t> allowedTouchBehaviors;
+    allowedTouchBehaviors.AppendElement(aBehavior);
+    apzc->SetAllowedTouchBehavior(allowedTouchBehaviors);
+
+    MockFunction<void(std::string checkPointName)> check;
+
+    {
+      InSequence s;
 
-  time += 1000;
+      EXPECT_CALL(check, Call("preHandleLongTap"));
+      EXPECT_CALL(*mcc, HandleLongTap(CSSPoint(touchX, touchStartY), 0, apzc->GetGuid())).Times(1);
+      EXPECT_CALL(check, Call("postHandleLongTap"));
+    }
+
+    mcc->CheckHasDelayedTask();
+
+    // Manually invoke the longpress while the touch is currently down.
+    check.Call("preHandleLongTap");
+    mcc->RunDelayedTask();
+    check.Call("postHandleLongTap");
 
-  MultiTouchInput mti =
-    MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, time, TimeStamp(), 0);
-  mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(touchX, touchEndY), ScreenSize(0, 0), 0, 0));
-  status = apzc->ReceiveInputEvent(mti);
-  EXPECT_EQ(nsEventStatus_eIgnore, status);
+    // Destroy pending MAX_TAP timeout task
+    mcc->DestroyOldestTask();
+    // Clear the waiting-for-content timeout task, then send the signal that
+    // content has handled this long tap. This takes the place of the
+    // "contextmenu" event.
+    mcc->ClearDelayedTask();
+    apzc->ContentReceivedTouch(true);
 
-  EXPECT_CALL(*mcc, HandleLongTapUp(CSSPoint(touchX, touchEndY), 0, apzc->GetGuid())).Times(0);
-  status = ApzcUp(apzc, touchX, touchEndY, time);
-  EXPECT_EQ(nsEventStatus_eIgnore, status);
+    time += 1000;
+
+    MultiTouchInput mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, time, TimeStamp(), 0);
+    mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(touchX, touchEndY), ScreenSize(0, 0), 0, 0));
+    status = apzc->ReceiveInputEvent(mti);
+    EXPECT_EQ(nsEventStatus_eIgnore, status);
 
-  // Flush the event queue. Once the "contextmenu" event is handled, any touch
-  // events that come from the same series of start->n*move->end events should
-  // be discarded, even if only the "contextmenu" event is preventDefaulted.
-  apzc->ContentReceivedTouch(false);
+    EXPECT_CALL(*mcc, HandleLongTapUp(CSSPoint(touchX, touchEndY), 0, apzc->GetGuid())).Times(0);
+    status = ApzcUp(apzc, touchX, touchEndY, time);
+    EXPECT_EQ(nsEventStatus_eIgnore, status);
 
-  ScreenPoint pointOut;
-  ViewTransform viewTransformOut;
-  apzc->SampleContentTransformForFrame(testStartTime, &viewTransformOut, pointOut);
+    // Flush the event queue. Once the "contextmenu" event is handled, any touch
+    // events that come from the same series of start->n*move->end events should
+    // be discarded, even if only the "contextmenu" event is preventDefaulted.
+    apzc->ContentReceivedTouch(false);
 
-  EXPECT_EQ(ScreenPoint(), pointOut);
-  EXPECT_EQ(ViewTransform(), viewTransformOut);
+    ScreenPoint pointOut;
+    ViewTransform viewTransformOut;
+    apzc->SampleContentTransformForFrame(testStartTime, &viewTransformOut, pointOut);
 
-  apzc->AssertStateIsReset();
-  apzc->Destroy();
-}
+    EXPECT_EQ(ScreenPoint(), pointOut);
+    EXPECT_EQ(ViewTransform(), viewTransformOut);
 
-TEST_F(AsyncPanZoomControllerTester, LongPress) {
+    apzc->AssertStateIsReset();
+  }
+};
+
+TEST_F(APZCLongPressTester, LongPress) {
   DoLongPressTest(mozilla::layers::AllowedTouchBehavior::NONE);
 }
 
-TEST_F(TouchActionEnabledTester, LongPressWithTouchAction) {
+TEST_F(APZCLongPressTester, LongPressWithTouchAction) {
+  SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
   DoLongPressTest(mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN
                   | mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN
                   | mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM);
 }
 
-TEST_F(AsyncPanZoomControllerTester, LongPressPreventDefault) {
+TEST_F(APZCLongPressTester, LongPressPreventDefault) {
   DoLongPressPreventDefaultTest(mozilla::layers::AllowedTouchBehavior::NONE);
 }
 
-TEST_F(TouchActionEnabledTester, LongPressPreventDefaultWithTouchAction) {
+TEST_F(APZCLongPressTester, LongPressPreventDefaultWithTouchAction) {
+  SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
   DoLongPressPreventDefaultTest(mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN
                                 | mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN
                                 | mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM);
 }
 
 // Layer tree for HitTesting1
 static already_AddRefed<mozilla::layers::Layer>
 CreateTestLayerTree1(nsRefPtr<LayerManager>& aLayerManager, nsTArray<nsRefPtr<Layer> >& aLayers) {
--- a/gfx/thebes/gfxFontInfoLoader.cpp
+++ b/gfx/thebes/gfxFontInfoLoader.cpp
@@ -24,31 +24,31 @@ FontInfoData::Load()
     }
 
     mLoadTime = TimeStamp::Now() - start;
 }
 
 class FontInfoLoadCompleteEvent : public nsRunnable {
     virtual ~FontInfoLoadCompleteEvent() {}
 
-    NS_DECL_THREADSAFE_ISUPPORTS
+    NS_DECL_ISUPPORTS_INHERITED
 
     FontInfoLoadCompleteEvent(FontInfoData *aFontInfo) :
         mFontInfo(aFontInfo)
     {}
 
     NS_IMETHOD Run();
 
     nsRefPtr<FontInfoData> mFontInfo;
 };
 
 class AsyncFontInfoLoader : public nsRunnable {
     virtual ~AsyncFontInfoLoader() {}
 
-    NS_DECL_THREADSAFE_ISUPPORTS
+    NS_DECL_ISUPPORTS_INHERITED
 
     AsyncFontInfoLoader(FontInfoData *aFontInfo) :
         mFontInfo(aFontInfo)
     {
         mCompleteEvent = new FontInfoLoadCompleteEvent(aFontInfo);
     }
 
     NS_IMETHOD Run();
@@ -65,33 +65,33 @@ FontInfoLoadCompleteEvent::Run()
         static_cast<gfxFontInfoLoader*>(gfxPlatformFontList::PlatformFontList());
 
     loader->FinalizeLoader(mFontInfo);
 
     mFontInfo = nullptr;
     return NS_OK;
 }
 
-NS_IMPL_ISUPPORTS(FontInfoLoadCompleteEvent, nsIRunnable);
+NS_IMPL_ISUPPORTS_INHERITED0(FontInfoLoadCompleteEvent, nsRunnable);
 
 // runs on separate thread
 nsresult
 AsyncFontInfoLoader::Run()
 {
     // load platform-specific font info
     mFontInfo->Load();
 
     // post a completion event that transfer the data to the fontlist
     NS_DispatchToMainThread(mCompleteEvent);
     mFontInfo = nullptr;
 
     return NS_OK;
 }
 
-NS_IMPL_ISUPPORTS(AsyncFontInfoLoader, nsIRunnable);
+NS_IMPL_ISUPPORTS_INHERITED0(AsyncFontInfoLoader, nsRunnable);
 
 NS_IMPL_ISUPPORTS(gfxFontInfoLoader::ShutdownObserver, nsIObserver)
 
 NS_IMETHODIMP
 gfxFontInfoLoader::ShutdownObserver::Observe(nsISupports *aSubject,
                                              const char *aTopic,
                                              const char16_t *someData)
 {
--- a/gfx/thebes/gfxWindowsPlatform.cpp
+++ b/gfx/thebes/gfxWindowsPlatform.cpp
@@ -110,16 +110,18 @@ public:
 };
 
 NS_IMPL_ISUPPORTS(GfxD2DSurfaceReporter, nsIMemoryReporter)
 
 #endif
 
 class GfxD2DVramReporter MOZ_FINAL : public nsIMemoryReporter
 {
+    ~GfxD2DVramReporter() {}
+
 public:
     NS_DECL_ISUPPORTS
 
     NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
                               nsISupports* aData, bool aAnonymize)
     {
         nsresult rv;
 
--- a/hal/windows/WindowsGamepad.cpp
+++ b/hal/windows/WindowsGamepad.cpp
@@ -276,32 +276,32 @@ public:
     if (mObserving) {
       nsCOMPtr<nsIObserverService> observerService =
         mozilla::services::GetObserverService();
       observerService->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID);
       mObserving = false;
     }
   }
 
-  virtual ~Observer()
-  {
-    Stop();
-  }
-
   void SetDeviceChangeTimer()
   {
     // Set stable timer, since we will get multiple devices-changed
     // notifications at once
     if (mTimer) {
       mTimer->Cancel();
       mTimer->Init(this, kDevicesChangedStableDelay, nsITimer::TYPE_ONE_SHOT);
     }
   }
 
 private:
+  virtual ~Observer()
+  {
+    Stop();
+  }
+
   // Gamepad service owns us, we just hold a reference back to it.
   WindowsGamepadService& mSvc;
   nsCOMPtr<nsITimer> mTimer;
   bool mObserving;
 };
 
 NS_IMPL_ISUPPORTS(Observer, nsIObserver);
 
--- a/image/src/ClippedImage.cpp
+++ b/image/src/ClippedImage.cpp
@@ -151,17 +151,17 @@ ClippedImage::ShouldClip()
       mShouldClip.construct(false);
     }
   }
 
   MOZ_ASSERT(!mShouldClip.empty(), "Should have computed a result");
   return mShouldClip.ref();
 }
 
-NS_IMPL_ISUPPORTS(ClippedImage, imgIContainer)
+NS_IMPL_ISUPPORTS_INHERITED0(ClippedImage, ImageWrapper)
 
 nsIntRect
 ClippedImage::FrameRect(uint32_t aWhichFrame)
 {
   if (!ShouldClip()) {
     return InnerImage()->FrameRect(aWhichFrame);
   }
 
--- a/image/src/ClippedImage.h
+++ b/image/src/ClippedImage.h
@@ -24,17 +24,17 @@ class DrawSingleTileCallback;
  * XXX(seth): There a known (performance, not correctness) issue with
  * GetImageContainer. See the comments for that method for more information.
  */
 class ClippedImage : public ImageWrapper
 {
   typedef mozilla::gfx::SourceSurface SourceSurface;
 
 public:
-  NS_DECL_ISUPPORTS
+  NS_DECL_ISUPPORTS_INHERITED
 
   virtual nsIntRect FrameRect(uint32_t aWhichFrame) MOZ_OVERRIDE;
 
   NS_IMETHOD GetWidth(int32_t* aWidth) MOZ_OVERRIDE;
   NS_IMETHOD GetHeight(int32_t* aHeight) MOZ_OVERRIDE;
   NS_IMETHOD GetIntrinsicSize(nsSize* aSize) MOZ_OVERRIDE;
   NS_IMETHOD GetIntrinsicRatio(nsSize* aRatio) MOZ_OVERRIDE;
   NS_IMETHOD_(mozilla::TemporaryRef<SourceSurface>)
--- a/image/src/FrozenImage.cpp
+++ b/image/src/FrozenImage.cpp
@@ -5,17 +5,17 @@
 
 #include "FrozenImage.h"
 
 using namespace mozilla::gfx;
 
 namespace mozilla {
 namespace image {
 
-NS_IMPL_ISUPPORTS(FrozenImage, imgIContainer)
+NS_IMPL_ISUPPORTS_INHERITED0(FrozenImage, ImageWrapper)
 
 nsIntRect
 FrozenImage::FrameRect(uint32_t /* aWhichFrame - ignored */)
 {
   return InnerImage()->FrameRect(FRAME_FIRST);
 }
 
 void
--- a/image/src/FrozenImage.h
+++ b/image/src/FrozenImage.h
@@ -24,17 +24,17 @@ namespace image {
  * XXX(seth): There a known (performance, not correctness) issue with
  * GetImageContainer. See the comments for that method for more information.
  */
 class FrozenImage : public ImageWrapper
 {
   typedef mozilla::gfx::SourceSurface SourceSurface;
 
 public:
-  NS_DECL_ISUPPORTS
+  NS_DECL_ISUPPORTS_INHERITED
 
   virtual nsIntRect FrameRect(uint32_t aWhichFrame) MOZ_OVERRIDE;
   virtual void IncrementAnimationConsumers() MOZ_OVERRIDE;
   virtual void DecrementAnimationConsumers() MOZ_OVERRIDE;
 
   NS_IMETHOD GetAnimated(bool* aAnimated) MOZ_OVERRIDE;
   NS_IMETHOD_(TemporaryRef<SourceSurface>)
     GetFrame(uint32_t aWhichFrame, uint32_t aFlags) MOZ_OVERRIDE;
--- a/image/src/OrientedImage.cpp
+++ b/image/src/OrientedImage.cpp
@@ -15,17 +15,17 @@ using namespace mozilla::gfx;
 
 using std::swap;
 using mozilla::layers::LayerManager;
 using mozilla::layers::ImageContainer;
 
 namespace mozilla {
 namespace image {
 
-NS_IMPL_ISUPPORTS(OrientedImage, imgIContainer)
+NS_IMPL_ISUPPORTS_INHERITED0(OrientedImage, ImageWrapper)
 
 nsIntRect
 OrientedImage::FrameRect(uint32_t aWhichFrame)
 {
   if (mOrientation.SwapsWidthAndHeight()) {
     nsIntRect innerRect = InnerImage()->FrameRect(aWhichFrame);
     return nsIntRect(innerRect.x, innerRect.y, innerRect.height, innerRect.width);
   } else {
--- a/image/src/OrientedImage.h
+++ b/image/src/OrientedImage.h
@@ -21,17 +21,17 @@ namespace image {
  * XXX(seth): There a known (performance, not correctness) issue with
  * GetImageContainer. See the comments for that method for more information.
  */
 class OrientedImage : public ImageWrapper
 {
   typedef mozilla::gfx::SourceSurface SourceSurface;
 
 public:
-  NS_DECL_ISUPPORTS
+  NS_DECL_ISUPPORTS_INHERITED
 
   virtual nsIntRect FrameRect(uint32_t aWhichFrame) MOZ_OVERRIDE;
 
   NS_IMETHOD GetWidth(int32_t* aWidth) MOZ_OVERRIDE;
   NS_IMETHOD GetHeight(int32_t* aHeight) MOZ_OVERRIDE;
   NS_IMETHOD GetIntrinsicSize(nsSize* aSize) MOZ_OVERRIDE;
   NS_IMETHOD GetIntrinsicRatio(nsSize* aRatio) MOZ_OVERRIDE;
   NS_IMETHOD_(mozilla::TemporaryRef<SourceSurface>)
--- a/intl/lwbrk/src/nsSemanticUnitScanner.cpp
+++ b/intl/lwbrk/src/nsSemanticUnitScanner.cpp
@@ -1,16 +1,16 @@
 /* -*- 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 "nsSemanticUnitScanner.h"
 
-NS_IMPL_ISUPPORTS(nsSemanticUnitScanner, nsISemanticUnitScanner)
+NS_IMPL_ISUPPORTS_INHERITED(nsSemanticUnitScanner, nsSampleWordBreaker, nsISemanticUnitScanner)
 
 nsSemanticUnitScanner::nsSemanticUnitScanner() : nsSampleWordBreaker()
 {
   /* member initializers and constructor code */
 }
 
 nsSemanticUnitScanner::~nsSemanticUnitScanner()
 {
--- a/intl/lwbrk/src/nsSemanticUnitScanner.h
+++ b/intl/lwbrk/src/nsSemanticUnitScanner.h
@@ -9,17 +9,17 @@
 #include "nsSampleWordBreaker.h"
 #include "nsISemanticUnitScanner.h"
 
 
 class nsSemanticUnitScanner : public nsISemanticUnitScanner
                             , public nsSampleWordBreaker
 {
 public:
-  NS_DECL_ISUPPORTS
+  NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSISEMANTICUNITSCANNER
 
   nsSemanticUnitScanner();
 
 private:
   virtual ~nsSemanticUnitScanner();
   /* additional members */
 };
--- a/js/public/RootingAPI.h
+++ b/js/public/RootingAPI.h
@@ -1013,22 +1013,22 @@ template <typename T> class MaybeRooted<
 
 template <typename T> class MaybeRooted<T, NoGC>
 {
   public:
     typedef T HandleType;
     typedef FakeRooted<T> RootType;
     typedef FakeMutableHandle<T> MutableHandleType;
 
-    static inline JS::Handle<T> toHandle(HandleType v) {
-        MOZ_ASSUME_UNREACHABLE("Bad conversion");
+    static JS::Handle<T> toHandle(HandleType v) {
+        MOZ_CRASH("Bad conversion");
     }
 
-    static inline JS::MutableHandle<T> toMutableHandle(MutableHandleType v) {
-        MOZ_ASSUME_UNREACHABLE("Bad conversion");
+    static JS::MutableHandle<T> toMutableHandle(MutableHandleType v) {
+        MOZ_CRASH("Bad conversion");
     }
 };
 
 } /* namespace js */
 
 namespace JS {
 
 template <typename T> template <typename S>
--- a/js/src/builtin/Intl.cpp
+++ b/js/src/builtin/Intl.cpp
@@ -73,37 +73,37 @@ using icu::NumberingSystem;
  * and return error codes. Signatures adapted from ICU header files locid.h,
  * numsys.h, ucal.h, ucol.h, udat.h, udatpg.h, uenum.h, unum.h; see the ICU
  * directory for license.
  */
 
 static int32_t
 u_strlen(const UChar *s)
 {
-    MOZ_ASSUME_UNREACHABLE("u_strlen: Intl API disabled");
+    MOZ_CRASH("u_strlen: Intl API disabled");
 }
 
 struct UEnumeration;
 
 static int32_t
 uenum_count(UEnumeration *en, UErrorCode *status)
 {
-    MOZ_ASSUME_UNREACHABLE("uenum_count: Intl API disabled");
+    MOZ_CRASH("uenum_count: Intl API disabled");
 }
 
 static const char *
 uenum_next(UEnumeration *en, int32_t *resultLength, UErrorCode *status)
 {
-    MOZ_ASSUME_UNREACHABLE("uenum_next: Intl API disabled");
+    MOZ_CRASH("uenum_next: Intl API disabled");
 }
 
 static void
 uenum_close(UEnumeration *en)
 {
-    MOZ_ASSUME_UNREACHABLE("uenum_close: Intl API disabled");
+    MOZ_CRASH("uenum_close: Intl API disabled");
 }
 
 struct UCollator;
 
 enum UColAttribute {
      UCOL_ALTERNATE_HANDLING,
      UCOL_CASE_FIRST,
      UCOL_CASE_LEVEL,
@@ -128,55 +128,55 @@ enum UCollationResult {
   UCOL_EQUAL = 0,
   UCOL_GREATER = 1,
   UCOL_LESS = -1
 };
 
 static int32_t
 ucol_countAvailable(void)
 {
-    MOZ_ASSUME_UNREACHABLE("ucol_countAvailable: Intl API disabled");
+    MOZ_CRASH("ucol_countAvailable: Intl API disabled");
 }
 
 static const char *
 ucol_getAvailable(int32_t localeIndex)
 {
-    MOZ_ASSUME_UNREACHABLE("ucol_getAvailable: Intl API disabled");
+    MOZ_CRASH("ucol_getAvailable: Intl API disabled");
 }
 
 static UCollator *
 ucol_open(const char *loc, UErrorCode *status)
 {
-    MOZ_ASSUME_UNREACHABLE("ucol_open: Intl API disabled");
+    MOZ_CRASH("ucol_open: Intl API disabled");
 }
 
 static void
 ucol_setAttribute(UCollator *coll, UColAttribute attr, UColAttributeValue value, UErrorCode *status)
 {
-    MOZ_ASSUME_UNREACHABLE("ucol_setAttribute: Intl API disabled");
+    MOZ_CRASH("ucol_setAttribute: Intl API disabled");
 }
 
 static UCollationResult
 ucol_strcoll(const UCollator *coll, const UChar *source, int32_t sourceLength,
              const UChar *target, int32_t targetLength)
 {
-    MOZ_ASSUME_UNREACHABLE("ucol_strcoll: Intl API disabled");
+    MOZ_CRASH("ucol_strcoll: Intl API disabled");
 }
 
 static void
 ucol_close(UCollator *coll)
 {
-    MOZ_ASSUME_UNREACHABLE("ucol_close: Intl API disabled");
+    MOZ_CRASH("ucol_close: Intl API disabled");
 }
 
 static UEnumeration *
 ucol_getKeywordValuesForLocale(const char *key, const char *locale, UBool commonlyUsed,
                                UErrorCode *status)
 {
-    MOZ_ASSUME_UNREACHABLE("ucol_getKeywordValuesForLocale: Intl API disabled");
+    MOZ_CRASH("ucol_getKeywordValuesForLocale: Intl API disabled");
 }
 
 struct UParseError;
 struct UFieldPosition;
 typedef void *UNumberFormat;
 
 enum UNumberFormatStyle {
     UNUM_DECIMAL = 1,
@@ -203,195 +203,195 @@ enum UNumberFormatAttribute {
 
 enum UNumberFormatTextAttribute {
   UNUM_CURRENCY_CODE,
 };
 
 static int32_t
 unum_countAvailable(void)
 {
-    MOZ_ASSUME_UNREACHABLE("unum_countAvailable: Intl API disabled");
+    MOZ_CRASH("unum_countAvailable: Intl API disabled");
 }
 
 static const char *
 unum_getAvailable(int32_t localeIndex)
 {
-    MOZ_ASSUME_UNREACHABLE("unum_getAvailable: Intl API disabled");
+    MOZ_CRASH("unum_getAvailable: Intl API disabled");
 }
 
 static UNumberFormat *
 unum_open(UNumberFormatStyle style, const UChar *pattern, int32_t patternLength,
           const char *locale, UParseError *parseErr, UErrorCode *status)
 {
-    MOZ_ASSUME_UNREACHABLE("unum_open: Intl API disabled");
+    MOZ_CRASH("unum_open: Intl API disabled");
 }
 
 static void
 unum_setAttribute(UNumberFormat *fmt, UNumberFormatAttribute  attr, int32_t newValue)
 {
-    MOZ_ASSUME_UNREACHABLE("unum_setAttribute: Intl API disabled");
+    MOZ_CRASH("unum_setAttribute: Intl API disabled");
 }
 
 static int32_t
 unum_formatDouble(const UNumberFormat *fmt, double number, UChar *result,
                   int32_t resultLength, UFieldPosition *pos, UErrorCode *status)
 {
-    MOZ_ASSUME_UNREACHABLE("unum_formatDouble: Intl API disabled");
+    MOZ_CRASH("unum_formatDouble: Intl API disabled");
 }
 
 static void
 unum_close(UNumberFormat *fmt)
 {
-    MOZ_ASSUME_UNREACHABLE("unum_close: Intl API disabled");
+    MOZ_CRASH("unum_close: Intl API disabled");
 }
 
 static void
 unum_setTextAttribute(UNumberFormat *fmt, UNumberFormatTextAttribute tag, const UChar *newValue,
                       int32_t newValueLength, UErrorCode *status)
 {
-    MOZ_ASSUME_UNREACHABLE("unum_setTextAttribute: Intl API disabled");
+    MOZ_CRASH("unum_setTextAttribute: Intl API disabled");
 }
 
 class Locale {
   public:
     Locale(const char *language, const char *country = 0, const char *variant = 0,
            const char *keywordsAndValues = 0);
 };
 
 Locale::Locale(const char *language, const char *country, const char *variant,
                const char *keywordsAndValues)
 {
-    MOZ_ASSUME_UNREACHABLE("Locale::Locale: Intl API disabled");
+    MOZ_CRASH("Locale::Locale: Intl API disabled");
 }
 
 class NumberingSystem {
   public:
     static NumberingSystem *createInstance(const Locale &inLocale, UErrorCode &status);
     const char *getName();
 };
 
 NumberingSystem *
 NumberingSystem::createInstance(const Locale &inLocale, UErrorCode &status)
 {
-    MOZ_ASSUME_UNREACHABLE("NumberingSystem::createInstance: Intl API disabled");
+    MOZ_CRASH("NumberingSystem::createInstance: Intl API disabled");
 }
 
 const char *
 NumberingSystem::getName()
 {
-    MOZ_ASSUME_UNREACHABLE("NumberingSystem::getName: Intl API disabled");
+    MOZ_CRASH("NumberingSystem::getName: Intl API disabled");
 }
 
 typedef void *UCalendar;
 
 enum UCalendarType {
   UCAL_TRADITIONAL,
   UCAL_DEFAULT = UCAL_TRADITIONAL,
   UCAL_GREGORIAN
 };
 
 static UCalendar *
 ucal_open(const UChar *zoneID, int32_t len, const char *locale,
           UCalendarType type, UErrorCode *status)
 {
-    MOZ_ASSUME_UNREACHABLE("ucal_open: Intl API disabled");
+    MOZ_CRASH("ucal_open: Intl API disabled");
 }
 
 static const char *
 ucal_getType(const UCalendar *cal, UErrorCode *status)
 {
-    MOZ_ASSUME_UNREACHABLE("ucal_getType: Intl API disabled");
+    MOZ_CRASH("ucal_getType: Intl API disabled");
 }
 
 static UEnumeration *
 ucal_getKeywordValuesForLocale(const char *key, const char *locale,
                                UBool commonlyUsed, UErrorCode *status)
 {
-    MOZ_ASSUME_UNREACHABLE("ucal_getKeywordValuesForLocale: Intl API disabled");
+    MOZ_CRASH("ucal_getKeywordValuesForLocale: Intl API disabled");
 }
 
 static void
 ucal_close(UCalendar *cal)
 {
-    MOZ_ASSUME_UNREACHABLE("ucal_close: Intl API disabled");
+    MOZ_CRASH("ucal_close: Intl API disabled");
 }
 
 typedef void *UDateTimePatternGenerator;
 
 static UDateTimePatternGenerator *
 udatpg_open(const char *locale, UErrorCode *pErrorCode)
 {
-    MOZ_ASSUME_UNREACHABLE("udatpg_open: Intl API disabled");
+    MOZ_CRASH("udatpg_open: Intl API disabled");
 }
 
 static int32_t
 udatpg_getBestPattern(UDateTimePatternGenerator *dtpg, const UChar *skeleton,
                       int32_t length, UChar *bestPattern, int32_t capacity,
                       UErrorCode *pErrorCode)
 {
-    MOZ_ASSUME_UNREACHABLE("udatpg_getBestPattern: Intl API disabled");
+    MOZ_CRASH("udatpg_getBestPattern: Intl API disabled");
 }
 
 static void
 udatpg_close(UDateTimePatternGenerator *dtpg)
 {
-    MOZ_ASSUME_UNREACHABLE("udatpg_close: Intl API disabled");
+    MOZ_CRASH("udatpg_close: Intl API disabled");
 }
 
 typedef void *UCalendar;
 typedef void *UDateFormat;
 
 enum UDateFormatStyle {
     UDAT_PATTERN = -2,
     UDAT_IGNORE = UDAT_PATTERN
 };
 
 static int32_t
 udat_countAvailable(void)
 {
-    MOZ_ASSUME_UNREACHABLE("udat_countAvailable: Intl API disabled");
+    MOZ_CRASH("udat_countAvailable: Intl API disabled");
 }
 
 static const char *
 udat_getAvailable(int32_t localeIndex)
 {
-    MOZ_ASSUME_UNREACHABLE("udat_getAvailable: Intl API disabled");
+    MOZ_CRASH("udat_getAvailable: Intl API disabled");
 }
 
 static UDateFormat *
 udat_open(UDateFormatStyle timeStyle, UDateFormatStyle dateStyle, const char *locale,
           const UChar *tzID, int32_t tzIDLength, const UChar *pattern,
           int32_t patternLength, UErrorCode *status)
 {
-    MOZ_ASSUME_UNREACHABLE("udat_open: Intl API disabled");
+    MOZ_CRASH("udat_open: Intl API disabled");
 }
 
 static const UCalendar *
 udat_getCalendar(const UDateFormat *fmt)
 {
-    MOZ_ASSUME_UNREACHABLE("udat_getCalendar: Intl API disabled");
+    MOZ_CRASH("udat_getCalendar: Intl API disabled");
 }
 
 static void
 ucal_setGregorianChange(UCalendar *cal, UDate date, UErrorCode *pErrorCode)
 {
-    MOZ_ASSUME_UNREACHABLE("ucal_setGregorianChange: Intl API disabled");
+    MOZ_CRASH("ucal_setGregorianChange: Intl API disabled");
 }
 
 static int32_t
 udat_format(const UDateFormat *format, UDate dateToFormat, UChar *result,
             int32_t resultLength, UFieldPosition *position, UErrorCode *status)
 {
-    MOZ_ASSUME_UNREACHABLE("udat_format: Intl API disabled");
+    MOZ_CRASH("udat_format: Intl API disabled");
 }
 
 static void
 udat_close(UDateFormat *format)
 {
-    MOZ_ASSUME_UNREACHABLE("udat_close: Intl API disabled");
+    MOZ_CRASH("udat_close: Intl API disabled");
 }
 
 #endif
 
 
 /******************** Common to Intl constructors ********************/
 
 static bool
@@ -979,17 +979,17 @@ intl_CompareStrings(JSContext *cx, UColl
     UCollationResult uresult = ucol_strcoll(coll,
                                             JSCharToUChar(chars1.start().get()), chars1.length(),
                                             JSCharToUChar(chars2.start().get()), chars2.length());
     int32_t res;
     switch (uresult) {
         case UCOL_LESS: res = -1; break;
         case UCOL_EQUAL: res = 0; break;
         case UCOL_GREATER: res = 1; break;
-        default: MOZ_ASSUME_UNREACHABLE("ucol_strcoll returned bad UCollationResult");
+        default: MOZ_CRASH("ucol_strcoll returned bad UCollationResult");
     }
     result.setInt32(res);
     return true;
 }
 
 bool
 js::intl_CompareStrings(JSContext *cx, unsigned argc, Value *vp)
 {
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -583,17 +583,17 @@ GCState(JSContext *cx, unsigned argc, js
     gc::State globalState = cx->runtime()->gc.state();
     if (globalState == gc::NO_INCREMENTAL)
         state = "none";
     else if (globalState == gc::MARK)
         state = "mark";
     else if (globalState == gc::SWEEP)
         state = "sweep";
     else
-        MOZ_ASSUME_UNREACHABLE("Unobserveable global GC state");
+        MOZ_CRASH("Unobserveable global GC state");
 
     JSString *str = JS_NewStringCopyZ(cx, state);
     if (!str)
         return false;
     args.rval().setString(str);
     return true;
 }
 
--- a/js/src/builtin/TypedObject.cpp
+++ b/js/src/builtin/TypedObject.cpp
@@ -270,17 +270,17 @@ ScalarTypeDescr::typeName(Type type)
     switch (type) {
 #define NUMERIC_TYPE_TO_STRING(constant_, type_, name_) \
         case constant_: return #name_;
         JS_FOR_EACH_SCALAR_TYPE_REPR(NUMERIC_TYPE_TO_STRING)
 #undef NUMERIC_TYPE_TO_STRING
       case Scalar::TypeMax:
         MOZ_CRASH();
     }
-    MOZ_ASSUME_UNREACHABLE("Invalid type");
+    MOZ_CRASH("Invalid type");
 }
 
 bool
 ScalarTypeDescr::call(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     if (args.length() < 1) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
@@ -371,17 +371,17 @@ ReferenceTypeDescr::alignment(Type t)
 ReferenceTypeDescr::typeName(Type type)
 {
     switch (type) {
 #define NUMERIC_TYPE_TO_STRING(constant_, type_, name_) \
         case constant_: return #name_;
         JS_FOR_EACH_REFERENCE_TYPE_REPR(NUMERIC_TYPE_TO_STRING)
 #undef NUMERIC_TYPE_TO_STRING
     }
-    MOZ_ASSUME_UNREACHABLE("Invalid type");
+    MOZ_CRASH("Invalid type");
 }
 
 bool
 js::ReferenceTypeDescr::call(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     JS_ASSERT(args.callee().is<ReferenceTypeDescr>());
@@ -413,17 +413,17 @@ js::ReferenceTypeDescr::call(JSContext *
         RootedString obj(cx, ToString<CanGC>(cx, args[0]));
         if (!obj)
             return false;
         args.rval().setString(&*obj);
         return true;
       }
     }
 
-    MOZ_ASSUME_UNREACHABLE("Unhandled Reference type");
+    MOZ_CRASH("Unhandled Reference type");
 }
 
 /***************************************************************************
  * X4 type objects
  *
  * Note: these are partially defined in SIMD.cpp
  */
 
@@ -1425,17 +1425,17 @@ JSObject *
 js_InitTypedObjectDummy(JSContext *cx, HandleObject obj)
 {
     /*
      * This function is entered into the jsprototypes.h table
      * as the initializer for `TypedObject`. It should not
      * be executed via the `standard_class_atoms` mechanism.
      */
 
-    MOZ_ASSUME_UNREACHABLE("shouldn't be initializing TypedObject via the JSProtoKey initializer mechanism");
+    MOZ_CRASH("shouldn't be initializing TypedObject via the JSProtoKey initializer mechanism");
 }
 
 /******************************************************************************
  * Typed objects
  */
 
 /*static*/ TypedObject *
 TypedObject::createUnattached(JSContext *cx,
@@ -1519,19 +1519,19 @@ TypedObjLengthFromType(TypeDescr &descr)
       case type::Struct:
       case type::X4:
         return 0;
 
       case type::SizedArray:
         return descr.as<SizedArrayTypeDescr>().length();
 
       case type::UnsizedArray:
-        MOZ_ASSUME_UNREACHABLE("TypedObjLengthFromType() invoked on unsized type");
+        MOZ_CRASH("TypedObjLengthFromType() invoked on unsized type");
     }
-    MOZ_ASSUME_UNREACHABLE("Invalid kind");
+    MOZ_CRASH("Invalid kind");
 }
 
 /*static*/ TypedObject *
 TypedObject::createDerived(JSContext *cx, HandleSizedTypeDescr type,
                            HandleTypedObject typedObj, int32_t offset)
 {
     JS_ASSERT(!typedObj->owner().isNeutered());
     JS_ASSERT(typedObj->typedMem() != NULL);
@@ -1598,17 +1598,17 @@ TypedObject::createZeroed(JSContext *cx,
 
         if (length)
             elementTypeRepr->initInstances(cx->runtime(), buffer->dataPointer(), length);
         obj->attach(*buffer, 0);
         return obj;
       }
     }
 
-    MOZ_ASSUME_UNREACHABLE("Bad TypeRepresentation Kind");
+    MOZ_CRASH("Bad TypeRepresentation Kind");
 }
 
 static bool
 ReportTypedObjTypeError(JSContext *cx,
                         const unsigned errorNumber,
                         HandleTypedObject obj)
 {
     // Serialize type string of obj
@@ -2271,17 +2271,17 @@ LengthForType(TypeDescr &descr)
 
       case type::SizedArray:
         return descr.as<SizedArrayTypeDescr>().length();
 
       case type::UnsizedArray:
         return 0;
     }
 
-    MOZ_ASSUME_UNREACHABLE("Invalid kind");
+    MOZ_CRASH("Invalid kind");
 }
 
 static bool
 CheckOffset(int32_t offset,
             int32_t size,
             int32_t alignment,
             int32_t bufferLength)
 {
@@ -3097,32 +3097,32 @@ visitReferences(SizedTypeDescr &descr,
             visitReferences(elementDescr, mem, visitor);
             mem += elementDescr.size();
         }
         return;
       }
 
       case type::UnsizedArray:
       {
-        MOZ_ASSUME_UNREACHABLE("Only Sized Type representations");
+        MOZ_CRASH("Only Sized Type representations");
       }
 
       case type::Struct:
       {
         StructTypeDescr &structDescr = descr.as<StructTypeDescr>();
         for (size_t i = 0; i < structDescr.fieldCount(); i++) {
             SizedTypeDescr &descr = structDescr.fieldDescr(i);
             size_t offset = structDescr.fieldOffset(i);
             visitReferences(descr, mem + offset, visitor);
         }
         return;
       }
     }
 
-    MOZ_ASSUME_UNREACHABLE("Invalid type repr kind");
+    MOZ_CRASH("Invalid type repr kind");
 }
 
 ///////////////////////////////////////////////////////////////////////////
 // Initializing instances
 
 namespace js {
 class MemoryInitVisitor {
     const JSRuntime *rt_;
@@ -3159,17 +3159,17 @@ js::MemoryInitVisitor::visitReference(Re
       {
         js::HeapPtrString *stringPtr =
             reinterpret_cast<js::HeapPtrString *>(mem);
         stringPtr->init(rt_->emptyString);
         return;
       }
     }
 
-    MOZ_ASSUME_UNREACHABLE("Invalid kind");
+    MOZ_CRASH("Invalid kind");
 }
 
 void
 SizedTypeDescr::initInstances(const JSRuntime *rt, uint8_t *mem, size_t length)
 {
     JS_ASSERT(length >= 1);
 
     MemoryInitVisitor visitor(rt);
@@ -3229,17 +3229,17 @@ js::MemoryTracingVisitor::visitReference
         js::HeapPtrString *stringPtr =
             reinterpret_cast<js::HeapPtrString *>(mem);
         if (*stringPtr)
             gc::MarkString(trace_, stringPtr, "reference-str");
         return;
       }
     }
 
-    MOZ_ASSUME_UNREACHABLE("Invalid kind");
+    MOZ_CRASH("Invalid kind");
 }
 
 void
 SizedTypeDescr::traceInstances(JSTracer *trace, uint8_t *mem, size_t length)
 {
     MemoryTracingVisitor visitor(trace);
 
     for (size_t i = 0; i < length; i++) {
--- a/js/src/builtin/TypedObject.h
+++ b/js/src/builtin/TypedObject.h
@@ -699,17 +699,17 @@ class TypedObject : public ArrayBufferVi
           case type::SizedArray:
             return typeDescr().as<SizedTypeDescr>().size();
 
           case type::UnsizedArray: {
             SizedTypeDescr &elementType = typeDescr().as<UnsizedArrayTypeDescr>().elementType();
             return elementType.size() * length();
           }
         }
-        MOZ_ASSUME_UNREACHABLE("unhandled typerepresentation kind");
+        MOZ_CRASH("unhandled typerepresentation kind");
     }
 
     uint8_t *typedMem(size_t offset) const {
         // It seems a bit surprising that one might request an offset
         // == size(), but it can happen when taking the "address of" a
         // 0-sized value. (In other words, we maintain the invariant
         // that `offset + size <= size()` -- this is always checked in
         // the caller's side.)
--- a/js/src/ctypes/CTypes.cpp
+++ b/js/src/ctypes/CTypes.cpp
@@ -2203,17 +2203,17 @@ ConvertToJS(JSContext* cx,
     JSObject* obj = CData::Create(cx, typeObj, parentObj, data, ownResult);
     if (!obj)
       return false;
 
     *result = OBJECT_TO_JSVAL(obj);
     break;
   }
   case TYPE_function:
-    MOZ_ASSUME_UNREACHABLE("cannot return a FunctionType");
+    MOZ_CRASH("cannot return a FunctionType");
   }
 
   return true;
 }
 
 // Determine if the contents of a typed array can be converted without
 // ambiguity to a C type. Elements of a Int8Array are converted to
 // ctypes.int8_t, UInt8Array to ctypes.uint8_t, etc.
@@ -2656,17 +2656,17 @@ ImplicitConvert(JSContext* cx,
       memcpy(buffer, intermediate.get(), structSize);
       break;
     }
 
     return TypeError(cx, "struct", val);
   }
   case TYPE_void_t:
   case TYPE_function:
-    MOZ_ASSUME_UNREACHABLE("invalid type");
+    MOZ_CRASH("invalid type");
   }
 
   return true;
 }
 
 // Convert jsval 'val' to a C binary representation of CType 'targetType',
 // storing the result in 'buffer'. This function is more forceful than
 // ImplicitConvert.
@@ -2725,17 +2725,17 @@ ExplicitConvert(JSContext* cx, HandleVal
   case TYPE_double:
   case TYPE_array:
   case TYPE_struct:
     // ImplicitConvert is sufficient. Re-throw the exception it generated.
     JS_SetPendingException(cx, ex);
     return false;
   case TYPE_void_t:
   case TYPE_function:
-    MOZ_ASSUME_UNREACHABLE("invalid type");
+    MOZ_CRASH("invalid type");
   }
   return true;
 }
 
 // Given a CType 'typeObj', generate a string describing the C type declaration
 // corresponding to 'typeObj'. For instance, the CType constructed from
 // 'ctypes.int32_t.ptr.array(4).ptr.ptr' will result in the type string
 // 'int32_t*(**)[4]'.
@@ -2897,17 +2897,17 @@ BuildTypeSource(JSContext* cx,
       break;
     case ABI_STDCALL:
       AppendString(result, "ctypes.stdcall_abi, ");
       break;
     case ABI_WINAPI:
       AppendString(result, "ctypes.winapi_abi, ");
       break;
     case INVALID_ABI:
-      MOZ_ASSUME_UNREACHABLE("invalid abi");
+      MOZ_CRASH("invalid abi");
     }
 
     // Recursively build the source string describing the function return and
     // argument types.
     BuildTypeSource(cx, fninfo->mReturnType, true, result);
 
     if (fninfo->mArgTypes.length() > 0) {
       AppendString(result, ", [");
@@ -3142,17 +3142,17 @@ BuildDataSource(JSContext* cx,
     }
 
     if (isImplicit)
       AppendString(result, "}");
 
     break;
   }
   case TYPE_void_t:
-    MOZ_ASSUME_UNREACHABLE("invalid type");
+    MOZ_CRASH("invalid type");
   }
 
   return true;
 }
 
 /*******************************************************************************
 ** JSAPI callback function implementations
 *******************************************************************************/
@@ -3599,17 +3599,17 @@ CType::GetFFIType(JSContext* cx, JSObjec
     result = ArrayType::BuildFFIType(cx, obj);
     break;
 
   case TYPE_struct:
     result = StructType::BuildFFIType(cx, obj);
     break;
 
   default:
-    MOZ_ASSUME_UNREACHABLE("simple types must have an ffi_type");
+    MOZ_CRASH("simple types must have an ffi_type");
   }
 
   if (!result)
     return nullptr;
   JS_SetReservedSlot(obj, SLOT_FFITYPE, PRIVATE_TO_JSVAL(result.get()));
   return result.forget();
 }
 
@@ -4746,18 +4746,22 @@ AddFieldToArray(JSContext* cx,
   RootedObject typeObj(cx, typeObj_);
   Rooted<JSFlatString*> name(cx, name_);
   RootedObject fieldObj(cx, JS_NewObject(cx, nullptr, NullPtr(), NullPtr()));
   if (!fieldObj)
     return false;
 
   *element = OBJECT_TO_JSVAL(fieldObj);
 
+  AutoStableStringChars nameChars(cx);
+  if (!nameChars.initTwoByte(cx, name))
+      return false;
+
   if (!JS_DefineUCProperty(cx, fieldObj,
-         name->chars(), name->length(),
+         nameChars.twoByteChars(), name->length(),
          typeObj,
          JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT))
     return false;
 
   return JS_FreezeObject(cx, fieldObj);
 }
 
 bool
@@ -5546,17 +5550,17 @@ FunctionType::BuildSymbolName(JSString* 
     // On Win64, stdcall is an alias to the default ABI for compatibility, so no
     // mangling is done.
     AppendString(result, name);
 #endif
     break;
   }
 
   case INVALID_ABI:
-    MOZ_ASSUME_UNREACHABLE("invalid abi");
+    MOZ_CRASH("invalid abi");
   }
 }
 
 static FunctionInfo*
 NewFunctionInfo(JSContext* cx,
                 jsval abiType,
                 jsval returnType,
                 jsval* argTypes,
@@ -6829,17 +6833,17 @@ CDataFinalizer::Methods::ToString(JSCont
   if (!JS_GetPrivate(objThis)) {
     // Pre-check whether CDataFinalizer::GetValue can fail
     // to avoid reporting an error when not appropriate.
     strMessage = JS_NewStringCopyZ(cx, "[CDataFinalizer - empty]");
     if (!strMessage) {
       return false;
     }
   } else if (!CDataFinalizer::GetValue(cx, objThis, value.address())) {
-    MOZ_ASSUME_UNREACHABLE("Could not convert an empty CDataFinalizer");
+    MOZ_CRASH("Could not convert an empty CDataFinalizer");
   } else {
     strMessage = ToString(cx, value);
     if (!strMessage) {
       return false;
     }
   }
   args.rval().setString(strMessage);
   return true;
@@ -7040,17 +7044,17 @@ CDataFinalizer::Construct(JSContext* cx,
   // of the function, which may be less precise.
   JSObject *objBestArgType = objArgType;
   if (valData.isObject()) {
     JSObject *objData = &valData.toObject();
     if (CData::IsCData(objData)) {
       objBestArgType = CData::GetCType(objData);
       size_t sizeBestArg;
       if (!CType::GetSafeSize(objBestArgType, &sizeBestArg)) {
-        MOZ_ASSUME_UNREACHABLE("object with unknown size");
+        MOZ_CRASH("object with unknown size");
       }
       if (sizeBestArg != sizeArg) {
         return TypeError(cx, "(an object with the same size as that expected by the C finalization function)", valData);
       }
     }
   }
 
   // Used by GetCType
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -888,17 +888,17 @@ EnterNestedScope(ExclusiveContext *cx, B
         break;
       }
       case STMT_WITH:
         JS_ASSERT(scopeObj->is<StaticWithObject>());
         if (!EmitInternedObjectOp(cx, scopeObjectIndex, JSOP_ENTERWITH, bce))
             return false;
         break;
       default:
-        MOZ_ASSUME_UNREACHABLE();
+        MOZ_CRASH("Unexpected scope statement");
     }
 
     uint32_t parent = BlockScopeNote::NoBlockScopeIndex;
     if (StmtInfoBCE *stmt = bce->topScopeStmt) {
         for (; stmt->staticScope != bce->staticScope; stmt = stmt->down) {}
         parent = stmt->blockScopeIndex;
     }
 
@@ -1248,17 +1248,17 @@ EmitVarOp(ExclusiveContext *cx, ParseNod
         JS_ASSERT_IF(pn->isUsed(), pn->pn_cookie.level() == 0);
         JS_ASSERT_IF(pn->isDefn(), pn->pn_cookie.level() == bce->script->staticLevel());
         return EmitUnaliasedVarOp(cx, op, pn->pn_cookie.slot(), bce);
     }
 
     switch (op) {
       case JSOP_GETARG: case JSOP_GETLOCAL: op = JSOP_GETALIASEDVAR; break;
       case JSOP_SETARG: case JSOP_SETLOCAL: op = JSOP_SETALIASEDVAR; break;
-      default: MOZ_ASSUME_UNREACHABLE("unexpected var op");
+      default: MOZ_CRASH("unexpected var op");
     }
 
     return EmitAliasedVarOp(cx, op, pn, bce);
 }
 
 static JSOp
 GetIncDecInfo(ParseNodeKind kind, bool *post)
 {
@@ -1348,17 +1348,17 @@ BytecodeEmitter::isAliasedName(ParseNode
         return script->formalIsAliased(pn->pn_cookie.slot());
       case Definition::VAR:
       case Definition::CONST:
         JS_ASSERT_IF(sc->allLocalsAliased(), script->varIsAliased(pn->pn_cookie.slot()));
         return script->varIsAliased(pn->pn_cookie.slot());
       case Definition::PLACEHOLDER:
       case Definition::NAMED_LAMBDA:
       case Definition::MISSING:
-        MOZ_ASSUME_UNREACHABLE("unexpected dn->kind");
+        MOZ_CRASH("unexpected dn->kind");
     }
     return false;
 }
 
 /*
  * Try to convert a *NAME op with a free name to a more specialized GNAME,
  * INTRINSIC or ALIASEDVAR op, which optimize accesses on that name.
  * Return true if a conversion was made.
@@ -1373,17 +1373,17 @@ TryConvertFreeName(BytecodeEmitter *bce,
      * cloned lazily upon first access.
      */
     if (bce->emitterMode == BytecodeEmitter::SelfHosting) {
         JSOp op;
         switch (pn->getOp()) {
           case JSOP_NAME:     op = JSOP_GETINTRINSIC; break;
           case JSOP_SETNAME:  op = JSOP_SETINTRINSIC; break;
           /* Other *NAME ops aren't (yet) supported in self-hosted code. */
-          default: MOZ_ASSUME_UNREACHABLE("intrinsic");
+          default: MOZ_CRASH("intrinsic");
         }
         pn->setOp(op);
         return true;
     }
 
     /*
      * When parsing inner functions lazily, parse nodes for outer functions no
      * longer exist and only the function's scope chain is available for
@@ -1481,17 +1481,17 @@ TryConvertFreeName(BytecodeEmitter *bce,
 
     JSOp op;
     switch (pn->getOp()) {
       case JSOP_NAME:     op = JSOP_GETGNAME; break;
       case JSOP_SETNAME:  op = JSOP_SETGNAME; break;
       case JSOP_SETCONST:
         // Not supported.
         return false;
-      default: MOZ_ASSUME_UNREACHABLE("gname");
+      default: MOZ_CRASH("gname");
     }
     pn->setOp(op);
     return true;
 }
 
 /*
  * BindNameToSlotHelper attempts to optimize name gets and sets to stack slot
  * loads and stores, given the compile-time information in bce and a PNK_NAME
@@ -1627,29 +1627,29 @@ BindNameToSlotHelper(ExclusiveContext *c
      * to stack slot. Look for an argument or variable in the function and
      * rewrite pn_op and update pn accordingly.
      */
     switch (dn->kind()) {
       case Definition::ARG:
         switch (op) {
           case JSOP_NAME:     op = JSOP_GETARG; break;
           case JSOP_SETNAME:  op = JSOP_SETARG; break;
-          default: MOZ_ASSUME_UNREACHABLE("arg");
+          default: MOZ_CRASH("arg");
         }
         JS_ASSERT(!pn->isConst());
         break;
 
       case Definition::VAR:
       case Definition::CONST:
       case Definition::LET:
         switch (op) {
           case JSOP_NAME:     op = JSOP_GETLOCAL; break;
           case JSOP_SETNAME:  op = JSOP_SETLOCAL; break;
           case JSOP_SETCONST: op = JSOP_SETLOCAL; break;
-          default: MOZ_ASSUME_UNREACHABLE("local");
+          default: MOZ_CRASH("local");
         }
         break;
 
       case Definition::NAMED_LAMBDA: {
         JS_ASSERT(dn->isOp(JSOP_CALLEE));
         JS_ASSERT(op != JSOP_CALLEE);
 
         /*
@@ -1696,17 +1696,17 @@ BindNameToSlotHelper(ExclusiveContext *c
         pn->pn_dflags |= PND_BOUND;
         return true;
       }
 
       case Definition::PLACEHOLDER:
         return true;
 
       case Definition::MISSING:
-        MOZ_ASSUME_UNREACHABLE("missing");
+        MOZ_CRASH("missing");
     }
 
     /*
      * The difference between the current static level and the static level of
      * the definition is the number of function scopes between the current
      * scope and dn's scope.
      */
     unsigned skip = bce->script->staticLevel() - dn->pn_cookie.level();
@@ -1889,17 +1889,17 @@ CheckSideEffects(ExclusiveContext *cx, B
               case PNK_CALL:
               case PNK_ELEM:
                 /* All these delete addressing modes have effects too. */
                 *answer = true;
                 return true;
               default:
                 return CheckSideEffects(cx, bce, pn2, answer);
             }
-            MOZ_ASSUME_UNREACHABLE("We have a returning default case");
+            MOZ_CRASH("We have a returning default case");
           }
 
           case PNK_TYPEOF:
           case PNK_VOID:
           case PNK_NOT:
           case PNK_BITNOT:
             if (pn->isOp(JSOP_NOT)) {
                 /* ! does not convert its operand via toString or valueOf. */
@@ -1912,17 +1912,17 @@ CheckSideEffects(ExclusiveContext *cx, B
              * All of PNK_INC, PNK_DEC, PNK_THROW, PNK_YIELD, and PNK_YIELD_STAR
              * have direct effects. Of the remaining unary-arity node types, we
              * can't easily prove that the operand never denotes an object with
              * a toString or valueOf method.
              */
             *answer = true;
             return true;
         }
-        MOZ_ASSUME_UNREACHABLE("We have a returning default case");
+        MOZ_CRASH("We have a returning default case");
 
       case PN_NAME:
         /*
          * Take care to avoid trying to bind a label name (labels, both for
          * statements and property values in object initialisers, have pn_op
          * defaulted to JSOP_NOP).
          */
         if (pn->isKind(PNK_NAME) && !pn->isOp(JSOP_NOP)) {
@@ -3120,17 +3120,17 @@ EmitDestructuringLHS(ExclusiveContext *c
 
               case JSOP_SETLOCAL:
               case JSOP_SETARG:
                 if (!EmitVarOp(cx, pn, pn->getOp(), bce))
                     return false;
                 break;
 
               default:
-                MOZ_ASSUME_UNREACHABLE("EmitDestructuringLHS: bad name op");
+                MOZ_CRASH("EmitDestructuringLHS: bad name op");
             }
             break;
 
           case PNK_DOT:
             // See the (PNK_NAME, JSOP_SETNAME) case above.
             //
             // In `a.x = b`, `a` is evaluated first, then `b`, then a
             // JSOP_SETPROP instruction.
@@ -3164,17 +3164,17 @@ EmitDestructuringLHS(ExclusiveContext *c
             // analysis. (The interpreter will never reach these instructions
             // since we just emitted JSOP_SETCALL, which always throws. It's
             // possible no analyses actually depend on this either.)
             if (Emit1(cx, bce, JSOP_POP) < 0)
                 return false;
             break;
 
           default:
-            MOZ_ASSUME_UNREACHABLE("EmitDestructuringLHS: bad lhs kind");
+            MOZ_CRASH("EmitDestructuringLHS: bad lhs kind");
         }
 
         // Pop the assigned value.
         if (Emit1(cx, bce, JSOP_POP) < 0)
             return false;
     }
 
     return true;
@@ -3735,17 +3735,17 @@ EmitAssignment(ExclusiveContext *cx, Byt
                 if (!EmitAtomOp(cx, lhs, JSOP_GETINTRINSIC, bce))
                     return false;
             } else {
                 JSOp op;
                 switch (lhs->getOp()) {
                   case JSOP_SETARG: op = JSOP_GETARG; break;
                   case JSOP_SETLOCAL: op = JSOP_GETLOCAL; break;
                   case JSOP_SETALIASEDVAR: op = JSOP_GETALIASEDVAR; break;
-                  default: MOZ_ASSUME_UNREACHABLE("Bad op");
+                  default: MOZ_CRASH("Bad op");
                 }
                 if (!EmitVarOp(cx, lhs, op, bce))
                     return false;
             }
             break;
           case PNK_DOT: {
             if (Emit1(cx, bce, JSOP_DUP) < 0)
                 return false;
@@ -3942,17 +3942,17 @@ ParseNode::getConstantValue(ExclusiveCon
             }
         }
 
         types::FixObjectType(cx, obj);
         vp.setObject(*obj);
         return true;
       }
       default:
-        MOZ_ASSUME_UNREACHABLE("Unexpected node");
+        MOZ_CRASH("Unexpected node");
     }
     return false;
 }
 
 static bool
 EmitSingletonInitialiser(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
 {
     RootedValue value(cx);
@@ -6039,17 +6039,17 @@ EmitObject(ExclusiveContext *cx, Bytecod
             obj = nullptr;
 
         if (isIndex) {
             obj = nullptr;
             switch (op) {
               case JSOP_INITPROP:        op = JSOP_INITELEM;        break;
               case JSOP_INITPROP_GETTER: op = JSOP_INITELEM_GETTER; break;
               case JSOP_INITPROP_SETTER: op = JSOP_INITELEM_SETTER; break;
-              default: MOZ_ASSUME_UNREACHABLE("Invalid op");
+              default: MOZ_CRASH("Invalid op");
             }
             if (Emit1(cx, bce, op) < 0)
                 return false;
         } else {
             JS_ASSERT(pn3->isKind(PNK_NAME) || pn3->isKind(PNK_STRING));
 
             // If we have { __proto__: expr }, implement prototype mutation.
             if (op == JSOP_INITPROP && pn3->pn_atom == cx->names().proto) {
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -188,17 +188,17 @@ ParseContext<FullParseHandler>::define(T
         dn->setOp((js_CodeSpec[dn->getOp()].format & JOF_SET) ? JSOP_SETLOCAL : JSOP_GETLOCAL);
         dn->pn_dflags |= (PND_LET | PND_BOUND);
         JS_ASSERT(dn->pn_cookie.level() == staticLevel); /* see bindLet */
         if (!decls_.addShadow(name, dn))
             return false;
         break;
 
       default:
-        MOZ_ASSUME_UNREACHABLE("unexpected kind");
+        MOZ_CRASH("unexpected kind");
     }
 
     return true;
 }
 
 template <>
 bool
 ParseContext<SyntaxParseHandler>::define(TokenStream &ts, HandlePropertyName name, Node pn,
@@ -284,17 +284,17 @@ AppendPackedBindings(const ParseContext<
             break;
           case Definition::CONST:
             kind = Binding::CONSTANT;
             break;
           case Definition::ARG:
             kind = Binding::ARGUMENT;
             break;
           default:
-            MOZ_ASSUME_UNREACHABLE("unexpected dn->kind");
+            MOZ_CRASH("unexpected dn->kind");
         }
 
         /*
          * Bindings::init does not check for duplicates so we must ensure that
          * only one binding with a given name is marked aliased. pc->decls
          * maintains the canonical definition for each name, so use that.
          */
         JS_ASSERT_IF(dn->isClosed(), pc->decls().lookupFirst(name) == dn);
@@ -4384,17 +4384,17 @@ Parser<FullParseHandler>::forStatement()
 
         switch (pn2->getKind()) {
           case PNK_NAME:
             /* Beware 'for (arguments in ...)' with or without a 'var'. */
             pn2->markAsAssigned();
             break;
 
           case PNK_ASSIGN:
-            MOZ_ASSUME_UNREACHABLE("forStatement TOK_ASSIGN");
+            MOZ_CRASH("forStatement TOK_ASSIGN");
 
           case PNK_ARRAY:
           case PNK_OBJECT:
             if (versionNumber() == JSVERSION_1_7) {
                 /*
                  * Destructuring for-in requires [key, value] enumeration
                  * in JS1.7.
                  */
@@ -4946,17 +4946,17 @@ Parser<ParseHandler>::yieldExpression()
             if (!exprNode)
                 return null();
         }
 
         return handler.newUnary(PNK_YIELD, JSOP_NOP, begin, exprNode);
       }
     }
 
-    MOZ_ASSUME_UNREACHABLE("yieldExpr");
+    MOZ_CRASH("yieldExpr");
 }
 
 template <>
 ParseNode *
 Parser<FullParseHandler>::withStatement()
 {
     // test262/ch12/12.10/12.10-0-1.js fails if we try to parse with-statements
     // in syntax-parse mode. See bug 892583.
@@ -5850,17 +5850,16 @@ Parser<ParseHandler>::unaryExpr()
                 return null();
             return handler.newUnary((tt == TOK_INC) ? PNK_POSTINCREMENT : PNK_POSTDECREMENT,
                                     JSOP_NOP,
                                     begin,
                                     pn);
         }
         return pn;
     }
-    MOZ_ASSUME_UNREACHABLE("unaryExpr");
 }
 
 /*
  * A dedicated helper for transplanting the legacy comprehension expression E in
  *
  *   [E for (V in I)]   // legacy array comprehension
  *   (E for (V in I))   // legacy generator expression
  *
@@ -7345,17 +7344,17 @@ Parser<ParseHandler>::objectLiteral()
         AssignmentType assignType;
         if (op == JSOP_INITPROP)
             assignType = VALUE;
         else if (op == JSOP_INITPROP_GETTER)
             assignType = GET;
         else if (op == JSOP_INITPROP_SETTER)
             assignType = SET;
         else
-            MOZ_ASSUME_UNREACHABLE("bad opcode in object initializer");
+            MOZ_CRASH("bad opcode in object initializer");
 
         AtomIndexAddPtr p = seen.lookupForAdd(atom);
         if (p) {
             jsatomid index = p.value();
             AssignmentType oldAssignType = AssignmentType(index);
             if ((oldAssignType & assignType) &&
                 (oldAssignType != VALUE || assignType != VALUE || pc->sc->needStrictChecks()))
             {
--- a/js/src/frontend/TokenStream.cpp
+++ b/js/src/frontend/TokenStream.cpp
@@ -1623,17 +1623,17 @@ TokenStream::getTokenInternal(Modifier m
         goto out;
 
       badchar:
       default:
         reportError(JSMSG_ILLEGAL_CHARACTER);
         goto error;
     }
 
-    MOZ_ASSUME_UNREACHABLE("should have jumped to |out| or |error|");
+    MOZ_CRASH("should have jumped to |out| or |error|");
 
   out:
     flags.isDirtyLine = true;
     tp->pos.end = userbuf.addressOfNextRawChar() - userbuf.base();
     JS_ASSERT(IsTokenSane(tp));
     return tp->type;
 
   error:
--- a/js/src/gc/Heap.h
+++ b/js/src/gc/Heap.h
@@ -187,16 +187,24 @@ class FreeSpan
         checkSpan(thingSize);
     }
 
     bool isEmpty() const {
         checkSpan();
         return !first;
     }
 
+    static size_t offsetOfFirst() {
+        return offsetof(FreeSpan, first);
+    }
+
+    static size_t offsetOfLast() {
+        return offsetof(FreeSpan, last);
+    }
+
     // Like nextSpan(), but no checking of the following span is done.
     FreeSpan *nextSpanUnchecked() const {
         return reinterpret_cast<FreeSpan *>(last);
     }
 
     const FreeSpan *nextSpan() const {
         JS_ASSERT(!isEmpty());
         return nextSpanUnchecked();
--- a/js/src/gc/RootMarking.cpp
+++ b/js/src/gc/RootMarking.cpp
@@ -750,17 +750,17 @@ js::gc::GCRuntime::markRuntime(JSTracer 
         } else if (*reinterpret_cast<void **>(key)){
             if (type == JS_GC_ROOT_STRING_PTR)
                 MarkStringRoot(trc, reinterpret_cast<JSString **>(key), name);
             else if (type == JS_GC_ROOT_OBJECT_PTR)
                 MarkObjectRoot(trc, reinterpret_cast<JSObject **>(key), name);
             else if (type == JS_GC_ROOT_SCRIPT_PTR)
                 MarkScriptRoot(trc, reinterpret_cast<JSScript **>(key), name);
             else
-                MOZ_ASSUME_UNREACHABLE("unexpected js::RootInfo::type value");
+                MOZ_CRASH("unexpected js::RootInfo::type value");
         }
     }
 
     MarkPersistentRootedChains(trc);
 
     if (rt->scriptAndCountsVector) {
         ScriptAndCountsVector &vec = *rt->scriptAndCountsVector;
         for (size_t i = 0; i < vec.length(); i++)
--- a/js/src/gc/Statistics.cpp
+++ b/js/src/gc/Statistics.cpp
@@ -239,23 +239,23 @@ class gcstats::StatisticsSerializer
  * to GC_REASON_3 and bump the max value.
  */
 JS_STATIC_ASSERT(JS::gcreason::NUM_TELEMETRY_REASONS >= JS::gcreason::NUM_REASONS);
 
 const char *
 js::gcstats::ExplainReason(JS::gcreason::Reason reason)
 {
     switch (reason) {
-#define SWITCH_REASON(name)                     \
+#define SWITCH_REASON(name)                         \
         case JS::gcreason::name:                    \
           return #name;
         GCREASONS(SWITCH_REASON)
 
         default:
-          MOZ_ASSUME_UNREACHABLE("bad GC reason");
+          MOZ_CRASH("bad GC reason");
 #undef SWITCH_REASON
     }
 }
 
 static double
 t(int64_t t)
 {
     return double(t) / PRMJ_USEC_PER_MSEC;
--- a/js/src/gc/StoreBuffer.cpp
+++ b/js/src/gc/StoreBuffer.cpp
@@ -61,17 +61,17 @@ StoreBuffer::WholeCellEdges::mark(JSTrac
             ArgumentsObject::trace(trc, object);
         MarkChildren(trc, object);
         return;
     }
 #ifdef JS_ION
     JS_ASSERT(kind == JSTRACE_JITCODE);
     static_cast<jit::JitCode *>(edge)->trace(trc);
 #else
-    MOZ_ASSUME_UNREACHABLE("Only objects can be in the wholeCellBuffer if IonMonkey is disabled.");
+    MOZ_CRASH("Only objects can be in the wholeCellBuffer if IonMonkey is disabled.");
 #endif
 }
 
 void
 StoreBuffer::CellPtrEdge::mark(JSTracer *trc)
 {
     if (!*edge)
         return;
--- a/js/src/gc/Tracer.cpp
+++ b/js/src/gc/Tracer.cpp
@@ -378,17 +378,17 @@ MarkStack::setBaseCapacity(JSGCMode mode
       case JSGC_MODE_GLOBAL:
       case JSGC_MODE_COMPARTMENT:
         baseCapacity_ = NON_INCREMENTAL_MARK_STACK_BASE_CAPACITY;
         break;
       case JSGC_MODE_INCREMENTAL:
         baseCapacity_ = INCREMENTAL_MARK_STACK_BASE_CAPACITY;
         break;
       default:
-        MOZ_ASSUME_UNREACHABLE("bad gc mode");
+        MOZ_CRASH("bad gc mode");
     }
 
     if (baseCapacity_ > maxCapacity_)
         baseCapacity_ = maxCapacity_;
 }
 
 void
 MarkStack::setMaxCapacity(size_t maxCapacity)
--- a/js/src/irregexp/RegExpAST.h
+++ b/js/src/irregexp/RegExpAST.h
@@ -60,17 +60,17 @@ class RegExpTree
     virtual bool IsAnchoredAtStart() { return false; }
     virtual bool IsAnchoredAtEnd() { return false; }
     virtual int min_match() = 0;
     virtual int max_match() = 0;
     // Returns the interval of registers used for captures within this
     // expression.
     virtual Interval CaptureRegisters() { return Interval::Empty(); }
     virtual void AppendToText(RegExpText* text) {
-        MOZ_ASSUME_UNREACHABLE("Bad call");
+        MOZ_CRASH("Bad call");
     }
 #define MAKE_ASTYPE(Name)                                               \
     virtual RegExp##Name* As##Name();                                   \
     virtual bool Is##Name();
     FOR_EACH_REG_EXP_TREE_TYPE(MAKE_ASTYPE)
 #undef MAKE_ASTYPE
 };
 
--- a/js/src/irregexp/RegExpEngine.cpp
+++ b/js/src/irregexp/RegExpEngine.cpp
@@ -155,17 +155,17 @@ CharacterRange::AddClassEscape(LifoAlloc
         ranges->append(CharacterRange::Everything());
         break;
         // This is the set of characters matched by the $ and ^ symbols
         // in multiline mode.
       case 'n':
         AddClass(kLineTerminatorRanges, kLineTerminatorRangeCount, ranges);
         break;
       default:
-        MOZ_ASSUME_UNREACHABLE("Bad character class escape");
+        MOZ_CRASH("Bad character class escape");
     }
 }
 
 // We need to check for the following characters: 0x39c 0x3bc 0x178.
 static inline bool
 RangeContainsLatin1Equivalents(CharacterRange range)
 {
     // TODO(dcarney): this could be a lot more efficient.
@@ -1370,18 +1370,17 @@ int
 TextElement::length() const
 {
     switch (text_type()) {
       case ATOM:
         return atom()->length();
       case CHAR_CLASS:
         return 1;
     }
-    MOZ_ASSUME_UNREACHABLE("Bad text type");
-    return 0;
+    MOZ_CRASH("Bad text type");
 }
 
 class FrequencyCollator
 {
   public:
     FrequencyCollator() : total_samples_(0) {
         for (int i = 0; i < RegExpMacroAssembler::kTableSize; i++) {
             frequencies_[i] = CharacterFrequency(i);
@@ -1978,17 +1977,17 @@ RegExpAssertion::ToNode(RegExpCompiler* 
         // Add the two alternatives to the ChoiceNode.
         GuardedAlternative eol_alternative(end_of_line);
         result->AddAlternative(eol_alternative);
         GuardedAlternative end_alternative(AssertionNode::AtEnd(on_success));
         result->AddAlternative(end_alternative);
         return result;
       }
       default:
-        MOZ_ASSUME_UNREACHABLE("Bad assertion type");
+        MOZ_CRASH("Bad assertion type");
     }
     return on_success;
 }
 
 RegExpNode *
 RegExpBackReference::ToNode(RegExpCompiler* compiler, RegExpNode* on_success)
 {
     return compiler->alloc()->newInfallible<BackReferenceNode>(RegExpCapture::StartRegister(index()),
@@ -2509,18 +2508,17 @@ Trace::PerformDeferredActions(LifoAlloc 
                         clear = true;
                     }
                     undo_action = DEFER_RESTORE;
                     JS_ASSERT(!absolute);
                     JS_ASSERT(value == 0);
                     break;
                   }
                   default:
-                    MOZ_ASSUME_UNREACHABLE("Bad action");
-                    break;
+                    MOZ_CRASH("Bad action");
                 }
             }
         }
         // Prepare for the undo-action (e.g., push if it's going to be popped).
         if (undo_action == DEFER_RESTORE) {
             pushes++;
             RegExpMacroAssembler::StackCheckFlag stack_check =
                 RegExpMacroAssembler::kNoStackLimitCheck;
@@ -2715,19 +2713,19 @@ EndNode::Emit(RegExpCompiler* compiler, 
     case ACCEPT:
         assembler->Succeed();
         return;
     case BACKTRACK:
         assembler->JumpOrBacktrack(trace->backtrack());
         return;
     case NEGATIVE_SUBMATCH_SUCCESS:
         // This case is handled in a different virtual method.
-        MOZ_ASSUME_UNREACHABLE("Bad action");
+        MOZ_CRASH("Bad action: NEGATIVE_SUBMATCH_SUCCESS");
     }
-    MOZ_ASSUME_UNREACHABLE("Bad action");
+    MOZ_CRASH("Bad action");
 }
 
 // Emit the code to check for a ^ in multiline mode (1-character lookbehind
 // that matches newline or the start of input).
 static void
 EmitHat(RegExpCompiler* compiler, RegExpNode* on_success, Trace* trace)
 {
     RegExpMacroAssembler* assembler = compiler->macro_assembler();
@@ -3539,18 +3537,17 @@ EmitAtomLetter(RegExpCompiler* compiler,
         // Fall through!
       case 3:
         macro_assembler->CheckCharacter(chars[0], &ok);
         macro_assembler->CheckCharacter(chars[1], &ok);
         macro_assembler->CheckNotCharacter(chars[2], on_failure);
         macro_assembler->Bind(&ok);
         break;
       default:
-        MOZ_ASSUME_UNREACHABLE("Bad length");
-        break;
+        MOZ_CRASH("Bad length");
     }
     return true;
 }
 
 // We call this repeatedly to generate code for each pass over the text node.
 // The passes are in increasing order of difficulty because we hope one
 // of the first passes will fail in which case we are saved the work of the
 // later passes.  for example for the case independent regexp /%[asdfghjkl]a/
@@ -4351,17 +4348,17 @@ ActionNode::Emit(RegExpCompiler* compile
         int clear_registers_to = clear_registers_from + clear_register_count - 1;
         assembler->ClearRegisters(clear_registers_from, clear_registers_to);
 
         JS_ASSERT(trace->backtrack() == nullptr);
         assembler->Backtrack();
         return;
       }
       default:
-        MOZ_ASSUME_UNREACHABLE("Bad action");
+        MOZ_CRASH("Bad action");
     }
 }
 
 void
 BackReferenceNode::Emit(RegExpCompiler* compiler, Trace* trace)
 {
     RegExpMacroAssembler* assembler = compiler->macro_assembler();
     if (!trace->is_trivial()) {
--- a/js/src/irregexp/RegExpEngine.h
+++ b/js/src/irregexp/RegExpEngine.h
@@ -521,17 +521,17 @@ class RegExpNode
     // we look forward.  This is used for a Boyer-Moore-like string searching
     // implementation.  TODO(erikcorry):  This should share more code with
     // EatsAtLeast, GetQuickCheckDetails.  The budget argument is used to limit
     // the number of nodes we are willing to look at in order to create this data.
     virtual void FillInBMInfo(int offset,
                               int budget,
                               BoyerMooreLookahead* bm,
                               bool not_at_start) {
-        MOZ_ASSUME_UNREACHABLE("Bad call");
+        MOZ_CRASH("Bad call");
     }
 
     // If we know that the input is ASCII then there are some nodes that can
     // never match.  This method returns a node that can be substituted for
     // itself, or nullptr if the node can never match.
     virtual RegExpNode* FilterASCII(int depth, bool ignore_case) { return this; }
 
     // Helper for FilterASCII.
@@ -899,24 +899,24 @@ class EndNode : public RegExpNode
                             int recursion_depth,
                             bool not_at_start) { return 0; }
     virtual void GetQuickCheckDetails(QuickCheckDetails* details,
                                       RegExpCompiler* compiler,
                                       int characters_filled_in,
                                       bool not_at_start)
     {
         // Returning 0 from EatsAtLeast should ensure we never get here.
-        MOZ_ASSUME_UNREACHABLE("Bad call");
+        MOZ_CRASH("Bad call");
     }
     virtual void FillInBMInfo(int offset,
                               int budget,
                               BoyerMooreLookahead* bm,
                               bool not_at_start) {
         // Returning 0 from EatsAtLeast should ensure we never get here.
-        MOZ_ASSUME_UNREACHABLE("Bad call");
+        MOZ_CRASH("Bad call");
     }
 
   private:
     Action action_;
 };
 
 class NegativeSubmatchSuccess : public EndNode
 {
--- a/js/src/irregexp/RegExpInterpreter.cpp
+++ b/js/src/irregexp/RegExpInterpreter.cpp
@@ -123,18 +123,17 @@ irregexp::InterpretCode(JSContext *cx, c
         return RegExpRunStatus_Error;
     for (size_t i = 0; i < matches->length() * 2; i++)
         registers[i] = -1;
 
     while (true) {
         int32_t insn = Load32Aligned(pc);
         switch (insn & BYTECODE_MASK) {
           BYTECODE(BREAK)
-            MOZ_ASSUME_UNREACHABLE("Bad bytecode");
-            return RegExpRunStatus_Error;
+            MOZ_CRASH("Bad bytecode: BREAK");
           BYTECODE(PUSH_CP)
             if (!stack.push(current))
                 return RegExpRunStatus_Error;
             pc += BC_PUSH_CP_LENGTH;
             break;
           BYTECODE(PUSH_BT)
             if (!stack.push(Load32Aligned(pc + 4)))
                 return RegExpRunStatus_Error;
@@ -236,20 +235,19 @@ irregexp::InterpretCode(JSContext *cx, c
           BYTECODE(LOAD_2_CURRENT_CHARS_UNCHECKED) {
             int pos = current + (insn >> BYTECODE_SHIFT);
             jschar next = chars[pos + 1];
             current_char = (chars[pos] | (next << (kBitsPerByte * sizeof(jschar))));
             pc += BC_LOAD_2_CURRENT_CHARS_UNCHECKED_LENGTH;
             break;
           }
           BYTECODE(LOAD_4_CURRENT_CHARS)
-            MOZ_ASSUME_UNREACHABLE("Ascii handling implemented");
-            break;
+            MOZ_CRASH("ASCII handling implemented");
           BYTECODE(LOAD_4_CURRENT_CHARS_UNCHECKED)
-            MOZ_ASSUME_UNREACHABLE("Ascii handling implemented");
+            MOZ_CRASH("ASCII handling implemented");
           BYTECODE(CHECK_4_CHARS) {
             uint32_t c = Load32Aligned(pc + 4);
             if (c == current_char)
                 pc = byteCode + Load32Aligned(pc + 8);
             else
                 pc += BC_CHECK_4_CHARS_LENGTH;
             break;
           }
@@ -447,18 +445,17 @@ irregexp::InterpretCode(JSContext *cx, c
             if (length - current > by) {
                 current = length - by;
                 current_char = chars[current - 1];
             }
             pc += BC_SET_CURRENT_POSITION_FROM_END_LENGTH;
             break;
           }
           default:
-            MOZ_ASSUME_UNREACHABLE("Bad bytecode");
-            break;
+            MOZ_CRASH("Bad bytecode");
         }
     }
 }
 
 template RegExpRunStatus
 irregexp::InterpretCode(JSContext *cx, const uint8_t *byteCode, const Latin1Char *chars, size_t current,
                         size_t length, MatchPairs *matches);
 
--- a/js/src/irregexp/RegExpParser.cpp
+++ b/js/src/irregexp/RegExpParser.cpp
@@ -189,18 +189,17 @@ RegExpBuilder::AddQuantifierToAtom(int m
             last_added_ = ADD_TERM;
             if (min == 0)
                 return;
             terms_.Add(alloc, atom);
             return;
         }
     } else {
         // Only call immediately after adding an atom or character!
-        MOZ_ASSUME_UNREACHABLE("Bad call");
-        return;
+        MOZ_CRASH("Bad call");
     }
     terms_.Add(alloc, alloc->newInfallible<RegExpQuantifier>(min, max, quantifier_type, atom));
     last_added_ = ADD_TERM;
 }
 
 // ----------------------------------------------------------------------------
 // RegExpParser
 
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/latin1/latin1.js
@@ -0,0 +1,121 @@
+if (!isLatin1("foo")) //TODO: remove this when removing latin1 flag
+    quit();
+
+function assertLatin1(s) {
+    assertEq(isLatin1(s), true, "String: " + s);
+}
+
+// string literals
+assertLatin1("");
+assertLatin1("x");
+assertLatin1("xy");
+assertLatin1("while");
+assertLatin1("xyaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\u00ff.");
+assertLatin1("-1");
+assertLatin1("50");
+assertLatin1("873939");
+assertLatin1("12.34");
+assertLatin1("NaN");
+
+// int32
+assertLatin1(String(1));
+assertLatin1("" + 40);
+assertLatin1(String(873939));
+assertLatin1(String(-1));
+assertLatin1((-12983).toString());
+assertLatin1(20..toString(16));
+assertLatin1((-20).toString(12));
+assertLatin1(12121..toString(16));
+assertLatin1(12144..toString(2));
+
+// doubles
+assertLatin1(String(12.34));
+assertLatin1(45.6.toString(16));
+assertLatin1(String(-0));
+assertLatin1(NaN.toString());
+assertLatin1("" + -Infinity);
+assertLatin1("a" + 85899345929);
+
+// other types
+assertLatin1(true.toString());
+assertLatin1(String(false));
+assertLatin1(String(null));
+assertLatin1(String(undefined));
+
+// objects
+assertLatin1(Math.toString());
+assertLatin1(({x:1}).toString());
+assertLatin1(({x:"foo"}).toSource());
+assertLatin1([1, "bar"].toString());
+
+// typeof
+assertLatin1(typeof "foo");
+assertLatin1(typeof assertEq);
+
+// join
+assertLatin1([1, "foo", null, true].join());
+assertLatin1([1, "foo", null, true].join(":"));
+
+// JSON
+assertLatin1(JSON.stringify({x:1,y:"bar",z:[null],1281298:Math.PI}));
+assertLatin1(JSON.stringify([1, 2, 3], null, "foo"));
+assertLatin1(JSON.stringify({x:1,y:"bar"}, function(k, v) {
+    assertLatin1(k);
+    return v;
+}));
+var arr = [1, 2, 3]; arr.length = 300;
+assertLatin1(JSON.stringify(arr, function(k, v) {
+    assertLatin1(k);
+    return v;
+}));
+
+// Date
+assertLatin1(new Date().toString());
+assertLatin1(new Date().toISOString());
+assertLatin1(Date());
+
+// native functions
+assertLatin1(Math.abs.toString());
+assertLatin1(Object.getOwnPropertyNames.name);
+
+// scripted functions
+function fun1(a, b) { return ["foo\xAA"]; }
+assertLatin1(fun1.toString());
+assertLatin1(fun1.name);
+
+// fromCharCode
+assertLatin1(String.fromCharCode(99));
+assertLatin1(String.fromCharCode(99, 0xff, 1));
+
+// charAt
+assertLatin1("foo\u00ff\u1200".charAt(3));
+
+// RegExps
+var re = /a\.*b[cC]+/g;
+assertLatin1(re.source);
+assertLatin1(re.toString());
+
+// For-in
+var o = {x: 1, y: 2, z\u00ff: 3, 987654: 4, 22: 5};
+for (var prop in o)
+    assertLatin1(prop);
+
+// Object.keys
+assertLatin1(Object.keys(o)[2]);
+
+// Error
+(function foo() {
+    var err;
+    try { this(); } catch(e) { err = e; }
+    assertEq(err.name, "TypeError");
+    assertLatin1(err.name);
+    assertLatin1(err.message);
+    assertLatin1(err.stack);
+    assertLatin1(err.toString());
+
+    try { throw new Error("foo"); } catch(e) { err = e; }
+    assertLatin1(err.name);
+    assertLatin1(err.message);
+    assertLatin1(err.stack);
+    assertLatin1(err.toString());
+})();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/parallel/bug1037657.js
@@ -0,0 +1,5 @@
+if (getBuildConfiguration().parallelJS) {
+  x = Array.buildPar(7, Symbol);
+  Array.prototype.push.call(x, 2);
+  x.mapPar(function(){}, 1)
+}
--- a/js/src/jit/AsmJS.cpp
+++ b/js/src/jit/AsmJS.cpp
@@ -5901,32 +5901,32 @@ StackDecrementForCall(MacroAssembler &ma
 }
 
 #if defined(JS_CODEGEN_ARM)
 // The ARM system ABI also includes d15 in the non volatile float registers.
 // Also exclude lr (a.k.a. r14) as we preserve it manually)
 static const RegisterSet NonVolatileRegs =
     RegisterSet(GeneralRegisterSet(Registers::NonVolatileMask &
                                    ~(uint32_t(1) << Registers::lr)),
-                FloatRegisterSet(FloatRegisters::NonVolatileMask | (1 << FloatRegisters::d15)));
+                FloatRegisterSet(FloatRegisters::NonVolatileMask | (1ULL << FloatRegisters::d15)));
 #else
 static const RegisterSet NonVolatileRegs =
     RegisterSet(GeneralRegisterSet(Registers::NonVolatileMask),
                 FloatRegisterSet(FloatRegisters::NonVolatileMask));
 #endif
 
 #if defined(JS_CODEGEN_MIPS)
 // Mips is using one more double slot due to stack alignment for double values.
 // Look at MacroAssembler::PushRegsInMask(RegisterSet set)
 static const unsigned FramePushedAfterSave = NonVolatileRegs.gprs().size() * sizeof(intptr_t) +
                                              NonVolatileRegs.fpus().size() * sizeof(double) +
                                              sizeof(double);
 #else
 static const unsigned FramePushedAfterSave = NonVolatileRegs.gprs().size() * sizeof(intptr_t) +
-                                             NonVolatileRegs.fpus().size() * sizeof(double);
+                                             NonVolatileRegs.fpus().getPushSizeInBytes();
 #endif
 
 static bool
 GenerateEntry(ModuleCompiler &m, const AsmJSModule::ExportedFunction &exportedFunc)
 {
     MacroAssembler &masm = m.masm();
 
     // In constrast to the system ABI, the Ion convention is that all registers
@@ -6648,17 +6648,17 @@ GenerateStackOverflowExit(ModuleCompiler
     // Don't worry about restoring the stack; throwLabel will pop everything.
     masm.jump(throwLabel);
     return !masm.oom();
 }
 
 static const RegisterSet AllRegsExceptSP =
     RegisterSet(GeneralRegisterSet(Registers::AllMask &
                                    ~(uint32_t(1) << Registers::StackPointer)),
-                FloatRegisterSet(FloatRegisters::AllMask));
+                FloatRegisterSet(FloatRegisters::AllDoubleMask));
 
 // The operation-callback exit is called from arbitrarily-interrupted asm.js
 // code. That means we must first save *all* registers and restore *all*
 // registers (except the stack pointer) when we resume. The address to resume to
 // (assuming that js::HandleExecutionInterrupt doesn't indicate that the
 // execution should be aborted) is stored in AsmJSActivation::resumePC_.
 // Unfortunately, loading this requires a scratch register which we don't have
 // after restoring all registers. To hack around this, push the resumePC on the
@@ -6771,22 +6771,22 @@ GenerateInterruptExit(ModuleCompiler &m,
     // Store resumePC into the return PC stack slot.
     LoadAsmJSActivationIntoRegister(masm, IntArgReg0);
     masm.loadPtr(Address(IntArgReg0, AsmJSActivation::offsetOfResumePC()), IntArgReg1);
     masm.storePtr(IntArgReg1, Address(r6, 14 * sizeof(uint32_t*)));
 
     // argument 0: cx
     masm.loadPtr(Address(IntArgReg0, AsmJSActivation::offsetOfContext()), IntArgReg0);
 
-    masm.PushRegsInMask(RegisterSet(GeneralRegisterSet(0), FloatRegisterSet(FloatRegisters::AllMask)));   // save all FP registers
+    masm.PushRegsInMask(RegisterSet(GeneralRegisterSet(0), FloatRegisterSet(FloatRegisters::AllDoubleMask)));   // save all FP registers
     masm.call(AsmJSImm_HandleExecutionInterrupt);
     masm.branchIfFalseBool(ReturnReg, throwLabel);
 
     // Restore the machine state to before the interrupt. this will set the pc!
-    masm.PopRegsInMask(RegisterSet(GeneralRegisterSet(0), FloatRegisterSet(FloatRegisters::AllMask)));   // restore all FP registers
+    masm.PopRegsInMask(RegisterSet(GeneralRegisterSet(0), FloatRegisterSet(FloatRegisters::AllDoubleMask)));   // restore all FP registers
     masm.mov(r6,sp);
     masm.as_vmsr(r5);
     masm.as_msr(r4);
     // Restore all GP registers
     masm.startDataTransferM(IsLoad, sp, IA, WriteBack);
     masm.transferReg(r0);
     masm.transferReg(r1);
     masm.transferReg(r2);
--- a/js/src/jit/AsmJSLink.cpp
+++ b/js/src/jit/AsmJSLink.cpp
@@ -465,17 +465,17 @@ NewExportedFunction(JSContext *cx, const
 static bool
 HandleDynamicLinkFailure(JSContext *cx, CallArgs args, AsmJSModule &module, HandlePropertyName name)
 {
     if (cx->isExceptionPending())
         return false;
 
     uint32_t begin = module.srcBodyStart();  // starts right after 'use asm'
     uint32_t end = module.srcEndBeforeCurly();
-    Rooted<JSFlatString*> src(cx, module.scriptSource()->substring(cx, begin, end));
+    Rooted<JSFlatString*> src(cx, module.scriptSource()->substringDontDeflate(cx, begin, end));
     if (!src)
         return false;
 
     RootedFunction fun(cx, NewFunction(cx, NullPtr(), nullptr, 0, JSFunction::INTERPRETED,
                                        cx->global(), name, JSFunction::FinalizeKind,
                                        TenuredObject));
     if (!fun)
         return false;
--- a/js/src/jit/BacktrackingAllocator.cpp
+++ b/js/src/jit/BacktrackingAllocator.cpp
@@ -771,37 +771,36 @@ BacktrackingAllocator::tryAllocateRegist
 
     JS_ASSERT_IF(interval->requirement()->kind() == Requirement::FIXED,
                  interval->requirement()->allocation() == LAllocation(r.reg));
 
     for (size_t i = 0; i < interval->numRanges(); i++) {
         AllocatedRange range(interval, interval->getRange(i)), existing;
         for (size_t a = 0; a < r.reg.numAliased(); a++) {
             PhysicalRegister &rAlias = registers[r.reg.aliased(a).code()];
-            if (rAlias.allocations.contains(range, &existing)) {
-                if (existing.interval->hasVreg()) {
-                    if (IonSpewEnabled(IonSpew_RegAlloc)) {
+            if (!rAlias.allocations.contains(range, &existing))
+                continue;
+            if (existing.interval->hasVreg()) {
+                if (IonSpewEnabled(IonSpew_RegAlloc)) {
                     IonSpew(IonSpew_RegAlloc, "  %s collides with v%u[%u] %s [weight %lu]",
+                            rAlias.reg.name(), existing.interval->vreg(),
+                            existing.interval->index(),
                             existing.range->toString(),
-                                rAlias.reg.name(), existing.interval->vreg(),
-                                existing.interval->index(),
-                                existing.range->toString(),
-                                computeSpillWeight(existing.interval));
-                    }
-                    if (!*pconflicting || computeSpillWeight(existing.interval) < computeSpillWeight(*pconflicting))
-                        *pconflicting = existing.interval;
-                } else {
-                    if (IonSpewEnabled(IonSpew_RegAlloc)) {
+                            computeSpillWeight(existing.interval));
+                }
+                if (!*pconflicting || computeSpillWeight(existing.interval) < computeSpillWeight(*pconflicting))
+                    *pconflicting = existing.interval;
+            } else {
+                if (IonSpewEnabled(IonSpew_RegAlloc)) {
                     IonSpew(IonSpew_RegAlloc, "  %s collides with fixed use %s",
-                                rAlias.reg.name(), existing.range->toString());
-                    }
-                    *pfixed = true;
+                            rAlias.reg.name(), existing.range->toString());
                 }
-                return true;
+                *pfixed = true;
             }
+            return true;
         }
     }
 
     IonSpew(IonSpew_RegAlloc, "  allocated to %s", r.reg.name());
 
     for (size_t i = 0; i < interval->numRanges(); i++) {
         AllocatedRange range(interval, interval->getRange(i));
         if (!r.allocations.insert(range))
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -1532,17 +1532,16 @@ CodeGenerator::visitMoveGroup(LMoveGroup
 
         const LAllocation *from = move.from();
         const LAllocation *to = move.to();
         LDefinition::Type type = move.type();
 
         // No bogus moves.
         JS_ASSERT(*from != *to);
         JS_ASSERT(!from->isConstant());
-
         MoveOp::Type moveType;
         switch (type) {
           case LDefinition::OBJECT:
           case LDefinition::SLOTS:
 #ifdef JS_NUNBOX32
           case LDefinition::TYPE:
           case LDefinition::PAYLOAD:
 #else
@@ -8543,22 +8542,32 @@ CodeGenerator::visitHasClass(LHasClass *
 
 bool
 CodeGenerator::visitAsmJSCall(LAsmJSCall *ins)
 {
     MAsmJSCall *mir = ins->mir();
 
 #if defined(JS_CODEGEN_ARM)
     if (!UseHardFpABI() && mir->callee().which() == MAsmJSCall::Callee::Builtin) {
+        // The soft ABI passes floating point arguments in GPRs. Since basically
+        // nothing is set up to handle this, the values are placed in the
+        // corresponding VFP registers, then transferred to GPRs immediately
+        // before the call. The mapping is sN <-> rN, where double registers
+        // can be treated as their two component single registers.
         for (unsigned i = 0, e = ins->numOperands(); i < e; i++) {
             LAllocation *a = ins->getOperand(i);
             if (a->isFloatReg()) {
                 FloatRegister fr = ToFloatRegister(a);
-                int srcId = fr.code() * 2;
-                masm.ma_vxfer(fr, Register::FromCode(srcId), Register::FromCode(srcId+1));
+                if (fr.isDouble()) {
+                    uint32_t srcId = fr.singleOverlay().id();
+                    masm.ma_vxfer(fr, Register::FromCode(srcId), Register::FromCode(srcId + 1));
+                } else {
+                    uint32_t srcId = fr.id();
+                    masm.ma_vxfer(fr, Register::FromCode(srcId));
+                }
             }
         }
     }
 #endif