Merge mozilla-central to b2g-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 15 Jul 2014 16:21:19 +0200
changeset 207924 13cd3098c602136c25c3e329d4cc6badd89c87ae
parent 207923 0958d9abca75465b5c59ea8d77bad14f8e5c816d (current diff)
parent 207889 835e22069c1a2bf00c4d9a57bac4299d185d8acc (diff)
child 207925 ab95805551fc4cd322d694a9898a9e6c161a3e74
push id6561
push userasasaki@mozilla.com
push dateMon, 21 Jul 2014 21:23:20 +0000
treeherdermozilla-aurora@428d4d3c8588 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone33.0a1
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..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index deaa8dda7e694066e0c854bd041b4297e5d3b5cc..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 5d1e7ccc4c9e910f3cd6974e9428aac300cc6412..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 0692ad7087c1b9300af7bdce87daa98b4ff8d9f7..0000000000000000000000000000000000000000
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
 
     if (mir->spIncrement())
         masm.freeStack(mir->spIncrement());
 
--- a/js/src/jit/IonFrames.cpp
+++ b/js/src/jit/IonFrames.cpp
@@ -348,19 +348,29 @@ JitFrameIterator::machineState() const
     uintptr_t *spill = spillBase();
 
     MachineState machine;
     for (GeneralRegisterBackwardIterator iter(reader.allGprSpills()); iter.more(); iter++)
         machine.setRegisterLocation(*iter, --spill);
 
     uint8_t *spillAlign = alignDoubleSpillWithOffset(reinterpret_cast<uint8_t *>(spill), 0);
 
-    double *floatSpill = reinterpret_cast<double *>(spillAlign);
-    for (FloatRegisterBackwardIterator iter(reader.allFloatSpills()); iter.more(); iter++)
-        machine.setRegisterLocation(*iter, --floatSpill);
+    char *floatSpill = reinterpret_cast<char *>(spillAlign);
+    FloatRegisterSet fregs = reader.allFloatSpills();
+    fregs = fregs.reduceSetForPush();
+    for (FloatRegisterBackwardIterator iter(fregs); iter.more(); iter++) {
+        floatSpill -= (*iter).size();
+        for (uint32_t a = 0; a < (*iter).numAlignedAliased(); a++) {
+            // Only say that registers that actually start here start here.
+            // e.g. d0 should not start at s1, only at s0.
+            FloatRegister ftmp;
+            (*iter).alignedAliased(a, &ftmp);
+            machine.setRegisterLocation(ftmp, (double*)floatSpill);
+        }
+    }
 
     return machine;
 }
 
 static void
 CloseLiveIterator(JSContext *cx, const InlineFrameIterator &frame, uint32_t localSlot)
 {
     SnapshotIterator si = frame.snapshotIterator();
@@ -1957,25 +1967,32 @@ InlineFrameIterator::computeScopeChain(V
 bool
 InlineFrameIterator::isFunctionFrame() const
 {
     return !!callee_;
 }