Merge latest green b2g-inbound changeset and mozilla-central; a=merge
authorEd Morley <emorley@mozilla.com>
Tue, 22 Jul 2014 17:27:35 +0100
changeset 195494 e5ced39f443bacc221ac4bd41f465bcc746aeb04
parent 195493 bc0aa44b28852cb58bda11ab2782f593a2496a0a (current diff)
parent 195472 fe7c119a55e2d3a8b67331db9d7f4c08bb1314d8 (diff)
child 195526 e740a64c284bb1f227ced27770296d0464944dbe
child 195531 0fadd5050d678b523bfd37c29f9914479b69b5d8
child 195556 4040fadb9b54a4b4bdc1307f100d3d4ddf1bedea
push id27183
push useremorley@mozilla.com
push dateTue, 22 Jul 2014 16:28:10 +0000
treeherdermozilla-central@e5ced39f443b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone34.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge latest green b2g-inbound changeset and mozilla-central; a=merge
mobile/android/search/java/org/mozilla/search/autocomplete/AutoCompleteRowView.java
mobile/android/search/java/org/mozilla/search/stream/PreloadAgent.java
mobile/android/search/res/drawable-hdpi/search_header.png
mobile/android/search/res/drawable-mdpi/search_header.png
mobile/android/search/res/drawable-xhdpi/search_header.png
mobile/android/search/res/layout/search_activity_detail.xml
mobile/android/search/res/layout/search_card.xml
testing/marionette/client/marionette/tests/head.js
--- a/Makefile.in
+++ b/Makefile.in
@@ -251,29 +251,33 @@ export MOZ_SOURCE_STAMP
 endif
 
 .PHONY: update-packaging
 update-packaging:
 	$(MAKE) -C tools/update-packaging
 
 .PHONY: pretty-package
 pretty-package:
-	$(MAKE) package MOZ_PKG_PRETTYNAMES=1
+	unset MOZ_SIGN_CMD && $(MAKE) package MOZ_PKG_PRETTYNAMES=1
 
 .PHONY: pretty-package-tests
 pretty-package-tests:
-	$(MAKE) package-tests MOZ_PKG_PRETTYNAMES=1
+	unset MOZ_SIGN_CMD && $(MAKE) package-tests MOZ_PKG_PRETTYNAMES=1
 
 .PHONY: pretty-l10n-check
 pretty-l10n-check:
-	$(MAKE) l10n-check MOZ_PKG_PRETTYNAMES=1
+	unset MOZ_SIGN_CMD && $(MAKE) l10n-check MOZ_PKG_PRETTYNAMES=1
 
 .PHONY: pretty-update-packaging
 pretty-update-packaging:
-	$(MAKE) -C tools/update-packaging MOZ_PKG_PRETTYNAMES=1
+	unset MOZ_SIGN_CMD && $(MAKE) -C tools/update-packaging MOZ_PKG_PRETTYNAMES=1
+
+.PHONY: pretty-installer
+pretty-installer:
+	unset MOZ_SIGN_CMD && $(MAKE) installer MOZ_PKG_PRETTYNAMES=1
 
 #XXX: this is a hack, since we don't want to clobber for MSVC
 # PGO support, but we can't do this test in client.mk
 ifneq ($(OS_ARCH)_$(GNU_CC), WINNT_)
 # No point in clobbering if PGO has been explicitly disabled.
 ifndef NO_PROFILE_GUIDED_OPTIMIZE
 maybe_clobber_profiledbuild: clean
 else
--- a/addon-sdk/moz.build
+++ b/addon-sdk/moz.build
@@ -1,13 +1,15 @@
 # -*- 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/.
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
 
+DIRS += ["source/modules/system"]
+
 JS_MODULES_PATH = 'modules/sdk'
 
 EXTRA_JS_MODULES += [
     'source/app-extension/bootstrap.js',
 ]
--- a/addon-sdk/source/lib/sdk/system/xul-app.js
+++ b/addon-sdk/source/lib/sdk/system/xul-app.js
@@ -2,183 +2,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/. */
 "use strict";
 
 module.metadata = {
   "stability": "experimental"
 };
 
-var { Cc, Ci } = require("chrome");
-
-var appInfo = Cc["@mozilla.org/xre/app-info;1"]
-              .getService(Ci.nsIXULAppInfo);
-var vc = Cc["@mozilla.org/xpcom/version-comparator;1"]
-         .getService(Ci.nsIVersionComparator);
-
-var ID = exports.ID = appInfo.ID;
-var name = exports.name = appInfo.name;
-var version = exports.version = appInfo.version;
-var platformVersion = exports.platformVersion = appInfo.platformVersion;
-
-// The following mapping of application names to GUIDs was taken from:
-//
-//   https://addons.mozilla.org/en-US/firefox/pages/appversions
-//
-// Using the GUID instead of the app's name is preferable because sometimes
-// re-branded versions of a product have different names: for instance,
-// Firefox, Minefield, Iceweasel, and Shiretoko all have the same
-// GUID.
-// This mapping is duplicated in `app-extensions/bootstrap.js`. They should keep
-// in sync, so if you change one, change the other too!
-
-var ids = exports.ids = {
-  Firefox: "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}",
-  Mozilla: "{86c18b42-e466-45a9-ae7a-9b95ba6f5640}",
-  Sunbird: "{718e30fb-e89b-41dd-9da7-e25a45638b28}",
-  SeaMonkey: "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}",
-  Fennec: "{aa3c5121-dab2-40e2-81ca-7ea25febc110}",
-  Thunderbird: "{3550f703-e582-4d05-9a08-453d09bdfdc6}"
-};
-
-function is(name) {
-  if (!(name in ids))
-    throw new Error("Unkown Mozilla Application: " + name);
-  return ID == ids[name];
-};
-exports.is = is;
-
-function isOneOf(names) {
-  for (var i = 0; i < names.length; i++)
-    if (is(names[i]))
-      return true;
-  return false;
-};
-exports.isOneOf = isOneOf;
-
-/**
- * Use this to check whether the given version (e.g. xulApp.platformVersion)
- * is in the given range. Versions must be in version comparator-compatible
- * format. See MDC for details:
- * https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIVersionComparator
- */
-var versionInRange = exports.versionInRange =
-function versionInRange(version, lowInclusive, highExclusive) {
-  return (vc.compare(version, lowInclusive) >= 0) &&
-         (vc.compare(version, highExclusive) < 0);
-}
-
-const reVersionRange = /^((?:<|>)?=?)?\s*((?:\d+[\S]*)|\*)(?:\s+((?:<|>)=?)?(\d+[\S]+))?$/;
-const reOnlyInifinity = /^[<>]?=?\s*[*x]$/;
-const reSubInfinity = /\.[*x]/g;
-const reHyphenRange = /^(\d+.*?)\s*-\s*(\d+.*?)$/;
-const reRangeSeparator = /\s*\|\|\s*/;
-
-const compares = {
-  "=": function (c) { return c === 0 },
-  ">=": function (c) { return c >= 0 },
-  "<=": function (c) { return c <= 0},
-  "<": function (c) { return c < 0 },
-  ">": function (c) { return c > 0 }
-}
-
-function normalizeRange(range) {
-    return range
-        .replace(reOnlyInifinity, "")
-        .replace(reSubInfinity, ".*")
-        .replace(reHyphenRange, ">=$1 <=$2")
-}
+var { Cu } = require("chrome");
+var { XulApp } = Cu.import("resource://gre/modules/sdk/system/XulApp.js", {});
 
-/**
- * Compare the versions given, using the comparison operator provided.
- * Internal use only.
- *
- * @example
- *  compareVersion("1.2", "<=", "1.*") // true
- *
- * @param {String} version
- *  A version to compare
- *
- * @param {String} comparison
- *  The comparison operator
- *
- * @param {String} compareVersion
- *  A version to compare
- */
-function compareVersion(version, comparison, compareVersion) {
-  let hasWildcard = compareVersion.indexOf("*") !== -1;
-
-  comparison = comparison || "=";
-
-  if (hasWildcard) {
-    switch (comparison) {
-      case "=":
-        let zeroVersion = compareVersion.replace(reSubInfinity, ".0");
-        return versionInRange(version, zeroVersion, compareVersion);
-      case ">=":
-        compareVersion = compareVersion.replace(reSubInfinity, ".0");
-        break;
-    }
-  }
-
-  let compare = compares[comparison];
-
-  return typeof compare === "function" && compare(vc.compare(version, compareVersion));
-}
-
-/**
- * Returns `true` if `version` satisfies the `versionRange` given.
- * If only an argument is passed, is used as `versionRange` and compared against
- * `xulApp.platformVersion`.
- *
- * `versionRange` is either a string which has one or more space-separated
- * descriptors, or a range like "fromVersion - toVersion".
- * Version range descriptors may be any of the following styles:
- *
- * - "version" Must match `version` exactly
- * - "=version" Same as just `version`
- * - ">version" Must be greater than `version`
- * - ">=version" Must be greater or equal than `version`
- * - "<version" Must be less than `version`
- * - "<=version" Must be less or equal than `version`
- * - "1.2.x" or "1.2.*" See 'X version ranges' below
- * - "*" or "" (just an empty string) Matches any version
- * - "version1 - version2" Same as ">=version1 <=version2"
- * - "range1 || range2" Passes if either `range1` or `range2` are satisfied
- *
- * For example, these are all valid:
- * - "1.0.0 - 2.9999.9999"
- * - ">=1.0.2 <2.1.2"
- * - ">1.0.2 <=2.3.4"
- * - "2.0.1"
- * - "<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0"
- * - "2.x" (equivalent to "2.*")
- * - "1.2.x" (equivalent to "1.2.*" and ">=1.2.0 <1.3.0")
- */
-function satisfiesVersion(version, versionRange) {
-  if (arguments.length === 1) {
-    versionRange = version;
-    version = appInfo.version;
-  }
-
-  let ranges = versionRange.trim().split(reRangeSeparator);
-
-  return ranges.some(function(range) {
-    range = normalizeRange(range);
-
-    // No versions' range specified means that any version satisfies the
-    // requirements.
-    if (range === "")
-      return true;
-
-    let matches = range.match(reVersionRange);
-
-    if (!matches)
-      return false;
-
-    let [, lowMod, lowVer, highMod, highVer] = matches;
-
-    return compareVersion(version, lowMod, lowVer) && (highVer !== undefined
-      ? compareVersion(version, highMod, highVer)
-      : true);
-  });
-}
-exports.satisfiesVersion = satisfiesVersion;
+Object.keys(XulApp).forEach(k => exports[k] = XulApp[k]);
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/modules/system/XulApp.js
@@ -0,0 +1,185 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+var EXPORTED_SYMBOLS = ["XulApp"];
+
+var { classes: Cc, interfaces: Ci } = Components;
+
+var exports = {};
+var XulApp = exports;
+
+var appInfo = Cc["@mozilla.org/xre/app-info;1"]
+              .getService(Ci.nsIXULAppInfo);
+var vc = Cc["@mozilla.org/xpcom/version-comparator;1"]
+         .getService(Ci.nsIVersionComparator);
+
+var ID = exports.ID = appInfo.ID;
+var name = exports.name = appInfo.name;
+var version = exports.version = appInfo.version;
+var platformVersion = exports.platformVersion = appInfo.platformVersion;
+
+// The following mapping of application names to GUIDs was taken from:
+//
+//   https://addons.mozilla.org/en-US/firefox/pages/appversions
+//
+// Using the GUID instead of the app's name is preferable because sometimes
+// re-branded versions of a product have different names: for instance,
+// Firefox, Minefield, Iceweasel, and Shiretoko all have the same
+// GUID.
+// This mapping is duplicated in `app-extensions/bootstrap.js`. They should keep
+// in sync, so if you change one, change the other too!
+
+var ids = exports.ids = {
+  Firefox: "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}",
+  Mozilla: "{86c18b42-e466-45a9-ae7a-9b95ba6f5640}",
+  Sunbird: "{718e30fb-e89b-41dd-9da7-e25a45638b28}",
+  SeaMonkey: "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}",
+  Fennec: "{aa3c5121-dab2-40e2-81ca-7ea25febc110}",
+  Thunderbird: "{3550f703-e582-4d05-9a08-453d09bdfdc6}"
+};
+
+function is(name) {
+  if (!(name in ids))
+    throw new Error("Unkown Mozilla Application: " + name);
+  return ID == ids[name];
+};
+exports.is = is;
+
+function isOneOf(names) {
+  for (var i = 0; i < names.length; i++)
+    if (is(names[i]))
+      return true;
+  return false;
+};
+exports.isOneOf = isOneOf;
+
+/**
+ * Use this to check whether the given version (e.g. xulApp.platformVersion)
+ * is in the given range. Versions must be in version comparator-compatible
+ * format. See MDC for details:
+ * https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIVersionComparator
+ */
+var versionInRange = exports.versionInRange =
+function versionInRange(version, lowInclusive, highExclusive) {
+  return (vc.compare(version, lowInclusive) >= 0) &&
+         (vc.compare(version, highExclusive) < 0);
+}
+
+const reVersionRange = /^((?:<|>)?=?)?\s*((?:\d+[\S]*)|\*)(?:\s+((?:<|>)=?)?(\d+[\S]+))?$/;
+const reOnlyInifinity = /^[<>]?=?\s*[*x]$/;
+const reSubInfinity = /\.[*x]/g;
+const reHyphenRange = /^(\d+.*?)\s*-\s*(\d+.*?)$/;
+const reRangeSeparator = /\s*\|\|\s*/;
+
+const compares = {
+  "=": function (c) { return c === 0 },
+  ">=": function (c) { return c >= 0 },
+  "<=": function (c) { return c <= 0},
+  "<": function (c) { return c < 0 },
+  ">": function (c) { return c > 0 }
+}
+
+function normalizeRange(range) {
+    return range
+        .replace(reOnlyInifinity, "")
+        .replace(reSubInfinity, ".*")
+        .replace(reHyphenRange, ">=$1 <=$2")
+}
+
+/**
+ * Compare the versions given, using the comparison operator provided.
+ * Internal use only.
+ *
+ * @example
+ *  compareVersion("1.2", "<=", "1.*") // true
+ *
+ * @param {String} version
+ *  A version to compare
+ *
+ * @param {String} comparison
+ *  The comparison operator
+ *
+ * @param {String} compareVersion
+ *  A version to compare
+ */
+function compareVersion(version, comparison, compareVersion) {
+  let hasWildcard = compareVersion.indexOf("*") !== -1;
+
+  comparison = comparison || "=";
+
+  if (hasWildcard) {
+    switch (comparison) {
+      case "=":
+        let zeroVersion = compareVersion.replace(reSubInfinity, ".0");
+        return versionInRange(version, zeroVersion, compareVersion);
+      case ">=":
+        compareVersion = compareVersion.replace(reSubInfinity, ".0");
+        break;
+    }
+  }
+
+  let compare = compares[comparison];
+
+  return typeof compare === "function" && compare(vc.compare(version, compareVersion));
+}
+
+/**
+ * Returns `true` if `version` satisfies the `versionRange` given.
+ * If only an argument is passed, is used as `versionRange` and compared against
+ * `xulApp.platformVersion`.
+ *
+ * `versionRange` is either a string which has one or more space-separated
+ * descriptors, or a range like "fromVersion - toVersion".
+ * Version range descriptors may be any of the following styles:
+ *
+ * - "version" Must match `version` exactly
+ * - "=version" Same as just `version`
+ * - ">version" Must be greater than `version`
+ * - ">=version" Must be greater or equal than `version`
+ * - "<version" Must be less than `version`
+ * - "<=version" Must be less or equal than `version`
+ * - "1.2.x" or "1.2.*" See 'X version ranges' below
+ * - "*" or "" (just an empty string) Matches any version
+ * - "version1 - version2" Same as ">=version1 <=version2"
+ * - "range1 || range2" Passes if either `range1` or `range2` are satisfied
+ *
+ * For example, these are all valid:
+ * - "1.0.0 - 2.9999.9999"
+ * - ">=1.0.2 <2.1.2"
+ * - ">1.0.2 <=2.3.4"
+ * - "2.0.1"
+ * - "<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0"
+ * - "2.x" (equivalent to "2.*")
+ * - "1.2.x" (equivalent to "1.2.*" and ">=1.2.0 <1.3.0")
+ */
+function satisfiesVersion(version, versionRange) {
+  if (arguments.length === 1) {
+    versionRange = version;
+    version = appInfo.version;
+  }
+
+  let ranges = versionRange.trim().split(reRangeSeparator);
+
+  return ranges.some(function(range) {
+    range = normalizeRange(range);
+
+    // No versions' range specified means that any version satisfies the
+    // requirements.
+    if (range === "")
+      return true;
+
+    let matches = range.match(reVersionRange);
+
+    if (!matches)
+      return false;
+
+    let [, lowMod, lowVer, highMod, highVer] = matches;
+
+    return compareVersion(version, lowMod, lowVer) && (highVer !== undefined
+      ? compareVersion(version, highMod, highVer)
+      : true);
+  });
+}
+exports.satisfiesVersion = satisfiesVersion;
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/modules/system/moz.build
@@ -0,0 +1,11 @@
+# -*- 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/.
+
+JS_MODULES_PATH = 'modules/sdk/system'
+
+EXTRA_JS_MODULES += [
+    'XulApp.js',
+]
--- a/browser/components/loop/content/shared/js/models.js
+++ b/browser/components/loop/content/shared/js/models.js
@@ -112,18 +112,17 @@ loop.shared.models = (function() {
         }
       }
 
       function handleResult(err, sessionData) {
         /*jshint validthis:true */
         this._clearPendingCallTimer();
 
         if (err) {
-          this.trigger("session:error", new Error(
-            "Retrieval of session information failed: HTTP " + err));
+          this._handleServerError(err);
           return;
         }
 
         if (options.outgoing) {
           // Setup pending call timeout.
           this._pendingCallTimer = setTimeout(
             handleOutgoingCallTimeout.bind(this), this.pendingCallTimeout);
         } else {
@@ -196,16 +195,41 @@ loop.shared.models = (function() {
      */
     endSession: function() {
       this.session.disconnect();
       this.set("ongoing", false)
           .once("session:ended", this.stopListening, this);
     },
 
     /**
+     * Handle a loop-server error, which has an optional `errno` property which
+     * is server error identifier.
+     *
+     * Triggers the following events:
+     *
+     * - `session:expired` for expired call urls
+     * - `session:error` for other generic errors
+     *
+     * @param  {Error} err Error object.
+     */
+    _handleServerError: function(err) {
+      switch (err.errno) {
+        // loop-server sends 404 + INVALID_TOKEN (errno 105) whenever a token is
+        // missing OR expired; we treat this information as if the url is always
+        // expired.
+        case 105:
+          this.trigger("session:expired", err);
+          break;
+        default:
+          this.trigger("session:error", err);
+          break;
+      }
+    },
+
+    /**
      * Clears current pending call timer, if any.
      */
     _clearPendingCallTimer: function() {
       if (this._pendingCallTimer) {
         clearTimeout(this._pendingCallTimer);
       }
     },
 
--- a/browser/components/loop/standalone/content/js/standaloneClient.js
+++ b/browser/components/loop/standalone/content/js/standaloneClient.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/. */
 
-/* global loop:true, hawk, deriveHawkCredentials */
+/* global loop:true */
 
 var loop = loop || {};
 loop.StandaloneClient = (function($) {
   "use strict";
 
   // The expected properties to be returned from the POST /calls request.
   var expectedCallsProperties = [ "sessionId", "sessionToken", "apiKey" ];
 
@@ -41,52 +41,43 @@ loop.StandaloneClient = (function($) {
 
       properties.forEach(function (property) {
         if (!data.hasOwnProperty(property)) {
           throw new Error("Invalid data received from server - missing " +
             property);
         }
       });
 
-      if (properties.length == 1) {
+      if (properties.length === 1) {
         return data[properties[0]];
       }
 
       return data;
     },
 
     /**
      * Generic handler for XHR failures.
      *
      * @param {Function} cb Callback(err)
      * @param jqXHR See jQuery docs
      * @param textStatus See jQuery docs
      * @param errorThrown See jQuery docs
      */
     _failureHandler: function(cb, jqXHR, textStatus, errorThrown) {
-      var error = "Unknown error.",
-          jsonRes = jqXHR && jqXHR.responseJSON || {};
-      // Received error response format:
-      // { "status": "errors",
-      //   "errors": [{
-      //      "location": "url",
-      //      "name": "token",
-      //      "description": "invalid token"
-      // }]}
-      if (jsonRes.status === "errors" && Array.isArray(jsonRes.errors)) {
-        error = "Details: " + jsonRes.errors.map(function(err) {
-          return Object.keys(err).map(function(field) {
-            return field + ": " + err[field];
-          }).join(", ");
-        }).join("; ");
-      }
-      var message = "HTTP " + jqXHR.status + " " + errorThrown +
-          "; " + error;
-      console.error(message);
-      cb(new Error(message));
+      var jsonErr = jqXHR && jqXHR.responseJSON || {};
+      var message = "HTTP " + jqXHR.status + " " + errorThrown;
+
+      // Logging the technical error to the console
+      console.error("Server error", message, jsonErr);
+
+      // Create an error with server error `errno` code attached as a property
+      var err = new Error(message);
+      err.errno = jsonErr.errno;
+
+      cb(err);
     },
 
     /**
      * Posts a call request to the server for a call represented by the
      * loopToken. Will return the session data for the call.
      *
      * @param  {String} loopToken The loopToken representing the call
      * @param  {String} callType The type of media in the call, e.g.
--- a/browser/components/loop/standalone/content/js/webapp.js
+++ b/browser/components/loop/standalone/content/js/webapp.js
@@ -1,39 +1,57 @@
+/** @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 */
+/* global loop:true, React */
+/* jshint newcap:false */
 
 var loop = loop || {};
-loop.webapp = (function($, _, OT) {
+loop.webapp = (function($, _, OT, webL10n) {
   "use strict";
 
   loop.config = loop.config || {};
   loop.config.serverUrl = loop.config.serverUrl || "http://localhost:5000";
 
   var sharedModels = loop.shared.models,
       sharedViews = loop.shared.views,
-      baseServerUrl = loop.config.serverUrl;
+      baseServerUrl = loop.config.serverUrl,
+      __ = webL10n.get;
 
   /**
    * App router.
    * @type {loop.webapp.WebappRouter}
    */
   var router;
 
   /**
    * Homepage view.
    */
   var HomeView = sharedViews.BaseView.extend({
     template: _.template('<p data-l10n-id="welcome"></p>')
   });
 
   /**
+   * Expired call URL view.
+   */
+  var CallUrlExpiredView = React.createClass({displayName: 'CallUrlExpiredView',
+    render: function() {
+      /* jshint ignore:start */
+      return (
+        // XXX proper UX/design should be implemented here (see bug 1000131)
+        React.DOM.div(null, __("call_url_unavailable_notification"))
+      );
+      /* jshint ignore:end */
+    }
+  });
+
+  /**
    * Conversation launcher view. A ConversationModel is associated and attached
    * as a `model` property.
    */
   var ConversationFormView = sharedViews.BaseView.extend({
     template: _.template([
       '<form>',
       '  <p>',
       '    <button class="btn btn-success" data-l10n-id="start_call"></button>',
@@ -88,17 +106,17 @@ loop.webapp = (function($, _, OT) {
      * Initiates the call.
      *
      * @param {SubmitEvent} event
      */
     initiate: function(event) {
       event.preventDefault();
       this.model.initiate({
         client: new loop.StandaloneClient({
-          baseServerUrl: baseServerUrl,
+          baseServerUrl: baseServerUrl
         }),
         outgoing: true,
         // For now, we assume both audio and video as there is no
         // other option to select.
         callType: "audio-video"
       });
       this.disableForm();
     }
@@ -107,25 +125,32 @@ loop.webapp = (function($, _, OT) {
   /**
    * Webapp Router.
    */
   var WebappRouter = loop.shared.router.BaseConversationRouter.extend({
     routes: {
       "":                    "home",
       "unsupportedDevice":   "unsupportedDevice",
       "unsupportedBrowser":  "unsupportedBrowser",
+      "call/expired":        "expired",
       "call/ongoing/:token": "loadConversation",
       "call/:token":         "initiate"
     },
 
     initialize: function() {
       // Load default view
       this.loadView(new HomeView());
 
       this.listenTo(this._conversation, "timeout", this._onTimeout);
+      this.listenTo(this._conversation, "session:expired",
+                    this._onSessionExpired);
+    },
+
+    _onSessionExpired: function() {
+      this.navigate("/call/expired", {trigger: true});
     },
 
     /**
      * @override {loop.shared.router.BaseConversationRouter.startCall}
      */
     startCall: function() {
       if (!this._conversation.get("loopToken")) {
         this._notifier.errorL10n("missing_conversation_info");
@@ -162,16 +187,20 @@ loop.webapp = (function($, _, OT) {
     unsupportedDevice: function() {
       this.loadView(new sharedViews.UnsupportedDeviceView());
     },
 
     unsupportedBrowser: function() {
       this.loadView(new sharedViews.UnsupportedBrowserView());
     },
 
+    expired: function() {
+      this.loadReactComponent(CallUrlExpiredView());
+    },
+
     /**
      * Loads conversation launcher view, setting the received conversation token
      * to the current conversation model. If a session is currently established,
      * terminates it first.
      *
      * @param  {String} loopToken Loop conversation token.
      */
     initiate: function(loopToken) {
@@ -230,15 +259,16 @@ loop.webapp = (function($, _, OT) {
       router.navigate("unsupportedDevice", {trigger: true});
     } else if (!OT.checkSystemRequirements()) {
       router.navigate("unsupportedBrowser", {trigger: true});
     }
   }
 
   return {
     baseServerUrl: baseServerUrl,
+    CallUrlExpiredView: CallUrlExpiredView,
     ConversationFormView: ConversationFormView,
     HomeView: HomeView,
     WebappHelper: WebappHelper,
     init: init,
     WebappRouter: WebappRouter
   };
-})(jQuery, _, window.OT);
+})(jQuery, _, window.OT, document.webL10n);
copy from browser/components/loop/standalone/content/js/webapp.js
copy to browser/components/loop/standalone/content/js/webapp.jsx
--- a/browser/components/loop/standalone/content/js/webapp.js
+++ b/browser/components/loop/standalone/content/js/webapp.jsx
@@ -1,39 +1,57 @@
+/** @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 */
+/* global loop:true, React */
+/* jshint newcap:false */
 
 var loop = loop || {};
-loop.webapp = (function($, _, OT) {
+loop.webapp = (function($, _, OT, webL10n) {
   "use strict";
 
   loop.config = loop.config || {};
   loop.config.serverUrl = loop.config.serverUrl || "http://localhost:5000";
 
   var sharedModels = loop.shared.models,
       sharedViews = loop.shared.views,
-      baseServerUrl = loop.config.serverUrl;
+      baseServerUrl = loop.config.serverUrl,
+      __ = webL10n.get;
 
   /**
    * App router.
    * @type {loop.webapp.WebappRouter}
    */
   var router;
 
   /**
    * Homepage view.
    */
   var HomeView = sharedViews.BaseView.extend({
     template: _.template('<p data-l10n-id="welcome"></p>')
   });
 
   /**
+   * Expired call URL view.
+   */
+  var CallUrlExpiredView = React.createClass({
+    render: function() {
+      /* jshint ignore:start */
+      return (
+        // XXX proper UX/design should be implemented here (see bug 1000131)
+        <div>{__("call_url_unavailable_notification")}</div>
+      );
+      /* jshint ignore:end */
+    }
+  });
+
+  /**
    * Conversation launcher view. A ConversationModel is associated and attached
    * as a `model` property.
    */
   var ConversationFormView = sharedViews.BaseView.extend({
     template: _.template([
       '<form>',
       '  <p>',
       '    <button class="btn btn-success" data-l10n-id="start_call"></button>',
@@ -88,17 +106,17 @@ loop.webapp = (function($, _, OT) {
      * Initiates the call.
      *
      * @param {SubmitEvent} event
      */
     initiate: function(event) {
       event.preventDefault();
       this.model.initiate({
         client: new loop.StandaloneClient({
-          baseServerUrl: baseServerUrl,
+          baseServerUrl: baseServerUrl
         }),
         outgoing: true,
         // For now, we assume both audio and video as there is no
         // other option to select.
         callType: "audio-video"
       });
       this.disableForm();
     }
@@ -107,25 +125,32 @@ loop.webapp = (function($, _, OT) {
   /**
    * Webapp Router.
    */
   var WebappRouter = loop.shared.router.BaseConversationRouter.extend({
     routes: {
       "":                    "home",
       "unsupportedDevice":   "unsupportedDevice",
       "unsupportedBrowser":  "unsupportedBrowser",
+      "call/expired":        "expired",
       "call/ongoing/:token": "loadConversation",
       "call/:token":         "initiate"
     },
 
     initialize: function() {
       // Load default view
       this.loadView(new HomeView());
 
       this.listenTo(this._conversation, "timeout", this._onTimeout);
+      this.listenTo(this._conversation, "session:expired",
+                    this._onSessionExpired);
+    },
+
+    _onSessionExpired: function() {
+      this.navigate("/call/expired", {trigger: true});
     },
 
     /**
      * @override {loop.shared.router.BaseConversationRouter.startCall}
      */
     startCall: function() {
       if (!this._conversation.get("loopToken")) {
         this._notifier.errorL10n("missing_conversation_info");
@@ -162,16 +187,20 @@ loop.webapp = (function($, _, OT) {
     unsupportedDevice: function() {
       this.loadView(new sharedViews.UnsupportedDeviceView());
     },
 
     unsupportedBrowser: function() {
       this.loadView(new sharedViews.UnsupportedBrowserView());
     },
 
+    expired: function() {
+      this.loadReactComponent(CallUrlExpiredView());
+    },
+
     /**
      * Loads conversation launcher view, setting the received conversation token
      * to the current conversation model. If a session is currently established,
      * terminates it first.
      *
      * @param  {String} loopToken Loop conversation token.
      */
     initiate: function(loopToken) {
@@ -230,15 +259,16 @@ loop.webapp = (function($, _, OT) {
       router.navigate("unsupportedDevice", {trigger: true});
     } else if (!OT.checkSystemRequirements()) {
       router.navigate("unsupportedBrowser", {trigger: true});
     }
   }
 
   return {
     baseServerUrl: baseServerUrl,
+    CallUrlExpiredView: CallUrlExpiredView,
     ConversationFormView: ConversationFormView,
     HomeView: HomeView,
     WebappHelper: WebappHelper,
     init: init,
     WebappRouter: WebappRouter
   };
-})(jQuery, _, window.OT);
+})(jQuery, _, window.OT, document.webL10n);
--- a/browser/components/loop/standalone/content/l10n/data.ini
+++ b/browser/components/loop/standalone/content/l10n/data.ini
@@ -14,16 +14,17 @@ 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.
+call_url_unavailable_notification=This URL is unavailable.
 
 [fr]
 call_has_ended=L'appel est terminé.
 call_timeout_notification_text=Votre appel n'a pas abouti.
 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.
@@ -35,8 +36,9 @@ 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.
+call_url_unavailable_notification=Cette URL n'est pas disponible.
--- a/browser/components/loop/test/shared/models_test.js
+++ b/browser/components/loop/test/shared/models_test.js
@@ -137,28 +137,52 @@ describe("loop.shared.models", function(
               client: fakeClient,
               outgoing: true
             });
 
             sinon.assert.calledOnce(conversation.setReady);
             sinon.assert.calledWith(conversation.setReady, fakeSessionData);
           });
 
-        it("should trigger a `session:error` on failure", function(done) {
-          requestCallInfoStub.callsArgWith(2,
-            new Error("failed: HTTP 400 Bad Request; fake"));
+        it("should trigger a `session:error` event errno is undefined",
+          function(done) {
+            var errMsg = "HTTP 500 Server Error; fake";
+            var err = new Error(errMsg);
+            requestCallInfoStub.callsArgWith(2, err);
+
+            conversation.on("session:error", function(err) {
+              expect(err.message).eql(errMsg);
+              done();
+            }).initiate({ client: fakeClient, outgoing: true });
+          });
 
-          conversation.on("session:error", function(err) {
-            expect(err.message).to.match(/failed: HTTP 400 Bad Request; fake/);
-            done();
-          }).initiate({
-            client: fakeClient,
-            outgoing: true
+        it("should trigger a `session:error` event when errno is not 105",
+          function(done) {
+            var errMsg = "HTTP 400 Bad Request; fake";
+            var err = new Error(errMsg);
+            err.errno = 101;
+            requestCallInfoStub.callsArgWith(2, err);
+
+            conversation.on("session:error", function(err) {
+              expect(err.message).eql(errMsg);
+              done();
+            }).initiate({ client: fakeClient, outgoing: true });
           });
-        });
+
+        it("should trigger a `session:expired` event when errno is 105",
+          function(done) {
+            var err = new Error("HTTP 404 Not Found; fake");
+            err.errno = 105;
+            requestCallInfoStub.callsArgWith(2, err);
+
+            conversation.on("session:expired", function(err2) {
+              expect(err2).eql(err);
+              done();
+            }).initiate({ client: fakeClient, outgoing: true });
+          });
 
         it("should end the session on outgoing call timeout", function() {
           requestCallInfoStub.callsArgWith(2, null, fakeSessionData);
 
           conversation.initiate({
             client: fakeClient,
             outgoing: true
           });
--- a/browser/components/loop/test/standalone/standalone_client_test.js
+++ b/browser/components/loop/test/standalone/standalone_client_test.js
@@ -10,25 +10,16 @@ describe("loop.StandaloneClient", functi
   "use strict";
 
   var sandbox,
       fakeXHR,
       requests = [],
       callback,
       fakeToken;
 
-  var fakeErrorRes = JSON.stringify({
-      status: "errors",
-      errors: [{
-        location: "url",
-        name: "token",
-        description: "invalid token"
-      }]
-    });
-
   beforeEach(function() {
     sandbox = sinon.sandbox.create();
     fakeXHR = sandbox.useFakeXMLHttpRequest();
     requests = [];
     // https://github.com/cjohansen/Sinon.JS/issues/393
     fakeXHR.xhr.onCreate = function (xhr) {
       requests.push(xhr);
     };
@@ -45,22 +36,29 @@ describe("loop.StandaloneClient", functi
       it("should require a baseServerUrl setting", function() {
         expect(function() {
           new loop.StandaloneClient();
         }).to.Throw(Error, /required/);
       });
     });
 
     describe("requestCallInfo", function() {
-      var client;
+      var client, fakeServerErrorDescription;
 
       beforeEach(function() {
         client = new loop.StandaloneClient(
           {baseServerUrl: "http://fake.api"}
         );
+        fakeServerErrorDescription = {
+          code: 401,
+          errno: 101,
+          error: "error",
+          message: "invalid token",
+          info: "error info"
+        };
       });
 
       it("should prevent launching a conversation when token is missing",
         function() {
           expect(function() {
             client.requestCallInfo();
           }).to.Throw(Error, /missing.*[Tt]oken/);
         });
@@ -86,23 +84,36 @@ describe("loop.StandaloneClient", functi
         requests[0].respond(200, {"Content-Type": "application/json"},
                             JSON.stringify(sessionData));
         sinon.assert.calledWithExactly(callback, null, sessionData);
       });
 
       it("should send an error when the request fails", function() {
         client.requestCallInfo("fake", "audio", callback);
 
-        requests[0].respond(400, {"Content-Type": "application/json"},
-                            fakeErrorRes);
+        requests[0].respond(401, {"Content-Type": "application/json"},
+                            JSON.stringify(fakeServerErrorDescription));
         sinon.assert.calledWithMatch(callback, sinon.match(function(err) {
-          return /400.*invalid token/.test(err.message);
+          return /HTTP 401 Unauthorized/.test(err.message);
         }));
       });
 
+      it("should attach the server error description object to the error " +
+         "passed to the callback",
+        function() {
+          client.requestCallInfo("fake", "audio", callback);
+
+          requests[0].respond(401, {"Content-Type": "application/json"},
+                              JSON.stringify(fakeServerErrorDescription));
+
+          sinon.assert.calledWithMatch(callback, sinon.match(function(err) {
+            return err.errno === fakeServerErrorDescription.errno;
+          }));
+        });
+
       it("should send an error if the data is not valid", function() {
         client.requestCallInfo("fake", "audio", callback);
 
         requests[0].respond(200, {"Content-Type": "application/json"},
                             '{"bad": "one"}');
         sinon.assert.calledWithMatch(callback, sinon.match(function(err) {
           return /Invalid data received/.test(err.message);
         }));
--- a/browser/components/loop/test/standalone/webapp_test.js
+++ b/browser/components/loop/test/standalone/webapp_test.js
@@ -135,16 +135,29 @@ describe("loop.webapp", function() {
           router.home();
 
           sinon.assert.calledOnce(router.loadView);
           sinon.assert.calledWith(router.loadView,
             sinon.match.instanceOf(loop.webapp.HomeView));
         });
       });
 
+      describe("#expired", function() {
+        it("should load the CallUrlExpiredView view", function() {
+          router.expired();
+
+          sinon.assert.calledOnce(router.loadReactComponent);
+          sinon.assert.calledWith(router.loadReactComponent,
+            sinon.match(function(value) {
+              return React.addons.TestUtils.isComponentOfType(
+                value, loop.webapp.CallUrlExpiredView);
+            }));
+        });
+      });
+
       describe("#initiate", function() {
         it("should set the token on the conversation model", function() {
           router.initiate("fakeToken");
 
           expect(conversation.get("loopToken")).eql("fakeToken");
         });
 
         it("should load the ConversationFormView", function() {
@@ -246,16 +259,24 @@ describe("loop.webapp", function() {
 
       it("should navigate to call/{token} when network disconnects",
         function() {
           conversation.trigger("session:network-disconnected");
 
           sinon.assert.calledOnce(router.navigate);
           sinon.assert.calledWithMatch(router.navigate, "call/fakeToken");
         });
+
+      it("should navigate to call/expired when a session:expired event is " +
+         "received", function() {
+        conversation.trigger("session:expired");
+
+        sinon.assert.calledOnce(router.navigate);
+        sinon.assert.calledWith(router.navigate, "/call/expired");
+      });
     });
   });
 
   describe("ConversationFormView", function() {
     var conversation;
 
     beforeEach(function() {
       conversation = new sharedModels.ConversationModel({}, {
--- a/browser/components/preferences/fonts.xul
+++ b/browser/components/preferences/fonts.xul
@@ -12,25 +12,17 @@
 
 <!DOCTYPE prefwindow SYSTEM "chrome://browser/locale/preferences/fonts.dtd" >
 
 <prefwindow id="FontsDialog" type="child"
             xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
             title="&fontsDialog.title;"
             dlgbuttons="accept,cancel,help"
             ondialoghelp="openPrefsHelp()"
-#ifdef XP_UNIX
-#ifdef XP_MACOSX
-            style="width: &window.macWidth; !important;">
-#else
-            style="width: &window.unixWidth; !important;">
-#endif
-#else
-            style="width: &window.width; !important;">
-#endif
+            style="">
 
   <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
 
   <prefpane id="FontsDialogPane"
             helpTopic="prefs-fonts-and-colors">
   
     <preferences id="fontPreferences">
       <preference id="font.language.group"  name="font.language.group"  type="wstring"/>
--- a/browser/components/preferences/in-content/content.js
+++ b/browser/components/preferences/in-content/content.js
@@ -148,18 +148,17 @@ var gContentPane = {
   },
 
   /**
    * Displays the fonts dialog, where web page font names and sizes can be
    * configured.
    */  
   configureFonts: function ()
   {
-    openDialog("chrome://browser/content/preferences/fonts.xul", 
-               "Browser:FontPreferences", null);
+    gSubDialog.open("chrome://browser/content/preferences/fonts.xul");
   },
 
   /**
    * Displays the colors dialog, where default web page/link/etc. colors can be
    * configured.
    */
   configureColors: function ()
   {
@@ -177,18 +176,17 @@ var gContentPane = {
   },
 
   /**
    * Displays the translation exceptions dialog where specific site and language
    * translation preferences can be set.
    */
   showTranslationExceptions: function ()
   {
-    openDialog("chrome://browser/content/preferences/translation.xul",
-               "Browser:TranslationExceptions", null);
+    gSubDialog.open("chrome://browser/content/preferences/translation.xul");
   },
 
   openTranslationProviderAttribution: function ()
   {
     Components.utils.import("resource:///modules/translation/Translation.jsm");
     Translation.openProviderAttribution();
   }
 };
--- a/browser/components/tabview/test/browser_tabview_bug598600.js
+++ b/browser/components/tabview/test/browser_tabview_bug598600.js
@@ -4,17 +4,17 @@
 let newWin;
 function test() {
   let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
 
   requestLongerTimeout(2);
   waitForExplicitFinish();
 
   // open a new window and setup the window state.
-  newWin = openDialog(getBrowserURL(), "_blank", "chrome,all,dialog=no");
+  newWin = openDialog(getBrowserURL(), "_blank", "chrome,all,dialog=no", "about:blank");
   whenWindowLoaded(newWin, function () {
     let newState = {
       windows: [{
         tabs: [{
           entries: [{ "url": "about:blank" }],
           hidden: true,
           attributes: {},
           extData: {
--- a/browser/components/tabview/test/browser_tabview_bug651311.js
+++ b/browser/components/tabview/test/browser_tabview_bug651311.js
@@ -24,11 +24,11 @@ function test() {
       ok(win.TabView.getContentWindow().UI, "content window is loaded");
       finish();
     });
   });
 }
 
 function newWindow(callback) {
   let opts = "chrome,all,dialog=no,height=800,width=800";
-  let win = window.openDialog(getBrowserURL(), "_blank", opts);
+  let win = window.openDialog(getBrowserURL(), "_blank", opts, "about:blank");
   whenDelayedStartupFinished(win, () => callback(win));
 }
--- a/browser/components/tabview/test/head.js
+++ b/browser/components/tabview/test/head.js
@@ -100,17 +100,17 @@ function afterAllTabItemsUpdated(callbac
 }
 
 // ---------
 function newWindowWithTabView(shownCallback, loadCallback, width, height) {
   let winWidth = width || 800;
   let winHeight = height || 800;
   let win = window.openDialog(getBrowserURL(), "_blank",
                               "chrome,all,dialog=no,height=" + winHeight +
-                              ",width=" + winWidth);
+                              ",width=" + winWidth, "about:blank");
 
   whenWindowLoaded(win, function () {
     if (loadCallback)
       loadCallback(win);
   });
 
   whenDelayedStartupFinished(win, function () {
     showTabView(function () shownCallback(win), win);
@@ -328,17 +328,17 @@ function whenDelayedStartupFinished(win,
 }
 
 // ----------
 function newWindowWithState(state, callback) {
   const ss = Cc["@mozilla.org/browser/sessionstore;1"]
              .getService(Ci.nsISessionStore);
 
   let opts = "chrome,all,dialog=no,height=800,width=800";
-  let win = window.openDialog(getBrowserURL(), "_blank", opts);
+  let win = window.openDialog(getBrowserURL(), "_blank", opts, "about:blank");
 
   let numConditions = 2;
   let check = function () {
     if (!--numConditions)
       callback(win);
   };
 
   whenDelayedStartupFinished(win, function () {
@@ -428,8 +428,24 @@ function waitForOnBeforeUnloadDialog(bro
     executeSoon(() => {
       let stack = browser.parentNode;
       let dialogs = stack.getElementsByTagNameNS(XUL_NS, "tabmodalprompt");
       let {button0, button1} = dialogs[0].ui;
       callback(button0, button1);
     });
   }, true);
 }
+
+/**
+ * Overrides browser.js' OpenBrowserWindow() function to enforce an initial
+ * tab different from about:home to not hit the network.
+ */
+function OpenBrowserWindow(aOptions) {
+  let features = "";
+  let url = "about:blank";
+
+  if (aOptions && aOptions.private || false) {
+    features = ",private";
+    url = "about:privatebrowsing";
+  }
+
+  return openDialog(getBrowserURL(), "", "chrome,all,dialog=no" + features, url);
+}
--- a/browser/config/mozconfigs/macosx-universal/nightly-nonunified
+++ b/browser/config/mozconfigs/macosx-universal/nightly-nonunified
@@ -1,3 +1,8 @@
+MOZ_AUTOMATION_PRETTY=1
+MOZ_AUTOMATION_UPLOAD=0
+MOZ_AUTOMATION_UNITTEST_SENDCHANGE=0
+MOZ_AUTOMATION_TALOS_SENDCHANGE=0
+MOZ_AUTOMATION_CHECK=0
 . "$topsrcdir/browser/config/mozconfigs/macosx-universal/nightly"
 
 ac_add_options --disable-unified-compilation
--- a/browser/config/mozconfigs/macosx64/debug
+++ b/browser/config/mozconfigs/macosx64/debug
@@ -1,8 +1,9 @@
+MOZ_AUTOMATION_TALOS_SENDCHANGE=0
 . $topsrcdir/build/macosx/mozconfig.common
 
 ac_add_options --enable-debug
 ac_add_options --enable-dmd
 ac_add_options --enable-accessibility
 ac_add_options --enable-signmar
 
 # Needed to enable breakpad in application.ini
--- a/browser/config/mozconfigs/macosx64/debug-nonunified
+++ b/browser/config/mozconfigs/macosx64/debug-nonunified
@@ -1,3 +1,7 @@
+MOZ_AUTOMATION_UPLOAD=0
+MOZ_AUTOMATION_UNITTEST_SENDCHANGE=0
+MOZ_AUTOMATION_TALOS_SENDCHANGE=0
+MOZ_AUTOMATION_CHECK=0
 . "$topsrcdir/browser/config/mozconfigs/macosx64/debug"
 
 ac_add_options --disable-unified-compilation
--- a/browser/config/mozconfigs/win32/beta
+++ b/browser/config/mozconfigs/win32/beta
@@ -1,7 +1,8 @@
+. "$topsrcdir/build/mozconfig.win-common"
 . "$topsrcdir/browser/config/mozconfigs/win32/common-opt"
 
 mk_add_options MOZ_PGO=1
 
 ac_add_options --enable-official-branding
 
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/mozconfigs/win32/debug
+++ b/browser/config/mozconfigs/win32/debug
@@ -1,8 +1,11 @@
+. "$topsrcdir/build/mozconfig.win-common"
+MOZ_AUTOMATION_L10N_CHECK=0
+MOZ_AUTOMATION_TALOS_SENDCHANGE=0
 . "$topsrcdir/browser/config/mozconfigs/common"
 
 ac_add_options --enable-debug
 ac_add_options --enable-dmd
 ac_add_options --enable-profiling  # needed for --enable-dmd to work on Windows
 ac_add_options --enable-signmar
 ac_add_options --enable-require-all-d3dc-versions
 
--- a/browser/config/mozconfigs/win32/debug-nonunified
+++ b/browser/config/mozconfigs/win32/debug-nonunified
@@ -1,3 +1,9 @@
+. "$topsrcdir/build/mozconfig.win-common"
+MOZ_AUTOMATION_CHECK=0
+MOZ_AUTOMATION_L10N_CHECK=0
+MOZ_AUTOMATION_TALOS_SENDCHANGE=0
+MOZ_AUTOMATION_UNITTEST_SENDCHANGE=0
+MOZ_AUTOMATION_UPLOAD=0
 . "$topsrcdir/browser/config/mozconfigs/win32/debug"
 
 ac_add_options --disable-unified-compilation
--- a/browser/config/mozconfigs/win32/nightly
+++ b/browser/config/mozconfigs/win32/nightly
@@ -1,8 +1,9 @@
+. "$topsrcdir/build/mozconfig.win-common"
 . "$topsrcdir/browser/config/mozconfigs/win32/common-opt"
 
 ac_add_options --enable-signmar
 ac_add_options --enable-profiling
 
 # Nightlies only since this has a cost in performance
 ac_add_options --enable-js-diagnostics
 
--- a/browser/config/mozconfigs/win32/nightly-nonunified
+++ b/browser/config/mozconfigs/win32/nightly-nonunified
@@ -1,3 +1,9 @@
+. "$topsrcdir/build/mozconfig.win-common"
+MOZ_AUTOMATION_PRETTY=1
+MOZ_AUTOMATION_UPLOAD=0
+MOZ_AUTOMATION_UNITTEST_SENDCHANGE=0
+MOZ_AUTOMATION_TALOS_SENDCHANGE=0
+MOZ_AUTOMATION_CHECK=0
 . "$topsrcdir/browser/config/mozconfigs/win32/nightly"
 
 ac_add_options --disable-unified-compilation
--- a/browser/config/mozconfigs/win32/release
+++ b/browser/config/mozconfigs/win32/release
@@ -1,10 +1,11 @@
 # This make file should be identical to the beta mozconfig, apart from the
 # safeguard below
+. "$topsrcdir/build/mozconfig.win-common"
 . "$topsrcdir/browser/config/mozconfigs/win32/common-opt"
 
 mk_add_options MOZ_PGO=1
 
 ac_add_options --enable-official-branding
 
 # safeguard against someone forgetting to re-set EARLY_BETA_OR_EARLIER in
 # defines.sh during the beta cycle
--- a/browser/config/mozconfigs/win64/debug
+++ b/browser/config/mozconfigs/win64/debug
@@ -1,8 +1,11 @@
+. "$topsrcdir/build/mozconfig.win-common"
+MOZ_AUTOMATION_L10N_CHECK=0
+MOZ_AUTOMATION_TALOS_SENDCHANGE=0
 . "$topsrcdir/browser/config/mozconfigs/common"
 
 ac_add_options --target=x86_64-pc-mingw32
 ac_add_options --host=x86_64-pc-mingw32
 
 ac_add_options --enable-debug
 ac_add_options --enable-dmd
 ac_add_options --enable-profiling  # needed for --enable-dmd to work on Windows
--- a/browser/config/mozconfigs/win64/nightly
+++ b/browser/config/mozconfigs/win64/nightly
@@ -1,8 +1,9 @@
+. "$topsrcdir/build/mozconfig.win-common"
 . "$topsrcdir/browser/config/mozconfigs/win64/common-win64"
 . "$topsrcdir/browser/config/mozconfigs/win64/common-opt"
 
 ac_add_options --enable-signmar
 ac_add_options --enable-profiling
 
 # Nightlies only since this has a cost in performance
 ac_add_options --enable-js-diagnostics
--- a/browser/confvars.sh
+++ b/browser/confvars.sh
@@ -60,11 +60,10 @@ if test "$OS_TARGET" = "WINNT" -o "$OS_T
 fi
 MOZ_WEBGL_CONFORMANT=1
 # Enable navigator.mozPay
 MOZ_PAY=1
 # Enable activities. These are used for FxOS developers currently.
 MOZ_ACTIVITIES=1
 MOZ_JSDOWNLOADS=1
 MOZ_WEBM_ENCODER=1
-# Enable exact rooting and generational GC on desktop.
-JSGC_USE_EXACT_ROOTING=1
+# Enable generational GC on desktop.
 JSGC_GENERATIONAL=1
--- a/browser/devtools/shared/widgets/VariablesView.jsm
+++ b/browser/devtools/shared/widgets/VariablesView.jsm
@@ -3638,19 +3638,24 @@ VariablesView.stringifiers.byObjectKind 
     return result + " {" + props.join(", ") + "}";
   }, // DOMEvent
 
   DOMNode: function(aGrip, {concise}) {
     let {preview} = aGrip;
 
     switch (preview.nodeType) {
       case Ci.nsIDOMNode.DOCUMENT_NODE: {
-        let location = WebConsoleUtils.abbreviateSourceURL(preview.location,
-                                                           { onlyCropQuery: !concise });
-        return aGrip.class + " \u2192 " + location;
+        let result = aGrip.class;
+        if (preview.location) {
+          let location = WebConsoleUtils.abbreviateSourceURL(preview.location,
+                                                            { onlyCropQuery: !concise });
+          result += " \u2192 " + location;
+        }
+
+        return result;
       }
 
       case Ci.nsIDOMNode.ATTRIBUTE_NODE: {
         let value = VariablesView.getString(preview.value, { noStringQuotes: true });
         return preview.nodeName + '="' + escapeHTML(value) + '"';
       }
 
       case Ci.nsIDOMNode.TEXT_NODE:
--- a/browser/devtools/webconsole/test/browser.ini
+++ b/browser/devtools/webconsole/test/browser.ini
@@ -245,16 +245,17 @@ run-if = os == "mac"
 [browser_webconsole_console_extras.js]
 [browser_webconsole_console_logging_api.js]
 [browser_webconsole_count.js]
 [browser_webconsole_dont_navigate_on_doubleclick.js]
 [browser_webconsole_execution_scope.js]
 [browser_webconsole_for_of.js]
 [browser_webconsole_history.js]
 [browser_webconsole_input_field_focus_on_panel_select.js]
+[browser_webconsole_inspect-parsed-documents.js]
 [browser_webconsole_js_input_expansion.js]
 [browser_webconsole_jsterm.js]
 [browser_webconsole_live_filtering_of_message_types.js]
 [browser_webconsole_live_filtering_on_search_strings.js]
 [browser_webconsole_message_node_id.js]
 [browser_webconsole_netlogging.js]
 [browser_webconsole_network_panel.js]
 [browser_webconsole_notifications.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_inspect-parsed-documents.js
@@ -0,0 +1,33 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that dynamically created (HTML|XML|SVG)Documents can be inspected by
+// clicking on the object in console (bug 1035198).
+
+const TEST_CASES = [
+  {
+    input: '(new DOMParser()).parseFromString("<a />", "text/html")',
+    output: "HTMLDocument",
+    inspectable: true,
+  },
+  {
+    input: '(new DOMParser()).parseFromString("<a />", "application/xml")',
+    output: "XMLDocument",
+    inspectable: true,
+  },
+  {
+    input: '(new DOMParser()).parseFromString("<svg></svg>", "image/svg+xml")',
+    output: "SVGDocument",
+    inspectable: true,
+  },
+];
+
+const TEST_URI = "data:text/html;charset=utf8," +
+  "browser_webconsole_inspect-parsed-documents.js";
+let test = asyncTest(function* () {
+    let {tab} = yield loadTab(TEST_URI);
+    let hud = yield openConsole(tab);
+    yield checkOutputForInputs(hud, TEST_CASES);
+});
--- a/browser/devtools/webconsole/test/browser_webconsole_output_05.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_output_05.js
@@ -64,19 +64,21 @@ let inputTests = [
   {
     input: "new Number(43)",
     output: "43",
     inspectable: true,
   },
 
   // 8
   {
-    input: "new String('hello world')",
-    output: '"hello world"',
+    input: "new String('hello')",
+    output: 'String [ "h", "e", "l", "l", "o" ]',
+    printOutput: "hello",
     inspectable: true,
+    variablesViewLabel: "String[5]"
   },
 ];
 
 function test() {
   Task.spawn(function*() {
     let {tab} = yield loadTab(TEST_URI);
     let hud = yield openConsole(tab);
     return checkOutputForInputs(hud, inputTests);
--- a/browser/devtools/webconsole/test/head.js
+++ b/browser/devtools/webconsole/test/head.js
@@ -39,16 +39,23 @@ const GROUP_INDENT = 12;
 const WEBCONSOLE_STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
 let WCU_l10n = new WebConsoleUtils.l10n(WEBCONSOLE_STRINGS_URI);
 
 gDevTools.testing = true;
 SimpleTest.registerCleanupFunction(() => {
   gDevTools.testing = false;
 });
 
+/**
+ * Define an async test based on a generator function
+ */
+function asyncTest(generator) {
+  return () => Task.spawn(generator).then(null, ok.bind(null, false)).then(finishTest);
+}
+
 function log(aMsg)
 {
   dump("*** WebConsoleTest: " + aMsg + "\n");
 }
 
 function pprint(aObj)
 {
   for (let prop in aObj) {
--- a/browser/devtools/webide/modules/app-manager.js
+++ b/browser/devtools/webide/modules/app-manager.js
@@ -528,17 +528,20 @@ exports.AppManager = AppManager = {
   untrackUSBRuntimes: function() {
     Devices.off("register", this._updateUSBRuntimes);
     Devices.off("unregister", this._updateUSBRuntimes);
     Devices.off("addon-status-updated", this._updateUSBRuntimes);
   },
   _updateUSBRuntimes: function() {
     this.runtimeList.usb = [];
     for (let id of Devices.available()) {
-      this.runtimeList.usb.push(new USBRuntime(id));
+      let r = new USBRuntime(id);
+      this.runtimeList.usb.push(r);
+      r.updateNameFromADB().then(
+        () => this.update("runtimelist"), () => {});
     }
     this.update("runtimelist");
   },
 
   get isWiFiScanningEnabled() {
     return Services.prefs.getBoolPref(WIFI_SCANNING_PREF);
   },
   scanForWiFiRuntimes: function() {
--- a/browser/devtools/webide/modules/runtimes.js
+++ b/browser/devtools/webide/modules/runtimes.js
@@ -28,17 +28,34 @@ USBRuntime.prototype = {
       connection.port = port;
       connection.connect();
     });
   },
   getID: function() {
     return this.id;
   },
   getName: function() {
-    return this.id;
+    return this._productModel || this.id;
+  },
+  updateNameFromADB: function() {
+    if (this._productModel) {
+      return promise.resolve();
+    }
+    let device = Devices.getByName(this.id);
+    let deferred = promise.defer();
+    if (device && device.shell) {
+      device.shell("getprop ro.product.model").then(stdout => {
+        this._productModel = stdout;
+        deferred.resolve();
+      }, () => {});
+    } else {
+      this._productModel = null;
+      deferred.reject();
+    }
+    return deferred.promise;
   },
 }
 
 function WiFiRuntime(deviceName) {
   this.deviceName = deviceName;
 }
 
 WiFiRuntime.prototype = {
--- a/browser/locales/en-US/chrome/browser/preferences/fonts.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/fonts.dtd
@@ -1,16 +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/. -->
 
 <!ENTITY  fontsDialog.title                       "Fonts">
-<!ENTITY  window.width                            "39em">
-<!ENTITY  window.macWidth                         "43em">
-<!ENTITY  window.unixWidth                        "41em">
 
 <!ENTITY  language.label                          "Fonts for:">
 <!ENTITY  language.accesskey                      "F">
 
 <!ENTITY  size.label                              "Size:">
 <!ENTITY  sizeProportional.accesskey              "z">
 <!ENTITY  sizeMonospace.accesskey                 "e">
 
--- a/browser/themes/shared/incontentprefs/preferences.css
+++ b/browser/themes/shared/incontentprefs/preferences.css
@@ -121,46 +121,55 @@ tab[selected] {
 .tab-text {
   font-size: 1.3rem;
   line-height: 22px;
 }
 
 /* buttons and menulists */
 
 button,
+colorpicker[type="button"],
 menulist {
   -moz-appearance: none;
   height: 30px;
   color: #333333;
   line-height: 20px;
   text-shadow: 0 1px 1px #FEFFFE;
   border: 1px solid #C1C1C1;
   -moz-border-top-colors: none !important;
   -moz-border-right-colors: none !important;
   -moz-border-bottom-colors: none !important;
   -moz-border-left-colors: none !important;
   border-radius: 2px;
   background-color: #FBFBFB;
 }
 
 button:not([disabled="true"]):hover,
+colorpicker[type="button"]:not([disabled="true"]):hover,
 menulist:not([disabled="true"]):hover {
   background-color: #EBEBEB;
 }
 
 button:not([disabled="true"]):hover:active,
+colorpicker[type="button"]:not([disabled="true"]):hover:active,
 menulist[open="true"]:not([disabled="true"]) {
   background-color: #DADADA;
 }
 
 button[disabled="true"],
+colorpicker[type="button"][disabled="true"],
 menulist[disabled="true"] {
   opacity: 0.5;
 }
 
+colorpicker[type="button"] {
+  padding: 6px;
+  width: 50px;
+}
+
 button > .button-box,
 menulist > .menulist-label-box {
   padding-right: 10px !important;
   padding-left: 10px !important;
 }
 
 button[type="menu"] > .button-box > .button-menu-dropmarker {
   -moz-appearance: none;
@@ -888,11 +897,16 @@ description > html|a {
 :-moz-any(dialog, window, prefwindow) resizer {
   display: none;
 }
 
 tree:not(#rejectsTree) {
   min-height: 15em;
 }
 
+:-moz-any(dialog, window, prefwindow) groupbox {
+  -moz-margin-start: 8px;
+  -moz-margin-end: 8px;
+}
+
 /**
  * End sub-dialog
  */
--- a/build/gen_mach_buildprops.py
+++ b/build/gen_mach_buildprops.py
@@ -25,16 +25,18 @@ def getFileHashAndSize(filename):
 
         size = os.path.getsize(filename)
     except:
         pass
 
     return (sha1Hash, size)
 
 def getMarProperties(filename):
+    if not os.path.exists(filename):
+        return {}
     (complete_mar_hash, complete_mar_size) = getFileHashAndSize(filename)
     return {
         'completeMarFilename': os.path.basename(filename),
         'completeMarSize': complete_mar_size,
         'completeMarHash': complete_mar_hash,
     }
 
 def getUrlProperties(filename):
--- a/build/macosx/mozconfig.common
+++ b/build/macosx/mozconfig.common
@@ -1,12 +1,18 @@
 # 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/.
 
+MOZ_AUTOMATION_L10N_CHECK=0
+
+if [ "x$IS_NIGHTLY" = "xyes" ]; then
+  MOZ_AUTOMATION_UPLOAD_SYMBOLS=1
+  MOZ_AUTOMATION_UPDATE_PACKAGING=1
+fi
 . "$topsrcdir/build/mozconfig.common"
 
 if [ -d "$topsrcdir/clang" ]; then
     # mozilla-central based build
     export CC=$topsrcdir/clang/bin/clang
     export CXX=$topsrcdir/clang/bin/clang++
     export LLVMCONFIG=$topsrcdir/clang/bin/llvm-config
 elif [ -d "$topsrcdir/../clang" ]; then
--- a/build/moz-automation.mk
+++ b/build/moz-automation.mk
@@ -18,16 +18,18 @@ include $(topsrcdir)/toolkit/mozapps/ins
 AUTOMATION_UPLOAD_OUTPUT = $(DIST)/automation-upload.txt
 
 # Helper variables to convert from MOZ_AUTOMATION_* variables to the
 # corresponding the make target
 tier_BUILD_SYMBOLS = buildsymbols
 tier_CHECK = check
 tier_L10N_CHECK = l10n-check
 tier_PRETTY_L10N_CHECK = pretty-l10n-check
+tier_INSTALLER = installer
+tier_PRETTY_INSTALLER = pretty-installer
 tier_PACKAGE = package
 tier_PRETTY_PACKAGE = pretty-package
 tier_PACKAGE_TESTS = package-tests
 tier_PRETTY_PACKAGE_TESTS = pretty-package-tests
 tier_UPDATE_PACKAGING = update-packaging
 tier_PRETTY_UPDATE_PACKAGING = pretty-update-packaging
 tier_UPLOAD_SYMBOLS = uploadsymbols
 tier_UPLOAD = upload
@@ -38,60 +40,72 @@ tier_UPLOAD = upload
 # dependencies between them).
 moz_automation_symbols = \
   PACKAGE_TESTS \
   PRETTY_PACKAGE_TESTS \
   BUILD_SYMBOLS \
   UPLOAD_SYMBOLS \
   PACKAGE \
   PRETTY_PACKAGE \
+  INSTALLER \
+  PRETTY_INSTALLER \
   UPDATE_PACKAGING \
   PRETTY_UPDATE_PACKAGING \
   CHECK \
   L10N_CHECK \
   PRETTY_L10N_CHECK \
   UPLOAD \
   $(NULL)
 MOZ_AUTOMATION_TIERS := $(foreach sym,$(moz_automation_symbols),$(if $(filter 1,$(MOZ_AUTOMATION_$(sym))),$(tier_$(sym))))
 
 # Dependencies between automation build steps
 automation/uploadsymbols: automation/buildsymbols
 
 automation/update-packaging: automation/package
+automation/pretty-update-packaging: automation/pretty-package
+automation/pretty-update-packaging: automation/pretty-installer
 
 automation/l10n-check: automation/package
+automation/l10n-check: automation/installer
+automation/pretty-l10n-check: automation/pretty-package
+automation/pretty-l10n-check: automation/pretty-installer
 
+automation/upload: automation/installer
 automation/upload: automation/package
 automation/upload: automation/package-tests
 automation/upload: automation/buildsymbols
 automation/upload: automation/update-packaging
 
-# automation/package and automation/check should depend on build (which is
+# automation/{pretty-}package and automation/check should depend on build (which is
 # implicit due to the way client.mk invokes automation/build), but buildsymbols
 # changes the binaries/libs, and that's what we package/test.
-automation/package: automation/buildsymbols
+automation/pretty-package: automation/buildsymbols
 automation/check: automation/buildsymbols
 
 # The 'pretty' versions of targets run before the regular ones to avoid
 # conflicts in writing to the same files.
+automation/installer: automation/pretty-installer
 automation/package: automation/pretty-package
 automation/package-tests: automation/pretty-package-tests
 automation/l10n-check: automation/pretty-l10n-check
 automation/update-packaging: automation/pretty-update-packaging
 
 automation/build: $(addprefix automation/,$(MOZ_AUTOMATION_TIERS))
 	$(PYTHON) $(topsrcdir)/build/gen_mach_buildprops.py --complete-mar-file $(DIST)/$(COMPLETE_MAR) --upload-output $(AUTOMATION_UPLOAD_OUTPUT)
 
 # make check runs with the keep-going flag so we can see all the failures
 AUTOMATION_EXTRA_CMDLINE-check = -k
 
 # We need the log from make upload to grep it for urls in order to set
 # properties.
 AUTOMATION_EXTRA_CMDLINE-upload = 2>&1 | tee $(AUTOMATION_UPLOAD_OUTPUT)
 
+# Note: We have to force -j1 here, at least until bug 1036563 is fixed.
+AUTOMATION_EXTRA_CMDLINE-l10n-check = -j1
+
 # The commands only run if the corresponding MOZ_AUTOMATION_* variable is
 # enabled. This means, for example, if we enable MOZ_AUTOMATION_UPLOAD, then
 # 'buildsymbols' will only run if MOZ_AUTOMATION_BUILD_SYMBOLS is also set.
 # However, the target automation/buildsymbols will still be executed in this
 # case because it is a prerequisite of automation/upload.
 define automation_commands
 $(call BUILDSTATUS,TIER_START $1)
 @$(MAKE) $1 $(AUTOMATION_EXTRA_CMDLINE-$1)
--- a/build/mozconfig.automation
+++ b/build/mozconfig.automation
@@ -9,25 +9,27 @@
 # package step will run.  This file contains the default settings, which can be
 # overridden by setting them earlier in the appropriate mozconfig.
 
 mk_add_options "export MOZ_AUTOMATION_BUILD_SYMBOLS=${MOZ_AUTOMATION_BUILD_SYMBOLS-1}"
 mk_add_options "export MOZ_AUTOMATION_CHECK=${MOZ_AUTOMATION_CHECK-1}"
 mk_add_options "export MOZ_AUTOMATION_L10N_CHECK=${MOZ_AUTOMATION_L10N_CHECK-1}"
 mk_add_options "export MOZ_AUTOMATION_PACKAGE=${MOZ_AUTOMATION_PACKAGE-1}"
 mk_add_options "export MOZ_AUTOMATION_PACKAGE_TESTS=${MOZ_AUTOMATION_PACKAGE_TESTS-1}"
+mk_add_options "export MOZ_AUTOMATION_INSTALLER=${MOZ_AUTOMATION_INSTALLER-0}"
 mk_add_options "export MOZ_AUTOMATION_TALOS_SENDCHANGE=${MOZ_AUTOMATION_TALOS_SENDCHANGE-1}"
 mk_add_options "export MOZ_AUTOMATION_UNITTEST_SENDCHANGE=${MOZ_AUTOMATION_UNITTEST_SENDCHANGE-1}"
 mk_add_options "export MOZ_AUTOMATION_UPDATE_PACKAGING=${MOZ_AUTOMATION_UPDATE_PACKAGING-0}"
 mk_add_options "export MOZ_AUTOMATION_UPLOAD=${MOZ_AUTOMATION_UPLOAD-1}"
 mk_add_options "export MOZ_AUTOMATION_UPLOAD_SYMBOLS=${MOZ_AUTOMATION_UPLOAD_SYMBOLS-0}"
 
 # If we are also building with MOZ_PKG_PRETTYNAMES, set the corresponding
 # stages.
 if test "$MOZ_AUTOMATION_PRETTY" = "1"; then
     mk_add_options "export MOZ_AUTOMATION_PRETTY_PACKAGE=${MOZ_AUTOMATION_PACKAGE-1}"
     mk_add_options "export MOZ_AUTOMATION_PRETTY_PACKAGE_TESTS=${MOZ_AUTOMATION_PACKAGE_TESTS-1}"
     mk_add_options "export MOZ_AUTOMATION_PRETTY_L10N_CHECK=${MOZ_AUTOMATION_L10N_CHECK-1}"
+    mk_add_options "export MOZ_AUTOMATION_PRETTY_INSTALLER=${MOZ_AUTOMATION_INSTALLER-0}"
 
     # Note that we always build the update packaging with pretty names even if
     # we don't build it without, so this is set to 1.
     mk_add_options "export MOZ_AUTOMATION_PRETTY_UPDATE_PACKAGING=1"
 fi
new file mode 100644
--- /dev/null
+++ b/build/mozconfig.win-common
@@ -0,0 +1,9 @@
+# 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 [ "x$IS_NIGHTLY" = "xyes" ]; then
+  MOZ_AUTOMATION_UPLOAD_SYMBOLS=1
+  MOZ_AUTOMATION_UPDATE_PACKAGING=1
+fi
+MOZ_AUTOMATION_INSTALLER=1
--- a/build/unix/mozconfig.asan
+++ b/build/unix/mozconfig.asan
@@ -1,11 +1,8 @@
-if [ "x$IS_NIGHTLY" = "xyes" ]; then
-  MOZ_AUTOMATION_UPDATE_PACKAGING=1
-fi
 MOZ_AUTOMATION_TALOS_SENDCHANGE=0
 MOZ_AUTOMATION_L10N_CHECK=0
 
 . "$topsrcdir/build/mozconfig.common"
 
 # Use Clang as specified in manifest
 export CC="$topsrcdir/clang/bin/clang -fgnu89-inline"
 export CXX="$topsrcdir/clang/bin/clang++"
--- a/configure.in
+++ b/configure.in
@@ -3873,17 +3873,17 @@ MOZ_LOCALE_SWITCHER=
 MOZ_ANDROID_SEARCH_ACTIVITY=
 MOZ_ANDROID_MLS_STUMBLER=
 ACCESSIBILITY=1
 MOZ_TIME_MANAGER=
 MOZ_PAY=
 MOZ_AUDIO_CHANNEL_MANAGER=
 NSS_NO_LIBPKIX=
 MOZ_CONTENT_SANDBOX=
-JSGC_USE_EXACT_ROOTING=
+JSGC_USE_EXACT_ROOTING=1
 JSGC_GENERATIONAL=
 
 case "$target_os" in
     mingw*)
         NS_ENABLE_TSF=1
         AC_DEFINE(NS_ENABLE_TSF)
         ;;
 esac
--- a/content/base/src/nsRange.cpp
+++ b/content/base/src/nsRange.cpp
@@ -2719,25 +2719,22 @@ nsRange::CreateContextualFragment(const 
     return nullptr;
   }
 
   return nsContentUtils::CreateContextualFragment(mStartParent, aFragment,
                                                   false, aRv);
 }
 
 static void ExtractRectFromOffset(nsIFrame* aFrame,
-                                  const nsIFrame* aRelativeTo, 
                                   const int32_t aOffset, nsRect* aR, bool aKeepLeft,
                                   bool aClampToEdge)
 {
   nsPoint point;
   aFrame->GetPointFromOffset(aOffset, &point);
 
-  point += aFrame->GetOffsetTo(aRelativeTo);
-
   if (!aClampToEdge && !aR->Contains(point)) {
     aR->width = 0;
     aR->x = point.x;
     return;
   }
 
   if (aClampToEdge) {
     point = aR->ClampPoint(point);
@@ -2778,25 +2775,26 @@ static nsresult GetPartialTextRect(nsLay
       int32_t fstart = f->GetContentOffset(), fend = f->GetContentEnd();
       if (fend <= aStartOffset || fstart >= aEndOffset)
         continue;
 
       // overlapping with the offset we want
       f->EnsureTextRun(nsTextFrame::eInflated);
       NS_ENSURE_TRUE(f->GetTextRun(nsTextFrame::eInflated), NS_ERROR_OUT_OF_MEMORY);
       bool rtl = f->GetTextRun(nsTextFrame::eInflated)->IsRightToLeft();
-      nsRect r(f->GetOffsetTo(relativeTo), f->GetSize());
+      nsRect r = f->GetRectRelativeToSelf();
       if (fstart < aStartOffset) {
         // aStartOffset is within this frame
-        ExtractRectFromOffset(f, relativeTo, aStartOffset, &r, rtl, aClampToEdge);
+        ExtractRectFromOffset(f, aStartOffset, &r, rtl, aClampToEdge);
       }
       if (fend > aEndOffset) {
         // aEndOffset is in the middle of this frame
-        ExtractRectFromOffset(f, relativeTo, aEndOffset, &r, !rtl, aClampToEdge);
+        ExtractRectFromOffset(f, aEndOffset, &r, !rtl, aClampToEdge);
       }
+      r = nsLayoutUtils::TransformFrameRectToAncestor(f, r, relativeTo);
       aCallback->AddRect(r);
     }
   }
   return NS_OK;
 }
 
 /* static */ void
 nsRange::CollectClientRects(nsLayoutUtils::RectCallback* aCollector,
@@ -2834,19 +2832,20 @@ nsRange::CollectClientRects(nsLayoutUtil
       if (textFrame) {
         int32_t outOffset;
         nsIFrame* outFrame;
         textFrame->GetChildFrameContainingOffset(aStartOffset, false,
           &outOffset, &outFrame);
         if (outFrame) {
            nsIFrame* relativeTo =
              nsLayoutUtils::GetContainingBlockForClientRect(outFrame);
-           nsRect r(outFrame->GetOffsetTo(relativeTo), outFrame->GetSize());
-           ExtractRectFromOffset(outFrame, relativeTo, aStartOffset, &r, false, aClampToEdge);
+           nsRect r = outFrame->GetRectRelativeToSelf();
+           ExtractRectFromOffset(outFrame, aStartOffset, &r, false, aClampToEdge);
            r.width = 0;
+           r = nsLayoutUtils::TransformFrameRectToAncestor(outFrame, r, relativeTo);
            aCollector->AddRect(r);
         }
       }
     }
     return;
   }
 
   do {
@@ -2865,17 +2864,18 @@ nsRange::CollectClientRects(nsLayoutUtil
          GetPartialTextRect(aCollector, content, 0, aEndOffset, aClampToEdge);
          continue;
        }
     }
 
     nsIFrame* frame = content->GetPrimaryFrame();
     if (frame) {
       nsLayoutUtils::GetAllInFlowRects(frame,
-        nsLayoutUtils::GetContainingBlockForClientRect(frame), aCollector);
+        nsLayoutUtils::GetContainingBlockForClientRect(frame), aCollector,
+        nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS);
     }
   } while (!iter.IsDone());
 }
 
 NS_IMETHODIMP
 nsRange::GetBoundingClientRect(nsIDOMClientRect** aResult)
 {
   *aResult = GetBoundingClientRect(true).take();
--- a/content/base/test/test_range_bounds.html
+++ b/content/base/test/test_range_bounds.html
@@ -15,29 +15,36 @@ https://bugzilla.mozilla.org/show_bug.cg
 <div id="content" style="font-family:monospace;font-size:12px;width:100px">
 <p>000000<span>0</span></p><div>00000<span>0</span></div><p>0000<span>0000</span>0000</p><div><span>000000000000 00000000000000 000000</span></div><div>000000000000 00000000000003 100305</div>
 </div>
 <div id="mixeddir" style="font-family:monospace;font-size:12px;width:100px"><span>english <bdo id="bdo" dir="rtl">rtl-overide english</bdo> word</span></div>
 <div id="mixeddir2" style="font-family:monospace;font-size:12px"><span>english <bdo id="bdo2" dir="rtl">rtl-override english</bdo> word</span></div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
-var isLTR=true;
+var isLTR = true;
+var isTransformed = false;
+
+function annotateName(name) {
+  return (isLTR ? 'isLTR ' : 'isRTL ') +
+    (isTransformed ? 'transformed ' : '') + name;
+}
+
 function isEmptyRect(rect, name) {
-  name = (isLTR ? 'isLTR ' : 'isRTL ') + name;
+  name = annotateName(name);
   is(rect.left, 0, name+'empty rect should have left = 0');
   is(rect.right, 0, name+'empty rect should have right = 0');
   is(rect.top, 0, name+'empty rect should have top = 0');
   is(rect.bottom, 0, name+'empty rect should have bottom = 0');
   is(rect.width, 0, name+'empty rect should have width = 0');
   is(rect.height, 0, name+'empty rect should have height = 0');
 }
 
 function isEmptyRectList(rectlist, name) {
-  name = (isLTR ? 'isLTR ' : 'isRTL ') + name;
+  name = annotateName(name);
   is(rectlist.length, 0, name + 'empty rectlist should have zero rects');
 }
 
 // round coordinates to the nearest 1/256 of a pixel
 function roundCoord(x) {
   return Math.round(x * 256) / 256;
 }
 
@@ -56,41 +63,41 @@ function runATest(obj) {
   var range = document.createRange();
   try {
     range.setStart(obj.range[0],obj.range[1]);
     if (obj.range.length>2) {
        range.setEnd(obj.range[2]||obj.range[0], obj.range[3]);
     }
     //test getBoundingClientRect()
     var rect = range.getBoundingClientRect();
-    var testname = (isLTR ? 'isLTR ' : 'isRTL ') + 
-      'range.getBoundingClientRect for ' + obj.name;
+    var testname = 'range.getBoundingClientRect for ' + obj.name;
     if (obj.rect) {
-      is(_getRect(rect),_getRect(obj.rect), testname);
+      is(_getRect(rect),_getRect(obj.rect), annotateName(testname));
     } else {
       isEmptyRect(rect,testname+": ");
     }
     //test getClientRects()
     var rectlist = range.getClientRects();
-    testname = (isLTR ? 'isLTR ' : 'isRTL ') +
-      'range.getClientRects for '+obj.name;
+    testname = 'range.getClientRects for ' + obj.name;
     if (!obj.rectList) {
       //rectList is not specified, use obj.rect to figure out rectList
       obj.rectList = obj.rect?[obj.rect]:[];
     }
     if (!obj.rectList.length) {
       isEmptyRectList(rectlist, testname+": ");
     } else {
-      is(rectlist.length, obj.rectList.length, testname+' should return '+obj.rectList.length+' rects.');
+      is(rectlist.length, obj.rectList.length,
+         annotateName(testname+' should return '+obj.rectList.length+' rects.'));
       if(!obj.rectList.forEach){
         //convert RectList to a real array
         obj.rectList=Array.prototype.slice.call(obj.rectList, 0);
       }
       obj.rectList.forEach(function(rect,i) {
-        is(_getRect(rectlist[i]),_getRect(rect),testname+": item at "+i);
+        is(_getRect(rectlist[i]),_getRect(rect),
+           annotateName(testname+": item at "+i));
       });
     }
   } finally {
     range.detach();
   }
 }
 /** Test for Bug 396392 **/
 function doTest(){
@@ -246,25 +253,31 @@ function testMixedDir(){
 		   firstSpanRect.height],
 		  [bdoRect.right - 7 * widthPerChar, bdoRect.right,
 		   bdoRect.top, bdoRect.bottom,
 		   7*widthPerChar, bdoRect.height]]});
 }
 function test(){
   //test ltr
   doTest();
-  testMixedDir();
 
+  //test rtl
   isLTR = false;
   var root = document.getElementById('content');
   root.dir = 'rtl';
+  doTest();
+  isLTR = true;
+  root.dir = 'ltr';
 
-  //test rtl
+  testMixedDir();
+
+  //test transforms
+  isTransformed = true;
+  root.style.transform = "translate(30px,50px)";
   doTest();
-  testMixedDir();
 
   SimpleTest.finish();
 }
 
 window.onload = function() {
   SimpleTest.waitForExplicitFinish();
   setTimeout(test, 0);
 };
--- a/content/canvas/src/WebGLContext.h
+++ b/content/canvas/src/WebGLContext.h
@@ -885,17 +885,17 @@ private:
         mBufferFetchingHasPerVertex = false;
         mMaxFetchedVertices = 0;
         mMaxFetchedInstances = 0;
     }
 
     bool DrawArrays_check(GLint first, GLsizei count, GLsizei primcount, const char* info);
     bool DrawElements_check(GLsizei count, GLenum type, WebGLintptr byteOffset,
                             GLsizei primcount, const char* info,
-                            GLuint* out_upperBound = nullptr);
+                            GLuint* out_upperBound);
     bool DrawInstanced_check(const char* info);
     void Draw_cleanup();
 
     void VertexAttrib1fv_base(GLuint idx, uint32_t arrayLength, const GLfloat* ptr);
     void VertexAttrib2fv_base(GLuint idx, uint32_t arrayLength, const GLfloat* ptr);
     void VertexAttrib3fv_base(GLuint idx, uint32_t arrayLength, const GLfloat* ptr);
     void VertexAttrib4fv_base(GLuint idx, uint32_t arrayLength, const GLfloat* ptr);
 
--- a/content/canvas/src/WebGLContextDraw.cpp
+++ b/content/canvas/src/WebGLContextDraw.cpp
@@ -328,17 +328,19 @@ WebGLContext::DrawElementsInstanced(GLen
                                     WebGLintptr byteOffset, GLsizei primcount)
 {
     if (IsContextLost())
         return;
 
     if (!ValidateDrawModeEnum(mode, "drawElementsInstanced: mode"))
         return;
 
-    if (!DrawElements_check(count, type, byteOffset, primcount, "drawElementsInstanced"))
+    GLuint upperBound = 0;
+    if (!DrawElements_check(count, type, byteOffset, primcount, "drawElementsInstanced",
+                            &upperBound))
         return;
 
     RunContextLossTimer();
     gl->fDrawElementsInstanced(mode, count, type, reinterpret_cast<GLvoid*>(byteOffset), primcount);
 
     Draw_cleanup();
 }
 
--- a/content/canvas/src/WebGLContextGL.cpp
+++ b/content/canvas/src/WebGLContextGL.cpp
@@ -2168,17 +2168,17 @@ WebGLContext::ReadPixels(GLint x, GLint 
         return rv.Throw(NS_ERROR_OUT_OF_MEMORY);
     }
 
     bool isSourceTypeFloat = false;
     if (mBoundFramebuffer &&
         mBoundFramebuffer->ColorAttachmentCount() &&
         mBoundFramebuffer->ColorAttachment(0).IsDefined())
     {
-        isSourceTypeFloat = mBoundFramebuffer->ColorAttachment(0).IsFloatType();
+        isSourceTypeFloat = mBoundFramebuffer->ColorAttachment(0).IsReadableFloat();
     }
 
     if (isReadTypeFloat != isSourceTypeFloat)
         return ErrorInvalidOperation("readPixels: Invalid type floatness");
 
     // Check the format and type params to assure they are an acceptable pair (as per spec)
     MakeContextCurrent();
 
--- a/content/canvas/src/WebGLFramebuffer.cpp
+++ b/content/canvas/src/WebGLFramebuffer.cpp
@@ -57,42 +57,36 @@ WebGLFramebuffer::Attachment::HasAlpha()
     if (Texture() && Texture()->HasImageInfoAt(mTexImageTarget, mTexImageLevel))
         format = Texture()->ImageInfoAt(mTexImageTarget, mTexImageLevel).WebGLFormat();
     else if (Renderbuffer())
         format = Renderbuffer()->InternalFormat();
     return FormatHasAlpha(format);
 }
 
 bool
-WebGLFramebuffer::Attachment::IsFloatType(FloatType floatType) const
+WebGLFramebuffer::Attachment::IsReadableFloat() const
 {
     if (Texture() && Texture()->HasImageInfoAt(mTexImageTarget, mTexImageLevel)) {
         GLenum type = Texture()->ImageInfoAt(mTexImageTarget, mTexImageLevel).WebGLType();
         switch (type) {
         case LOCAL_GL_FLOAT:
-            return floatType == FloatType::Full ||
-                   floatType == FloatType::Any;
         case LOCAL_GL_HALF_FLOAT_OES:
-            return floatType == FloatType::Half ||
-                   floatType == FloatType::Any;
+            return true;
         }
         return false;
     }
 
     if (Renderbuffer()) {
         GLenum format = Renderbuffer()->InternalFormat();
         switch (format) {
         case LOCAL_GL_RGB16F:
         case LOCAL_GL_RGBA16F:
-            return floatType == FloatType::Half ||
-                   floatType == FloatType::Any;
         case LOCAL_GL_RGB32F:
         case LOCAL_GL_RGBA32F:
-            return floatType == FloatType::Full ||
-                   floatType == FloatType::Any;
+            return true;
         }
         return false;
     }
 
     MOZ_ASSERT(false, "Should not get here.");
     return false;
 }
 
@@ -631,36 +625,16 @@ IsIncomplete(const WebGLFramebuffer::Att
 bool
 WebGLFramebuffer::HasIncompleteAttachments() const
 {
     bool hasIncomplete = false;
 
     size_t count = mColorAttachments.Length();
     for (size_t i = 0; i < count; i++) {
         hasIncomplete |= IsIncomplete(mColorAttachments[i]);
-
-        if (mColorAttachments[i].IsDefined()) {
-            // Excerpt from http://www.khronos.org/registry/webgl/extensions/OES_texture_float/
-            // New implementations should not implicitly support float rendering and
-            // applications should be modified to explicitly enable WEBGL_color_buffer_float.
-            if (mColorAttachments[i].IsFloatType(Attachment::FloatType::Full) &&
-                !Context()->IsExtensionEnabled(WebGLExtensionID::WEBGL_color_buffer_float))
-            {
-                hasIncomplete |= true;
-            }
-
-            // Excerpt from http://www.khronos.org/registry/webgl/extensions/OES_texture_half_float/
-            // New implementations should not implicitly support float rendering and
-            // applications should be modified to explicitly enable OES_color_buffer_half_float.
-            if (mColorAttachments[i].IsFloatType(Attachment::FloatType::Half) &&
-                !Context()->IsExtensionEnabled(WebGLExtensionID::EXT_color_buffer_half_float))
-            {
-                hasIncomplete |= true;
-            }
-        }
     }
 
     hasIncomplete |= IsIncomplete(mDepthAttachment);
     hasIncomplete |= IsIncomplete(mStencilAttachment);
     hasIncomplete |= IsIncomplete(mDepthStencilAttachment);
 
     return hasIncomplete;
 }
--- a/content/canvas/src/WebGLFramebuffer.h
+++ b/content/canvas/src/WebGLFramebuffer.h
@@ -6,17 +6,16 @@
 #ifndef WEBGLFRAMEBUFFER_H_
 #define WEBGLFRAMEBUFFER_H_
 
 #include "WebGLObjectModel.h"
 
 #include "nsWrapperCache.h"
 
 #include "mozilla/LinkedList.h"
-#include "mozilla/TypedEnum.h"
 
 namespace mozilla {
 
 class WebGLFramebufferAttachable;
 class WebGLTexture;
 class WebGLRenderbuffer;
 namespace gl {
     class GLContext;
@@ -48,25 +47,17 @@ public:
 
         bool IsDefined() const {
             return Texture() || Renderbuffer();
         }
 
         bool IsDeleteRequested() const;
 
         bool HasAlpha() const;
-
-        // For IsFloatType()
-        MOZ_BEGIN_NESTED_ENUM_CLASS(FloatType)
-            Any = 0,
-            Half,
-            Full
-        MOZ_END_NESTED_ENUM_CLASS(FloatType)
-
-        bool IsFloatType(FloatType floatType = FloatType::Any) const;
+        bool IsReadableFloat() const;
 
         void SetTexImage(WebGLTexture* tex, GLenum target, GLint level);
         void SetRenderbuffer(WebGLRenderbuffer* rb);
 
         const WebGLTexture* Texture() const {
             return mTexturePtr;
         }
         WebGLTexture* Texture() {
@@ -200,13 +191,11 @@ private:
     // we only store pointers to attached renderbuffers, not to attached textures, because
     // we will only need to initialize renderbuffers. Textures are already initialized.
     nsTArray<Attachment> mColorAttachments;
     Attachment mDepthAttachment,
                mStencilAttachment,
                mDepthStencilAttachment;
 };
 
-MOZ_FINISH_NESTED_ENUM_CLASS(WebGLFramebuffer::Attachment::FloatType)
-
 } // namespace mozilla
 
 #endif
--- a/content/canvas/test/mochitest.ini
+++ b/content/canvas/test/mochitest.ini
@@ -209,11 +209,12 @@ skip-if = (buildapp == 'b2g' && toolkit 
 [test_mozGetAsFile.html]
 [test_strokeText_throw.html]
 [test_toBlob.html]
 [test_toDataURL_alpha.html]
 [test_toDataURL_lowercase_ascii.html]
 [test_toDataURL_parameters.html]
 [test_windingRuleUndefined.html]
 [test_2d.fillText.gradient.html]
+skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # bug 1040965
 [test_2d_composite_canvaspattern_setTransform.html]
 [test_createPattern_broken.html]
 [test_setlinedash.html]
--- a/content/canvas/test/webgl-conformance/conformance/extensions/oes-texture-float.html
+++ b/content/canvas/test/webgl-conformance/conformance/extensions/oes-texture-float.html
@@ -151,20 +151,24 @@ function runRenderTargetTest(testProgram
     gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.FLOAT, null);
     glErrorShouldBe(gl, gl.NO_ERROR, "floating-point texture allocation should succeed if OES_texture_float is enabled");
 
     // Use this texture as a render target.
     var fbo = gl.createFramebuffer();
     gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
     gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
     gl.bindTexture(gl.TEXTURE_2D, null);
-    if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
-        debug("floating-point " + formatString + " render target not supported -- this is legal");
+    shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_COMPLETE");
+    // While strictly speaking it is probably legal for a WebGL implementation to support
+    // floating-point textures but not as attachments to framebuffer objects, any such
+    // implementation is so poor that it arguably should not advertise support for the
+    // OES_texture_float extension. For this reason the conformance test requires that the
+    // framebuffer is complete here.
+    if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE)
         return;
-    }
 
     var renderProgram =
         wtu.setupProgram(gl,
                          ["positionVertexShader", "floatingPointFragmentShader"],
                          ['vPosition'],
                          [0]);
     wtu.drawQuad(gl);
     glErrorShouldBe(gl, gl.NO_ERROR, "rendering to floating-point texture should succeed");
--- a/content/canvas/test/webgl-conformance/failing_tests_android.txt
+++ b/content/canvas/test/webgl-conformance/failing_tests_android.txt
@@ -1,8 +1,9 @@
+conformance/extensions/oes-texture-float.html
 conformance/extensions/oes-vertex-array-object.html
 conformance/glsl/functions/glsl-function-abs.html
 conformance/glsl/functions/glsl-function-faceforward.html
 conformance/glsl/functions/glsl-function-sign.html
 conformance/glsl/functions/glsl-function-smoothstep-float.html
 conformance/glsl/functions/glsl-function-step-float.html
 conformance/glsl/functions/glsl-function-step-gentype.html
 conformance/limits/gl-max-texture-dimensions.html
--- a/content/canvas/test/webgl-conformance/failing_tests_android_x86.txt
+++ b/content/canvas/test/webgl-conformance/failing_tests_android_x86.txt
@@ -1,4 +1,5 @@
 # Failures for our android x86 and arm emulator test environments.
 
+conformance/extensions/oes-texture-float.html
 conformance/programs/get-active-test.html
 conformance/textures/texture-npot.html
--- a/content/canvas/test/webgl-conformance/failing_tests_linux_mesa.txt
+++ b/content/canvas/test/webgl-conformance/failing_tests_linux_mesa.txt
@@ -1,2 +1,3 @@
 conformance/textures/texture-size-cube-maps.html
+conformance/extensions/oes-texture-float.html
 conformance/glsl/functions/glsl-function-sin.html
--- a/content/canvas/test/webgl-conformance/skipped_tests_android_x86.txt
+++ b/content/canvas/test/webgl-conformance/skipped_tests_android_x86.txt
@@ -1,15 +1,8 @@
-# OES-texture-float is failing on Android 2.3, but not Android 4.0 when
-# there is a way to differentiate these two slaves this should be removed
-# and updated as appropriately. The Android 2.3 slave is returning red
-# when it should be green. This is believed to be a driver bug and not
-# FF bug at the moment.
-conformance/extensions/oes-texture-float.html
-
 conformance/extensions/oes-vertex-array-object.html
 conformance/glsl/functions/glsl-function-abs.html
 conformance/glsl/functions/glsl-function-faceforward.html
 conformance/glsl/functions/glsl-function-sign.html
 conformance/glsl/functions/glsl-function-sin.html
 conformance/glsl/functions/glsl-function-step-float.html
 conformance/glsl/functions/glsl-function-step-gentype.html
 conformance/glsl/functions/glsl-function-smoothstep-float.html
--- a/content/media/MediaDecoder.cpp
+++ b/content/media/MediaDecoder.cpp
@@ -1206,17 +1206,20 @@ void MediaDecoder::PlaybackPositionChang
 
   double lastTime = mCurrentTime;
 
   // Control the scope of the monitor so it is not
   // held while the timeupdate and the invalidate is run.
   {
     ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
     if (mDecoderStateMachine) {
-      if (!IsSeeking()) {
+      // Don't update the official playback position when paused which is
+      // expected by the script. (The current playback position might be still
+      // advancing for a while after paused.)
+      if (!IsSeeking() && mPlayState != PLAY_STATE_PAUSED) {
         // Only update the current playback position if we're not seeking.
         // If we are seeking, the update could have been scheduled on the
         // state machine thread while we were playing but after the seek
         // algorithm set the current playback position on the main thread,
         // and we don't want to override the seek algorithm and change the
         // current time after the seek has started but before it has
         // completed.
         if (GetDecodedStream()) {
--- a/content/media/test/test_fastSeek.html
+++ b/content/media/test/test_fastSeek.html
@@ -52,19 +52,19 @@ https://bugzilla.mozilla.org/show_bug.cg
          ") should be end up roughly after keyframe (" + keyframe + ") after fastSeek");
 
       ok(v.currentTime <= v.target,
          v.name + " seekTo=" + v.target + " currentTime (" + v.currentTime +
          ") should be end up less than target after fastSeek");
 
       v.keyframeIndex++
       if (v.keyframeIndex == v.keyframes.length) {
-        manager.finished(v.token);
         v.src = "";
         v.parentNode.removeChild(v);
+        manager.finished(v.token);
       } else {
         doSeek(v);
       }
     }
 
     function startTest(test, token) {
       manager.started(token);
       v = document.createElement("video");
--- a/content/media/test/test_playback_rate.html
+++ b/content/media/test/test_playback_rate.html
@@ -5,19 +5,16 @@
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
   <script type="text/javascript" src="manifest.js"></script>
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type='application/javascript;version=1.8'>
 
-// Run one test at a time for this test case is time-sensitive and B2G
-// emulator could be very slow.
-PARALLEL_TESTS = 1;
 let manager = new MediaTestManager;
 
 function rangeCheck(lhs, rhs, threshold) {
   var diff = Math.abs(lhs - rhs);
   if (diff < threshold) {
     return true;
   }
   return false;
@@ -77,24 +74,17 @@ function afterNullPlaybackRate(e) {
 
   // skip if we have received 'ended' event or 'ended' event is pending.
   if (t.gotEnded || t.ended) {
     return;
   }
 
   t.testedForNull = true;
 
-  // The currentTime won't be perfectly equal because of the latency
-  // compensation. We expect no more that 300ms difference.
-  let THRESHOLD = 0.3;
-  // More tolerance on B2G for it is slow
-  if (SpecialPowers.Services.appinfo.name == "B2G") {
-    THRESHOLD = 0.7;
-  }
-  ok(rangeCheck(t.currentTime, t.oldCurrentTime, THRESHOLD), "Current time should not change when playbackRate is null (" + t.currentTime + " " + t.oldCurrentTime + ").");
+  ok(t.currentTime == t.oldCurrentTime, "Current time should not change when playbackRate is null (" + t.currentTime + " " + t.oldCurrentTime + ").");
   ok(!t.paused, "The element should not be in paused state.");
   t.removeEventListener("paused", onpaused);
   is(t.pausedReceived, undefined, "Paused event should not have been received.");
   t.timestamp = Date.now();
   t.oldCurrentTime = t.currentTime;
   t.playbackRate = VERY_FAST_RATE;
   is(t.playbackRate, FAST_RATE, "Playback rate should be clamped to " + FAST_RATE + ".");
 }
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -1423,31 +1423,41 @@ this.DOMApplicationRegistry = {
     let json = yield AppsUtils.loadJSONAsync(file.path);
     if (!json) {
       debug("startDownload: No update manifest found at " + file.path + " " +
             aManifestURL);
       throw new Error("MISSING_UPDATE_MANIFEST");
     }
 
     let manifest = new ManifestHelper(json, app.manifestURL);
-    let [aId, aManifest] = yield this.downloadPackage(manifest, {
-        manifestURL: aManifestURL,
-        origin: app.origin,
-        installOrigin: app.installOrigin,
-        downloadSize: app.downloadSize
-      }, isUpdate);
+    let newApp = {
+      manifestURL: aManifestURL,
+      origin: app.origin,
+      installOrigin: app.installOrigin,
+      downloadSize: app.downloadSize
+    };
+
+    let newManifest, newId;
+
+    try {
+      [newId, newManifest] = yield this.downloadPackage(id, app, manifest, newApp, isUpdate);
+    } catch (ex) {
+      this.revertDownloadPackage(id, app, newApp, isUpdate, ex);
+      throw ex;
+    }
 
     // Success! Keep the zip in of TmpD, we'll move it out when
     // applyDownload() will be called.
     // Save the manifest in TmpD also
-    let manFile = OS.Path.join(OS.Constants.Path.tmpDir, "webapps", aId,
+    let manFile = OS.Path.join(OS.Constants.Path.tmpDir, "webapps", newId,
                                "manifest.webapp");
-    yield this._writeFile(manFile, JSON.stringify(aManifest));
-
-    app = this.webapps[aId];
+    yield this._writeFile(manFile, JSON.stringify(newManifest));
+
+    app = this.webapps[id];
+
     // Set state and fire events.
     app.downloading = false;
     app.downloadAvailable = false;
     app.readyToApplyDownload = true;
     app.updateTime = Date.now();
 
     yield this._saveApps();
 
@@ -2276,17 +2286,17 @@ this.DOMApplicationRegistry = {
   },
 
   // This function is called after we called the onsuccess callback on the
   // content side. This let the webpage the opportunity to set event handlers
   // on the app before we start firing progress events.
   queuedDownload: {},
   queuedPackageDownload: {},
 
-  onInstallSuccessAck: function(aManifestURL, aDontNeedNetwork) {
+  onInstallSuccessAck: Task.async(function*(aManifestURL, aDontNeedNetwork) {
     // If we are offline, register to run when we'll be online.
     if ((Services.io.offline) && !aDontNeedNetwork) {
       let onlineWrapper = {
         observe: function(aSubject, aTopic, aData) {
           Services.obs.removeObserver(onlineWrapper,
                                       "network:offline-status-changed");
           DOMApplicationRegistry.onInstallSuccessAck(aManifestURL);
         }
@@ -2309,21 +2319,28 @@ this.DOMApplicationRegistry = {
     let packageDownload = this.queuedPackageDownload[aManifestURL];
     if (packageDownload) {
       let manifest = packageDownload.manifest;
       let newApp = packageDownload.app;
       let installSuccessCallback = packageDownload.callback;
 
       delete this.queuedPackageDownload[aManifestURL];
 
-      this.downloadPackage(manifest, newApp, false).then(
-        this._onDownloadPackage.bind(this, newApp, installSuccessCallback)
-      );
+      let id = this._appIdForManifestURL(newApp.manifestURL);
+      let oldApp = this.webapps[id];
+      let newManifest, newId;
+      try {
+        [newId, newManifest] = yield this.downloadPackage(id, oldApp, manifest, newApp, false);
+
+        yield this._onDownloadPackage(newApp, installSuccessCallback, newId, newManifest);
+      } catch (ex) {
+        this.revertDownloadPackage(id, oldApp, newApp, false, ex);
+      }
     }
-  },
+  }),
 
   _setupApp: function(aData, aId) {
     let app = aData.app;
 
     // app can be uninstalled
     app.removable = true;
 
     if (aData.isPackage) {
@@ -2598,17 +2615,17 @@ this.DOMApplicationRegistry = {
    *        the callback to call on install success
    *
    * Passed params:
    *
    * @param aId {Integer} the unique ID of the application
    * @param aManifest {Object} The manifest of the application
    */
   _onDownloadPackage: Task.async(function*(aNewApp, aInstallSuccessCallback,
-                               [aId, aManifest]) {
+                                           aId, aManifest) {
     debug("_onDownloadPackage");
     // Success! Move the zip out of TmpD.
     let app = this.webapps[aId];
     let zipFile =
       FileUtils.getFile("TmpD", ["webapps", aId, "application.zip"], true);
     let dir = this._getAppDir(aId);
     zipFile.moveTo(dir, "application.zip");
     let tmpDir = FileUtils.getDir("TmpD", ["webapps", aId], true, true);
@@ -2732,109 +2749,98 @@ this.DOMApplicationRegistry = {
 
         elem.manifest = manifestCache[id];
       }
 
       return aData;
     }.bind(this)).then(null, Cu.reportError);
   },
 
-  downloadPackage: function(aManifest, aNewApp, aIsUpdate, aOnSuccess) {
+  downloadPackage: Task.async(function*(aId, aOldApp, aManifest, aNewApp, aIsUpdate) {
     // Here are the steps when installing a package:
     // - create a temp directory where to store the app.
     // - download the zip in this directory.
     // - check the signature on the zip.
     // - extract the manifest from the zip and check it.
     // - ask confirmation to the user.
     // - add the new app to the registry.
-    // If we fail at any step, we revert the previous ones and return an error.
-
-    // We define these outside the task to use them in its reject handler.
-    let id = this._appIdForManifestURL(aNewApp.manifestURL);
-    let oldApp = this.webapps[id];
-
-    return Task.spawn((function*() {
-      yield this._ensureSufficientStorage(aNewApp);
-
-      let fullPackagePath = aManifest.fullPackagePath();
-
-      // Check if it's a local file install (we've downloaded/sideloaded the
-      // package already, it existed on the build, or it came with an APK).
-      // Note that this variable also controls whether files signed with expired
-      // certificates are accepted or not. If isLocalFileInstall is true and the
-      // device date is earlier than the build generation date, then the signature
-      // will be accepted even if the certificate is expired.
-      let isLocalFileInstall =
-        Services.io.extractScheme(fullPackagePath) === 'file';
-
-      debug("About to download " + fullPackagePath);
-
-      let requestChannel = this._getRequestChannel(fullPackagePath,
-                                                   isLocalFileInstall,
-                                                   oldApp,
-                                                   aNewApp);
-
-      AppDownloadManager.add(
-        aNewApp.manifestURL,
-        {
-          channel: requestChannel,
-          appId: id,
-          previousState: aIsUpdate ? "installed" : "pending"
-        }
-      );
-
-      // We set the 'downloading' flag to true right before starting the fetch.
-      oldApp.downloading = true;
-
-      // We determine the app's 'installState' according to its previous
-      // state. Cancelled download should remain as 'pending'. Successfully
-      // installed apps should morph to 'updating'.
-      oldApp.installState = aIsUpdate ? "updating" : "pending";
-
-      // initialize the progress to 0 right now
-      oldApp.progress = 0;
-
-      // Save the current state of the app to handle cases where we may be
-      // retrying a past download.
-      yield DOMApplicationRegistry._saveApps();
-      DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
+    yield this._ensureSufficientStorage(aNewApp);
+
+    let fullPackagePath = aManifest.fullPackagePath();
+    // Check if it's a local file install (we've downloaded/sideloaded the
+    // package already, it existed on the build, or it came with an APK).
+    // Note that this variable also controls whether files signed with expired
+    // certificates are accepted or not. If isLocalFileInstall is true and the
+    // device date is earlier than the build generation date, then the signature
+    // will be accepted even if the certificate is expired.
+    let isLocalFileInstall =
+      Services.io.extractScheme(fullPackagePath) === 'file';
+
+    debug("About to download " + fullPackagePath);
+
+    let requestChannel = this._getRequestChannel(fullPackagePath,
+                                                 isLocalFileInstall,
+                                                 aOldApp,
+                                                 aNewApp);
+
+    AppDownloadManager.add(
+      aNewApp.manifestURL,
+      {
+        channel: requestChannel,
+        appId: aId,
+        previousState: aIsUpdate ? "installed" : "pending"
+      }
+    );
+
+    // We set the 'downloading' flag to true right before starting the fetch.
+    aOldApp.downloading = true;
+
+    // We determine the app's 'installState' according to its previous
+    // state. Cancelled download should remain as 'pending'. Successfully
+    // installed apps should morph to 'updating'.
+    aOldApp.installState = aIsUpdate ? "updating" : "pending";
+
+    // initialize the progress to 0 right now
+    aOldApp.progress = 0;
+
+    // Save the current state of the app to handle cases where we may be
+    // retrying a past download.
+    yield DOMApplicationRegistry._saveApps();
+
+    DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
         // Clear any previous download errors.
         error: null,
-        app: oldApp,
+        app: aOldApp,
         manifestURL: aNewApp.manifestURL
-      });
-
-      let zipFile = yield this._getPackage(requestChannel, id, oldApp, aNewApp);
-      let hash = yield this._computeFileHash(zipFile.path);
-
-      let responseStatus = requestChannel.responseStatus;
-      let oldPackage = (responseStatus == 304 || hash == oldApp.packageHash);
-
-      if (oldPackage) {
-        debug("package's etag or hash unchanged; sending 'applied' event");
-        // The package's Etag or hash has not changed.
-        // We send an "applied" event right away so code awaiting that event
-        // can proceed to access the app.  We also throw an error to alert
-        // the caller that the package wasn't downloaded.
-        this._sendAppliedEvent(aNewApp, oldApp, id);
-        throw new Error("PACKAGE_UNCHANGED");
-      }
-
-      let newManifest = yield this._openAndReadPackage(zipFile, oldApp, aNewApp,
-              isLocalFileInstall, aIsUpdate, aManifest, requestChannel, hash);
-
-      AppDownloadManager.remove(aNewApp.manifestURL);
-
-      return [oldApp.id, newManifest];
-
-    }).bind(this)).then(
-      aOnSuccess,
-      this._revertDownloadPackage.bind(this, id, oldApp, aNewApp, aIsUpdate)
-    );
-  },
+    });
+
+    let zipFile = yield this._getPackage(requestChannel, aId, aOldApp, aNewApp);
+    let hash = yield this._computeFileHash(zipFile.path);
+
+    let responseStatus = requestChannel.responseStatus;
+    let oldPackage = (responseStatus == 304 || hash == aOldApp.packageHash);
+
+    if (oldPackage) {
+      debug("package's etag or hash unchanged; sending 'applied' event");
+      // The package's Etag or hash has not changed.
+      // We send an "applied" event right away so code awaiting that event
+      // can proceed to access the app. We also throw an error to alert
+      // the caller that the package wasn't downloaded.
+      this._sendAppliedEvent(aNewApp, aOldApp, aId);
+      throw new Error("PACKAGE_UNCHANGED");
+    }
+
+    let newManifest = yield this._openAndReadPackage(zipFile, aOldApp, aNewApp,
+            isLocalFileInstall, aIsUpdate, aManifest, requestChannel, hash);
+
+    AppDownloadManager.remove(aNewApp.manifestURL);
+
+    return [aOldApp.id, newManifest];
+
+  }),
 
   _ensureSufficientStorage: function(aNewApp) {
     let deferred = Promise.defer();
 
     let navigator = Services.wm.getMostRecentWindow(chromeWindowType)
                             .navigator;
     let deviceStorage = null;
 
@@ -3489,18 +3495,18 @@ this.DOMApplicationRegistry = {
       }
 
     } else if (isInstalled) {
       throw "WRONG_APP_STORE_ID";
     }
   },
 
   // Removes the directory we created, and sends an error to the DOM side.
-  _revertDownloadPackage: function(aId, aOldApp, aNewApp, aIsUpdate, aError) {
-    debug("Cleanup: " + aError + "\n" + aError.stack);
+  revertDownloadPackage: function(aId, aOldApp, aNewApp, aIsUpdate, aError) {
+    debug("Error downloading package: " + aError);
     let dir = FileUtils.getDir("TmpD", ["webapps", aId], true, true);
     try {
       dir.remove(true);
     } catch (e) { }
 
     // We avoid notifying the error to the DOM side if the app download
     // was cancelled via cancelDownload, which already sends its own
     // notification.
@@ -3531,18 +3537,16 @@ this.DOMApplicationRegistry = {
         manifestURL: aNewApp.manifestURL
       });
       this.broadcastMessage("Webapps:FireEvent", {
         eventType: "downloaderror",
         manifestURL:  aNewApp.manifestURL
       });
     });
     AppDownloadManager.remove(aNewApp.manifestURL);
-
-    throw aError;
   },
 
   doUninstall: function(aData, aMm) {
     this.uninstall(aData.manifestURL,
       function onsuccess() {
         aMm.sendAsyncMessage("Webapps:Uninstall:Return:OK", aData);
       },
       function onfailure() {
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -1383,16 +1383,27 @@ TabChild::BrowserFrameProvideWindow(nsID
 }
 
 bool
 TabChild::HasValidInnerSize()
 {
   return mHasValidInnerSize;
 }
 
+void
+TabChild::SendPendingTouchPreventedResponse(bool aPreventDefault,
+                                            const ScrollableLayerGuid& aGuid)
+{
+  if (mPendingTouchPreventedResponse) {
+    MOZ_ASSERT(aGuid == mPendingTouchPreventedGuid);
+    SendContentReceivedTouch(mPendingTouchPreventedGuid, aPreventDefault);
+    mPendingTouchPreventedResponse = false;
+  }
+}
+
 #ifdef DEBUG
 PContentPermissionRequestChild*
 TabChild:: SendPContentPermissionRequestConstructor(PContentPermissionRequestChild* aActor,
                                                     const InfallibleTArray<PermissionRequest>& aRequests,
                                                     const IPC::Principal& aPrincipal)
 {
   PCOMContentPermissionRequestChild* child = static_cast<PCOMContentPermissionRequestChild*>(aActor);
   PContentPermissionRequestChild* request = PBrowserChild::SendPContentPermissionRequestConstructor(aActor, aRequests, aPrincipal);
@@ -1829,16 +1840,18 @@ TabChild::FireSingleTapEvent(LayoutDevic
 
 bool
 TabChild::RecvHandleLongTap(const CSSPoint& aPoint, const ScrollableLayerGuid& aGuid)
 {
   if (!mGlobal || !mTabChildGlobal) {
     return true;
   }
 
+  SendPendingTouchPreventedResponse(false, aGuid);
+
   bool eventHandled =
       DispatchMouseEvent(NS_LITERAL_STRING("contextmenu"),
                          APZCCallbackHelper::ApplyCallbackTransform(aPoint, aGuid),
                          2, 1, 0, false,
                          nsIDOMMouseEvent::MOZ_SOURCE_TOUCH);
 
   // If no one handle context menu, fire MOZLONGTAP event
   if (!eventHandled) {
@@ -2140,21 +2153,17 @@ TabChild::RecvRealTouchEvent(const Widge
     }
     // fall through
   case NS_TOUCH_CANCEL:
     if (mTouchEndIsClick != Unknown) {
       mActiveElementManager->HandleTouchEnd(mTouchEndIsClick == IsClick);
     }
     // fall through
   case NS_TOUCH_MOVE: {
-    if (mPendingTouchPreventedResponse) {
-      MOZ_ASSERT(aGuid == mPendingTouchPreventedGuid);
-      SendContentReceivedTouch(mPendingTouchPreventedGuid, isTouchPrevented);
-      mPendingTouchPreventedResponse = false;
-    }
+    SendPendingTouchPreventedResponse(isTouchPrevented, aGuid);
     break;
   }
 
   default:
     NS_WARNING("Unknown touch event type");
   }
 
   return true;
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -550,16 +550,19 @@ private:
                               nsIURI* aURI,
                               const nsAString& aName,
                               const nsACString& aFeatures,
                               bool* aWindowIsNew,
                               nsIDOMWindow** aReturn);
 
     bool HasValidInnerSize();
 
+    void SendPendingTouchPreventedResponse(bool aPreventDefault,
+                                           const ScrollableLayerGuid& aGuid);
+
     class CachedFileDescriptorInfo;
     class CachedFileDescriptorCallbackRunnable;
 
     TextureFactoryIdentifier mTextureFactoryIdentifier;
     nsCOMPtr<nsIWebNavigation> mWebNav;
     nsCOMPtr<nsIWidget> mWidget;
     nsCOMPtr<nsIURI> mLastURI;
     RenderFrameChild* mRemoteFrame;
--- a/dom/src/geolocation/nsGeolocation.cpp
+++ b/dom/src/geolocation/nsGeolocation.cpp
@@ -497,33 +497,31 @@ nsGeolocationRequest::StopTimeoutTimer()
 void
 nsGeolocationRequest::SendLocation(nsIDOMGeoPosition* aPosition)
 {
   if (mShutdown) {
     // Ignore SendLocationEvents issued before we were cleared.
     return;
   }
 
-  nsRefPtr<Position> wrapped, cachedWrapper = mLocator->GetCachedPosition();
-  if (cachedWrapper && aPosition == cachedWrapper->GetWrappedGeoPosition()) {
-    wrapped = cachedWrapper;
-  } else if (aPosition) {
+  nsRefPtr<Position> wrapped;
+
+  if (aPosition) {
     nsCOMPtr<nsIDOMGeoPositionCoords> coords;
     aPosition->GetCoords(getter_AddRefs(coords));
     if (coords) {
       wrapped = new Position(ToSupports(mLocator), aPosition);
     }
   }
 
   if (!wrapped) {
     NotifyError(nsIDOMGeoPositionError::POSITION_UNAVAILABLE);
     return;
   }
 
-  mLocator->SetCachedPosition(wrapped);
   if (!mIsWatchPositionRequest) {
     // Cancel timer and position updates in case the position
     // callback spins the event loop
     Shutdown();
   }
 
   nsAutoMicroTask mt;
   if (mCallback.HasWebIDLCallback()) {
@@ -1025,17 +1023,16 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
   NS_INTERFACE_MAP_ENTRY(nsIDOMGeoGeolocation)
   NS_INTERFACE_MAP_ENTRY(nsIGeolocationUpdate)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(Geolocation)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(Geolocation)
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Geolocation,
-                                      mCachedPosition,
                                       mPendingCallbacks,
                                       mWatchingCallbacks,
                                       mPendingRequests)
 
 Geolocation::Geolocation()
 : mLastWatchId(0)
 {
   SetIsDOMBinding();
@@ -1199,28 +1196,16 @@ Geolocation::NotifyError(uint16_t aError
   for (uint32_t i = 0; i < mWatchingCallbacks.Length(); i++) {
     mWatchingCallbacks[i]->NotifyErrorAndShutdown(aErrorCode);
   }
 
   return NS_OK;
 }
 
 void
-Geolocation::SetCachedPosition(Position* aPosition)
-{
-  mCachedPosition = aPosition;
-}
-
-Position*
-Geolocation::GetCachedPosition()
-{
-  return mCachedPosition;
-}
-
-void
 Geolocation::GetCurrentPosition(PositionCallback& aCallback,
                                 PositionErrorCallback* aErrorCallback,
                                 const PositionOptions& aOptions,
                                 ErrorResult& aRv)
 {
   GeoPositionCallback successCallback(&aCallback);
   GeoPositionErrorCallback errorCallback(aErrorCallback);
 
--- a/dom/src/geolocation/nsGeolocation.h
+++ b/dom/src/geolocation/nsGeolocation.h
@@ -140,19 +140,16 @@ public:
   nsresult Init(nsIDOMWindow* contentDom=nullptr);
 
   nsIDOMWindow* GetParentObject() const;
   virtual JSObject* WrapObject(JSContext *aCtx) MOZ_OVERRIDE;
 
   int32_t WatchPosition(PositionCallback& aCallback, PositionErrorCallback* aErrorCallback, const PositionOptions& aOptions, ErrorResult& aRv);
   void GetCurrentPosition(PositionCallback& aCallback, PositionErrorCallback* aErrorCallback, const PositionOptions& aOptions, ErrorResult& aRv);
 
-  void SetCachedPosition(Position* aPosition);
-  Position* GetCachedPosition();
-
   // Returns true if any of the callbacks are repeating
   bool HasActiveCallbacks();
 
   // Register an allowed request
   void NotifyAllowedRequest(nsGeolocationRequest* aRequest);
 
   // Remove request from all callbacks arrays
   void RemoveRequest(nsGeolocationRequest* request);
@@ -201,19 +198,16 @@ private:
   nsWeakPtr mOwner;
 
   // where the content was loaded from
   nsCOMPtr<nsIPrincipal> mPrincipal;
 
   // owning back pointer.
   nsRefPtr<nsGeolocationService> mService;
 
-  // cached Position wrapper
-  nsRefPtr<Position> mCachedPosition;
-
   // Watch ID
   uint32_t mLastWatchId;
 
   // Pending requests are used when the service is not ready
   nsTArray<nsRefPtr<nsGeolocationRequest> > mPendingRequests;
 };
 
 class PositionError MOZ_FINAL : public nsIDOMGeoPositionError,
--- a/dom/tests/mochitest/geolocation/test_cachedPosition.html
+++ b/dom/tests/mochitest/geolocation/test_cachedPosition.html
@@ -16,74 +16,68 @@ https://bugzilla.mozilla.org/show_bug.cg
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
+/*
+The request cache vs. the PositionOptions cache is confusing to the reader, to explain:
+
+Testing uses mochitest httpd, so the network cache must be disabled in order for requests
+to propagate to mochitest httpd. Otherwise, every request looks like a geoip request,
+and the network request cache sees no reason to make a web request for location for geoip
+if it has already made geoip (or better) requests.
+
+We should investigate providing fake wifi and cell scans to the
+network location provider, then the network cache does not need to be shut off
+AND it will get testing coverage.
+
+*/
 resume_geolocationProvider(function() {
     force_prompt(true, function () {
-      set_network_request_cache_enabled(false, function() {
-        test1();
-      });
+      set_network_request_cache_enabled(false, test_cachedPosition)
     });
 });
 
 function done() {
   set_network_request_cache_enabled(true, function() {
     resume_geolocationProvider(function() {
       SimpleTest.finish();
     });
   });
 }
 
 function errorCallback(err) {
   ok(false, "error callback should not have been called");
   done();
 }
 
-function testCachedPosition() {
-  var cached = null;
-  navigator.geolocation.getCurrentPosition(function(pos) {
-    // get cached position
-    cached = pos;
+function areTimesEqual(loc1, loc2) {
+  return loc1.timestamp === loc2.timestamp;
+}
 
-    navigator.geolocation.getCurrentPosition(function(pos) {
-      // force use of cached position, make sure
-      // it's equal to what we have
-      is(pos, cached, "position should be equal to cached position");
-      resume_geolocationProvider(function() {
+function test_cachedPosition() {
+   var cached = null;
+   navigator.geolocation.getCurrentPosition(function(pos) {
+    // first call is just to warm up the cache
+
+     navigator.geolocation.getCurrentPosition(function(pos) {
+      cached = pos;
+
+      navigator.geolocation.getCurrentPosition(function(pos) {
+        ok(areTimesEqual(pos, cached), "position should be equal to cached position");
         navigator.geolocation.getCurrentPosition(function(pos) {
           // force new position, can't be the one we have
-          isnot(pos, cached, "new position should be different from the cached");
+          ok(!areTimesEqual(pos, cached), "position should not be equal to cached position");
           done();
         }, errorCallback, {maximumAge: 0});
-      });
-    }, errorCallback, {maximumAge: 21600000});
-  }, errorCallback, {maximumAge: 21600000});
-}
-
-// ensure we have a position in cache,
-// and stop receiving new positions once we do so the
-// cache doesn't change
-var watchID;
-function test1() {
-  watchID = navigator.geolocation.watchPosition(
-    function(pos) {
-      info("Stopping geolocation provider");
-      stop_geolocationProvider(function() {});
-    }, function(err) {
-      is(err.code, err.TIMEOUT, "got TIMEOUT for watchPosition");
-
-      // no new positions in a while,
-      // the cache should be stable now.
-      navigator.geolocation.clearWatch(watchID);
-      testCachedPosition();
-    }, {maximumAge: 0, timeout: 1000}
-  );
-}
+      }, errorCallback, {maximumAge: 21600000});
+     }, errorCallback, {maximumAge: 21600000});
+   }, errorCallback, {maximumAge: 21600000});
+ }
 </script>
 </pre>
 </body>
 </html>
 
--- a/gfx/layers/CopyableCanvasLayer.cpp
+++ b/gfx/layers/CopyableCanvasLayer.cpp
@@ -5,17 +5,16 @@
 
 #include "BasicLayersImpl.h"            // for FillWithMask, etc
 #include "CopyableCanvasLayer.h"
 #include "GLContext.h"                  // for GLContext
 #include "GLScreenBuffer.h"             // for GLScreenBuffer
 #include "SharedSurface.h"              // for SharedSurface
 #include "SharedSurfaceGL.h"              // for SharedSurface
 #include "SurfaceStream.h"              // for SurfaceStream
-#include "gfxMatrix.h"                  // for gfxMatrix
 #include "gfxPattern.h"                 // for gfxPattern, etc
 #include "gfxPlatform.h"                // for gfxPlatform, gfxImageFormat
 #include "gfxRect.h"                    // for gfxRect
 #include "gfxUtils.h"                   // for gfxUtils
 #include "gfx2DGlue.h"                  // for thebes --> moz2d transition
 #include "mozilla/gfx/BaseSize.h"       // for BaseSize
 #include "mozilla/gfx/Tools.h"
 #include "nsDebug.h"                    // for NS_ASSERTION, NS_WARNING, etc
--- a/gfx/layers/LayerScope.cpp
+++ b/gfx/layers/LayerScope.cpp
@@ -376,19 +376,19 @@ public:
 
     virtual bool Write() = 0;
 
     static bool WriteToStream(Packet& aPacket) {
         if (!WebSocketHelper::GetSocketManager())
             return true;
 
         uint32_t size = aPacket.ByteSize();
-        nsAutoArrayPtr<uint8_t> data(new uint8_t[size]);
-        aPacket.SerializeToArray(data, size);
-        return WebSocketHelper::GetSocketManager()->WriteAll(data, size);
+        UniquePtr<uint8_t[]> data(new uint8_t[size]);
+        aPacket.SerializeToArray(data.get(), size);
+        return WebSocketHelper::GetSocketManager()->WriteAll(data.get(), size);
     }
 
 protected:
     Packet::DataType mDataType;
 };
 
 class DebugGLFrameStatusData : public DebugGLData
 {
@@ -468,26 +468,26 @@ private:
 
         if (aImage) {
             tp->set_width(aImage->GetSize().width);
             tp->set_height(aImage->GetSize().height);
             tp->set_stride(aImage->Stride());
 
             mDatasize = aImage->GetSize().height * aImage->Stride();
 
-            nsAutoArrayPtr<char> compresseddata(
+            UniquePtr<char[]> compresseddata(
                 new char[LZ4::maxCompressedSize(mDatasize)]);
-            if (compresseddata.get()) {
+            if (compresseddata) {
                 int ndatasize = LZ4::compress((char*)aImage->GetData(),
                                               mDatasize,
-                                              compresseddata);
+                                              compresseddata.get());
                 if (ndatasize > 0) {
                     mDatasize = ndatasize;
                     tp->set_dataformat((1 << 16 | tp->dataformat()));
-                    tp->set_data(compresseddata, mDatasize);
+                    tp->set_data(compresseddata.get(), mDatasize);
                 } else {
                     NS_WARNING("Compress data failed");
                     tp->set_data(aImage->GetData(), mDatasize);
                 }
             } else {
                 NS_WARNING("Couldn't new compressed data.");
                 tp->set_data(aImage->GetData(), mDatasize);
             }
@@ -606,17 +606,17 @@ public:
 
     /* nsIRunnable impl; send the data */
 
     NS_IMETHODIMP Run() {
         DebugGLData *d;
         nsresult rv = NS_OK;
 
         while ((d = mList.popFirst()) != nullptr) {
-            nsAutoPtr<DebugGLData> cleaner(d);
+            UniquePtr<DebugGLData> cleaner(d);
             if (!d->Write()) {
                 rv = NS_ERROR_FAILURE;
                 break;
             }
         }
 
         Cleanup();
 
--- a/gfx/layers/LayerSorter.cpp
+++ b/gfx/layers/LayerSorter.cpp
@@ -75,20 +75,18 @@ static gfxFloat RecoverZDepth(const gfx3
  * in particular) and this generates the 'correct' looking ordering. For planes
  * that truely intersect, then there is no correct ordering and this remains
  * unsolved without changing our rendering code.
  */
 static LayerSortOrder CompareDepth(Layer* aOne, Layer* aTwo) {
   gfxRect ourRect = aOne->GetEffectiveVisibleRegion().GetBounds();
   gfxRect otherRect = aTwo->GetEffectiveVisibleRegion().GetBounds();
 
-  gfx3DMatrix ourTransform;
-  To3DMatrix(aOne->GetTransform(), ourTransform);
-  gfx3DMatrix otherTransform;
-  To3DMatrix(aTwo->GetTransform(), otherTransform);
+  gfx3DMatrix ourTransform = To3DMatrix(aOne->GetTransform());
+  gfx3DMatrix otherTransform = To3DMatrix(aTwo->GetTransform());
 
   // Transform both rectangles and project into 2d space.
   gfxQuad ourTransformedRect = ourTransform.TransformRect(ourRect);
   gfxQuad otherTransformedRect = otherTransform.TransformRect(otherRect);
 
   gfxRect ourBounds = ourTransformedRect.GetBounds();
   gfxRect otherBounds = otherTransformedRect.GetBounds();
 
--- a/gfx/layers/LayerTreeInvalidation.cpp
+++ b/gfx/layers/LayerTreeInvalidation.cpp
@@ -109,17 +109,17 @@ struct LayerPropertiesBase : public Laye
   {
     MOZ_COUNT_CTOR(LayerPropertiesBase);
     if (aLayer->GetMaskLayer()) {
       mMaskLayer = CloneLayerTreePropertiesInternal(aLayer->GetMaskLayer());
     }
     if (mUseClipRect) {
       mClipRect = *aLayer->GetClipRect();
     }
-    gfx::To3DMatrix(aLayer->GetTransform(), mTransform);
+    mTransform = gfx::To3DMatrix(aLayer->GetTransform());
   }
   LayerPropertiesBase()
     : mLayer(nullptr)
     , mMaskLayer(nullptr)
   {
     MOZ_COUNT_CTOR(LayerPropertiesBase);
   }
   ~LayerPropertiesBase()
@@ -131,18 +131,17 @@ struct LayerPropertiesBase : public Laye
                                          NotifySubDocInvalidationFunc aCallback,
                                          bool* aGeometryChanged);
 
   virtual void MoveBy(const nsIntPoint& aOffset);
 
   nsIntRegion ComputeChange(NotifySubDocInvalidationFunc aCallback,
                             bool& aGeometryChanged)
   {
-    gfx3DMatrix transform;
-    gfx::To3DMatrix(mLayer->GetTransform(), transform);
+    gfx3DMatrix transform = gfx::To3DMatrix(mLayer->GetTransform());
     bool transformChanged = !mTransform.FuzzyEqual(transform) ||
                             mLayer->GetPostXScale() != mPostXScale ||
                             mLayer->GetPostYScale() != mPostYScale;
     Layer* otherMask = mLayer->GetMaskLayer();
     const nsIntRect* otherClip = mLayer->GetClipRect();
     nsIntRegion result;
     if ((mMaskLayer ? mMaskLayer->mLayer : nullptr) != otherMask ||
         (mUseClipRect != !!otherClip) ||
@@ -180,19 +179,18 @@ struct LayerPropertiesBase : public Laye
     }
 
     mLayer->ClearInvalidRect();
     return result;
   }
 
   nsIntRect NewTransformedBounds()
   {
-    gfx3DMatrix transform;
-    gfx::To3DMatrix(mLayer->GetTransform(), transform);
-    return TransformRect(mLayer->GetVisibleRegion().GetBounds(), transform);
+    return TransformRect(mLayer->GetVisibleRegion().GetBounds(),
+                         gfx::To3DMatrix(mLayer->GetTransform()));
   }
 
   nsIntRect OldTransformedBounds()
   {
     return TransformRect(mVisibleRegion.GetBounds(), mTransform);
   }
 
   virtual nsIntRegion ComputeChangeInternal(NotifySubDocInvalidationFunc aCallback,
@@ -289,19 +287,18 @@ struct ContainerLayerProperties : public
           invalidateChildsCurrentArea = true;
         }
       } else {
         // |child| is new, or was reordered to a higher index
         invalidateChildsCurrentArea = true;
       }
       if (invalidateChildsCurrentArea) {
         aGeometryChanged = true;
-        gfx3DMatrix transform;
-        gfx::To3DMatrix(child->GetTransform(), transform);
-        AddTransformedRegion(result, child->GetVisibleRegion(), transform);
+        AddTransformedRegion(result, child->GetVisibleRegion(),
+                             gfx::To3DMatrix(child->GetTransform()));
         if (aCallback) {
           NotifySubdocumentInvalidationRecursive(child, aCallback);
         } else {
           ClearInvalidations(child);
         }
       }
     }
 
@@ -310,19 +307,17 @@ struct ContainerLayerProperties : public
       AddRegion(result, mChildren[i]->OldTransformedBounds());
       i++;
     }
 
     if (aCallback) {
       aCallback(container, result);
     }
 
-    gfx3DMatrix transform;
-    gfx::To3DMatrix(mLayer->GetTransform(), transform);
-    result.Transform(transform);
+    result.Transform(gfx::To3DMatrix(mLayer->GetTransform()));
     return result;
   }
 
   // The old list of children:
   nsAutoTArray<nsAutoPtr<LayerPropertiesBase>,1> mChildren;
   float mPreXScale;
   float mPreYScale;
 };
@@ -442,18 +437,17 @@ LayerPropertiesBase::ComputeDifferences(
 {
   NS_ASSERTION(aRoot, "Must have a layer tree to compare against!");
   if (mLayer != aRoot) {
     if (aCallback) {
       NotifySubdocumentInvalidationRecursive(aRoot, aCallback);
     } else {
       ClearInvalidations(aRoot);
     }
-    gfx3DMatrix transform;
-    gfx::To3DMatrix(aRoot->GetTransform(), transform);
+    gfx3DMatrix transform = gfx::To3DMatrix(aRoot->GetTransform());
     nsIntRect result = TransformRect(aRoot->GetVisibleRegion().GetBounds(), transform);
     result = result.Union(OldTransformedBounds());
     if (aGeometryChanged != nullptr) {
       *aGeometryChanged = true;
     }
     return result;
   } else {
     bool geometryChanged = (aGeometryChanged != nullptr) ? *aGeometryChanged : false;
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -9,17 +9,16 @@
 #include <stdint.h>                     // for uint32_t, uint64_t, uint8_t
 #include <stdio.h>                      // for FILE
 #include <sys/types.h>                  // for int32_t, int64_t
 #include "FrameMetrics.h"               // for FrameMetrics
 #include "Units.h"                      // for LayerMargin, LayerPoint
 #include "gfxContext.h"                 // for GraphicsOperator
 #include "gfxTypes.h"
 #include "gfxColor.h"                   // for gfxRGBA
-#include "gfxMatrix.h"                  // for gfxMatrix
 #include "GraphicsFilter.h"             // for GraphicsFilter
 #include "gfxPoint.h"                   // for gfxPoint
 #include "gfxRect.h"                    // for gfxRect
 #include "gfx2DGlue.h"
 #include "mozilla/Assertions.h"         // for MOZ_ASSERT_HELPER2, etc
 #include "mozilla/DebugOnly.h"          // for DebugOnly
 #include "mozilla/EventForwards.h"      // for nsPaintEvent
 #include "mozilla/RefPtr.h"             // for TemporaryRef
--- a/gfx/layers/RenderTrace.cpp
+++ b/gfx/layers/RenderTrace.cpp
@@ -10,18 +10,18 @@
 #include "Layers.h"
 
 
 namespace mozilla {
 namespace layers {
 
 static int colorId = 0;
 
-static gfx3DMatrix GetRootTransform(Layer *aLayer) {
-  gfx3DMatrix layerTrans = aLayer->GetTransform();
+static gfx::Matrix4x4 GetRootTransform(Layer *aLayer) {
+  gfx::Matrix4x4 layerTrans = aLayer->GetTransform();
   layerTrans.ProjectTo2D();
   if (aLayer->GetParent() != nullptr) {
     return GetRootTransform(aLayer->GetParent()) * layerTrans;
   }
   return layerTrans;
 }
 
 void RenderTraceLayers(Layer *aLayer, const char *aColor, const gfx3DMatrix aRootTransform, bool aReset) {
@@ -48,18 +48,18 @@ void RenderTraceLayers(Layer *aLayer, co
         child; child = child->GetNextSibling()) {
     RenderTraceLayers(child, aColor, aRootTransform, false);
   }
 
   if (aReset) colorId = 0;
 }
 
 void RenderTraceInvalidateStart(Layer *aLayer, const char *aColor, const nsIntRect aRect) {
-  gfx3DMatrix trans = GetRootTransform(aLayer);
-  gfxRect rect(aRect.x, aRect.y, aRect.width, aRect.height);
+  gfx::Matrix4x4 trans = GetRootTransform(aLayer);
+  gfx::Rect rect(aRect.x, aRect.y, aRect.width, aRect.height);
   trans.TransformBounds(rect);
 
   printf_stderr("%s RENDERTRACE %u fillrect #%s %i %i %i %i\n",
     aLayer->Name(), (int)PR_IntervalNow(),
     aColor,
     (int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height);
 }
 void RenderTraceInvalidateEnd(Layer *aLayer, const char *aColor) {
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -239,18 +239,17 @@ APZCTreeManager::UpdatePanZoomController
         if (state->mController->GetTouchSensitiveRegion(&touchSensitiveRegion)) {
           // Note: we assume here that touchSensitiveRegion is in the CSS pixels
           // of our parent layer, which makes this coordinate conversion
           // correct.
           visible = visible.Intersect(touchSensitiveRegion
                                       * metrics.mDevPixelsPerCSSPixel
                                       * metrics.GetParentResolution());
         }
-        gfx3DMatrix transform;
-        gfx::To3DMatrix(aLayer->GetTransform(), transform);
+        gfx3DMatrix transform = gfx::To3DMatrix(aLayer->GetTransform());
 
         apzc->SetLayerHitTestData(visible, aTransform, transform);
         APZCTM_LOG("Setting rect(%f %f %f %f) as visible region for APZC %p\n", visible.x, visible.y,
                                                                               visible.width, visible.height,
                                                                               apzc);
 
         mApzcTreeLog << "APZC " << guid
                      << "\tcb=" << visible
@@ -307,19 +306,17 @@ APZCTreeManager::UpdatePanZoomController
   mApzcTreeLog << '\n';
 
   // Accumulate the CSS transform between layers that have an APZC, but exclude any
   // any layers that do have an APZC, and reset the accumulation at those layers.
   if (apzc) {
     aTransform = gfx3DMatrix();
   } else {
     // Multiply child layer transforms on the left so they get applied first
-    gfx3DMatrix matrix;
-    gfx::To3DMatrix(aLayer->GetTransform(), matrix);
-    aTransform = matrix * aTransform;
+    aTransform = gfx::To3DMatrix(aLayer->GetTransform()) * aTransform;
   }
 
   uint64_t childLayersId = (aLayer->AsRefLayer() ? aLayer->AsRefLayer()->GetReferentId() : aLayersId);
   // If there's no APZC at this level, any APZCs for our child layers will
   // have our siblings as siblings.
   AsyncPanZoomController* next = apzc ? nullptr : aNextSibling;
   for (Layer* child = aLayer->GetLastChild(); child; child = child->GetPrevSibling()) {
     gfx::TreeAutoIndent indent(mApzcTreeLog);
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -2339,16 +2339,25 @@ void AsyncPanZoomController::NotifyLayer
 
     mX.CancelTouch();
     mY.CancelTouch();
     SetState(NOTHING);
 
     mFrameMetrics = aLayerMetrics;
     mLastDispatchedPaintMetrics = aLayerMetrics;
     ShareCompositorFrameMetrics();
+
+    if (mFrameMetrics.GetDisplayPortMargins() != LayerMargin()) {
+      // A non-zero display port margin here indicates a displayport has
+      // been set by a previous APZC for the content at this guid. The
+      // scrollable rect may have changed since then, making the margins
+      // wrong, so we need to calculate a new display port.
+      APZC_LOG("%p detected non-empty margins which probably need updating\n", this);
+      needContentRepaint = true;
+    }
   } else {
     // If we're not taking the aLayerMetrics wholesale we still need to pull
     // in some things into our local mFrameMetrics because these things are
     // determined by Gecko and our copy in mFrameMetrics may be stale.
 
     if (FuzzyEqualsAdditive(mFrameMetrics.mCompositionBounds.width, aLayerMetrics.mCompositionBounds.width) &&
         mFrameMetrics.mDevPixelsPerCSSPixel == aLayerMetrics.mDevPixelsPerCSSPixel) {
       float parentResolutionChange = aLayerMetrics.GetParentResolution().scale
--- a/gfx/layers/basic/BasicCompositor.cpp
+++ b/gfx/layers/basic/BasicCompositor.cpp
@@ -130,22 +130,21 @@ DrawSurfaceWithTextureCoords(DrawTarget 
                      aTextureCoords.width * aSource->GetSize().width,
                      aTextureCoords.height * aSource->GetSize().height);
 
   // Floating point error can accumulate above and we know our visible region
   // is integer-aligned, so round it out.
   sourceRect.Round();
 
   // Compute a transform that maps sourceRect to aDestRect.
-  gfxMatrix transform =
+  Matrix matrix =
     gfxUtils::TransformRectToRect(sourceRect,
-                                  gfxPoint(aDestRect.x, aDestRect.y),
-                                  gfxPoint(aDestRect.XMost(), aDestRect.y),
-                                  gfxPoint(aDestRect.XMost(), aDestRect.YMost()));
-  Matrix matrix = ToMatrix(transform);
+                                  gfx::IntPoint(aDestRect.x, aDestRect.y),
+                                  gfx::IntPoint(aDestRect.XMost(), aDestRect.y),
+                                  gfx::IntPoint(aDestRect.XMost(), aDestRect.YMost()));
 
   // Only use REPEAT if aTextureCoords is outside (0, 0, 1, 1).
   gfx::Rect unitRect(0, 0, 1, 1);
   ExtendMode mode = unitRect.Contains(aTextureCoords) ? ExtendMode::CLAMP : ExtendMode::REPEAT;
 
   FillRectWithMask(aDest, aDestRect, aSource, aFilter, DrawOptions(aOpacity),
                    mode, aMask, aMaskTransform, &matrix);
 }
@@ -258,17 +257,17 @@ BasicCompositor::DrawQuad(const gfx::Rec
       return;
     }
 
     Matrix destTransform;
     destTransform.Translate(-aRect.x, -aRect.y);
     dest->SetTransform(destTransform);
 
     // Get the bounds post-transform.
-    To3DMatrix(aTransform, new3DTransform);
+    new3DTransform = To3DMatrix(aTransform);
     gfxRect bounds = new3DTransform.TransformBounds(ThebesRect(aRect));
     bounds.IntersectRect(bounds, gfxRect(offset.x, offset.y, buffer->GetSize().width, buffer->GetSize().height));
 
     transformBounds = ToRect(bounds);
     transformBounds.RoundOut();
 
     // Propagate the coordinate offset to our 2D draw target.
     newTransform.Translate(transformBounds.x, transformBounds.y);
--- a/gfx/layers/basic/BasicLayerManager.cpp
+++ b/gfx/layers/basic/BasicLayerManager.cpp
@@ -892,18 +892,17 @@ BasicLayerManager::PaintLayer(gfxContext
                      (aLayer->GetDebugColorIndex() & 4) ? 1.0 : 0.0,
                      1.0);
 
       nsRefPtr<gfxContext> temp = new gfxContext(untransformedDT, Point(bounds.x, bounds.y));
       temp->SetColor(color);
       temp->Paint();
     }
 #endif
-    gfx3DMatrix effectiveTransform;
-    gfx::To3DMatrix(aLayer->GetEffectiveTransform(), effectiveTransform);
+    gfx3DMatrix effectiveTransform = gfx::To3DMatrix(aLayer->GetEffectiveTransform());
     nsRefPtr<gfxASurface> result =
       Transform3D(untransformedDT->Snapshot(), aTarget, bounds,
                   effectiveTransform, destRect);
 
     if (result) {
       aTarget->SetSource(result, destRect.TopLeft());
       // Azure doesn't support EXTEND_NONE, so to avoid extending the edges
       // of the source surface out to the current clip region, clip to
--- a/gfx/layers/client/ClientTiledThebesLayer.cpp
+++ b/gfx/layers/client/ClientTiledThebesLayer.cpp
@@ -72,19 +72,17 @@ GetTransformToAncestorsParentLayer(Layer
     if (iter->AsContainerLayer()) {
       // If the layer has a non-transient async transform then we need to apply it here
       // because it will get applied by the APZ in the compositor as well
       const FrameMetrics& metrics = iter->AsContainerLayer()->GetFrameMetrics();
       transform = transform * gfx::Matrix4x4().Scale(metrics.mResolution.scale, metrics.mResolution.scale, 1.f);
     }
     transform = transform * iter->GetTransform();
   }
-  gfx3DMatrix ret;
-  gfx::To3DMatrix(transform, ret);
-  return ret;
+  return gfx::To3DMatrix(transform);
 }
 
 void
 ClientTiledThebesLayer::GetAncestorLayers(ContainerLayer** aOutScrollAncestor,
                                           ContainerLayer** aOutDisplayPortAncestor)
 {
   ContainerLayer* scrollAncestor = nullptr;
   ContainerLayer* displayPortAncestor = nullptr;
--- a/gfx/layers/client/CompositableClient.cpp
+++ b/gfx/layers/client/CompositableClient.cpp
@@ -181,23 +181,24 @@ CompositableClient::GetAsyncID() const
 {
   if (mCompositableChild) {
     return mCompositableChild->mAsyncID;
   }
   return 0; // zero is always an invalid async ID
 }
 
 TemporaryRef<BufferTextureClient>
-CompositableClient::CreateBufferTextureClient(SurfaceFormat aFormat,
-                                              TextureFlags aTextureFlags,
-                                              gfx::BackendType aMoz2DBackend)
+CompositableClient::CreateBufferTextureClient(gfx::SurfaceFormat aFormat,
+                                              gfx::IntSize aSize,
+                                              gfx::BackendType aMoz2DBackend,
+                                              TextureFlags aTextureFlags)
 {
-  return TextureClient::CreateBufferTextureClient(GetForwarder(), aFormat,
-                                                  aTextureFlags | mTextureFlags,
-                                                  aMoz2DBackend);
+  return TextureClient::CreateForRawBufferAccess(GetForwarder(),
+                                                 aFormat, aSize, aMoz2DBackend,
+                                                 aTextureFlags | mTextureFlags);
 }
 
 TemporaryRef<TextureClient>
 CompositableClient::CreateTextureClientForDrawing(gfx::SurfaceFormat aFormat,
                                                   gfx::IntSize aSize,
                                                   gfx::BackendType aMoz2DBackend,
                                                   TextureFlags aTextureFlags,
                                                   TextureAllocationFlags aAllocFlags)
--- a/gfx/layers/client/CompositableClient.h
+++ b/gfx/layers/client/CompositableClient.h
@@ -126,18 +126,19 @@ public:
   CompositableClient(CompositableForwarder* aForwarder, TextureFlags aFlags = TextureFlags::NO_FLAGS);
 
   virtual TextureInfo GetTextureInfo() const = 0;
 
   LayersBackend GetCompositorBackendType() const;
 
   TemporaryRef<BufferTextureClient>
   CreateBufferTextureClient(gfx::SurfaceFormat aFormat,
-                            TextureFlags aFlags = TextureFlags::DEFAULT,
-                            gfx::BackendType aMoz2dBackend = gfx::BackendType::NONE);
+                            gfx::IntSize aSize,
+                            gfx::BackendType aMoz2dBackend = gfx::BackendType::NONE,
+                            TextureFlags aFlags = TextureFlags::DEFAULT);
 
   TemporaryRef<TextureClient>
   CreateTextureClientForDrawing(gfx::SurfaceFormat aFormat,
                                 gfx::IntSize aSize,
                                 gfx::BackendType aMoz2DBackend,
                                 TextureFlags aTextureFlags,
                                 TextureAllocationFlags aAllocFlags = ALLOC_DEFAULT);
 
--- a/gfx/layers/client/ImageClient.cpp
+++ b/gfx/layers/client/ImageClient.cpp
@@ -204,21 +204,22 @@ ImageClientSingle::UpdateImageInternal(I
 
     if (mFrontBuffer && mFrontBuffer->IsImmutable()) {
       autoRemoveTexture.mTexture = mFrontBuffer;
       mFrontBuffer = nullptr;
     }
 
     bool bufferCreated = false;
     if (!mFrontBuffer) {
-      mFrontBuffer = CreateBufferTextureClient(gfx::SurfaceFormat::YUV, TextureFlags::DEFAULT);
       gfx::IntSize ySize(data->mYSize.width, data->mYSize.height);
       gfx::IntSize cbCrSize(data->mCbCrSize.width, data->mCbCrSize.height);
-      if (!mFrontBuffer->AsTextureClientYCbCr()->AllocateForYCbCr(ySize, cbCrSize, data->mStereoMode)) {
-        mFrontBuffer = nullptr;
+      mFrontBuffer = TextureClient::CreateForYCbCr(GetForwarder(),
+                                                   ySize, cbCrSize, data->mStereoMode,
+                                                   TextureFlags::DEFAULT|mTextureFlags);
+      if (!mFrontBuffer) {
         return false;
       }
       bufferCreated = true;
     }
 
     if (!mFrontBuffer->Lock(OpenMode::OPEN_WRITE_ONLY)) {
       mFrontBuffer = nullptr;
       return false;
--- a/gfx/layers/client/SimpleTextureClientPool.cpp
+++ b/gfx/layers/client/SimpleTextureClientPool.cpp
@@ -65,25 +65,27 @@ SimpleTextureClientPool::GetTextureClien
   if (mAvailableTextureClients.size()) {
     textureClient = mAvailableTextureClients.top();
     mAvailableTextureClients.pop();
     RECYCLE_LOG("%s Skip allocate (%i left), returning %p\n", (mFormat == SurfaceFormat::B8G8R8A8?"poolA":"poolX"), mAvailableTextureClients.size(), textureClient.get());
 
   } else {
     // No unused clients in the pool, create one
     if (gfxPrefs::ForceShmemTiles()) {
-      textureClient = TextureClient::CreateBufferTextureClient(mSurfaceAllocator,
-        mFormat, TextureFlags::IMMEDIATE_UPLOAD | TextureFlags::RECYCLE, gfx::BackendType::NONE);
-      if (!textureClient->AllocateForSurface(mSize, ALLOC_DEFAULT)) {
-        NS_WARNING("TextureClient::AllocateForSurface failed!");
-      }
+      textureClient = TextureClient::CreateForRawBufferAccess(mSurfaceAllocator,
+        mFormat, mSize, gfx::BackendType::NONE,
+        TextureFlags::IMMEDIATE_UPLOAD | TextureFlags::RECYCLE, ALLOC_DEFAULT);
     } else {
       textureClient = TextureClient::CreateForDrawing(mSurfaceAllocator,
         mFormat, mSize, gfx::BackendType::NONE, TextureFlags::DEFAULT | TextureFlags::RECYCLE);
     }
+    if (!textureClient) {
+      NS_WARNING("Failed to allocate a TextureClient!");
+      return nullptr;
+    }
     RECYCLE_LOG("%s Must allocate (0 left), returning %p\n", (mFormat == SurfaceFormat::B8G8R8A8?"poolA":"poolX"), textureClient.get());
   }
 
   if (aAutoRecycle) {
     mOutstandingTextureClients.push_back(textureClient);
     textureClient->SetRecycleCallback(SimpleTextureClientPool::WaitForCompositorRecycleCallback, this);
   }
 
--- a/gfx/layers/client/TextureClient.cpp
+++ b/gfx/layers/client/TextureClient.cpp
@@ -225,16 +225,35 @@ DisableGralloc(SurfaceFormat aFormat, co
   }
 #endif
 
   return false;
 }
 #endif
 
 static
+TemporaryRef<BufferTextureClient>
+CreateBufferTextureClient(ISurfaceAllocator* aAllocator,
+                          SurfaceFormat aFormat,
+                          TextureFlags aTextureFlags,
+                          gfx::BackendType aMoz2DBackend)
+{
+  if (aAllocator->IsSameProcess()) {
+    RefPtr<BufferTextureClient> result = new MemoryTextureClient(aAllocator, aFormat,
+                                                                 aMoz2DBackend,
+                                                                 aTextureFlags);
+    return result.forget();
+  }
+  RefPtr<BufferTextureClient> result = new ShmemTextureClient(aAllocator, aFormat,
+                                                              aMoz2DBackend,
+                                                              aTextureFlags);
+  return result.forget();
+}
+
+static
 TemporaryRef<TextureClient>
 CreateTextureClientForDrawing(ISurfaceAllocator* aAllocator,
                               SurfaceFormat aFormat,
                               TextureFlags aTextureFlags,
                               gfx::BackendType aMoz2DBackend,
                               const gfx::IntSize& aSizeHint)
 {
   if (aMoz2DBackend == gfx::BackendType::NONE) {
@@ -308,17 +327,17 @@ CreateTextureClientForDrawing(ISurfaceAl
       result = new GrallocTextureClientOGL(aAllocator, aFormat, aMoz2DBackend,
                                            aTextureFlags);
     }
   }
 #endif
 
   // Can't do any better than a buffer texture client.
   if (!result) {
-    result = TextureClient::CreateBufferTextureClient(aAllocator, aFormat, aTextureFlags, aMoz2DBackend);
+    result = CreateBufferTextureClient(aAllocator, aFormat, aTextureFlags, aMoz2DBackend);
   }
 
   MOZ_ASSERT(!result || result->CanExposeDrawTarget(), "texture cannot expose a DrawTarget?");
   return result;
 }
 
 // static
 TemporaryRef<TextureClient>
@@ -382,34 +401,39 @@ TextureClient::CreateForYCbCr(ISurfaceAl
 
   if (!texture->AllocateForYCbCr(aYSize, aCbCrSize, aStereoMode)) {
     return nullptr;
   }
 
   return texture;
 }
 
-
 // static
 TemporaryRef<BufferTextureClient>
-TextureClient::CreateBufferTextureClient(ISurfaceAllocator* aAllocator,
-                                         SurfaceFormat aFormat,
-                                         TextureFlags aTextureFlags,
-                                         gfx::BackendType aMoz2DBackend)
+TextureClient::CreateWithBufferSize(ISurfaceAllocator* aAllocator,
+                     gfx::SurfaceFormat aFormat,
+                     size_t aSize,
+                     TextureFlags aTextureFlags)
 {
+  RefPtr<BufferTextureClient> texture;
   if (aAllocator->IsSameProcess()) {
-    RefPtr<BufferTextureClient> result = new MemoryTextureClient(aAllocator, aFormat,
-                                                                 aMoz2DBackend,
-                                                                 aTextureFlags);
-    return result.forget();
+    texture = new MemoryTextureClient(aAllocator, gfx::SurfaceFormat::YUV,
+                                      gfx::BackendType::NONE,
+                                      aTextureFlags);
+  } else {
+    texture = new ShmemTextureClient(aAllocator, gfx::SurfaceFormat::YUV,
+                                     gfx::BackendType::NONE,
+                                     aTextureFlags);
   }
-  RefPtr<BufferTextureClient> result = new ShmemTextureClient(aAllocator, aFormat,
-                                                              aMoz2DBackend,
-                                                              aTextureFlags);
-  return result.forget();
+
+  if (!texture->Allocate(aSize)) {
+    return nullptr;
+  }
+
+  return texture;
 }
 
 TextureClient::TextureClient(TextureFlags aFlags)
   : mFlags(aFlags)
   , mShared(false)
   , mValid(true)
 {}
 
--- a/gfx/layers/client/TextureClient.h
+++ b/gfx/layers/client/TextureClient.h
@@ -112,26 +112,16 @@ public:
  */
 class TextureClient
   : public AtomicRefCountedWithFinalize<TextureClient>
 {
 public:
   TextureClient(TextureFlags aFlags = TextureFlags::DEFAULT);
   virtual ~TextureClient();
 
-  // Creates a TextureClient that can be accessed through a raw pointer.
-  // XXX - this doesn't allocate the texture data.
-  // Prefer CreateForRawBufferAccess which returns a BufferTextureClient
-  // only if allocation suceeded.
-  static TemporaryRef<BufferTextureClient>
-  CreateBufferTextureClient(ISurfaceAllocator* aAllocator,
-                            gfx::SurfaceFormat aFormat,
-                            TextureFlags aTextureFlags,
-                            gfx::BackendType aMoz2dBackend);
-
   // Creates and allocates a TextureClient usable with Moz2D.
   static TemporaryRef<TextureClient>
   CreateForDrawing(ISurfaceAllocator* aAllocator,
                    gfx::SurfaceFormat aFormat,
                    gfx::IntSize aSize,
                    gfx::BackendType aMoz2dBackend,
                    TextureFlags aTextureFlags,
                    TextureAllocationFlags flags = ALLOC_DEFAULT);
@@ -149,16 +139,25 @@ public:
   static TemporaryRef<BufferTextureClient>
   CreateForRawBufferAccess(ISurfaceAllocator* aAllocator,
                            gfx::SurfaceFormat aFormat,
                            gfx::IntSize aSize,
                            gfx::BackendType aMoz2dBackend,
                            TextureFlags aTextureFlags,
                            TextureAllocationFlags flags = ALLOC_DEFAULT);
 
+  // Creates and allocates a BufferTextureClient (can beaccessed through raw
+  // pointers) with a certain buffer size. It's unfortunate that we need this.
+  // providing format and sizes could let us do more optimization.
+  static TemporaryRef<BufferTextureClient>
+  CreateWithBufferSize(ISurfaceAllocator* aAllocator,
+                       gfx::SurfaceFormat aFormat,
+                       size_t aSize,
+                       TextureFlags aTextureFlags);
+
   virtual TextureClientYCbCr* AsTextureClientYCbCr() { return nullptr; }
 
   /**
    * Locks the shared data, allowing the caller to get access to it.
    *
    * Please always lock/unlock when accessing the shared data.
    * If Lock() returns false, you should not attempt to access the shared data.
    */
@@ -199,31 +198,16 @@ public:
 
   // TextureClients that can expose a DrawTarget should override this method.
   virtual gfx::SurfaceFormat GetFormat() const
   {
     return gfx::SurfaceFormat::UNKNOWN;
   }
 
   /**
-   * Allocates for a given surface size, taking into account the pixel format
-   * which is part of the state of the TextureClient.
-   *
-   * Does not clear the surface by default, clearing the surface can be done
-   * by passing the CLEAR_BUFFER flag.
-   *
-   * TextureClients that can expose a DrawTarget should override this method.
-   */
-  virtual bool AllocateForSurface(gfx::IntSize aSize,
-                                  TextureAllocationFlags flags = ALLOC_DEFAULT)
-  {
-    return false;
-  }
-
-  /**
    * Copies a rectangle from this texture client to a position in aTarget.
    * It is assumed that the necessary locks are in place; so this should at
    * least have a read lock and aTarget should at least have a write lock.
    */
   virtual bool CopyToTextureClient(TextureClient* aTarget,
                                    const gfx::IntRect* aRect,
                                    const gfx::IntPoint* aPoint);
 
@@ -365,16 +349,32 @@ private:
 protected:
   /**
    * An invalid TextureClient cannot provide access to its shared data
    * anymore. This usually means it will soon be destroyed.
    */
   void MarkInvalid() { mValid = false; }
 
   /**
+   * Allocates for a given surface size, taking into account the pixel format
+   * which is part of the state of the TextureClient.
+   *
+   * Does not clear the surface by default, clearing the surface can be done
+   * by passing the CLEAR_BUFFER flag.
+   *
+   * TextureClients that can expose a DrawTarget should override this method.
+   */
+  virtual bool AllocateForSurface(gfx::IntSize aSize,
+                                  TextureAllocationFlags flags = ALLOC_DEFAULT)
+  {
+    return false;
+  }
+
+
+  /**
    * Should only be called *once* per texture, in TextureClient::InitIPDLActor.
    * Some texture implementations rely on the fact that the descriptor will be
    * deserialized.
    * Calling ToSurfaceDescriptor again after it has already returned true,
    * or never constructing a TextureHost with aDescriptor may result in a memory
    * leak (see CairoTextureClientD3D9 for example).
    */
   virtual bool ToSurfaceDescriptor(SurfaceDescriptor& aDescriptor) = 0;
--- a/gfx/layers/client/TextureClientPool.cpp
+++ b/gfx/layers/client/TextureClientPool.cpp
@@ -50,19 +50,19 @@ TextureClientPool::GetTextureClient()
 
   // We're increasing the number of outstanding TextureClients without reusing a
   // client, we may need to free a deferred-return TextureClient.
   ShrinkToMaximumSize();
 
   // No unused clients in the pool, create one
   if (gfxPrefs::ForceShmemTiles()) {
     // gfx::BackendType::NONE means use the content backend
-    textureClient = TextureClient::CreateBufferTextureClient(mSurfaceAllocator,
-      mFormat, TextureFlags::IMMEDIATE_UPLOAD, gfx::BackendType::NONE);
-    textureClient->AllocateForSurface(mSize, ALLOC_DEFAULT);
+    textureClient = TextureClient::CreateForRawBufferAccess(mSurfaceAllocator,
+      mFormat, mSize, gfx::BackendType::NONE,
+      TextureFlags::IMMEDIATE_UPLOAD, ALLOC_DEFAULT);
   } else {
     textureClient = TextureClient::CreateForDrawing(mSurfaceAllocator,
       mFormat, mSize, gfx::BackendType::NONE, TextureFlags::IMMEDIATE_UPLOAD);
   }
 
   return textureClient;
 }
 
--- a/gfx/layers/client/TiledContentClient.cpp
+++ b/gfx/layers/client/TiledContentClient.cpp
@@ -1,26 +1,28 @@
 /* -*- Mode: C++; tab-width: 20; 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 "mozilla/layers/TiledContentClient.h"
 #include <math.h>                       // for ceil, ceilf, floor
+#include <algorithm>
 #include "ClientTiledThebesLayer.h"     // for ClientTiledThebesLayer
 #include "GeckoProfiler.h"              // for PROFILER_LABEL
 #include "ClientLayerManager.h"         // for ClientLayerManager
 #include "CompositorChild.h"            // for CompositorChild
 #include "gfxContext.h"                 // for gfxContext, etc
 #include "gfxPlatform.h"                // for gfxPlatform
 #include "gfxPrefs.h"                   // for gfxPrefs
 #include "gfxRect.h"                    // for gfxRect
 #include "mozilla/MathAlgorithms.h"     // for Abs
 #include "mozilla/gfx/Point.h"          // for IntSize
 #include "mozilla/gfx/Rect.h"           // for Rect
+#include "mozilla/gfx/Tools.h"          // for BytesPerPixel
 #include "mozilla/layers/CompositableForwarder.h"
 #include "mozilla/layers/ShadowLayers.h"  // for ShadowLayerForwarder
 #include "TextureClientPool.h"
 #include "nsDebug.h"                    // for NS_ASSERTION
 #include "nsISupportsImpl.h"            // for gfxContext::AddRef, etc
 #include "nsSize.h"                     // for nsIntSize
 #include "gfxReusableSharedImageSurfaceWrapper.h"
 #include "nsMathUtils.h"               // for NS_roundf
@@ -808,16 +810,78 @@ ClientTiledLayerBuffer::PaintThebes(cons
 #endif
 
   mLastPaintOpaque = mThebesLayer->CanUseOpaqueSurface();
   mCallback = nullptr;
   mCallbackData = nullptr;
   mSinglePaintDrawTarget = nullptr;
 }
 
+void PadDrawTargetOutFromRegion(RefPtr<DrawTarget> drawTarget, nsIntRegion &region)
+{
+  struct LockedBits {
+    uint8_t *data;
+    IntSize size;
+    int32_t stride;
+    SurfaceFormat format;
+    static int clamp(int x, int min, int max)
+    {
+      if (x < min)
+	x = min;
+      if (x > max)
+	x = max;
+      return x;
+    }
+
+    static void visitor(void *closure, VisitSide side, int x1, int y1, int x2, int y2) {
+      LockedBits *lb = static_cast<LockedBits*>(closure);
+      uint8_t *bitmap = lb->data;
+      const int bpp = gfx::BytesPerPixel(lb->format);
+      const int stride = lb->stride;
+      const int width = lb->size.width;
+      const int height = lb->size.height;
+
+      if (side == VisitSide::TOP) {
+	if (y1 > 0) {
+	  x1 = clamp(x1, 0, width - 1);
+	  x2 = clamp(x2, 0, width - 1);
+	  memcpy(&bitmap[x1*bpp + (y1-1) * stride], &bitmap[x1*bpp + y1 * stride], (x2 - x1) * bpp);
+	}
+      } else if (side == VisitSide::BOTTOM) {
+	if (y1 < height) {
+	  x1 = clamp(x1, 0, width - 1);
+	  x2 = clamp(x2, 0, width - 1);
+	  memcpy(&bitmap[x1*bpp + y1 * stride], &bitmap[x1*bpp + (y1-1) * stride], (x2 - x1) * bpp);
+	}
+      } else if (side == VisitSide::LEFT) {
+	if (x1 > 0) {
+	  while (y1 != y2) {
+	    memcpy(&bitmap[(x1-1)*bpp + y1 * stride], &bitmap[x1*bpp + y1*stride], bpp);
+	    y1++;
+	  }
+	}
+      } else if (side == VisitSide::RIGHT) {
+	if (x1 < width) {
+	  while (y1 != y2) {
+	    memcpy(&bitmap[x1*bpp + y1 * stride], &bitmap[(x1-1)*bpp + y1*stride], bpp);
+	    y1++;
+	  }
+	}
+      }
+
+    }
+  } lb;
+
+  if (drawTarget->LockBits(&lb.data, &lb.size, &lb.stride, &lb.format)) {
+    // we can only pad software targets so if we can't lock the bits don't pad
+    region.VisitEdges(lb.visitor, &lb);
+    drawTarget->ReleaseBits(lb.data);
+  }
+}
+
 TileClient
 ClientTiledLayerBuffer::ValidateTile(TileClient aTile,
                                     const nsIntPoint& aTileOrigin,
                                     const nsIntRegion& aDirtyRegion)
 {
   PROFILER_LABEL("ClientTiledLayerBuffer", "ValidateTile",
     js::ProfileEntry::Category::GRAPHICS);
 
@@ -885,16 +949,36 @@ ClientTiledLayerBuffer::ValidateTile(Til
                             drawRect.height);
       gfx::IntPoint copyTarget(NS_roundf(drawRect.x), NS_roundf(drawRect.y));
       drawTarget->CopySurface(source, copyRect, copyTarget);
 
       // Mark the newly updated area as invalid in the front buffer
       aTile.mInvalidFront.Or(aTile.mInvalidFront, nsIntRect(copyTarget.x, copyTarget.y, copyRect.width, copyRect.height));
     }
 
+    // only worry about padding when not doing low-res
+    // because it simplifies the math and the artifacts
+    // won't be noticable
+    if (mResolution == 1) {
+      nsIntRect unscaledTile = nsIntRect(aTileOrigin.x,
+					 aTileOrigin.y,
+					 GetTileSize().width,
+					 GetTileSize().height);
+
+      nsIntRegion tileValidRegion = GetValidRegion();
+      tileValidRegion.Or(tileValidRegion, aDirtyRegion);
+      // We only need to pad out if the tile has area that's not valid
+      if (!tileValidRegion.Contains(unscaledTile)) {
+	tileValidRegion = tileValidRegion.Intersect(unscaledTile);
+	// translate the region into tile space and pad
+	tileValidRegion.MoveBy(-nsIntPoint(unscaledTile.x, unscaledTile.y));
+	PadDrawTargetOutFromRegion(drawTarget, tileValidRegion);
+      }
+    }
+
     // The new buffer is now validated, remove the dirty region from it.
     aTile.mInvalidBack.Sub(nsIntRect(0, 0, GetTileSize().width, GetTileSize().height),
                            offsetScaledDirtyRegion);
   } else {
     // Area of the full tile...
     nsIntRegion tileRegion =
       nsIntRect(aTileOrigin.x, aTileOrigin.y,
                 GetScaledTileSize().width, GetScaledTileSize().height);
@@ -992,18 +1076,17 @@ GetCompositorSideCompositionBounds(Conta
                                    const gfx3DMatrix& aTransformToCompBounds,
                                    const ViewTransform& aAPZTransform)
 {
   gfx3DMatrix nonTransientAPZTransform = gfx3DMatrix::ScalingMatrix(
     aScrollAncestor->GetFrameMetrics().mResolution.scale,
     aScrollAncestor->GetFrameMetrics().mResolution.scale,
     1.f);
 
-  gfx3DMatrix layerTransform;
-  gfx::To3DMatrix(aScrollAncestor->GetTransform(), layerTransform);
+  gfx3DMatrix layerTransform = gfx::To3DMatrix(aScrollAncestor->GetTransform());
 
   // First take off the last two "terms" of aTransformToCompBounds, which
   // are the scroll ancestor's local transform and the APZ's nontransient async
   // transform.
   gfx3DMatrix transform = aTransformToCompBounds;
   transform = transform * layerTransform.Inverse();
   transform = transform * nonTransientAPZTransform.Inverse();
 
--- a/gfx/layers/composite/AsyncCompositionManager.cpp
+++ b/gfx/layers/composite/AsyncCompositionManager.cpp
@@ -400,19 +400,17 @@ SampleValue(float aPortion, Animation& a
   gfxPoint3D scaledOrigin =
     gfxPoint3D(NS_round(NSAppUnitsToFloatPixels(origin.x, data.appUnitsPerDevPixel())),
                NS_round(NSAppUnitsToFloatPixels(origin.y, data.appUnitsPerDevPixel())),
                0.0f);
 
   transform.Translate(scaledOrigin);
 
   InfallibleTArray<TransformFunction> functions;
-  Matrix4x4 realTransform;
-  ToMatrix4x4(transform, realTransform);
-  functions.AppendElement(TransformMatrix(realTransform));
+  functions.AppendElement(TransformMatrix(ToMatrix4x4(transform)));
   *aValue = functions;
 }
 
 static bool
 SampleAnimations(Layer* aLayer, TimeStamp aPoint)
 {
   AnimationArray& animations = aLayer->GetAnimations();
   InfallibleTArray<AnimData>& animationData = aLayer->GetAnimationData();
@@ -508,20 +506,17 @@ SampleAnimations(Layer* aLayer, TimeStam
   }
 
   return activeAnimations;
 }
 
 Matrix4x4
 CombineWithCSSTransform(const gfx3DMatrix& treeTransform, Layer* aLayer)
 {
-  Matrix4x4 result;
-  ToMatrix4x4(treeTransform, result);
-  result = result * aLayer->GetTransform();
-  return result;
+  return ToMatrix4x4(treeTransform) * aLayer->GetTransform();
 }
 
 bool
 AsyncCompositionManager::ApplyAsyncContentTransformToTree(TimeStamp aCurrentFrame,
                                                           Layer *aLayer,
                                                           bool* aWantNextFrame)
 {
   bool appliedTransform = false;
@@ -696,19 +691,17 @@ ApplyAsyncTransformToScrollbarForContent
   Matrix4x4 transform = scrollbarTransform * aScrollbar->GetTransform();
 
   if (aScrollbarIsChild) {
     // If the scrollbar layer is a child of the content it is a scrollbar for, then we
     // need to do an extra untransform to cancel out the transient async transform on
     // the content. This is needed because otherwise that transient async transform is
     // part of the effective transform of this scrollbar, and the scrollbar will jitter
     // as the content scrolls.
-    Matrix4x4 targetUntransform;
-    ToMatrix4x4(transientTransform.Inverse(), targetUntransform);
-    transform = transform * targetUntransform;
+    transform = transform * ToMatrix4x4(transientTransform.Inverse());
   }
 
   // GetTransform already takes the pre- and post-scale into account.  Since we
   // will apply the pre- and post-scale again when computing the effective
   // transform, we must apply the inverses here.
   transform.Scale(1.0f/aScrollbar->GetPreXScale(),
                   1.0f/aScrollbar->GetPreYScale(),
                   1);
@@ -766,18 +759,17 @@ void
 AsyncCompositionManager::TransformScrollableLayer(Layer* aLayer)
 {
   LayerComposite* layerComposite = aLayer->AsLayerComposite();
   ContainerLayer* container = aLayer->AsContainerLayer();
 
   const FrameMetrics& metrics = container->GetFrameMetrics();
   // We must apply the resolution scale before a pan/zoom transform, so we call
   // GetTransform here.
-  gfx3DMatrix currentTransform;
-  To3DMatrix(aLayer->GetTransform(), currentTransform);
+  gfx3DMatrix currentTransform = To3DMatrix(aLayer->GetTransform());
   Matrix4x4 oldTransform = aLayer->GetTransform();
 
   gfx3DMatrix treeTransform;
 
   CSSToLayerScale geckoZoom = metrics.LayersPixelsPerCSSPixel();
 
   LayerIntPoint scrollOffsetLayerPixels = RoundedToInt(metrics.GetScrollOffset() * geckoZoom);
 
@@ -844,19 +836,17 @@ AsyncCompositionManager::TransformScroll
   // transform, we must apply the inverse resolution scale here.
   gfx3DMatrix computedTransform = treeTransform * currentTransform;
   computedTransform.Scale(1.0f/container->GetPreXScale(),
                           1.0f/container->GetPreYScale(),
                           1);
   computedTransform.ScalePost(1.0f/container->GetPostXScale(),
                               1.0f/container->GetPostYScale(),
                               1);
-  Matrix4x4 matrix;
-  ToMatrix4x4(computedTransform, matrix);
-  layerComposite->SetShadowTransform(matrix);
+  layerComposite->SetShadowTransform(ToMatrix4x4(computedTransform));
   NS_ASSERTION(!layerComposite->GetShadowTransformSetByAnimation(),
                "overwriting animated transform!");
 
   // Apply resolution scaling to the old transform - the layer tree as it is
   // doesn't have the necessary transform to display correctly.
   oldTransform.Scale(metrics.mResolution.scale, metrics.mResolution.scale, 1);
 
   // Make sure that overscroll and under-zoom are represented in the old
--- a/gfx/layers/composite/LayerManagerComposite.cpp
+++ b/gfx/layers/composite/LayerManagerComposite.cpp
@@ -582,17 +582,17 @@ LayerManagerComposite::ComputeRenderInte
   }
 
   // If the layer's a container, recurse into all of its children
   ContainerLayer* container = aLayer->AsContainerLayer();
   if (container) {
     // Accumulate the transform of intermediate surfaces
     gfx3DMatrix transform = aTransform;
     if (container->UseIntermediateSurface()) {
-      gfx::To3DMatrix(aLayer->GetEffectiveTransform(), transform);
+      transform = gfx::To3DMatrix(aLayer->GetEffectiveTransform());
       transform.PreMultiply(aTransform);
     }
     for (Layer* child = aLayer->GetFirstChild(); child;
          child = child->GetNextSibling()) {
       ComputeRenderIntegrityInternal(child, aScreenRegion, aLowPrecisionScreenRegion, transform);
     }
     return;
   }
@@ -604,18 +604,17 @@ LayerManagerComposite::ComputeRenderInte
   }
 
   // See if there's any incomplete rendering
   nsIntRegion incompleteRegion = aLayer->GetEffectiveVisibleRegion();
   incompleteRegion.Sub(incompleteRegion, thebesLayer->GetValidRegion());
 
   if (!incompleteRegion.IsEmpty()) {
     // Calculate the transform to get between screen and layer space
-    gfx3DMatrix transformToScreen;
-    To3DMatrix(aLayer->GetEffectiveTransform(), transformToScreen);
+    gfx3DMatrix transformToScreen = To3DMatrix(aLayer->GetEffectiveTransform());
     transformToScreen.PreMultiply(aTransform);
 
     SubtractTransformedRegion(aScreenRegion, incompleteRegion, transformToScreen);
 
     // See if there's any incomplete low-precision rendering
     TiledLayerComposer* composer = nullptr;
     LayerComposite* shadow = aLayer->AsLayerComposite();
     if (shadow) {
@@ -684,18 +683,17 @@ LayerManagerComposite::ComputeRenderInte
 #ifdef MOZ_ANDROID_OMTC
   // Use the transform on the primary scrollable layer and its FrameMetrics
   // to find out how much of the viewport the current displayport covers
   Layer* primaryScrollable = GetPrimaryScrollableLayer();
   if (primaryScrollable) {
     // This is derived from the code in
     // AsyncCompositionManager::TransformScrollableLayer
     const FrameMetrics& metrics = primaryScrollable->AsContainerLayer()->GetFrameMetrics();
-    gfx3DMatrix transform;
-    gfx::To3DMatrix(primaryScrollable->GetEffectiveTransform(), transform);
+    gfx3DMatrix transform = gfx::To3DMatrix(primaryScrollable->GetEffectiveTransform());
     transform.ScalePost(metrics.mResolution.scale, metrics.mResolution.scale, 1);
 
     // Clip the screen rect to the document bounds
     gfxRect documentBounds =
       transform.TransformBounds(gfxRect(metrics.mScrollableRect.x - metrics.GetScrollOffset().x,
                                         metrics.mScrollableRect.y - metrics.GetScrollOffset().y,
                                         metrics.mScrollableRect.width,
                                         metrics.mScrollableRect.height));
--- a/gfx/layers/composite/TextureHost.cpp
+++ b/gfx/layers/composite/TextureHost.cpp
@@ -573,54 +573,51 @@ BufferTextureHost::GetAsSurface()
   return result.forget();
 }
 
 ShmemTextureHost::ShmemTextureHost(const ipc::Shmem& aShmem,
                                    gfx::SurfaceFormat aFormat,
                                    ISurfaceAllocator* aDeallocator,
                                    TextureFlags aFlags)
 : BufferTextureHost(aFormat, aFlags)
-, mShmem(new ipc::Shmem(aShmem))
+, mShmem(MakeUnique<ipc::Shmem>(aShmem))
 , mDeallocator(aDeallocator)
 {
   MOZ_COUNT_CTOR(ShmemTextureHost);
 }
 
 ShmemTextureHost::~ShmemTextureHost()
 {
   DeallocateDeviceData();
-  delete mShmem;
+  mShmem = nullptr;
   MOZ_COUNT_DTOR(ShmemTextureHost);
 }
 
 void
 ShmemTextureHost::DeallocateSharedData()
 {
   if (mShmem) {
     MOZ_ASSERT(mDeallocator,
                "Shared memory would leak without a ISurfaceAllocator");
     mDeallocator->DeallocShmem(*mShmem);
-    delete mShmem;
     mShmem = nullptr;
   }
 }
 
 void
 ShmemTextureHost::ForgetSharedData()
 {
   if (mShmem) {
-    delete mShmem;
     mShmem = nullptr;
   }
 }
 
 void
 ShmemTextureHost::OnShutdown()
 {
-  delete mShmem;
   mShmem = nullptr;
 }
 
 uint8_t* ShmemTextureHost::GetBuffer()
 {
   return mShmem ? mShmem->get<uint8_t>() : nullptr;
 }
 
--- a/gfx/layers/composite/TextureHost.h
+++ b/gfx/layers/composite/TextureHost.h
@@ -14,16 +14,17 @@
 #include "mozilla/RefPtr.h"             // for RefPtr, TemporaryRef, etc
 #include "mozilla/gfx/2D.h"             // for DataSourceSurface
 #include "mozilla/gfx/Point.h"          // for IntSize, IntPoint
 #include "mozilla/gfx/Types.h"          // for SurfaceFormat, etc
 #include "mozilla/layers/CompositorTypes.h"  // for TextureFlags, etc
 #include "mozilla/layers/FenceUtils.h"  // for FenceHandle
 #include "mozilla/layers/LayersTypes.h"  // for LayerRenderState, etc
 #include "mozilla/mozalloc.h"           // for operator delete
+#include "mozilla/UniquePtr.h"          // for UniquePtr
 #include "nsCOMPtr.h"                   // for already_AddRefed
 #include "nsDebug.h"                    // for NS_RUNTIMEABORT
 #include "nsISupportsImpl.h"            // for MOZ_COUNT_CTOR, etc
 #include "nsRegion.h"                   // for nsIntRegion
 #include "nsTraceRefcnt.h"              // for MOZ_COUNT_CTOR, etc
 #include "nscore.h"                     // for nsACString
 #include "mozilla/layers/AtomicRefCountedWithFinalize.h"
 
@@ -564,17 +565,17 @@ public:
 
   virtual size_t GetBufferSize() MOZ_OVERRIDE;
 
   virtual const char *Name() MOZ_OVERRIDE { return "ShmemTextureHost"; }
 
   virtual void OnShutdown() MOZ_OVERRIDE;
 
 protected:
-  mozilla::ipc::Shmem* mShmem;
+  UniquePtr<mozilla::ipc::Shmem> mShmem;
   RefPtr<ISurfaceAllocator> mDeallocator;
 };
 
 /**
  * TextureHost that wraps raw memory.
  * The corresponding texture on the client side is MemoryTextureClient.
  * Can obviously not be used in a cross process setup.
  * This TextureHost is backend-independent.
--- a/gfx/layers/ipc/LayerTransactionParent.cpp
+++ b/gfx/layers/ipc/LayerTransactionParent.cpp
@@ -624,18 +624,17 @@ LayerTransactionParent::RecvGetAnimation
     *aTransform = mozilla::void_t();
     return true;
   }
 
   // The following code recovers the untranslated transform
   // from the shadow transform by undoing the translations in
   // AsyncCompositionManager::SampleValue.
 
-  gfx3DMatrix transform;
-  gfx::To3DMatrix(layer->AsLayerComposite()->GetShadowTransform(), transform);
+  gfx3DMatrix transform = gfx::To3DMatrix(layer->AsLayerComposite()->GetShadowTransform());
   if (ContainerLayer* c = layer->AsContainerLayer()) {
     // Undo the scale transform applied by AsyncCompositionManager::SampleValue
     transform.ScalePost(1.0f/c->GetInheritedXScale(),
                         1.0f/c->GetInheritedYScale(),
                         1.0f);
   }
   float scale = 1;
   gfxPoint3D scaledOrigin;
--- a/gfx/layers/ipc/SharedPlanarYCbCrImage.cpp
+++ b/gfx/layers/ipc/SharedPlanarYCbCrImage.cpp
@@ -102,20 +102,22 @@ SharedPlanarYCbCrImage::SetData(const Pl
 // needs to be overriden because the parent class sets mBuffer which we
 // do not want to happen.
 uint8_t*
 SharedPlanarYCbCrImage::AllocateAndGetNewBuffer(uint32_t aSize)
 {
   NS_ABORT_IF_FALSE(!mTextureClient, "This image already has allocated data");
   size_t size = YCbCrImageDataSerializer::ComputeMinBufferSize(aSize);
 
-  mTextureClient = mCompositable->CreateBufferTextureClient(gfx::SurfaceFormat::YUV);
+  mTextureClient = TextureClient::CreateWithBufferSize(mCompositable->GetForwarder(),
+                                                       gfx::SurfaceFormat::YUV, size,
+                                                       mCompositable->GetTextureFlags());
+
   // get new buffer _without_ setting mBuffer.
-  if (!mTextureClient->Allocate(size)) {
-    mTextureClient = nullptr;
+  if (!mTextureClient) {
     return nullptr;
   }
 
   // update buffer size
   mBufferSize = size;
 
   YCbCrImageDataSerializer serializer(mTextureClient->GetBuffer(), mTextureClient->GetBufferSize());
   return serializer.GetData();
@@ -148,19 +150,20 @@ SharedPlanarYCbCrImage::SetDataNoCopy(co
                                   aData.mStereoMode);
 }
 
 uint8_t*
 SharedPlanarYCbCrImage::AllocateBuffer(uint32_t aSize)
 {
   NS_ABORT_IF_FALSE(!mTextureClient,
                     "This image already has allocated data");
-  mTextureClient = mCompositable->CreateBufferTextureClient(gfx::SurfaceFormat::YUV);
-  if (!mTextureClient->Allocate(aSize)) {
-    mTextureClient = nullptr;
+  mTextureClient = TextureClient::CreateWithBufferSize(mCompositable->GetForwarder(),
+                                                       gfx::SurfaceFormat::YUV, aSize,
+                                                       mCompositable->GetTextureFlags());
+  if (!mTextureClient) {
     return nullptr;
   }
   return mTextureClient->GetBuffer();
 }
 
 bool
 SharedPlanarYCbCrImage::IsValid() {
   return !!mTextureClient;
--- a/gfx/layers/ipc/SharedRGBImage.cpp
+++ b/gfx/layers/ipc/SharedRGBImage.cpp
@@ -74,18 +74,20 @@ SharedRGBImage::~SharedRGBImage()
     ImageBridgeChild::DispatchReleaseImageClient(mCompositable.forget().drop());
   }
 }
 
 bool
 SharedRGBImage::Allocate(gfx::IntSize aSize, gfx::SurfaceFormat aFormat)
 {
   mSize = aSize;
-  mTextureClient = mCompositable->CreateBufferTextureClient(aFormat);
-  return mTextureClient->AllocateForSurface(aSize);
+  mTextureClient = mCompositable->CreateBufferTextureClient(aFormat, aSize,
+                                                            gfx::BackendType::NONE,
+                                                            TextureFlags::DEFAULT);
+  return !!mTextureClient;
 }
 
 uint8_t*
 SharedRGBImage::GetBuffer()
 {
   if (!mTextureClient) {
     return nullptr;
   }
--- a/gfx/layers/opengl/CompositorOGL.cpp
+++ b/gfx/layers/opengl/CompositorOGL.cpp
@@ -10,17 +10,16 @@
 #include "GLContextProvider.h"          // for GLContextProvider
 #include "GLContext.h"                  // for GLContext
 #include "GLUploadHelpers.h"
 #include "Layers.h"                     // for WriteSnapshotToDumpFile
 #include "LayerScope.h"                 // for LayerScope
 #include "gfx2DGlue.h"                  // for ThebesFilter
 #include "gfx3DMatrix.h"                // for gfx3DMatrix
 #include "gfxCrashReporterUtils.h"      // for ScopedGfxFeatureReporter
-#include "gfxMatrix.h"                  // for gfxMatrix
 #include "GraphicsFilter.h"             // for GraphicsFilter
 #include "gfxPlatform.h"                // for gfxPlatform
 #include "gfxPrefs.h"                   // for gfxPrefs
 #include "gfxRect.h"                    // for gfxRect
 #include "gfxUtils.h"                   // for NextPowerOfTwo, gfxUtils, etc
 #include "mozilla/ArrayUtils.h"         // for ArrayLength
 #include "mozilla/Preferences.h"        // for Preferences
 #include "mozilla/gfx/BasePoint.h"      // for BasePoint
@@ -1125,37 +1124,34 @@ CompositorOGL::DrawQuad(const Rect& aRec
   case EffectTypes::RGB: {
       TexturedEffect* texturedEffect =
           static_cast<TexturedEffect*>(aEffectChain.mPrimaryEffect.get());
       TextureSource *source = texturedEffect->mTexture;
 
       didSetBlendMode = SetBlendMode(gl(), blendMode, texturedEffect->mPremultiplied);
 
       gfx::Filter filter = texturedEffect->mFilter;
-      gfx3DMatrix textureTransform;
-      gfx::To3DMatrix(source->AsSourceOGL()->GetTextureTransform(), textureTransform);
+      Matrix4x4 textureTransform = source->AsSourceOGL()->GetTextureTransform();
 
 #ifdef MOZ_WIDGET_ANDROID
-      gfxMatrix textureTransform2D;
+      gfx::Matrix textureTransform2D;
       if (filter != gfx::Filter::POINT &&
           aTransform.Is2DIntegerTranslation() &&
           textureTransform.Is2D(&textureTransform2D) &&
           textureTransform2D.HasOnlyIntegerTranslation()) {
         // On Android we encounter small resampling errors in what should be
         // pixel-aligned compositing operations. This works around them. This
         // code should not be needed!
         filter = gfx::Filter::POINT;
       }
 #endif
       source->AsSourceOGL()->BindTexture(LOCAL_GL_TEXTURE0, filter);
 
       program->SetTextureUnit(0);
-      Matrix4x4 transform;
-      ToMatrix4x4(textureTransform, transform);
-      program->SetTextureTransform(transform);
+      program->SetTextureTransform(textureTransform);
 
       if (maskType != MaskType::MaskNone) {
         BindMaskForProgram(program, sourceMask, LOCAL_GL_TEXTURE1, maskQuadTransform);
       }
 
       BindAndDrawQuadWithTextureRect(program, aRect, texturedEffect->mTextureCoords, source);
     }
     break;
--- a/gfx/src/nsFont.cpp
+++ b/gfx/src/nsFont.cpp
@@ -263,17 +263,16 @@ void nsFont::AddFontFeaturesToStyle(gfxF
 
   // -- alternates
   if (variantAlternates & NS_FONT_VARIANT_ALTERNATES_HISTORICAL) {
     setting.mValue = 1;
     setting.mTag = TRUETYPE_TAG('h','i','s','t');
     aStyle->featureSettings.AppendElement(setting);
   }
 
-
   // -- copy font-specific alternate info into style
   //    (this will be resolved after font-matching occurs)
   aStyle->alternateValues.AppendElements(alternateValues);
   aStyle->featureValueLookup = featureValueLookup;
 
   // -- caps
   // passed into gfxFontStyle to deal with appropriate fallback.
   // for now, font-variant setting overrides font-variant-caps
@@ -327,32 +326,22 @@ void nsFont::AddFontFeaturesToStyle(gfxF
   if (variantNumeric) {
     AddFontFeaturesBitmask(variantNumeric,
                            NS_FONT_VARIANT_NUMERIC_LINING,
                            NS_FONT_VARIANT_NUMERIC_ORDINAL,
                            numericDefaults, aStyle->featureSettings);
   }
 
   // -- position
-  setting.mTag = 0;
-  setting.mValue = 1;
-  switch (variantPosition) {
-    case NS_FONT_VARIANT_POSITION_SUPER:
-      setting.mTag = TRUETYPE_TAG('s','u','p','s');
-      aStyle->featureSettings.AppendElement(setting);
-      break;
+  aStyle->variantSubSuper = variantPosition;
 
-    case NS_FONT_VARIANT_POSITION_SUB:
-      setting.mTag = TRUETYPE_TAG('s','u','b','s');
-      aStyle->featureSettings.AppendElement(setting);
-      break;
-
-    default:
-      break;
-  }
+  // indicate common-path case when neither variantCaps or variantSubSuper are set
+  aStyle->noFallbackVariantFeatures =
+    (aStyle->variantCaps == NS_FONT_VARIANT_CAPS_NORMAL) &&
+    (variantPosition == NS_FONT_VARIANT_POSITION_NORMAL);
 
   // add in features from font-feature-settings
   aStyle->featureSettings.AppendElements(fontFeatureSettings);
 
   // enable grayscale antialiasing for text
   if (smoothing == NS_FONT_SMOOTHING_GRAYSCALE) {
     aStyle->useGrayscaleAntialiasing = true;
   }
--- a/gfx/src/nsRegion.cpp
+++ b/gfx/src/nsRegion.cpp
@@ -365,16 +365,180 @@ void nsRegion::SimplifyOutwardByArea(uin
     // of rects stored in data.
     this->mImpl.data->numRects = reducedCount;
   } else {
     *this = GetBounds();
   }
 }
 
 
+typedef void (*visit_fn)(void *closure, VisitSide side, int x1, int y1, int x2, int y2);
+
+static void VisitNextEdgeBetweenRect(visit_fn visit, void *closure, VisitSide side,
+				     pixman_box32_t *&r1, pixman_box32_t *&r2, const int y, int &x1)
+{
+  // check for overlap
+  if (r1->x2 >= r2->x1) {
+    MOZ_ASSERT(r2->x1 >= x1);
+    visit(closure, side, x1, y, r2->x1, y);
+
+    // find the rect that ends first or always drop the one that comes first?
+    if (r1->x2 < r2->x2) {
+      x1 = r1->x2;
+      r1++;
+    } else {
+      x1 = r2->x2;
+      r2++;
+    }
+  } else {
+    MOZ_ASSERT(r1->x2 < r2->x2);
+    // we handle the corners by just extending the top and bottom edges
+    visit(closure, side, x1, y, r1->x2+1, y);
+    r1++;
+    x1 = r2->x1 - 1;
+  }
+}
+
+//XXX: if we need to this can compute the end of the row
+static void
+VisitSides(visit_fn visit, void *closure, pixman_box32_t *r, pixman_box32_t *r_end)
+{
+  // XXX: we can drop LEFT/RIGHT and just use the orientation
+  // of the line if it makes sense
+  while (r != r_end) {
+    visit(closure, VisitSide::LEFT, r->x1, r->y1, r->x1, r->y2);
+    visit(closure, VisitSide::RIGHT, r->x2, r->y1, r->x2, r->y2);
+    r++;
+  }
+}
+
+static void
+VisitAbove(visit_fn visit, void *closure, pixman_box32_t *r, pixman_box32_t *r_end)
+{
+  while (r != r_end) {
+    visit(closure, VisitSide::TOP, r->x1-1, r->y1, r->x2+1, r->y1);
+    r++;
+  }
+}
+
+static void
+VisitBelow(visit_fn visit, void *closure, pixman_box32_t *r, pixman_box32_t *r_end)
+{
+  while (r != r_end) {
+    visit(closure, VisitSide::BOTTOM, r->x1-1, r->y2, r->x2+1, r->y2);
+    r++;
+  }
+}
+
+static pixman_box32_t *
+VisitInbetween(visit_fn visit, void *closure, pixman_box32_t *r1,
+               pixman_box32_t *r1_end,
+               pixman_box32_t *r2,
+               pixman_box32_t *r2_end)
+{
+  const int y = r1->y2;
+  int x1;
+
+  /* Find the left-most edge */
+  if (r1->x1 < r2->x1) {
+    x1 = r1->x1 - 1;
+  } else {
+    x1 = r2->x1 - 1;
+  }
+
+  while (r1 != r1_end && r2 != r2_end) {
+    MOZ_ASSERT((x1 >= (r1->x1 - 1)) || (x1 >= (r2->x1 - 1)));
+    if (r1->x1 < r2->x1) {
+      VisitNextEdgeBetweenRect(visit, closure, VisitSide::BOTTOM, r1, r2, y, x1);
+    } else {
+      VisitNextEdgeBetweenRect(visit, closure, VisitSide::TOP, r2, r1, y, x1);
+    }
+  }
+
+  /* Finish up which ever row has remaining rects*/
+  if (r1 != r1_end) {
+    // top row
+    do {
+      visit(closure, VisitSide::BOTTOM, x1, y, r1->x2 + 1, y);
+      r1++;
+      if (r1 == r1_end)
+	break;
+      x1 = r1->x1 - 1;
+    } while (true);
+  } else if (r2 != r2_end) {
+    // bottom row
+    do {
+      visit(closure, VisitSide::TOP, x1, y, r2->x2 + 1, y);
+      r2++;
+      if (r2 == r2_end)
+	break;
+      x1 = r2->x1 - 1;
+    } while (true);
+  }
+
+  return 0;
+}
+
+void nsRegion::VisitEdges (visit_fn visit, void *closure)
+{
+  pixman_box32_t *boxes;
+  int n;
+  boxes = pixman_region32_rectangles(&mImpl, &n);
+
+  // if we have no rectangles then we're done
+  if (!n)
+    return;
+
+  pixman_box32_t *end = boxes + n;
+  pixman_box32_t *topRectsEnd = boxes + 1;
+  pixman_box32_t *topRects = boxes;
+
+  // find the end of the first span of rectangles
+  while (topRectsEnd < end && topRectsEnd->y1 == topRects->y1) {
+    topRectsEnd++;
+  }
+
+  // In order to properly handle convex corners we always visit the sides first
+  // that way when we visit the corners we can pad using the value from the sides
+  VisitSides(visit, closure, topRects, topRectsEnd);
+
+  VisitAbove(visit, closure, topRects, topRectsEnd);
+
+  pixman_box32_t *bottomRects = topRects;
+  pixman_box32_t *bottomRectsEnd = topRectsEnd;
+  if (topRectsEnd != end) {
+    do {
+      // find the next row of rects
+      bottomRects = topRectsEnd;
+      bottomRectsEnd = topRectsEnd + 1;
+      while (bottomRectsEnd < end && bottomRectsEnd->y1 == bottomRects->y1) {
+        bottomRectsEnd++;
+      }
+
+      VisitSides(visit, closure, bottomRects, bottomRectsEnd);
+
+      if (topRects->y2 == bottomRects->y1) {
+        VisitInbetween(visit, closure, topRects, topRectsEnd,
+                                       bottomRects, bottomRectsEnd);
+      } else {
+        VisitBelow(visit, closure, topRects, topRectsEnd);
+        VisitAbove(visit, closure, bottomRects, bottomRectsEnd);
+      }
+
+      topRects = bottomRects;
+      topRectsEnd = bottomRectsEnd;
+    } while (bottomRectsEnd != end);
+  }
+
+  // the bottom of the region doesn't touch anything else so we
+  // can always visit it at the end
+  VisitBelow(visit, closure, bottomRects, bottomRectsEnd);
+}
+
+
 void nsRegion::SimplifyInward (uint32_t aMaxRects)
 {
   NS_ASSERTION(aMaxRects >= 1, "Invalid max rect count");
 
   if (GetNumRects() <= aMaxRects)
     return;
 
   SetEmpty();
--- a/gfx/src/nsRegion.h
+++ b/gfx/src/nsRegion.h
@@ -12,16 +12,17 @@
 #include "gfxCore.h"                    // for NS_GFX
 #include "nsCoord.h"                    // for nscoord
 #include "nsError.h"                    // for nsresult
 #include "nsPoint.h"                    // for nsIntPoint, nsPoint
 #include "nsRect.h"                     // for nsIntRect, nsRect
 #include "nsMargin.h"                   // for nsIntMargin
 #include "nsStringGlue.h"               // for nsCString
 #include "xpcom-config.h"               // for CPP_THROW_NEW
+#include "mozilla/TypedEnum.h"          // for the VisitEdges typed enum
 
 class nsIntRegion;
 class gfx3DMatrix;
 
 #include "pixman.h"
 
 /* For information on the internal representation look at pixman-region.c
  *
@@ -32,16 +33,23 @@ class gfx3DMatrix;
  * representation. This means that nsIntRegion will have more predictable
  * performance characteristics than the old nsRegion and should not become
  * degenerate.
  *
  * The pixman region code originates from X11 which has spread to a variety of
  * projects including Qt, Gtk, Wine. It should perform reasonably well.
  */
 
+MOZ_BEGIN_ENUM_CLASS(VisitSide)
+	TOP,
+	BOTTOM,
+	LEFT,
+	RIGHT
+MOZ_END_ENUM_CLASS(VisitSide)
+
 class nsRegionRectIterator;
 
 class nsRegion
 {
 
   friend class nsRegionRectIterator;
 
 public:
@@ -259,16 +267,31 @@ public:
   void SimplifyOutwardByArea(uint32_t aThreshold);
   /**
    * Make sure the region has at most aMaxRects by removing area from
    * it if necessary. The simplified region will be a subset of the
    * original region.
    */
   void SimplifyInward (uint32_t aMaxRects);
 
+  /**
+   * VisitEdges is a weird kind of function that we use for padding
+   * out surfaces to prevent texture filtering artifacts.
+   * It calls the visitFn callback for each of the exterior edges of
+   * the regions. The top and bottom edges will be expanded 1 pixel
+   * to the left and right if there's an outside corner. The order
+   * the edges are visited is not guaranteed.
+   *
+   * visitFn has a side parameter that can be TOP,BOTTOM,LEFT,RIGHT
+   * and specifies which kind of edge is being visited. x1, y1, x2, y2
+   * are the coordinates of the line. (x1 == x2) || (y1 == y2)
+   */
+  typedef void (*visitFn)(void *closure, VisitSide side, int x1, int y1, int x2, int y2);
+  void VisitEdges(visitFn, void *closure);
+
   nsCString ToString() const;
 private:
   pixman_region32_t mImpl;
 
 #ifndef MOZ_TREE_PIXMAN
   // For compatibility with pixman versions older than 0.25.2.
   static inline void
   pixman_region32_clear(pixman_region32_t *region)
@@ -397,16 +420,24 @@ public:
     return IsEqual(aRgn);
   }
 
   void Swap(nsIntRegion* aOther)
   {
     mImpl.Swap(&aOther->mImpl);
   }
 
+  void AndWith(const nsIntRegion& aOther)
+  {
+    And(*this, aOther);
+  }
+  void AndWith(const nsIntRect& aOther)
+  {
+    And(*this, aOther);
+  }
   nsIntRegion& And  (const nsIntRegion& aRgn1,   const nsIntRegion& aRgn2)
   {
     mImpl.And (aRgn1.mImpl, aRgn2.mImpl);
     return *this;
   }
   nsIntRegion& And  (const nsIntRegion& aRegion, const nsIntRect& aRect)
   {
     mImpl.And (aRegion.mImpl, ToRect (aRect));
@@ -420,16 +451,24 @@ public:
   {
     nsIntRect TmpRect;
 
     TmpRect.IntersectRect (aRect1, aRect2);
     mImpl = ToRect (TmpRect);
     return *this;
   }
 
+  void OrWith(const nsIntRegion& aOther)
+  {
+    Or(*this, aOther);
+  }
+  void OrWith(const nsIntRect& aOther)
+  {
+    Or(*this, aOther);
+  }
   nsIntRegion& Or   (const nsIntRegion& aRgn1,   const nsIntRegion& aRgn2)
   {
     mImpl.Or (aRgn1.mImpl, aRgn2.mImpl);
     return *this;
   }
   nsIntRegion& Or   (const nsIntRegion& aRegion, const nsIntRect& aRect)
   {
     mImpl.Or (aRegion.mImpl, ToRect (aRect));
@@ -440,16 +479,24 @@ public:
     return  Or   (aRegion, aRect);
   }
   nsIntRegion& Or   (const nsIntRect& aRect1, const nsIntRect& aRect2)
   {
     mImpl = ToRect (aRect1);
     return Or (*this, aRect2);
   }
 
+  void XorWith(const nsIntRegion& aOther)
+  {
+    Xor(*this, aOther);
+  }
+  void XorWith(const nsIntRect& aOther)
+  {
+    Xor(*this, aOther);
+  }
   nsIntRegion& Xor  (const nsIntRegion& aRgn1,   const nsIntRegion& aRgn2)
   {
     mImpl.Xor (aRgn1.mImpl, aRgn2.mImpl);
     return *this;
   }
   nsIntRegion& Xor  (const nsIntRegion& aRegion, const nsIntRect& aRect)
   {
     mImpl.Xor (aRegion.mImpl, ToRect (aRect));
@@ -460,16 +507,24 @@ public:
     return  Xor  (aRegion, aRect);
   }
   nsIntRegion& Xor  (const nsIntRect& aRect1, const nsIntRect& aRect2)
   {
     mImpl = ToRect (aRect1);
     return Xor (*this, aRect2);
   }
 
+  void SubOut(const nsIntRegion& aOther)
+  {
+    Sub(*this, aOther);
+  }
+  void SubOut(const nsIntRect& aOther)
+  {
+    Sub(*this, aOther);
+  }
   nsIntRegion& Sub  (const nsIntRegion& aRgn1,   const nsIntRegion& aRgn2)
   {
     mImpl.Sub (aRgn1.mImpl, aRgn2.mImpl);
     return *this;
   }
   nsIntRegion& Sub  (const nsIntRegion& aRegion, const nsIntRect& aRect)
   {
     mImpl.Sub (aRegion.mImpl, ToRect (aRect));
@@ -480,16 +535,20 @@ public:
     return Sub (nsIntRegion (aRect), aRegion);
   }
   nsIntRegion& Sub  (const nsIntRect& aRect1, const nsIntRect& aRect2)
   {
     mImpl = ToRect (aRect1);
     return Sub (*this, aRect2);
   }
 
+  bool Contains (int aX, int aY) const
+  {
+    return Contains(nsIntRect(aX, aY, 1, 1));
+  }
   bool Contains (const nsIntRect& aRect) const
   {
     return mImpl.Contains (ToRect (aRect));
   }
   bool Contains (const nsIntRegion& aRgn) const
   {
     return mImpl.Contains (aRgn.mImpl);
   }
@@ -586,16 +645,22 @@ public:
    * it if necessary. The simplified region will be a subset of the
    * original region.
    */
   void SimplifyInward (uint32_t aMaxRects)
   {
     mImpl.SimplifyInward (aMaxRects);
   }
 
+  typedef void (*visitFn)(void *closure, VisitSide side, int x1, int y1, int x2, int y2);
+  void VisitEdges (visitFn visit, void *closure)
+  {
+    mImpl.VisitEdges (visit, closure);
+  }
+
   nsCString ToString() const { return mImpl.ToString(); }
 
 private:
   nsRegion mImpl;
 
   static nsRect ToRect(const nsIntRect& aRect)
   {
     return nsRect (aRect.x, aRect.y, aRect.width, aRect.height);
--- a/gfx/tests/gtest/TestLayers.cpp
+++ b/gfx/tests/gtest/TestLayers.cpp
@@ -189,19 +189,17 @@ already_AddRefed<Layer> CreateLayerTree(
       parentContainerLayer = parentContainerLayer->GetParent();
       lastLayer = nullptr;
     } else {
       nsRefPtr<Layer> layer = CreateLayer(aLayerTreeDescription[i], manager.get());
       if (aVisibleRegions) {
         layer->SetVisibleRegion(aVisibleRegions[layerNumber]);
       }
       if (aTransforms) {
-        Matrix4x4 transform;
-        ToMatrix4x4(aTransforms[layerNumber], transform);
-        layer->SetBaseTransform(transform);
+        layer->SetBaseTransform(ToMatrix4x4(aTransforms[layerNumber]));
       }
       aLayersOut.AppendElement(layer);
       layerNumber++;
       if (rootLayer && !parentContainerLayer) {
         MOZ_CRASH();
       }
       if (!rootLayer) {
         rootLayer = layer;
--- a/gfx/tests/gtest/TestRegion.cpp
+++ b/gfx/tests/gtest/TestRegion.cpp
@@ -1,16 +1,20 @@
 /* -*- 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 <algorithm>
+
 #include "gtest/gtest.h"
 #include "nsRegion.h"
 
+using namespace std;
+
 class TestLargestRegion {
 public:
   static void TestSingleRect(nsRect r) {
     nsRegion region(r);
     EXPECT_TRUE(region.GetLargestRectangle().IsEqualInterior(r));
   }
   // Construct a rectangle, remove part of it, then check the remainder
   static void TestNonRectangular() {
@@ -167,16 +171,17 @@ TEST(Gfx, RegionScaleToInside) {
     result.Or(result, nsIntRect(0,750,318,18));
 
     EXPECT_TRUE(result.IsEqual(scaled)) <<
       "scaled result incorrect";
   }
 
 }
 
+
 TEST(Gfx, RegionSimplify) {
   { // ensure simplify works on a single rect
     nsRegion r(nsRect(0,100,200,100));
 
     r.SimplifyOutwardByArea(100*100);
 
     nsRegion result(nsRect(0,100,200,100));
 
@@ -276,10 +281,187 @@ TEST(Gfx, RegionSimplify) {
       "regions not merged";
   }
 
   { // empty region
     // just make sure this doesn't crash.
     nsRegion r;
     r.SimplifyOutwardByArea(100);
   }
+}
+
+#define DILATE_VALUE 0x88
+#define REGION_VALUE 0xff
+
+struct RegionBitmap {
+  RegionBitmap(unsigned char *bitmap, int width, int height) : bitmap(bitmap), width(width), height(height) {}
+
+  void clear() {
+    for (int y = 0; y < height; y++) {
+      for (int x = 0; x < width; x++) {
+	bitmap[x + y * width] = 0;
+      }
+    }
+  }
+
+  void set(nsRegion &region) {
+    clear();
+    nsRegionRectIterator iter(region);
+    for (const nsRect* r = iter.Next(); r; r = iter.Next()) {
+      for (int y = r->y; y < r->YMost(); y++) {
+        for (int x = r->x; x < r->XMost(); x++) {
+	  bitmap[x + y * width] = REGION_VALUE;
+	}
+      }
+    }
+  }
+
+  void dilate() {
+    for (int y = 0; y < height; y++) {
+      for (int x = 0; x < width; x++) {
+	if (bitmap[x + y * width] == REGION_VALUE) {
+	  for (int yn = max(y - 1, 0); yn <= min(y + 1, height - 1); yn++) {
+	    for (int xn = max(x - 1, 0); xn <= min(x + 1, width - 1); xn++) {
+	      if (bitmap[xn + yn * width] == 0)
+		bitmap[xn + yn * width] = DILATE_VALUE;
+	    }
+	  }
+	}
+      }
+    }
+  }
+  void compare(RegionBitmap &reference) {
+    for (int y = 0; y < height; y++) {
+      for (int x = 0; x < width; x++) {
+	EXPECT_EQ(bitmap[x + y * width], reference.bitmap[x + y * width]);
+      }
+    }
+  }
+
+  unsigned char *bitmap;
+  int width;
+  int height;
+};
+
+void VisitEdge(void *closure, VisitSide side, int x1, int y1, int x2, int y2)
+{
+  RegionBitmap *visitor = static_cast<RegionBitmap*>(closure);
+  unsigned char *bitmap = visitor->bitmap;
+  const int width = visitor->width;
+
+  if (side == VisitSide::TOP) {
+    while (x1 != x2) {
+      bitmap[x1 + (y1 - 1) * width] = DILATE_VALUE;
+      x1++;
+    }
+  } else if (side == VisitSide::BOTTOM) {
+    while (x1 != x2) {
+      bitmap[x1 + y1 * width] = DILATE_VALUE;
+      x1++;
+    }
+  } else if (side == VisitSide::LEFT) {
+    while (y1 != y2) {
+      bitmap[x1 - 1 + y1 *width] = DILATE_VALUE;
+      y1++;
+    }
+  } else if (side == VisitSide::RIGHT) {
+    while (y1 != y2) {
+      bitmap[x1 + y1 * width] = DILATE_VALUE;
+      y1++;
+    }
+  }
+}
+
+void TestVisit(nsRegion &r)
+{
+  unsigned char reference[600 * 600];
+  unsigned char result[600 * 600];
+  RegionBitmap ref(reference, 600, 600);
+  RegionBitmap res(result, 600, 600);
+
+  ref.set(r);
+  ref.dilate();
+
+  res.set(r);
+  r.VisitEdges(VisitEdge, &res);
+  res.compare(ref);
+}
+
+TEST(Gfx, RegionVisitEdges) {
+  { // visit edges
+    nsRegion r(nsRect(20, 20, 100, 100));
+    r.Or(r, nsRect(20, 120, 200, 100));
+    TestVisit(r);
+  }
+
+  { // two rects side by side - 1 pixel inbetween
+    nsRegion r(nsRect(20, 20, 100, 100));
+    r.Or(r, nsRect(121, 20, 100, 100));
+    TestVisit(r);
+  }
+
+  { // two rects side by side - 2 pixels inbetween
+    nsRegion r(nsRect(20, 20, 100, 100));
+    r.Or(r, nsRect(122, 20, 100, 100));
+    TestVisit(r);
+  }
+
+  {
+    // only corner of the rects are touching
+    nsRegion r(nsRect(20, 20, 100, 100));
+    r.Or(r, nsRect(120, 120, 100, 100));
+
+    TestVisit(r);
+  }
+
+  {
+    // corners are 1 pixel away
+    nsRegion r(nsRect(20, 20, 100, 100));
+    r.Or(r, nsRect(121, 120, 100, 100));
+
+    TestVisit(r);
+  }
+
+  {
+    // vertically separated
+    nsRegion r(nsRect(20, 20, 100, 100));
+    r.Or(r, nsRect(120, 125, 100, 100));
+
+    TestVisit(r);
+  }
+
+  {
+    // not touching
+    nsRegion r(nsRect(20, 20, 100, 100));
+    r.Or(r, nsRect(130, 120, 100, 100));
+    r.Or(r, nsRect(240, 20, 100, 100));
+
+    TestVisit(r);
+  }
+
+  { // rect with a hole in it
+    nsRegion r(nsRect(20, 20, 100, 100));
+    r.Sub(r, nsRect(40, 40, 10, 10));
+
+    TestVisit(r);
+  }
+  {
+    // left overs
+    nsRegion r(nsRect(20, 20, 10, 10));
+    r.Or(r, nsRect(50, 20, 10, 10));
+    r.Or(r, nsRect(90, 20, 10, 10));
+    r.Or(r, nsRect(24, 30, 10, 10));
+    r.Or(r, nsRect(20, 40, 15, 10));
+    r.Or(r, nsRect(50, 40, 15, 10));
+    r.Or(r, nsRect(90, 40, 15, 10));
+
+    TestVisit(r);
+  }
+
+  {
+    // vertically separated
+    nsRegion r(nsRect(20, 20, 100, 100));
+    r.Or(r, nsRect(120, 125, 100, 100));
+
+    TestVisit(r);
+  }
 
 }
--- a/gfx/thebes/gfx2DGlue.h
+++ b/gfx/thebes/gfx2DGlue.h
@@ -371,54 +371,58 @@ inline gfxContext::GraphicsOperator Theb
     return gfxContext::OPERATOR_COLOR;
   case CompositionOp::OP_LUMINOSITY:
     return gfxContext::OPERATOR_LUMINOSITY;
   default:
     return gfxContext::OPERATOR_OVER;
   }
 }
 
-inline void
-ToMatrix4x4(const gfx3DMatrix& aIn, Matrix4x4& aOut)
+inline Matrix4x4
+ToMatrix4x4(const gfx3DMatrix& aIn)
 {
-  aOut._11 = aIn._11;
-  aOut._12 = aIn._12;
-  aOut._13 = aIn._13;
-  aOut._14 = aIn._14;
-  aOut._21 = aIn._21;
-  aOut._22 = aIn._22;
-  aOut._23 = aIn._23;
-  aOut._24 = aIn._24;
-  aOut._31 = aIn._31;
-  aOut._32 = aIn._32;
-  aOut._33 = aIn._33;
-  aOut._34 = aIn._34;
-  aOut._41 = aIn._41;
-  aOut._42 = aIn._42;
-  aOut._43 = aIn._43;
-  aOut._44 = aIn._44;
+  Matrix4x4 m;
+  m._11 = aIn._11;
+  m._12 = aIn._12;
+  m._13 = aIn._13;
+  m._14 = aIn._14;
+  m._21 = aIn._21;
+  m._22 = aIn._22;
+  m._23 = aIn._23;
+  m._24 = aIn._24;
+  m._31 = aIn._31;
+  m._32 = aIn._32;
+  m._33 = aIn._33;
+  m._34 = aIn._34;
+  m._41 = aIn._41;
+  m._42 = aIn._42;
+  m._43 = aIn._43;
+  m._44 = aIn._44;
+  return m;
 }
 
-inline void
-To3DMatrix(const Matrix4x4& aIn, gfx3DMatrix& aOut)
+inline gfx3DMatrix
+To3DMatrix(const Matrix4x4& aIn)
 {
-  aOut._11 = aIn._11;
-  aOut._12 = aIn._12;
-  aOut._13 = aIn._13;
-  aOut._14 = aIn._14;
-  aOut._21 = aIn._21;
-  aOut._22 = aIn._22;
-  aOut._23 = aIn._23;
-  aOut._24 = aIn._24;
-  aOut._31 = aIn._31;
-  aOut._32 = aIn._32;
-  aOut._33 = aIn._33;
-  aOut._34 = aIn._34;
-  aOut._41 = aIn._41;
-  aOut._42 = aIn._42;
-  aOut._43 = aIn._43;
-  aOut._44 = aIn._44;
+  gfx3DMatrix m;
+  m._11 = aIn._11;
+  m._12 = aIn._12;
+  m._13 = aIn._13;
+  m._14 = aIn._14;
+  m._21 = aIn._21;
+  m._22 = aIn._22;
+  m._23 = aIn._23;
+  m._24 = aIn._24;
+  m._31 = aIn._31;
+  m._32 = aIn._32;
+  m._33 = aIn._33;
+  m._34 = aIn._34;
+  m._41 = aIn._41;
+  m._42 = aIn._42;
+  m._43 = aIn._43;
+  m._44 = aIn._44;
+  return m;
 }
 
 }
 }
 
 #endif
--- a/gfx/thebes/gfx3DMatrix.cpp
+++ b/gfx/thebes/gfx3DMatrix.cpp
@@ -813,17 +813,16 @@ gfxRect gfx3DMatrix::ProjectRectBounds(c
       max_y = max(point2d.y, max_y);
     }
 
     int next = (i == 3) ? 0 : i + 1;
     if (points[i].HasPositiveWCoord() != points[next].HasPositiveWCoord()) {
       // If the line between two points crosses the w=0 plane, then interpolate a point
       // as close to the w=0 plane as possible and use that instead.
       gfxPointH3D intercept = ComputePerspectivePlaneIntercept(points[i], points[next]);
-      MOZ_ASSERT(intercept.HasPositiveWCoord());
 
       gfxPoint point2d = intercept.As2DPoint();
       min_x = min(point2d.x, min_x);
       max_x = max(point2d.x, max_x);
       min_y = min(point2d.y, min_y);
       max_y = max(point2d.y, max_y);
     }
   }
--- a/gfx/thebes/gfxBlur.cpp
+++ b/gfx/thebes/gfxBlur.cpp
@@ -4,60 +4,59 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "gfxBlur.h"
 #include "gfxContext.h"
 #include "gfxPlatform.h"
 
 #include "mozilla/gfx/Blur.h"
 #include "mozilla/gfx/2D.h"
+#include "mozilla/UniquePtr.h"
 #include "nsExpirationTracker.h"
 #include "nsClassHashtable.h"
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 
 gfxAlphaBoxBlur::gfxAlphaBoxBlur()
- : mBlur(nullptr)
 {
 }
 
 gfxAlphaBoxBlur::~gfxAlphaBoxBlur()
 {
   mContext = nullptr;
-  delete mBlur;
 }
 
 gfxContext*
 gfxAlphaBoxBlur::Init(const gfxRect& aRect,
                       const gfxIntSize& aSpreadRadius,
                       const gfxIntSize& aBlurRadius,
                       const gfxRect* aDirtyRect,
                       const gfxRect* aSkipRect)
 {
     mozilla::gfx::Rect rect(Float(aRect.x), Float(aRect.y),
                             Float(aRect.width), Float(aRect.height));
     IntSize spreadRadius(aSpreadRadius.width, aSpreadRadius.height);
     IntSize blurRadius(aBlurRadius.width, aBlurRadius.height);
-    nsAutoPtr<mozilla::gfx::Rect> dirtyRect;
+    UniquePtr<Rect> dirtyRect;
     if (aDirtyRect) {
-      dirtyRect = new mozilla::gfx::Rect(Float(aDirtyRect->x),
-                                         Float(aDirtyRect->y),
-                                         Float(aDirtyRect->width),
-                                         Float(aDirtyRect->height));
+      dirtyRect = MakeUnique<Rect>(Float(aDirtyRect->x),
+                                   Float(aDirtyRect->y),
+                                   Float(aDirtyRect->width),
+                                   Float(aDirtyRect->height));
     }
-    nsAutoPtr<mozilla::gfx::Rect> skipRect;
+    UniquePtr<Rect> skipRect;
     if (aSkipRect) {
-      skipRect = new mozilla::gfx::Rect(Float(aSkipRect->x),
-                                        Float(aSkipRect->y),
-                                        Float(aSkipRect->width),
-                                        Float(aSkipRect->height));
+      skipRect = MakeUnique<Rect>(Float(aSkipRect->x),
+                                  Float(aSkipRect->y),
+                                  Float(aSkipRect->width),
+                                  Float(aSkipRect->height));
     }
 
-    mBlur = new AlphaBoxBlur(rect, spreadRadius, blurRadius, dirtyRect, skipRect);
+    mBlur = MakeUnique<AlphaBoxBlur>(rect, spreadRadius, blurRadius, dirtyRect.get(), skipRect.get());
     int32_t blurDataSize = mBlur->GetSurfaceAllocationSize();
     if (blurDataSize <= 0)
         return nullptr;
 
     IntSize size = mBlur->GetSize();
 
     // Make an alpha-only surface to draw on. We will play with the data after
     // everything is drawn to create a blur effect.
--- a/gfx/thebes/gfxBlur.h
+++ b/gfx/thebes/gfxBlur.h
@@ -6,16 +6,17 @@
 #ifndef GFX_BLUR_H
 #define GFX_BLUR_H
 
 #include "gfxTypes.h"
 #include "nsSize.h"
 #include "nsAutoPtr.h"
 #include "gfxPoint.h"
 #include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
 
 class gfxContext;
 struct gfxRect;
 struct gfxRGBA;
 struct gfxCornerSizes;
 class gfxMatrix;
 
 namespace mozilla {
@@ -144,12 +145,12 @@ protected:
     /**
      * The temporary alpha surface.
      */
     nsAutoArrayPtr<unsigned char> mData;
 
      /**
       * The object that actually does the blurring for us.
       */
-    mozilla::gfx::AlphaBoxBlur *mBlur;
+    mozilla::UniquePtr<mozilla::gfx::AlphaBoxBlur> mBlur;
 };
 
 #endif /* GFX_BLUR_H */
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -30,16 +30,17 @@
 #include "gfxScriptItemizer.h"
 #include "nsSpecialCasingData.h"
 #include "nsTextRunTransformations.h"
 #include "nsUnicodeProperties.h"
 #include "nsMathUtils.h"
 #include "nsBidiUtils.h"
 #include "nsUnicodeRange.h"
 #include "nsStyleConsts.h"
+#include "mozilla/AppUnits.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/Likely.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "mozilla/Telemetry.h"
 #include "gfxSVGGlyphs.h"
 #include "gfxMathTable.h"
@@ -171,32 +172,43 @@ gfxFontEntry::gfxFontEntry(const nsAStri
     mHBFace(nullptr),
     mGrFace(nullptr),
     mGrFaceRefCnt(0)
 {
     memset(&mDefaultSubSpaceFeatures, 0, sizeof(mDefaultSubSpaceFeatures));
     memset(&mNonDefaultSubSpaceFeatures, 0, sizeof(mNonDefaultSubSpaceFeatures));
 }
 
+static PLDHashOperator
+DestroyHBSet(const uint32_t& aTag, hb_set_t*& aSet, void *aUserArg)
+{
+    hb_set_destroy(aSet);
+    return PL_DHASH_NEXT;
+}
+
 gfxFontEntry::~gfxFontEntry()
 {
     if (mCOLR) {
         hb_blob_destroy(mCOLR);
     }
 
     if (mCPAL) {
         hb_blob_destroy(mCPAL);
     }
 
     // For downloaded fonts, we need to tell the user font cache that this
     // entry is being deleted.
     if (!mIsProxy && IsUserFont() && !IsLocalUserFont()) {
         gfxUserFontSet::UserFontCache::ForgetFont(this);
     }
 
+    if (mFeatureInputs) {
+        mFeatureInputs->Enumerate(DestroyHBSet, nullptr);
+    }
+
     // By the time the entry is destroyed, all font instances that were
     // using it should already have been deleted, and so the HB and/or Gr
     // face objects should have been released.
     MOZ_ASSERT(!mHBFace);
     MOZ_ASSERT(!mGrFaceInitialized);
 }
 
 bool gfxFontEntry::IsSymbolFont() 
@@ -894,20 +906,24 @@ PR_STATIC_ASSERT(MOZ_NUM_SCRIPT_CODES <=
 
 bool
 gfxFontEntry::SupportsOpenTypeFeature(int32_t aScript, uint32_t aFeatureTag)
 {
     if (!mSupportedFeatures) {
         mSupportedFeatures = new nsDataHashtable<nsUint32HashKey,bool>();
     }
 
+    // note: high-order three bytes *must* be unique for each feature
+    // listed below (see SCRIPT_FEATURE macro def'n)
     NS_ASSERTION(aFeatureTag == HB_TAG('s','m','c','p') ||
                  aFeatureTag == HB_TAG('c','2','s','c') ||
                  aFeatureTag == HB_TAG('p','c','a','p') ||
-                 aFeatureTag == HB_TAG('c','2','p','c'),
+                 aFeatureTag == HB_TAG('c','2','p','c') ||
+                 aFeatureTag == HB_TAG('s','u','p','s') ||
+                 aFeatureTag == HB_TAG('s','u','b','s'),
                  "use of unknown feature tag");
 
     // note: graphite feature support uses the last script index
     NS_ASSERTION(aScript < FEATURE_SCRIPT_MASK - 1,
                  "need to bump the size of the feature shift");
 
     uint32_t scriptFeature = SCRIPT_FEATURE(aScript, aFeatureTag);
     bool result;
@@ -915,25 +931,18 @@ gfxFontEntry::SupportsOpenTypeFeature(in
         return result;
     }
 
     result = false;
 
     hb_face_t *face = GetHBFace();
 
     if (hb_ot_layout_has_substitution(face)) {
-        // Decide what harfbuzz script code will be used for shaping
-        hb_script_t hbScript;
-        if (aScript <= MOZ_SCRIPT_INHERITED) {
-            // For unresolved "common" or "inherited" runs, default to Latin
-            // for now. (Compare gfxHarfBuzzShaper.)
-            hbScript = HB_SCRIPT_LATIN;
-        } else {
-            hbScript = hb_script_t(GetScriptTagForCode(aScript));
-        }
+        hb_script_t hbScript =
+            gfxHarfBuzzShaper::GetHBScriptUsedForShaping(aScript);
 
         // Get the OpenType tag(s) that match this script code
         hb_tag_t scriptTags[4] = {
             HB_TAG_NONE,
             HB_TAG_NONE,
             HB_TAG_NONE,
             HB_TAG_NONE
         };
@@ -967,27 +976,91 @@ gfxFontEntry::SupportsOpenTypeFeature(in
 
     hb_face_destroy(face);
 
     mSupportedFeatures->Put(scriptFeature, result);
 
     return result;
 }
 
+const hb_set_t*
+gfxFontEntry::InputsForOpenTypeFeature(int32_t aScript, uint32_t aFeatureTag)
+{
+    if (!mFeatureInputs) {
+        mFeatureInputs = new nsDataHashtable<nsUint32HashKey,hb_set_t*>();
+    }
+
+    NS_ASSERTION(aFeatureTag == HB_TAG('s','u','p','s') ||
+                 aFeatureTag == HB_TAG('s','u','b','s'),
+                 "use of unknown feature tag");
+
+    uint32_t scriptFeature = SCRIPT_FEATURE(aScript, aFeatureTag);
+    hb_set_t *inputGlyphs;
+    if (mFeatureInputs->Get(scriptFeature, &inputGlyphs)) {
+        return inputGlyphs;
+    }
+
+    inputGlyphs = hb_set_create();
+
+    hb_face_t *face = GetHBFace();
+
+    if (hb_ot_layout_has_substitution(face)) {
+        hb_script_t hbScript =
+            gfxHarfBuzzShaper::GetHBScriptUsedForShaping(aScript);
+
+        // Get the OpenType tag(s) that match this script code
+        hb_tag_t scriptTags[4] = {
+            HB_TAG_NONE,
+            HB_TAG_NONE,
+            HB_TAG_NONE,
+            HB_TAG_NONE
+        };
+        hb_ot_tags_from_script(hbScript, &scriptTags[0], &scriptTags[1]);
+
+        // Replace the first remaining NONE with DEFAULT
+        hb_tag_t* scriptTag = &scriptTags[0];
+        while (*scriptTag != HB_TAG_NONE) {
+            ++scriptTag;
+        }
+        *scriptTag = HB_OT_TAG_DEFAULT_SCRIPT;
+
+        const hb_tag_t kGSUB = HB_TAG('G','S','U','B');
+        hb_tag_t features[2] = { aFeatureTag, HB_TAG_NONE };
+        hb_set_t *featurelookups = hb_set_create();
+        hb_ot_layout_collect_lookups(face, kGSUB, scriptTags, nullptr,
+                                     features, featurelookups);
+        hb_codepoint_t index = -1;
+        while (hb_set_next(featurelookups, &index)) {
+            hb_ot_layout_lookup_collect_glyphs(face, kGSUB, index,
+                                               nullptr, inputGlyphs,
+                                               nullptr, nullptr);
+        }
+    }
+
+    hb_face_destroy(face);
+
+    mFeatureInputs->Put(scriptFeature, inputGlyphs);
+    return inputGlyphs;
+}
+
 bool
 gfxFontEntry::SupportsGraphiteFeature(uint32_t aFeatureTag)
 {
     if (!mSupportedFeatures) {
         mSupportedFeatures = new nsDataHashtable<nsUint32HashKey,bool>();
     }
 
+    // note: high-order three bytes *must* be unique for each feature
+    // listed below (see SCRIPT_FEATURE macro def'n)
     NS_ASSERTION(aFeatureTag == HB_TAG('s','m','c','p') ||
                  aFeatureTag == HB_TAG('c','2','s','c') ||
                  aFeatureTag == HB_TAG('p','c','a','p') ||
-                 aFeatureTag == HB_TAG('c','2','p','c'),
+                 aFeatureTag == HB_TAG('c','2','p','c') ||
+                 aFeatureTag == HB_TAG('s','u','p','s') ||
+                 aFeatureTag == HB_TAG('s','u','b','s'),
                  "use of unknown feature tag");
 
     // graphite feature check uses the last script slot
     uint32_t scriptFeature = SCRIPT_FEATURE(FEATURE_SCRIPT_MASK, aFeatureTag);
     bool result;
     if (mSupportedFeatures->Get(scriptFeature, &result)) {
         return result;
     }
@@ -2133,16 +2206,17 @@ gfxFontShaper::MergeFontFeatures(
     const nsTArray<gfxFontFeature>& styleRuleFeatures =
         aStyle->featureSettings;
 
     // bail immediately if nothing to do
     if (styleRuleFeatures.IsEmpty() &&
         aFontFeatures.IsEmpty() &&
         !aDisableLigatures &&
         aStyle->variantCaps == NS_FONT_VARIANT_CAPS_NORMAL &&
+        aStyle->variantSubSuper == NS_FONT_VARIANT_POSITION_NORMAL &&
         numAlts == 0) {
         return false;
     }
 
     // Ligature features are enabled by default in the generic shaper,
     // so we explicitly turn them off if necessary (for letter-spacing)
     if (aDisableLigatures) {
         aMergedFeatures.Put(HB_TAG('l','i','g','a'), 0);
@@ -2185,16 +2259,28 @@ gfxFontShaper::MergeFontFeatures(
         case NS_FONT_VARIANT_CAPS_UNICASE:
             aMergedFeatures.Put(HB_TAG('u','n','i','c'), 1);
             break;
 
         default:
             break;
     }
 
+    // font-variant-position - handled here due to the need for fallback
+    switch (aStyle->variantSubSuper) {
+        case NS_FONT_VARIANT_POSITION_SUPER:
+            aMergedFeatures.Put(HB_TAG('s','u','p','s'), 1);
+            break;
+        case NS_FONT_VARIANT_POSITION_SUB:
+            aMergedFeatures.Put(HB_TAG('s','u','b','s'), 1);
+            break;
+        default:
+            break;
+    }
+
     // add font-specific feature values from style rules
     if (aStyle->featureValueLookup && numAlts > 0) {
         nsAutoTArray<gfxFontFeature,4> featureList;
 
         // insert list of alternate feature settings
         LookupAlternateValues(aStyle->featureValueLookup, aFamilyName,
                               aStyle->alternateValues, featureList);
 
@@ -2835,16 +2921,90 @@ gfxFont::SupportsVariantCaps(int32_t aSc
 
     NS_ASSERTION(!(!ok && aFallbackToSmallCaps),
                  "if we found a usable fallback, that counts as ok");
 
     return ok;
 }
 
 bool
+gfxFont::SupportsSubSuperscript(uint32_t aSubSuperscript,
+                                const uint8_t *aString,
+                                uint32_t aLength, int32_t aRunScript)
+{
+    NS_ConvertASCIItoUTF16 unicodeString(reinterpret_cast<const char*>(aString),
+                                         aLength);
+    return SupportsSubSuperscript(aSubSuperscript, unicodeString.get(),
+                                  aLength, aRunScript);
+}
+
+bool
+gfxFont::SupportsSubSuperscript(uint32_t aSubSuperscript,
+                                const char16_t *aString,
+                                uint32_t aLength, int32_t aRunScript)
+{
+    NS_ASSERTION(aSubSuperscript == NS_FONT_VARIANT_POSITION_SUPER ||
+                 aSubSuperscript == NS_FONT_VARIANT_POSITION_SUB,
+                 "unknown value of font-variant-position");
+
+    uint32_t feature = aSubSuperscript == NS_FONT_VARIANT_POSITION_SUPER ?
+                       HB_TAG('s','u','p','s') : HB_TAG('s','u','b','s');
+
+    if (!SupportsFeature(aRunScript, feature)) {
+        return false;
+    }
+
+    // xxx - for graphite, don't really know how to sniff lookups so bail
+    if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
+        return true;
+    }
+
+    if (!mHarfBuzzShaper) {
+        mHarfBuzzShaper = new gfxHarfBuzzShaper(this);
+    }
+    gfxHarfBuzzShaper* shaper =
+        static_cast<gfxHarfBuzzShaper*>(mHarfBuzzShaper.get());
+    if (!shaper->Initialize()) {
+        return false;
+    }
+
+    // get the hbset containing input glyphs for the feature
+    const hb_set_t *inputGlyphs = mFontEntry->InputsForOpenTypeFeature(aRunScript, feature);
+
+    // create an hbset containing default glyphs for the script run
+    hb_set_t *defaultGlyphsInRun = hb_set_create();
+
+    // for each character, get the glyph id
+    for (uint32_t i = 0; i < aLength; i++) {
+        uint32_t ch = aString[i];
+
+        if ((i + 1 < aLength) && NS_IS_HIGH_SURROGATE(ch) &&
+                             NS_IS_LOW_SURROGATE(aString[i + 1])) {
+            i++;
+            ch = SURROGATE_TO_UCS4(ch, aString[i]);
+        }
+
+        if (ch == 0xa0) {
+            ch = ' ';
+        }
+
+        hb_codepoint_t gid = shaper->GetGlyph(ch, 0);
+        hb_set_add(defaultGlyphsInRun, gid);
+    }
+
+    // intersect with input glyphs, if size is not the same ==> fallback
+    uint32_t origSize = hb_set_get_population(defaultGlyphsInRun);
+    hb_set_intersect(defaultGlyphsInRun, inputGlyphs);
+    uint32_t intersectionSize = hb_set_get_population(defaultGlyphsInRun);
+    hb_set_destroy(defaultGlyphsInRun);
+
+    return origSize == intersectionSize;
+}
+
+bool
 gfxFont::HasFeatureSet(uint32_t aFeature, bool& aFeatureOn)
 {
     aFeatureOn = false;
 
     if (mStyle.featureSettings.IsEmpty() &&
         GetFontEntry()->mFeatureSettings.IsEmpty()) {
         return false;
     }
@@ -3184,16 +3344,20 @@ gfxFont::Draw(gfxTextRun *aTextRun, uint
             strikes = NS_lroundf(GetSyntheticBoldOffset() / xscale);
         }
     }
 
     uint32_t i;
     // Current position in appunits
     double x = aPt->x;
     double y = aPt->y;
+    double origY = aPt->y;
+    if (mStyle.baselineOffset != 0.0) {
+        y += mStyle.baselineOffset * appUnitsPerDevUnit;
+    }
 
     RefPtr<DrawTarget> dt = aContext->GetDrawTarget();
 
     bool paintSVGGlyphs = !aCallbacks || aCallbacks->mShouldPaintSVGGlyphs;
     bool emittedGlyphs = false;
 
     {
       RefPtr<ScaledFont> scaledFont = GetScaledFont(dt);
@@ -3443,17 +3607,17 @@ gfxFont::Draw(gfxTextRun *aTextRun, uint
           aCallbacks->NotifyGlyphPathEmitted();
       }
 
       dt->SetTransform(oldMat);
 
       dt->SetPermitSubpixelAA(oldSubpixelAA);
     }
 
-    *aPt = gfxPoint(x, y);
+    *aPt = gfxPoint(x, origY);
 }
 
 bool
 gfxFont::RenderSVGGlyph(gfxContext *aContext, gfxPoint aPoint, DrawMode aDrawMode,
                         uint32_t aGlyphId, gfxTextContextPaint *aContextPaint)
 {
     if (!GetFontEntry()->HasSVGGlyph(aGlyphId)) {
         return false;
@@ -5275,100 +5439,116 @@ gfxFontGroup::InitTextRun(gfxContext *aC
     }
 
 #ifdef PR_LOGGING
     PRLogModuleInfo *log = (mStyle.systemFont ?
                             gfxPlatform::GetLog(eGfxLog_textrunui) :
                             gfxPlatform::GetLog(eGfxLog_textrun));
 #endif
 
-    if (sizeof(T) == sizeof(uint8_t) && !transformedString) {
-
-#ifdef PR_LOGGING
-        if (MOZ_UNLIKELY(PR_LOG_TEST(log, PR_LOG_WARNING))) {
-            nsAutoCString lang;
-            mStyle.language->ToUTF8String(lang);
-            nsAutoString families;
-            mFamilyList.ToString(families);
-            nsAutoCString str((const char*)aString, aLength);
-            PR_LOG(log, PR_LOG_WARNING,\
-                   ("(%s) fontgroup: [%s] default: %s lang: %s script: %d "
-                    "len %d weight: %d width: %d style: %s size: %6.2f %d-byte "
-                    "TEXTRUN [%s] ENDTEXTRUN\n",
-                    (mStyle.systemFont ? "textrunui" : "textrun"),
-                    NS_ConvertUTF16toUTF8(families).get(),
-                    (mFamilyList.GetDefaultFontType() == eFamily_serif ?
-                     "serif" :
-                     (mFamilyList.GetDefaultFontType() == eFamily_sans_serif ?
-                      "sans-serif" : "none")),
-                    lang.get(), MOZ_SCRIPT_LATIN, aLength,
-                    uint32_t(mStyle.weight), uint32_t(mStyle.stretch),
-                    (mStyle.style & NS_FONT_STYLE_ITALIC ? "italic" :
-                    (mStyle.style & NS_FONT_STYLE_OBLIQUE ? "oblique" :
-                                                            "normal")),
-                    mStyle.size,
-                    sizeof(T),
-                    str.get()));
-        }
-#endif
-
-        // the text is still purely 8-bit; bypass the script-run itemizer
-        // and treat it as a single Latin run
-        InitScriptRun(aContext, aTextRun, aString,
-                      0, aLength, MOZ_SCRIPT_LATIN);
-    } else {
-        const char16_t *textPtr;
-        if (transformedString) {
-            textPtr = transformedString.get();
-        } else {
-            // typecast to avoid compilation error for the 8-bit version,
-            // even though this is dead code in that case
-            textPtr = reinterpret_cast<const char16_t*>(aString);
-        }
-
-        // split into script runs so that script can potentially influence
-        // the font matching process below
-        gfxScriptItemizer scriptRuns(textPtr, aLength);
-
-        uint32_t runStart = 0, runLimit = aLength;
-        int32_t runScript = MOZ_SCRIPT_LATIN;
-        while (scriptRuns.Next(runStart, runLimit, runScript)) {
+    // variant fallback handling may end up passing through this twice
+    bool redo;
+    do {
+        redo = false;
+
+        if (sizeof(T) == sizeof(uint8_t) && !transformedString) {
 
 #ifdef PR_LOGGING
             if (MOZ_UNLIKELY(PR_LOG_TEST(log, PR_LOG_WARNING))) {
                 nsAutoCString lang;
                 mStyle.language->ToUTF8String(lang);
                 nsAutoString families;
                 mFamilyList.ToString(families);
-                uint32_t runLen = runLimit - runStart;
+                nsAutoCString str((const char*)aString, aLength);
                 PR_LOG(log, PR_LOG_WARNING,\
                        ("(%s) fontgroup: [%s] default: %s lang: %s script: %d "
-                        "len %d weight: %d width: %d style: %s size: %6.2f "
-                        "%d-byte TEXTRUN [%s] ENDTEXTRUN\n",
+                        "len %d weight: %d width: %d style: %s size: %6.2f %d-byte "
+                        "TEXTRUN [%s] ENDTEXTRUN\n",
                         (mStyle.systemFont ? "textrunui" : "textrun"),
                         NS_ConvertUTF16toUTF8(families).get(),
                         (mFamilyList.GetDefaultFontType() == eFamily_serif ?
                          "serif" :
                          (mFamilyList.GetDefaultFontType() == eFamily_sans_serif ?
                           "sans-serif" : "none")),
-                        lang.get(), runScript, runLen,
+                        lang.get(), MOZ_SCRIPT_LATIN, aLength,
                         uint32_t(mStyle.weight), uint32_t(mStyle.stretch),
                         (mStyle.style & NS_FONT_STYLE_ITALIC ? "italic" :
                         (mStyle.style & NS_FONT_STYLE_OBLIQUE ? "oblique" :
                                                                 "normal")),
                         mStyle.size,
                         sizeof(T),
-                        NS_ConvertUTF16toUTF8(textPtr + runStart, runLen).get()));
+                        str.get()));
             }
 #endif
 
-            InitScriptRun(aContext, aTextRun, textPtr + runStart,
-                          runStart, runLimit - runStart, runScript);
-        }
-    }
+            // the text is still purely 8-bit; bypass the script-run itemizer
+            // and treat it as a single Latin run
+            InitScriptRun(aContext, aTextRun, aString,
+                          0, aLength, MOZ_SCRIPT_LATIN);
+        } else {
+            const char16_t *textPtr;
+            if (transformedString) {
+                textPtr = transformedString.get();
+            } else {
+                // typecast to avoid compilation error for the 8-bit version,
+                // even though this is dead code in that case
+                textPtr = reinterpret_cast<const char16_t*>(aString);
+            }
+
+            // split into script runs so that script can potentially influence
+            // the font matching process below
+            gfxScriptItemizer scriptRuns(textPtr, aLength);
+
+            uint32_t runStart = 0, runLimit = aLength;
+            int32_t runScript = MOZ_SCRIPT_LATIN;
+            while (scriptRuns.Next(runStart, runLimit, runScript)) {
+
+    #ifdef PR_LOGGING
+                if (MOZ_UNLIKELY(PR_LOG_TEST(log, PR_LOG_WARNING))) {
+                    nsAutoCString lang;
+                    mStyle.language->ToUTF8String(lang);
+                    nsAutoString families;
+                    mFamilyList.ToString(families);
+                    uint32_t runLen = runLimit - runStart;
+                    PR_LOG(log, PR_LOG_WARNING,\
+                           ("(%s) fontgroup: [%s] default: %s lang: %s script: %d "
+                            "len %d weight: %d width: %d style: %s size: %6.2f "
+                            "%d-byte TEXTRUN [%s] ENDTEXTRUN\n",
+                            (mStyle.systemFont ? "textrunui" : "textrun"),
+                            NS_ConvertUTF16toUTF8(families).get(),
+                            (mFamilyList.GetDefaultFontType() == eFamily_serif ?
+                             "serif" :
+                             (mFamilyList.GetDefaultFontType() == eFamily_sans_serif ?
+                              "sans-serif" : "none")),
+                            lang.get(), runScript, runLen,
+                            uint32_t(mStyle.weight), uint32_t(mStyle.stretch),
+                            (mStyle.style & NS_FONT_STYLE_ITALIC ? "italic" :
+                            (mStyle.style & NS_FONT_STYLE_OBLIQUE ? "oblique" :
+                                                                    "normal")),
+                            mStyle.size,
+                            sizeof(T),
+                            NS_ConvertUTF16toUTF8(textPtr + runStart, runLen).get()));
+                }
+    #endif
+
+                InitScriptRun(aContext, aTextRun, textPtr + runStart,
+                              runStart, runLimit - runStart, runScript);
+            }
+        }
+
+        // if shaping was aborted due to lack of feature support, clear out
+        // glyph runs and redo shaping with fallback forced on
+        if (aTextRun->GetShapingState() == gfxTextRun::eShapingState_Aborted) {
+            redo = true;
+            aTextRun->SetShapingState(
+                gfxTextRun::eShapingState_ForceFallbackFeature);
+            aTextRun->ClearGlyphsAndCharacters();
+        }
+
+    } while (redo);
 
     if (sizeof(T) == sizeof(char16_t) && aLength > 0) {
         gfxTextRun::CompressedGlyph *glyph = aTextRun->GetCharacterGlyphs();
         if (!glyph->IsSimpleGlyph()) {
             glyph->SetClusterStart(true);
         }
     }
 
@@ -5393,57 +5573,117 @@ gfxFontGroup::InitScriptRun(gfxContext *
                             const T *aString, // text for this script run,
                                               // not the entire textrun
                             uint32_t aOffset, // position of the script run
                                               // within the textrun
                             uint32_t aLength, // length of the script run
                             int32_t aRunScript)
 {
     NS_ASSERTION(aLength > 0, "don't call InitScriptRun for a 0-length run");
+    NS_ASSERTION(aTextRun->GetShapingState() != gfxTextRun::eShapingState_Aborted,
+                 "don't call InitScriptRun with aborted shaping state");
 
     gfxFont *mainFont = GetFontAt(0);
 
     uint32_t runStart = 0;
     nsAutoTArray<gfxTextRange,3> fontRanges;
     ComputeRanges(fontRanges, aString, aLength, aRunScript);
     uint32_t numRanges = fontRanges.Length();
 
     for (uint32_t r = 0; r < numRanges; r++) {
         const gfxTextRange& range = fontRanges[r];
         uint32_t matchedLength = range.Length();
         gfxFont *matchedFont = range.font;
 
         // create the glyph run for this range
-        if (matchedFont) {
-            bool needsFakeSmallCaps = false;
+        if (matchedFont && mStyle.noFallbackVariantFeatures) {
+            // common case - just do glyph layout and record the
+            // resulting positioned glyphs
+            aTextRun->AddGlyphRun(matchedFont, range.matchType,
+                                  aOffset + runStart, (matchedLength > 0));
+            if (!matchedFont->SplitAndInitTextRun(aContext, aTextRun,
+                                                  aString + runStart,
+                                                  aOffset + runStart,
+                                                  matchedLength,
+                                                  aRunScript)) {
+                // glyph layout failed! treat as missing glyphs
+                matchedFont = nullptr;
+            }
+        } else if (matchedFont) {
+            // shape with some variant feature that requires fallback handling
             bool petiteToSmallCaps = false;
             bool syntheticLower = false;
             bool syntheticUpper = false;
 
-            if (mStyle.variantCaps != NS_FONT_VARIANT_CAPS_NORMAL) {
-                needsFakeSmallCaps =
-                    !matchedFont->SupportsVariantCaps(aRunScript,
-                        mStyle.variantCaps, petiteToSmallCaps,
-                        syntheticLower, syntheticUpper);
-            }
-            if (needsFakeSmallCaps) {
+            if (mStyle.variantSubSuper != NS_FONT_VARIANT_POSITION_NORMAL &&
+                (aTextRun->GetShapingState() ==
+                     gfxTextRun::eShapingState_ForceFallbackFeature ||
+                 !matchedFont->SupportsSubSuperscript(mStyle.variantSubSuper,
+                                                      aString, aLength,
+                                                      aRunScript)))
+            {
+                // fallback for subscript/superscript variant glyphs
+
+                // if the feature was already used, abort and force
+                // fallback across the entire textrun
+                gfxTextRun::ShapingState ss = aTextRun->GetShapingState();
+
+                if (ss == gfxTextRun::eShapingState_Normal) {
+                    aTextRun->SetShapingState(gfxTextRun::eShapingState_ShapingWithFallback);
+                } else if (ss == gfxTextRun::eShapingState_ShapingWithFeature) {
+                    aTextRun->SetShapingState(gfxTextRun::eShapingState_Aborted);
+                    return;
+                }
+
+                nsRefPtr<gfxFont> subSuperFont =
+                    matchedFont->GetSubSuperscriptFont(aTextRun->GetAppUnitsPerDevUnit());
+                aTextRun->AddGlyphRun(subSuperFont, range.matchType,
+                                      aOffset + runStart, (matchedLength > 0));
+                if (!subSuperFont->SplitAndInitTextRun(aContext, aTextRun,
+                                                       aString + runStart,
+                                                       aOffset + runStart,
+                                                       matchedLength,
+                                                       aRunScript)) {
+                    // glyph layout failed! treat as missing glyphs
+                    matchedFont = nullptr;
+                }
+            } else if (mStyle.variantCaps != NS_FONT_VARIANT_CAPS_NORMAL &&
+                       !matchedFont->SupportsVariantCaps(aRunScript,
+                                                         mStyle.variantCaps,
+                                                         petiteToSmallCaps,
+                                                         syntheticLower,
+                                                         syntheticUpper))
+            {
+                // fallback for small-caps variant glyphs
                 if (!matchedFont->InitFakeSmallCapsRun(aContext, aTextRun,
                                                        aString + runStart,
                                                        aOffset + runStart,
                                                        matchedLength,
                                                        range.matchType,
                                                        aRunScript,
                                                        syntheticLower,
                                                        syntheticUpper)) {
                     matchedFont = nullptr;
                 }
             } else {
+                // shape normally with variant feature enabled
+                gfxTextRun::ShapingState ss = aTextRun->GetShapingState();
+
+                // adjust the shaping state if necessary
+                if (ss == gfxTextRun::eShapingState_Normal) {
+                    aTextRun->SetShapingState(gfxTextRun::eShapingState_ShapingWithFeature);
+                } else if (ss == gfxTextRun::eShapingState_ShapingWithFallback) {
+                    // already have shaping results using fallback, need to redo
+                    aTextRun->SetShapingState(gfxTextRun::eShapingState_Aborted);
+                    return;
+                }
+
+                // do glyph layout and record the resulting positioned glyphs
                 aTextRun->AddGlyphRun(matchedFont, range.matchType,
                                       aOffset + runStart, (matchedLength > 0));
-                // do glyph layout and record the resulting positioned glyphs
                 if (!matchedFont->SplitAndInitTextRun(aContext, aTextRun,
                                                       aString + runStart,
                                                       aOffset + runStart,
                                                       matchedLength,
                                                       aRunScript)) {
                     // glyph layout failed! treat as missing glyphs
                     matchedFont = nullptr;
                 }
@@ -5713,16 +5953,26 @@ gfxFont::GetSmallCapsFont()
     gfxFontStyle style(*GetStyle());
     style.size *= SMALL_CAPS_SCALE_FACTOR;
     style.variantCaps = NS_FONT_VARIANT_CAPS_NORMAL;
     gfxFontEntry* fe = GetFontEntry();
     bool needsBold = style.weight >= 600 && !fe->IsBold();
     return fe->FindOrMakeFont(&style, needsBold);
 }
 
+already_AddRefed<gfxFont>
+gfxFont::GetSubSuperscriptFont(int32_t aAppUnitsPerDevPixel)
+{
+    gfxFontStyle style(*GetStyle());
+    style.AdjustForSubSuperscript(aAppUnitsPerDevPixel);
+    gfxFontEntry* fe = GetFontEntry();
+    bool needsBold = style.weight >= 600 && !fe->IsBold();
+    return fe->FindOrMakeFont(&style, needsBold);
+}
+
 gfxTextRun *
 gfxFontGroup::GetEllipsisTextRun(int32_t aAppUnitsPerDevPixel,
                                  LazyReferenceContextGetter& aRefContextGetter)
 {
     if (mCachedEllipsisTextRun &&
         mCachedEllipsisTextRun->GetAppUnitsPerDevUnit() == aAppUnitsPerDevPixel) {
         return mCachedEllipsisTextRun;
     }
@@ -6152,43 +6402,47 @@ gfxFontStyle::ParseFontLanguageOverride(
   while (index++ < 4) {
     result = (result << 8) + 0x20;
   }
   return result;
 }
 
 gfxFontStyle::gfxFontStyle() :
     language(nsGkAtoms::x_western),
-    size(DEFAULT_PIXEL_FONT_SIZE), sizeAdjust(0.0f),
+    size(DEFAULT_PIXEL_FONT_SIZE), sizeAdjust(0.0f), baselineOffset(0.0f),
     languageOverride(NO_FONT_LANGUAGE_OVERRIDE),
     weight(NS_FONT_WEIGHT_NORMAL), stretch(NS_FONT_STRETCH_NORMAL),
     systemFont(true), printerFont(false), useGrayscaleAntialiasing(false),
     style(NS_FONT_STYLE_NORMAL),
     allowSyntheticWeight(true), allowSyntheticStyle(true),
-    variantCaps(NS_FONT_VARIANT_CAPS_NORMAL)
+    noFallbackVariantFeatures(true),
+    variantCaps(NS_FONT_VARIANT_CAPS_NORMAL),
+    variantSubSuper(NS_FONT_VARIANT_POSITION_NORMAL)
 {
 }
 
 gfxFontStyle::gfxFontStyle(uint8_t aStyle, uint16_t aWeight, int16_t aStretch,
                            gfxFloat aSize, nsIAtom *aLanguage,
                            float aSizeAdjust, bool aSystemFont,
                            bool aPrinterFont,
                            bool aAllowWeightSynthesis,
                            bool aAllowStyleSynthesis,
                            const nsString& aLanguageOverride):
     language(aLanguage),
-    size(aSize), sizeAdjust(aSizeAdjust),
+    size(aSize), sizeAdjust(aSizeAdjust), baselineOffset(0.0f),
     languageOverride(ParseFontLanguageOverride(aLanguageOverride)),
     weight(aWeight), stretch(aStretch),
     systemFont(aSystemFont), printerFont(aPrinterFont),
     useGrayscaleAntialiasing(false),
     style(aStyle),
     allowSyntheticWeight(aAllowWeightSynthesis),
     allowSyntheticStyle(aAllowStyleSynthesis),
-    variantCaps(NS_FONT_VARIANT_CAPS_NORMAL)
+    noFallbackVariantFeatures(true),
+    variantCaps(NS_FONT_VARIANT_CAPS_NORMAL),
+    variantSubSuper(NS_FONT_VARIANT_POSITION_NORMAL)
 {
     MOZ_ASSERT(!mozilla::IsNaN(size));
     MOZ_ASSERT(!mozilla::IsNaN(sizeAdjust));
 
     if (weight > 900)
         weight = 900;
     if (weight < 100)
         weight = 100;
@@ -6206,24 +6460,27 @@ gfxFontStyle::gfxFontStyle(uint8_t aStyl
         language = nsGkAtoms::x_western;
     }
 }
 
 gfxFontStyle::gfxFontStyle(const gfxFontStyle& aStyle) :
     language(aStyle.language),
     featureValueLookup(aStyle.featureValueLookup),
     size(aStyle.size), sizeAdjust(aStyle.sizeAdjust),
+    baselineOffset(aStyle.baselineOffset),
     languageOverride(aStyle.languageOverride),
     weight(aStyle.weight), stretch(aStyle.stretch),
     systemFont(aStyle.systemFont), printerFont(aStyle.printerFont),
     useGrayscaleAntialiasing(aStyle.useGrayscaleAntialiasing),
     style(aStyle.style),
     allowSyntheticWeight(aStyle.allowSyntheticWeight),
     allowSyntheticStyle(aStyle.allowSyntheticStyle),
-    variantCaps(aStyle.variantCaps)
+    noFallbackVariantFeatures(aStyle.noFallbackVariantFeatures),
+    variantCaps(aStyle.variantCaps),
+    variantSubSuper(aStyle.variantSubSuper)
 {
     featureSettings.AppendElements(aStyle.featureSettings);
     alternateValues.AppendElements(aStyle.alternateValues);
 }
 
 int8_t
 gfxFontStyle::ComputeWeight() const
 {
@@ -6233,16 +6490,48 @@ gfxFontStyle::ComputeWeight() const
         baseWeight = 0;
     if (baseWeight > 9)
         baseWeight = 9;
 
     return baseWeight;
 }
 
 void
+gfxFontStyle::AdjustForSubSuperscript(int32_t aAppUnitsPerDevPixel)
+{
+    NS_PRECONDITION(variantSubSuper != NS_FONT_VARIANT_POSITION_NORMAL &&
+                    baselineOffset == 0,
+                    "can't adjust this style for sub/superscript");
+
+    // calculate the baseline offset (before changing the size)
+    if (variantSubSuper == NS_FONT_VARIANT_POSITION_SUPER) {
+        baselineOffset = size * -NS_FONT_SUPERSCRIPT_OFFSET_RATIO;
+    } else {
+        baselineOffset = size * NS_FONT_SUBSCRIPT_OFFSET_RATIO;
+    }
+
+    // calculate reduced size, roughly mimicing behavior of font-size: smaller
+    float cssSize = size * aAppUnitsPerDevPixel / AppUnitsPerCSSPixel();
+    if (cssSize < NS_FONT_SUB_SUPER_SMALL_SIZE) {
+        cssSize *= NS_FONT_SUB_SUPER_SIZE_RATIO_SMALL;
+    } else if (cssSize >= NS_FONT_SUB_SUPER_SMALL_SIZE) {
+        cssSize *= NS_FONT_SUB_SUPER_SIZE_RATIO_LARGE;
+    } else {
+        gfxFloat t = (cssSize - NS_FONT_SUB_SUPER_SMALL_SIZE) /
+                         (NS_FONT_SUB_SUPER_LARGE_SIZE -
+                          NS_FONT_SUB_SUPER_SMALL_SIZE);
+        size *= (1.0 - t) * NS_FONT_SUB_SUPER_SIZE_RATIO_SMALL +
+                    t * NS_FONT_SUB_SUPER_SIZE_RATIO_LARGE;
+    }
+
+    // clear the variant field
+    variantSubSuper = NS_FONT_VARIANT_POSITION_NORMAL;
+}
+
+void
 gfxShapedText::SetupClusterBoundaries(uint32_t         aOffset,
                                       const char16_t *aString,
                                       uint32_t         aLength)
 {
     CompressedGlyph *glyphs = GetCharacterGlyphs() + aOffset;
 
     gfxTextRun::CompressedGlyph extendCluster;
     extendCluster.SetComplex(false, true, 0);
@@ -6484,16 +6773,17 @@ gfxTextRun::Create(const gfxTextRunFacto
 }
 
 gfxTextRun::gfxTextRun(const gfxTextRunFactory::Parameters *aParams,
                        uint32_t aLength, gfxFontGroup *aFontGroup, uint32_t aFlags)
     : gfxShapedText(aLength, aFlags, aParams->mAppUnitsPerDevUnit)
     , mUserData(aParams->mUserData)
     , mFontGroup(aFontGroup)
     , mReleasedFontGroup(false)
+    , mShapingState(eShapingState_Normal)
 {
     NS_ASSERTION(mAppUnitsPerDevUnit > 0, "Invalid app unit scale");
     MOZ_COUNT_CTOR(gfxTextRun);
     NS_ADDREF(mFontGroup);
 
 #ifndef RELEASE_BUILD
     gfxTextPerfMetrics *tp = aFontGroup->GetTextPerfMetrics();
     if (tp) {
@@ -7549,16 +7839,25 @@ gfxTextRun::CopyGlyphDataFrom(gfxTextRun
         nsresult rv = AddGlyphRun(font, iter.GetGlyphRun()->mMatchType,
                                   start - aStart + aDest, false);
         if (NS_FAILED(rv))
             return;
     }
 }
 
 void
+gfxTextRun::ClearGlyphsAndCharacters()
+{
+    ResetGlyphRuns();
+    memset(reinterpret_cast<char*>(mCharacterGlyphs), 0,
+           mLength * sizeof(CompressedGlyph));
+    mDetailedGlyphs = nullptr;
+}
+
+void
 gfxTextRun::SetSpaceGlyph(gfxFont *aFont, gfxContext *aContext,
                           uint32_t aCharIndex)
 {
     if (SetSpaceGlyphIfSimple(aFont, aContext, aCharIndex, ' ')) {
         return;
     }
 
     aFont->InitWordCache();
--- a/gfx/thebes/gfxFont.h
+++ b/gfx/thebes/gfxFont.h
@@ -108,16 +108,19 @@ struct gfxFontStyle {
     gfxFloat size;
 
     // The aspect-value (ie., the ratio actualsize:actualxheight) that any
     // actual physical font created from this font structure must have when
     // rendering or measuring a string. A value of 0 means no adjustment
     // needs to be done.
     float sizeAdjust;
 
+    // baseline offset, used when simulating sub/superscript glyphs
+    float baselineOffset;
+
     // Language system tag, to override document language;
     // an OpenType "language system" tag represented as a 32-bit integer
     // (see http://www.microsoft.com/typography/otspec/languagetags.htm).
     // Normally 0, so font rendering will use the document or element language
     // (see above) to control any language-specific rendering, but the author
     // can override this for cases where the options implemented in the font
     // do not directly match the actual language. (E.g. lang may be Macedonian,
     // but the font in use does not explicitly support this; the author can
@@ -145,49 +148,62 @@ struct gfxFontStyle {
 
     // The style of font (normal, italic, oblique)
     uint8_t style : 2;
 
     // Whether synthetic styles are allowed
     bool allowSyntheticWeight : 1;
     bool allowSyntheticStyle : 1;
 
+    // some variant features require fallback which complicates the shaping
+    // code, so set up a bool to indicate when shaping with fallback is needed
+    bool noFallbackVariantFeatures : 1;
+
     // caps variant (small-caps, petite-caps, etc.)
     uint8_t variantCaps;
 
+    // sub/superscript variant
+    uint8_t variantSubSuper;
+
     // Return the final adjusted font size for the given aspect ratio.
     // Not meant to be called when sizeAdjust = 0.
     gfxFloat GetAdjustedSize(gfxFloat aspect) const {
         NS_ASSERTION(sizeAdjust != 0.0, "Not meant to be called when sizeAdjust = 0");
         gfxFloat adjustedSize = std::max(NS_round(size*(sizeAdjust/aspect)), 1.0);
         return std::min(adjustedSize, FONT_MAX_SIZE);
     }
 
     PLDHashNumber Hash() const {
         return ((style + (systemFont << 7) +
             (weight << 8)) + uint32_t(size*1000) + uint32_t(sizeAdjust*1000)) ^
             nsISupportsHashKey::HashKey(language);
     }
 
     int8_t ComputeWeight() const;
 
+    // Adjust this style to simulate sub/superscript (as requested in the
+    // variantSubSuper field) using size and baselineOffset instead.
+    void AdjustForSubSuperscript(int32_t aAppUnitsPerDevPixel);
+
     bool Equals(const gfxFontStyle& other) const {
         return
             (*reinterpret_cast<const uint64_t*>(&size) ==
              *reinterpret_cast<const uint64_t*>(&other.size)) &&
             (style == other.style) &&
             (variantCaps == other.variantCaps) &&
+            (variantSubSuper == other.variantSubSuper) &&
             (allowSyntheticWeight == other.allowSyntheticWeight) &&
             (allowSyntheticStyle == other.allowSyntheticStyle) &&
             (systemFont == other.systemFont) &&
             (printerFont == other.printerFont) &&
             (useGrayscaleAntialiasing == other.useGrayscaleAntialiasing) &&
             (weight == other.weight) &&
             (stretch == other.stretch) &&
             (language == other.language) &&
+            (baselineOffset == other.baselineOffset) &&
             (*reinterpret_cast<const uint32_t*>(&sizeAdjust) ==
              *reinterpret_cast<const uint32_t*>(&other.sizeAdjust)) &&
             (featureSettings == other.featureSettings) &&
             (languageOverride == other.languageOverride) &&
             (alternateValues == other.alternateValues) &&
             (featureValueLookup == other.featureValueLookup);
     }
 
@@ -280,16 +296,20 @@ public:
     bool IgnoreGDEF() const { return mIgnoreGDEF; }
     bool IgnoreGSUB() const { return mIgnoreGSUB; }
 
     // whether a feature is supported by the font (limited to a small set
     // of features for which some form of fallback needs to be implemented)
     bool SupportsOpenTypeFeature(int32_t aScript, uint32_t aFeatureTag);
     bool SupportsGraphiteFeature(uint32_t aFeatureTag);
 
+    // returns a set containing all input glyph ids for a given feature
+    const hb_set_t*
+    InputsForOpenTypeFeature(int32_t aScript, uint32_t aFeatureTag);
+
     virtual bool IsSymbolFont();
 
     virtual bool HasFontTable(uint32_t aTableTag);
 
     inline bool HasGraphiteTables() {
         if (!mCheckedForGraphiteTables) {
             CheckForGraphiteTables();
             mCheckedForGraphiteTables = true;
@@ -570,16 +590,17 @@ public:
     nsAutoArrayPtr<uint8_t> mUVSData;
     nsAutoPtr<gfxUserFontData> mUserFontData;
     nsAutoPtr<gfxSVGGlyphs> mSVGGlyphs;
     // list of gfxFonts that are using SVG glyphs
     nsTArray<gfxFont*> mFontsUsingSVGGlyphs;
     nsAutoPtr<gfxMathTable> mMathTable;
     nsTArray<gfxFontFeature> mFeatureSettings;
     nsAutoPtr<nsDataHashtable<nsUint32HashKey,bool>> mSupportedFeatures;
+    nsAutoPtr<nsDataHashtable<nsUint32HashKey,hb_set_t*>> mFeatureInputs;
     uint32_t         mLanguageOverride;
 
     // Color Layer font support
     hb_blob_t*       mCOLR;
     hb_blob_t*       mCPAL;
 
 protected:
     friend class gfxPlatformFontList;
@@ -1634,16 +1655,27 @@ public:
 
     // whether the font supports "real" small caps, petite caps etc.
     // aFallbackToSmallCaps true when petite caps should fallback to small caps
     bool SupportsVariantCaps(int32_t aScript, uint32_t aVariantCaps,
                              bool& aFallbackToSmallCaps,
                              bool& aSyntheticLowerToSmallCaps,
                              bool& aSyntheticUpperToSmallCaps);
 
+    // whether the font supports subscript/superscript feature
+    // for fallback, need to verify that all characters in the run
+    // have variant substitutions
+    bool SupportsSubSuperscript(uint32_t aSubSuperscript,
+                                const uint8_t *aString,
+                                uint32_t aLength, int32_t aRunScript);
+
+    bool SupportsSubSuperscript(uint32_t aSubSuperscript,
+                                const char16_t *aString,
+                                uint32_t aLength, int32_t aRunScript);
+
     // Subclasses may choose to look up glyph ids for characters.
     // If they do not override this, gfxHarfBuzzShaper will fetch the cmap
     // table and use that.
     virtual bool ProvidesGetGlyph() const {
         return false;
     }
     // Map unicode character to glyph ID.
     // Only used if ProvidesGetGlyph() returns true.
@@ -1977,17 +2009,28 @@ public:
     // Get a dimensionless math constant (e.g. a percentage);
     // may only be called if mFontEntry->TryGetMathTable has succeeded
     // (i.e. the font is known to be a valid OpenType math font).
     float GetMathConstant(gfxFontEntry::MathConstant aConstant)
     {
         return mFontEntry->GetMathConstant(aConstant);
     }
 
+    // return a cloned font resized and offset to simulate sub/superscript glyphs
+    virtual already_AddRefed<gfxFont>
+    GetSubSuperscriptFont(int32_t aAppUnitsPerDevPixel);
+
 protected:
+
+    // set the font size and offset used for
+    // synthetic subscript/superscript glyphs
+    void CalculateSubSuperSizeAndOffset(int32_t aAppUnitsPerDevPixel,
+                                        gfxFloat& aSubSuperSizeRatio,
+                                        float& aBaselineOffset);
+
     // Return a font that is a "clone" of this one, but reduced to 80% size
     // (and with variantCaps set to normal).
     // Default implementation relies on gfxFontEntry::CreateFontInstance;
     // backends that don't implement that will need to override this and use
     // an alternative technique. (gfxPangoFonts, I'm looking at you...)
     virtual already_AddRefed<gfxFont> GetSmallCapsFont();
 
     // subclasses may provide (possibly hinted) glyph widths (in font units);
@@ -3300,16 +3343,19 @@ public:
     void SortGlyphRuns();
     void SanitizeGlyphRuns();
 
     CompressedGlyph* GetCharacterGlyphs() {
         NS_ASSERTION(mCharacterGlyphs, "failed to initialize mCharacterGlyphs");
         return mCharacterGlyphs;
     }
 
+    // clean out results from shaping in progress, used for fallback scenarios
+    void ClearGlyphsAndCharacters();
+
     void SetSpaceGlyph(gfxFont *aFont, gfxContext *aContext, uint32_t aCharIndex);
 
     // Set the glyph data for the given character index to the font's
     // space glyph, IF this can be done as a "simple" glyph record
     // (not requiring a DetailedGlyph entry). This avoids the need to call
     // the font shaper and go through the shaped-word cache for most spaces.
     //
     // The parameter aSpaceChar is the original character code for which
@@ -3421,16 +3467,33 @@ public:
         }
         mFlags |= gfxTextRunFactory::TEXT_RUN_SIZE_ACCOUNTED;
         return SizeOfIncludingThis(aMallocSizeOf);
     }
     void ResetSizeOfAccountingFlags() {
         mFlags &= ~gfxTextRunFactory::TEXT_RUN_SIZE_ACCOUNTED;
     }
 
+    // shaping state - for some font features, fallback is required that
+    // affects the entire run. for example, fallback for one script/font
+    // portion of a textrun requires fallback to be applied to the entire run
+
+    enum ShapingState {
+        eShapingState_Normal,                 // default state
+        eShapingState_ShapingWithFeature,     // have shaped with feature
+        eShapingState_ShapingWithFallback,    // have shaped with fallback
+        eShapingState_Aborted,                // abort initial iteration
+        eShapingState_ForceFallbackFeature    // redo with fallback forced on
+    };
+
+    ShapingState GetShapingState() const { return mShapingState; }
+    void SetShapingState(ShapingState aShapingState) {
+        mShapingState = aShapingState;
+    }
+
 #ifdef DEBUG
     void Dump(FILE* aOutput);
 #endif
 
 protected:
     /**
      * Create a textrun, and set its mCharacterGlyphs to point immediately
      * after the base object; this is ONLY used in conjunction with placement
@@ -3517,16 +3580,20 @@ private:
     gfxSkipChars      mSkipChars;
     nsExpirationState mExpirationState;
 
     bool              mSkipDrawing; // true if the font group we used had a user font
                                     // download that's in progress, so we should hide text
                                     // until the download completes (or timeout fires)
     bool              mReleasedFontGroup; // we already called NS_RELEASE on
                                           // mFontGroup, so don't do it again
+
+    // shaping state for handling variant fallback features
+    // such as subscript/superscript variant glyphs
+    ShapingState      mShapingState;
 };
 
 class gfxFontGroup : public gfxTextRunFactory {
 public:
     class FamilyFace {
     public:
         FamilyFace() { }
 
--- a/gfx/thebes/gfxFontConstants.h
+++ b/gfx/thebes/gfxFontConstants.h
@@ -195,9 +195,18 @@ enum {
 #define NS_FONT_VARIANT_POSITION_NORMAL             0
 #define NS_FONT_VARIANT_POSITION_SUPER              1
 #define NS_FONT_VARIANT_POSITION_SUB                2
 
 // based on fixed offset values used within WebKit
 #define NS_FONT_SUBSCRIPT_OFFSET_RATIO     (0.20)
 #define NS_FONT_SUPERSCRIPT_OFFSET_RATIO   (0.34)
 
+// this roughly corresponds to font-size: smaller behavior
+// at smaller sizes <20px the ratio is closer to 0.8 while at
+// larger sizes >45px the ratio is closer to 0.667 and in between
+// a blend of values is used
+#define NS_FONT_SUB_SUPER_SIZE_RATIO_SMALL       (0.82)
+#define NS_FONT_SUB_SUPER_SIZE_RATIO_LARGE       (0.667)
+#define NS_FONT_SUB_SUPER_SMALL_SIZE             (20.0)
+#define NS_FONT_SUB_SUPER_LARGE_SIZE             (45.0)
+
 #endif
--- a/gfx/thebes/gfxHarfBuzzShaper.cpp
+++ b/gfx/thebes/gfxHarfBuzzShaper.cpp
@@ -998,23 +998,18 @@ gfxHarfBuzzShaper::ShapeText(gfxContext 
     bool isRightToLeft = aShapedText->IsRightToLeft();
     hb_buffer_t *buffer = hb_buffer_create();
     hb_buffer_set_unicode_funcs(buffer, sHBUnicodeFuncs);
     hb_buffer_set_direction(buffer, isRightToLeft ? HB_DIRECTION_RTL :
                                                     HB_DIRECTION_LTR);
     hb_script_t scriptTag;
     if (aShapedText->Flags() & gfxTextRunFactory::TEXT_USE_MATH_SCRIPT) {
         scriptTag = sMathScript;
-    } else if (aScript <= MOZ_SCRIPT_INHERITED) {
-        // For unresolved "common" or "inherited" runs, default to Latin for
-        // now.  (Should we somehow use the language or locale to try and infer
-        // a better default?)
-        scriptTag = HB_SCRIPT_LATIN;
     } else {
-        scriptTag = hb_script_t(GetScriptTagForCode(aScript));
+        scriptTag = GetHBScriptUsedForShaping(aScript);
     }
     hb_buffer_set_script(buffer, scriptTag);
 
     hb_language_t language;
     if (style->languageOverride) {
         language = hb_ot_tag_to_language(style->languageOverride);
     } else if (entry->mLanguageOverride) {
         language = hb_ot_tag_to_language(entry->mLanguageOverride);
--- a/gfx/thebes/gfxHarfBuzzShaper.h
+++ b/gfx/thebes/gfxHarfBuzzShaper.h
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef GFX_HARFBUZZSHAPER_H
 #define GFX_HARFBUZZSHAPER_H
 
 #include "gfxFont.h"
 
 #include "harfbuzz/hb.h"
+#include "nsUnicodeProperties.h"
 
 class gfxHarfBuzzShaper : public gfxFontShaper {
 public:
     gfxHarfBuzzShaper(gfxFont *aFont);
     virtual ~gfxHarfBuzzShaper();
 
     /*
      * For HarfBuzz font callback functions, font_data is a ptr to a
@@ -46,16 +47,31 @@ public:
     // get harfbuzz horizontal advance in 16.16 fixed point format.
     static hb_position_t
     HBGetGlyphHAdvance(hb_font_t *font, void *font_data,
                        hb_codepoint_t glyph, void *user_data);
 
     hb_position_t GetHKerning(uint16_t aFirstGlyph,
                               uint16_t aSecondGlyph) const;
 
+    static hb_script_t
+    GetHBScriptUsedForShaping(int32_t aScript) {
+        // Decide what harfbuzz script code will be used for shaping
+        hb_script_t hbScript;
+        if (aScript <= MOZ_SCRIPT_INHERITED) {
+            // For unresolved "common" or "inherited" runs,
+            // default to Latin for now.
+            hbScript = HB_SCRIPT_LATIN;
+        } else {
+            hbScript =
+                hb_script_t(mozilla::unicode::GetScriptTagForCode(aScript));
+        }
+        return hbScript;
+    }
+
 protected:
     nsresult SetGlyphsFromRun(gfxContext      *aContext,
                               gfxShapedText   *aShapedText,
                               uint32_t         aOffset,
                               uint32_t         aLength,
                               const char16_t *aText,
                               hb_buffer_t     *aBuffer);
 
--- a/gfx/thebes/gfxPangoFonts.cpp
+++ b/gfx/thebes/gfxPangoFonts.cpp
@@ -653,17 +653,23 @@ public:
     static already_AddRefed<gfxFcFont>
     GetOrMakeFont(FcPattern *aRequestedPattern, FcPattern *aFontPattern,
                   const gfxFontStyle *aFontStyle);
 
 #ifdef USE_SKIA
     virtual mozilla::TemporaryRef<mozilla::gfx::GlyphRenderingOptions> GetGlyphRenderingOptions();
 #endif
 
+    // return a cloned font resized and offset to simulate sub/superscript glyphs
+    virtual already_AddRefed<gfxFont>
+    GetSubSuperscriptFont(int32_t aAppUnitsPerDevPixel);
+
 protected:
+    virtual already_AddRefed<gfxFont> MakeScaledFont(gfxFontStyle *aFontStyle,
+                                                     gfxFloat aFontScale);
     virtual already_AddRefed<gfxFont> GetSmallCapsFont();
 
 private:
     gfxFcFont(cairo_scaled_font_t *aCairoFont, gfxFcFontEntry *aFontEntry,
               const gfxFontStyle *aFontStyle);
 
     // key for locating a gfxFcFont corresponding to a cairo_scaled_font
     static cairo_user_data_key_t sGfxFontKey;
@@ -1515,50 +1521,63 @@ gfxFcFont::~gfxFcFont()
 {
     cairo_scaled_font_set_user_data(mScaledFont,
                                     &sGfxFontKey,
                                     nullptr,
                                     nullptr);
 }
 
 already_AddRefed<gfxFont>
-gfxFcFont::GetSmallCapsFont()
+gfxFcFont::GetSubSuperscriptFont(int32_t aAppUnitsPerDevPixel)
 {
     gfxFontStyle style(*GetStyle());
-    style.size *= SMALL_CAPS_SCALE_FACTOR;
-    style.variantCaps = NS_FONT_VARIANT_CAPS_NORMAL;
+    style.AdjustForSubSuperscript(aAppUnitsPerDevPixel);
+    return MakeScaledFont(&style, style.size / GetStyle()->size);
+}
+
+already_AddRefed<gfxFont>
+gfxFcFont::MakeScaledFont(gfxFontStyle *aFontStyle, gfxFloat aScaleFactor)
+{
     gfxFcFontEntry* fe = static_cast<gfxFcFontEntry*>(GetFontEntry());
-    nsRefPtr<gfxFont> font = gfxFontCache::GetCache()->Lookup(fe, &style);
+    nsRefPtr<gfxFont> font = gfxFontCache::GetCache()->Lookup(fe, aFontStyle);
     if (font) {
         return font.forget();
     }
 
     cairo_matrix_t fontMatrix;
     cairo_scaled_font_get_font_matrix(mScaledFont, &fontMatrix);
-    cairo_matrix_scale(&fontMatrix,
-                       SMALL_CAPS_SCALE_FACTOR, SMALL_CAPS_SCALE_FACTOR);
+    cairo_matrix_scale(&fontMatrix, aScaleFactor, aScaleFactor);
 
     cairo_matrix_t ctm;
     cairo_scaled_font_get_ctm(mScaledFont, &ctm);
 
     cairo_font_options_t *options = cairo_font_options_create();
     cairo_scaled_font_get_font_options(mScaledFont, options);
 
-    cairo_scaled_font_t *smallFont =
+    cairo_scaled_font_t *newFont =
         cairo_scaled_font_create(cairo_scaled_font_get_font_face(mScaledFont),
                                  &fontMatrix, &ctm, options);
     cairo_font_options_destroy(options);
 
-    font = new gfxFcFont(smallFont, fe, &style);
+    font = new gfxFcFont(newFont, fe, aFontStyle);
     gfxFontCache::GetCache()->AddNew(font);
-    cairo_scaled_font_destroy(smallFont);
+    cairo_scaled_font_destroy(newFont);
 
     return font.forget();
 }
 
+already_AddRefed<gfxFont>
+gfxFcFont::GetSmallCapsFont()
+{
+    gfxFontStyle style(*GetStyle());
+    style.size *= SMALL_CAPS_SCALE_FACTOR;
+    style.variantCaps = NS_FONT_VARIANT_CAPS_NORMAL;
+    return MakeScaledFont(&style, SMALL_CAPS_SCALE_FACTOR);
+}
+
 /* static */ void
 gfxPangoFontGroup::Shutdown()
 {
     // Resetting gFTLibrary in case this is wanted again after a
     // cairo_debug_reset_static_data.
     gFTLibrary = nullptr;
 }
 
new file mode 100644
--- /dev/null
+++ b/image/src/DynamicImage.cpp
@@ -0,0 +1,355 @@
+/* -*- 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 "DynamicImage.h"
+#include "gfxPlatform.h"
+#include "gfxUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/RefPtr.h"
+#include "Orientation.h"
+
+#include "mozilla/MemoryReporting.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using mozilla::layers::LayerManager;
+using mozilla::layers::ImageContainer;
+
+namespace mozilla {
+namespace image {
+
+// Inherited methods from Image.
+
+nsresult
+DynamicImage::Init(const char* aMimeType, uint32_t aFlags)
+{
+  return NS_OK;
+}
+
+already_AddRefed<imgStatusTracker>
+DynamicImage::GetStatusTracker()
+{
+  return nullptr;
+}
+
+nsIntRect
+DynamicImage::FrameRect(uint32_t aWhichFrame)
+{
+  gfxIntSize size(mDrawable->Size());
+  return nsIntRect(0, 0, size.width, size.height);
+}
+
+uint32_t
+DynamicImage::SizeOfData()
+{
+  // We don't know the answer to this (and the same goes for the other
+  // memory-related methods) since gfxDrawable doesn't expose a way to check.
+  return 0;
+}
+
+size_t
+DynamicImage::HeapSizeOfSourceWithComputedFallback(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+  return 0;
+}
+
+size_t
+DynamicImage::HeapSizeOfDecodedWithComputedFallback(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+  return 0;
+}
+
+size_t
+DynamicImage::NonHeapSizeOfDecoded() const
+{
+  return 0;
+}
+
+size_t
+DynamicImage::OutOfProcessSizeOfDecoded() const
+{
+  return 0;
+}
+
+size_t
+DynamicImage::HeapSizeOfVectorImageDocument(nsACString* aDocURL) const
+{
+  return 0;
+}
+
+void
+DynamicImage::IncrementAnimationConsumers()
+{ }
+
+void
+DynamicImage::DecrementAnimationConsumers()
+{ }
+
+#ifdef DEBUG
+uint32_t
+DynamicImage::GetAnimationConsumers()
+{
+  return 0;
+}
+#endif
+
+nsresult
+DynamicImage::OnImageDataAvailable(nsIRequest* aRequest,
+                                   nsISupports* aContext,
+                                   nsIInputStream* aInStr,
+                                   uint64_t aSourceOffset,
+                                   uint32_t aCount)
+{
+  return NS_OK;
+}
+
+nsresult
+DynamicImage::OnImageDataComplete(nsIRequest* aRequest,
+                                  nsISupports* aContext,
+                                  nsresult aStatus,
+                                  bool aLastPart)
+{
+  return NS_OK;
+}
+
+nsresult
+DynamicImage::OnNewSourceData()
+{
+  return NS_OK;
+}
+
+void
+DynamicImage::SetInnerWindowID(uint64_t aInnerWindowId)
+{ }
+
+uint64_t
+DynamicImage::InnerWindowID() const
+{
+  return 0;
+}
+
+bool
+DynamicImage::HasError()
+{
+  return !mDrawable;
+}
+
+void
+DynamicImage::SetHasError()
+{ }
+
+ImageURL*
+DynamicImage::GetURI()
+{
+  return nullptr;
+}
+
+// Methods inherited from XPCOM interfaces.
+
+NS_IMPL_ISUPPORTS(DynamicImage, imgIContainer)
+
+NS_IMETHODIMP
+DynamicImage::GetWidth(int32_t* aWidth)
+{
+  *aWidth = mDrawable->Size().width;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+DynamicImage::GetHeight(int32_t* aHeight)
+{
+  *aHeight = mDrawable->Size().height;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+DynamicImage::GetIntrinsicSize(nsSize* aSize)
+{
+  gfxIntSize intSize(mDrawable->Size());
+  *aSize = nsSize(intSize.width, intSize.height);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+DynamicImage::GetIntrinsicRatio(nsSize* aSize)
+{
+  gfxIntSize intSize(mDrawable->Size());
+  *aSize = nsSize(intSize.width, intSize.height);
+  return NS_OK;
+}
+
+NS_IMETHODIMP_(Orientation)
+DynamicImage::GetOrientation()
+{
+  return Orientation();
+}
+
+NS_IMETHODIMP
+DynamicImage::GetType(uint16_t* aType)
+{
+  *aType = imgIContainer::TYPE_RASTER;
+  return NS_OK;
+}
+
+NS_IMETHODIMP_(uint16_t)
+DynamicImage::GetType()
+{
+  return imgIContainer::TYPE_RASTER;
+}
+
+NS_IMETHODIMP
+DynamicImage::GetAnimated(bool* aAnimated)
+{
+  *aAnimated = false;
+  return NS_OK;
+}
+
+NS_IMETHODIMP_(TemporaryRef<SourceSurface>)
+DynamicImage::GetFrame(uint32_t aWhichFrame,
+                       uint32_t aFlags)
+{
+  gfxIntSize size(mDrawable->Size());
+
+  RefPtr<DrawTarget> dt = gfxPlatform::GetPlatform()->
+    CreateOffscreenContentDrawTarget(IntSize(size.width, size.height),
+                                     SurfaceFormat::B8G8R8A8);
+  nsRefPtr<gfxContext> context = new gfxContext(dt);
+
+  nsresult rv = Draw(context, GraphicsFilter::FILTER_NEAREST, gfxMatrix(),
+                     gfxRect(0, 0, size.width, size.height),
+                     nsIntRect(0, 0, size.width, size.height),
+                     size, nullptr, aWhichFrame, aFlags);
+
+  NS_ENSURE_SUCCESS(rv, nullptr);
+  return dt->Snapshot();
+}
+
+NS_IMETHODIMP_(bool)
+DynamicImage::FrameIsOpaque(uint32_t aWhichFrame)
+{
+  // XXX(seth): For performance reasons it'd be better to return true here, but
+  // I'm not sure how we can guarantee it for an arbitrary gfxDrawable.
+  return false;
+}
+
+NS_IMETHODIMP
+DynamicImage::GetImageContainer(LayerManager* aManager, ImageContainer** _retval)
+{
+  *_retval = nullptr;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+DynamicImage::Draw(gfxContext* aContext,
+                   GraphicsFilter aFilter,
+                   const gfxMatrix& aUserSpaceToImageSpace,
+                   const gfxRect& aFill,
+                   const nsIntRect& aSubimage,
+                   const nsIntSize& aViewportSize,
+                   const SVGImageContext* aSVGContext,
+                   uint32_t aWhichFrame,
+                   uint32_t aFlags)
+{
+  gfxIntSize drawableSize(mDrawable->Size());
+  gfxRect imageRect(0, 0, drawableSize.width, drawableSize.height);
+  gfxRect sourceRect = aUserSpaceToImageSpace.TransformBounds(aFill);
+
+  gfxUtils::DrawPixelSnapped(aContext, mDrawable, aUserSpaceToImageSpace,
+                             aSubimage, sourceRect, imageRect, aFill,
+                             SurfaceFormat::B8G8R8A8, aFilter, aFlags);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+DynamicImage::RequestDecode()
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+DynamicImage::StartDecoding()
+{
+  return NS_OK;
+}
+
+bool
+DynamicImage::IsDecoded()
+{
+  return true;
+}
+
+NS_IMETHODIMP
+DynamicImage::LockImage()
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+DynamicImage::UnlockImage()
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+DynamicImage::RequestDiscard()
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+DynamicImage::RequestRefresh(const mozilla::TimeStamp& aTime)
+{ }
+
+NS_IMETHODIMP
+DynamicImage::GetAnimationMode(uint16_t* aAnimationMode)
+{
+  *aAnimationMode = kNormalAnimMode;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+DynamicImage::SetAnimationMode(uint16_t aAnimationMode)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+DynamicImage::ResetAnimation()
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP_(float)
+DynamicImage::GetFrameIndex(uint32_t aWhichFrame)
+{
+  return 0;
+}
+
+NS_IMETHODIMP_(int32_t)
+DynamicImage::GetFirstFrameDelay()
+{
+  return 0;
+}
+
+NS_IMETHODIMP_(void)
+DynamicImage::SetAnimationStartTime(const mozilla::TimeStamp& aTime)
+{ }
+
+NS_IMETHODIMP_(nsIntRect)
+DynamicImage::GetImageSpaceInvalidationRect(const nsIntRect& aRect)
+{
+  return aRect;
+}
+
+already_AddRefed<imgIContainer>
+DynamicImage::Unwrap()
+{
+  nsCOMPtr<imgIContainer> self(this);
+  return self.forget();
+}
+
+} // namespace image
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/image/src/DynamicImage.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_IMAGELIB_DYNAMICIMAGE_H_
+#define MOZILLA_IMAGELIB_DYNAMICIMAGE_H_
+
+#include "mozilla/MemoryReporting.h"
+#include "gfxDrawable.h"
+#include "Image.h"
+
+namespace mozilla {
+namespace image {
+
+/**
+ 
+ * An Image that is dynamically created. The content of the image is provided by
+ * a gfxDrawable. It's anticipated that most uses of DynamicImage will be
+ * ephemeral.
+ */
+class DynamicImage : public Image
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_IMGICONTAINER
+
+  DynamicImage(gfxDrawable* aDrawable)
+    : mDrawable(aDrawable)
+  {
+    MOZ_ASSERT(aDrawable, "Must have a gfxDrawable to wrap");
+  }
+
+  // Inherited methods from Image.
+  virtual nsresult Init(const char* aMimeType, uint32_t aFlags) MOZ_OVERRIDE;
+
+  virtual already_AddRefed<imgStatusTracker> GetStatusTracker() MOZ_OVERRIDE;
+  virtual nsIntRect FrameRect(uint32_t aWhichFrame) MOZ_OVERRIDE;
+
+  virtual uint32_t SizeOfData() MOZ_OVERRIDE;
+  virtual size_t HeapSizeOfSourceWithComputedFallback(mozilla::MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+  virtual size_t HeapSizeOfDecodedWithComputedFallback(mozilla::MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+  virtual size_t NonHeapSizeOfDecoded() const MOZ_OVERRIDE;
+  virtual size_t OutOfProcessSizeOfDecoded() const MOZ_OVERRIDE;
+  virtual size_t HeapSizeOfVectorImageDocument(nsACString* aDocURL = nullptr) const MOZ_OVERRIDE;
+
+  virtual void IncrementAnimationConsumers() MOZ_OVERRIDE;
+  virtual void DecrementAnimationConsumers() MOZ_OVERRIDE;
+#ifdef DEBUG
+  virtual uint32_t GetAnimationConsumers() MOZ_OVERRIDE;
+#endif
+
+  virtual nsresult OnImageDataAvailable(nsIRequest* aRequest,
+                                        nsISupports* aContext,
+                                        nsIInputStream* aInStr,
+                                        uint64_t aSourceOffset,
+                                        uint32_t aCount) MOZ_OVERRIDE;
+  virtual nsresult OnImageDataComplete(nsIRequest* aRequest,
+                                       nsISupports* aContext,
+                                       nsresult aStatus,
+                                       bool aLastPart) MOZ_OVERRIDE;
+  virtual nsresult OnNewSourceData() MOZ_OVERRIDE;
+
+  virtual void SetInnerWindowID(uint64_t aInnerWindowId) MOZ_OVERRIDE;
+  virtual uint64_t InnerWindowID() const MOZ_OVERRIDE;
+
+  virtual bool HasError() MOZ_OVERRIDE;
+  virtual void SetHasError() MOZ_OVERRIDE;
+
+  virtual ImageURL* GetURI() MOZ_OVERRIDE;
+
+private:
+  virtual ~DynamicImage() { }
+
+  nsRefPtr<gfxDrawable> mDrawable;
+};
+
+} // namespace image
+} // namespace mozilla
+
+#endif // MOZILLA_IMAGELIB_DYNAMICIMAGE_H_
--- a/image/src/ImageOps.cpp
+++ b/image/src/ImageOps.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "imgIContainer.h"
 #include "ClippedImage.h"
+#include "DynamicImage.h"
 #include "FrozenImage.h"
 #include "OrientedImage.h"
 #include "Image.h"
 
 #include "ImageOps.h"
 
 namespace mozilla {
 namespace image {
@@ -55,10 +56,17 @@ ImageOps::Orient(Image* aImage, Orientat
 /* static */ already_AddRefed<imgIContainer>
 ImageOps::Orient(imgIContainer* aImage, Orientation aOrientation)
 {
   nsCOMPtr<imgIContainer> orientedImage =
     new OrientedImage(static_cast<Image*>(aImage), aOrientation);
   return orientedImage.forget();
 }
 
+/* static */ already_AddRefed<imgIContainer>
+ImageOps::CreateFromDrawable(gfxDrawable* aDrawable)
+{
+  nsCOMPtr<imgIContainer> drawableImage = new DynamicImage(aDrawable);
+  return drawableImage.forget();
+}
+
 } // namespace image
 } // namespace mozilla
--- a/image/src/ImageOps.h
+++ b/image/src/ImageOps.h
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef MOZILLA_IMAGELIB_IMAGEOPS_H_
 #define MOZILLA_IMAGELIB_IMAGEOPS_H_
 
 #include "nsCOMPtr.h"
 
+class gfxDrawable;
 class imgIContainer;
 struct nsIntRect;
 
 namespace mozilla {
 namespace image {
 
 class Image;
 struct Orientation;
@@ -44,16 +45,23 @@ public:
    * the specified orientation.
    *
    * @param aImage         The existing image.
    * @param aOrientation   The desired orientation.
    */
   static already_AddRefed<Image> Orient(Image* aImage, Orientation aOrientation);
   static already_AddRefed<imgIContainer> Orient(imgIContainer* aImage, Orientation aOrientation);
 
+  /**
+   * Creates an image from a gfxDrawable.
+   *
+   * @param aDrawable      The gfxDrawable.
+   */
+  static already_AddRefed<imgIContainer> CreateFromDrawable(gfxDrawable* aDrawable);
+
 private:
   // This is a static utility class, so disallow instantiation.
   virtual ~ImageOps() = 0;
 };
 
 } // namespace image
 } // namespace mozilla
 
--- a/image/src/moz.build
+++ b/image/src/moz.build
@@ -12,16 +12,17 @@ EXPORTS += [
     'Orientation.h',
     'SurfaceCache.h'
 ]
 
 UNIFIED_SOURCES += [
     'ClippedImage.cpp',
     'Decoder.cpp',
     'DiscardTracker.cpp',
+    'DynamicImage.cpp',
     'FrameAnimator.cpp',
     'FrameBlender.cpp',
     'FrameSequence.cpp',
     'FrozenImage.cpp',
     'Image.cpp',
     'ImageFactory.cpp',
     'ImageMetadata.cpp',
     'ImageOps.cpp',
--- a/ipc/glue/GeckoChildProcessHost.cpp
+++ b/ipc/glue/GeckoChildProcessHost.cpp
@@ -236,21 +236,28 @@ uint32_t GeckoChildProcessHost::GetSuppo
 #endif
 
   return base::GetCurrentProcessArchitecture();
 }
 
 void
 GeckoChildProcessHost::PrepareLaunch()
 {
+#ifndef GMP_CRASHREPORTER_READY
+// See bug 1041525
+  if (mProcessType != GeckoProcessType_GMPlugin) {
+#endif
 #ifdef MOZ_CRASHREPORTER
   if (CrashReporter::GetEnabled()) {
     CrashReporter::OOPInit();
   }
 #endif
+#ifndef GMP_CRASHREPORTER_READY
+  }
+#endif
 
 #ifdef XP_WIN
   if (mProcessType == GeckoProcessType_Plugin) {
     InitWindowsGroupID();
   }
 #endif
 }
 
--- a/js/public/CallArgs.h
+++ b/js/public/CallArgs.h
@@ -357,16 +357,23 @@ class MOZ_STACK_CLASS CallArgs : public 
     static CallArgs create(unsigned argc, Value *argv) {
         CallArgs args;
         args.clearUsedRval();
         args.argv_ = argv;
         args.argc_ = argc;
         return args;
     }
 
+  public:
+    /*
+     * Returns true if there are at least |required| arguments passed in. If
+     * false, it reports an error message on the context.
+     */
+    bool requireAtLeast(JSContext *cx, const char *fnname, unsigned required);
+
 };
 
 MOZ_ALWAYS_INLINE CallArgs
 CallArgsFromVp(unsigned argc, Value *vp)
 {
     return CallArgs::create(argc, vp + 2);
 }
 
new file mode 100644
--- /dev/null
+++ b/js/public/ProfilingFrameIterator.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * 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 js_ProfilingFrameIterator_h
+#define js_ProfilingFrameIterator_h
+
+#include "mozilla/Alignment.h"
+
+#include <stdint.h>
+
+#include "js/Utility.h"
+
+class JSAtom;
+struct JSRuntime;
+namespace js { class AsmJSActivation; class AsmJSProfilingFrameIterator; }
+
+namespace JS {
+
+// This iterator can be used to walk the stack of a thread suspended at an
+// arbitrary pc. To provide acurate results, profiling must have been enabled
+// (via EnableRuntimeProfilingStack) before executing the callstack being
+// unwound.
+class JS_PUBLIC_API(ProfilingFrameIterator)
+{
+    js::AsmJSActivation *activation_;
+
+    static const unsigned StorageSpace = 5 * sizeof(void*);
+    mozilla::AlignedStorage<StorageSpace> storage_;
+    js::AsmJSProfilingFrameIterator &iter() {
+        JS_ASSERT(!done());
+        return *reinterpret_cast<js::AsmJSProfilingFrameIterator*>(storage_.addr());
+    }
+    const js::AsmJSProfilingFrameIterator &iter() const {
+        JS_ASSERT(!done());
+        return *reinterpret_cast<const js::AsmJSProfilingFrameIterator*>(storage_.addr());
+    }
+
+    void settle();
+
+  public:
+    struct RegisterState
+    {
+        void *pc;
+        void *sp;
+#if defined(JS_CODEGEN_ARM)
+        void *lr;
+#endif
+    };
+
+    ProfilingFrameIterator(JSRuntime *rt, const RegisterState &state);
+    ~ProfilingFrameIterator();
+    void operator++();
+    bool done() const { return !activation_; }
+
+    enum Kind {
+        Function,
+        AsmJSTrampoline,
+        CppFunction
+    };
+    Kind kind() const;
+
+    // Methods available if kind() == Function:
+    JSAtom *functionDisplayAtom() const;
+    const char *functionFilename() const;
+
+    // Methods available if kind() != Function
+    const char *nonFunctionDescription() const;
+};
+
+} // namespace JS
+
+#endif  /* js_ProfilingFrameIterator_h */
--- a/js/src/assembler/assembler/X86Assembler.h
+++ b/js/src/assembler/assembler/X86Assembler.h
@@ -446,16 +446,23 @@ public:
     bool oom() const { return m_formatter.oom(); }
 
     void nop()
     {
         spew("nop");
         m_formatter.oneByteOp(OP_NOP);
     }
 
+    void twoByteNop()
+    {
+        spew("nop (2 byte)");
+        m_formatter.prefix(PRE_OPERAND_SIZE);
+        m_formatter.oneByteOp(OP_NOP);
+    }
+
     // Stack operations:
 
     void push_r(RegisterID reg)
     {
         spew("push       %s", nameIReg(reg));
         m_formatter.oneByteOp(OP_PUSH_EAX, reg);
     }
 
--- a/js/src/devtools/rootAnalysis/expect.b2g.json
+++ b/js/src/devtools/rootAnalysis/expect.b2g.json
@@ -1,3 +1,3 @@
 {
-  "expect-hazards": 4
+  "expect-hazards": 0
 }
new file mode 100644
--- /dev/null
+++ b/js/src/doc/Debugger/Debugger.Memory.md
@@ -0,0 +1,23 @@
+# `Debugger.Memory`
+
+If `dbg` is a `Debugger` instance, then `dbg.memory` is an instance of
+`Debugger.Memory` whose methods and accessors operate on `dbg`. This class
+exists only to hold member functions and accessors related to memory analysis,
+keeping them separate from other `Debugger` facilities.
+
+## Accessor Properties of the `Debugger.Memory.prototype` Object
+
+<code id="trackingallocationsites">trackingAllocationSites</code>
+:   A boolean value indicating whether this `Debugger.Memory` instance is
+    capturing the JavaScript execution stack when each Object is allocated. This
+    accessor property has both a getter and setter: assigning to it enables or
+    disables the allocation site tracking. Reading the accessor produces `true`
+    if the Debugger is capturing stacks for Object allocations, and `false`
+    otherwise. Allocation site tracking is initially disabled in a new Debugger.
+
+    Assignment is fallible: if the Debugger cannot track allocation sites, it
+    throws an `Error` instance.
+
+    You can retrieve the allocation site for a given object with the
+    [`Debugger.Object.prototype.allocationSite`][allocation-site] accessor
+    property.
--- a/js/src/doc/Debugger/Debugger.Object.md
+++ b/js/src/doc/Debugger/Debugger.Object.md
@@ -194,16 +194,21 @@ from its prototype:
 
 `global`
 :   A `Debugger.Object` instance referring to the global object in whose
     scope the referent was allocated. This does not unwrap cross-compartment
     wrappers: if the referent is a wrapper, the result refers to the
     wrapper's global, not the wrapped object's global. The result refers to
     the global directly, not via a wrapper.
 
+<code id="allocationsite">allocationSite</code>
+:   If [object allocation site tracking][tracking-allocs] was enabled when this
+    `Debugger.Object`'s referent was allocated, return the
+    [JavaScript execution stack][saved-frame] captured at the time of the
+    allocation. Otherwise, return `null`.
 
 
 ## Function Properties of the Debugger.Object prototype
 
 The functions described below may only be called with a `this` value
 referring to a `Debugger.Object` instance; they may not be used as methods
 of other kinds of objects. The descriptions use "referent" to mean "the
 referent of this `Debugger.Object` instance".
--- a/js/src/doc/Debugger/Debugger.md
+++ b/js/src/doc/Debugger/Debugger.md
@@ -245,16 +245,19 @@ other kinds of objects.
     the designated global object.
 
     The `Debugger` instance does not hold a strong reference to its
     debuggee globals: if a debuggee global is not otherwise reachable, then
     it is dropped from the `Debugger`'s set of debuggees. (Naturally, the
     [`Debugger.Object`][object] instance this method returns does hold a strong
     reference to the added global.)
 
+    If this debugger is [tracking allocation sites][tracking-allocs] and cannot
+    track allocation sites for <i>global</i>, this method throws an `Error`.
+
 <code>removeDebuggee(<i>global</i>)</code>
 :   Remove the global object designated by <i>global</i> from this
     `Debugger` instance's set of debuggees. Return `undefined`.
 
     This method interprets <i>global</i> using the same rules that
     [`addDebuggee`][add] does.
 
 <code>hasDebuggee(<i>global</i>)</code>
--- a/js/src/doc/Debugger/config.sh
+++ b/js/src/doc/Debugger/config.sh
@@ -24,24 +24,30 @@ markdown Debugger.Frame.md Debugger-API/
   label 'frame'                                 "Debugger.Frame"
   label 'vf'            '#visible-frames'       "Debugger.Frame: Visible Frames"
   label 'generator'     '#generator-frames'     "Debugger.Frame: Generator Frames"
   label 'inv fr'        '#invf'                 "Debugger.Frame: Invocation Frames"
   label 'fr eval'       '#eval'                 "Debugger.Frame: Eval"
 
 markdown Debugger.Object.md Debugger-API/Debugger.Object
   label 'object'                                "Debugger.Object"
+  label 'allocation-site' '#allocationsite'     "Debugger.Object: allocationSite"
 
 markdown Debugger.Script.md Debugger-API/Debugger.Script
   label 'script'                                "Debugger.Script"
 
 markdown Debugger.Source.md Debugger-API/Debugger.Source
   label 'source'                                "Debugger.Source"
 
+markdown Debugger.Memory.md Debugger-API/Debugger.Memory
+  label 'memory'                                "Debugger.Memory"
+  label 'tracking-allocs' '#trackingallocationsites' "Debugger.Memory: trackingAllocationSites"
+
 # Images:
 RBASE=https://mdn.mozillademos.org/files
 resource 'img-shadows'            shadows.svg                        $RBASE/7225/shadows.svg
 resource 'img-chrome-pref'        enable-chrome-devtools.png         $RBASE/7233/enable-chrome-devtools.png
 resource 'img-scratchpad-browser' scratchpad-browser-environment.png $RBASE/7229/scratchpad-browser-environment.png
 resource 'img-example-alert'      debugger-alert.png                 $RBASE/7231/debugger-alert.png
 
 # External links:
 absolute-label 'protocol' https://wiki.mozilla.org/Remote_Debugging_Protocol "Remote Debugging Protocol"
+absolute-label 'saved-frame' https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/SavedFrame "SavedFrame"
--- a/js/src/doc/README.md
+++ b/js/src/doc/README.md
@@ -4,16 +4,19 @@ SpiderMonkey in-tree documentation
 This directory contains documentation for selected parts of SpiderMonkey. The
 docs are published on the [Mozilla Developer Network][mdn], but this is the
 authoritative copy; the MDN pages are updated from these files by a script. At
 the moment, we have:
 
 - `js/src/doc/Debugger`, SpiderMonkey's JavaScript debugging API, commonly
   known as `Debugger`.
 
+- `js/src/doc/SavedFrame`, SpiderMonkey's compact representation for captured
+  call stacks.
+
 and that's it.
 
 To format the documentation, you'll need to install [Pandoc][], a
 documentation format conversion program. Pandoc can convert Markdown text,
 which is pleasant to read and write, into HTML, which is what MDN expects.
 
 [mdn]: http://developer.mozilla.org "Mozilla Developer Network"
 [Pandoc]: http://www.johnmacfarlane.net/pandoc/installing.html
new file mode 100644
--- /dev/null
+++ b/js/src/doc/SavedFrame/SavedFrame.md
@@ -0,0 +1,33 @@
+# `SavedFrame`
+
+A `SavedFrame` instance is a singly linked list of stack frames. It represents a
+JavaScript call stack at a past moment of execution. Younger frames hold a
+reference to the frames that invoked them. The older tails are shared across
+many younger frames.
+
+
+## Accessor Properties of the `SavedFrame.prototype` Object
+
+`source`
+:   The source URL for this stack frame, as a string.
+
+`line`
+:   The line number for this stack frame.
+
+`column`
+:   The column number for this stack frame.
+
+`functionDisplayName`
+:   Either SpiderMonkey's inferred name for this stack frame's function, or
+    `null`.
+
+`parent`
+:   Either this stack frame's parent stack frame (the next older frame), or
+    `null` if this is the oldest frame in the captured stack.
+
+
+## Function Properties of the `SavedFrame.prototype` Object
+
+`toString`
+:   Return this frame and its parents formatted as a human readable stack trace
+    string.
new file mode 100644
--- /dev/null
+++ b/js/src/doc/SavedFrame/config.sh
@@ -0,0 +1,6 @@
+### Description of SavedFrame docs: how to format, where to install.
+### See js/src/doc/README.md for a description.
+
+base-url https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/
+
+markdown SavedFrame.md SavedFrame
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/asm.js/testProfiling.js
@@ -0,0 +1,131 @@
+load(libdir + "asm.js");
+
+// Single-step profiling currently only works in the ARM simulator
+if (!getBuildConfiguration()["arm-simulator"])
+    quit();
+
+// Test profiling enablement while asm.js is running.
+var stacks;
+var ffi = function(enable) {
+    if (enable == +1)
+        enableSPSProfiling();
+    if (enable == -1)
+        disableSPSProfiling();
+    enableSingleStepProfiling();
+    stacks = disableSingleStepProfiling();
+}
+var f = asmLink(asmCompile('global','ffis',USE_ASM + "var ffi=ffis.ffi; function g(i) { i=i|0; ffi(i|0) } function f(i) { i=i|0; g(i|0) } return f"), null, {ffi});
+f(0);
+assertEq(String(stacks), "");
+f(+1);
+assertEq(String(stacks), "");
+f(0);
+assertEq(String(stacks), "*gf*");
+f(-1);
+assertEq(String(stacks), "*gf*");
+f(0);
+assertEq(String(stacks), "");
+
+// Enable profiling for the rest of the tests.
+enableSPSProfiling();
+
+var f = asmLink(asmCompile(USE_ASM + "function f() { return 42 } return f"));
+enableSingleStepProfiling();
+assertEq(f(), 42);
+var stacks = disableSingleStepProfiling();
+assertEq(String(stacks), ",*,f*,*,");
+
+var f = asmLink(asmCompile(USE_ASM + "function g(i) { i=i|0; return (i+1)|0 } function f() { return g(42)|0 } return f"));
+enableSingleStepProfiling();
+assertEq(f(), 43);
+var stacks = disableSingleStepProfiling();
+assertEq(String(stacks), ",*,f*,gf*,f*,*,");
+
+var f = asmLink(asmCompile(USE_ASM + "function g1() { return 1 } function g2() { return 2 } function f(i) { i=i|0; return TBL[i&1]()|0 } var TBL=[g1,g2]; return f"));
+enableSingleStepProfiling();
+assertEq(f(0), 1);
+assertEq(f(1), 2);
+var stacks = disableSingleStepProfiling();
+assertEq(String(stacks), ",*,f*,g1f*,f*,*,,*,f*,g2f*,f*,*,");
+
+function testBuiltinD2D(name) {
+    var f = asmLink(asmCompile('g', USE_ASM + "var fun=g.Math." + name + "; function f(d) { d=+d; return +fun(d) } return f"), this);
+    enableSingleStepProfiling();
+    assertEq(f(.1), eval("Math." + name + "(.1)"));
+    var stacks = disableSingleStepProfiling();
+    assertEq(String(stacks), ",*,f*,Math." + name + "f*,f*,*,");
+}
+for (name of ['sin', 'cos', 'tan', 'asin', 'acos', 'atan', 'ceil', 'floor', 'exp', 'log'])
+    testBuiltinD2D(name);
+function testBuiltinF2F(name) {
+    var f = asmLink(asmCompile('g', USE_ASM + "var tof=g.Math.fround; var fun=g.Math." + name + "; function f(d) { d=tof(d); return tof(fun(d)) } return f"), this);
+    enableSingleStepProfiling();
+    assertEq(f(.1), eval("Math.fround(Math." + name + "(Math.fround(.1)))"));
+    var stacks = disableSingleStepProfiling();
+    assertEq(String(stacks), ",*,f*,Math." + name + "f*,f*,*,");
+}
+for (name of ['ceil', 'floor'])
+    testBuiltinF2F(name);
+function testBuiltinDD2D(name) {
+    var f = asmLink(asmCompile('g', USE_ASM + "var fun=g.Math." + name + "; function f(d, e) { d=+d; e=+e; return +fun(d,e) } return f"), this);
+    enableSingleStepProfiling();
+    assertEq(f(.1, .2), eval("Math." + name + "(.1, .2)"));
+    var stacks = disableSingleStepProfiling();
+    assertEq(String(stacks), ",*,f*,Math." + name + "f*,f*,*,");
+}
+for (name of ['atan2', 'pow'])
+    testBuiltinDD2D(name);
+
+// FFI tests:
+setJitCompilerOption("ion.usecount.trigger", 10);
+setJitCompilerOption("baseline.usecount.trigger", 0);
+setJitCompilerOption("offthread-compilation.enable", 0);
+
+var ffi1 = function() { return 10 }
+var ffi2 = function() { return 73 }
+var f = asmLink(asmCompile('g','ffis', USE_ASM + "var ffi1=ffis.ffi1, ffi2=ffis.ffi2; function f() { return ((ffi1()|0) + (ffi2()|0))|0 } return f"), null, {ffi1,ffi2});
+// Interpreter FFI exit
+enableSingleStepProfiling();
+assertEq(f(), 83);
+var stacks = disableSingleStepProfiling();
+assertEq(String(stacks), ",*,f*,*f*,f*,*f*,f*,*,");
+// Ion FFI exit
+for (var i = 0; i < 20; i++)
+    assertEq(f(), 83);
+enableSingleStepProfiling();
+assertEq(f(), 83);
+var stacks = disableSingleStepProfiling();
+assertEq(String(stacks), ",*,f*,*f*,f*,*f*,f*,*,");
+
+var ffi1 = function() { return 15 }
+var ffi2 = function() { return f2() + 17 }
+var {f1,f2} = asmLink(asmCompile('g','ffis', USE_ASM + "var ffi1=ffis.ffi1, ffi2=ffis.ffi2; function f2() { return ffi1()|0 } function f1() { return ffi2()|0 } return {f1:f1, f2:f2}"), null, {ffi1, ffi2});
+// Interpreter FFI exit
+enableSingleStepProfiling();
+assertEq(f1(), 32);
+var stacks = disableSingleStepProfiling();
+assertEq(String(stacks), ",*,f1*,*f1*,**f1*,f2**f1*,*f2**f1*,f2**f1*,**f1*,*f1*,f1*,*,");
+// Ion FFI exit
+for (var i = 0; i < 20; i++)
+    assertEq(f1(), 32);
+enableSingleStepProfiling();
+assertEq(f1(), 32);
+var stacks = disableSingleStepProfiling();
+assertEq(String(stacks), ",*,f1*,*f1*,**f1*,f2**f1*,*f2**f1*,f2**f1*,**f1*,*f1*,f1*,*,");
+
+// This takes forever to run.
+// Stack-overflow exit test
+//var limit = -1;
+//var maxct = 0;
+//function ffi(ct) { if (ct == limit) { enableSingleStepProfiling(); print("enabled"); } maxct = ct; }
+//var f = asmLink(asmCompile('g', 'ffis',USE_ASM + "var ffi=ffis.ffi; var ct=0; function rec(){ ct=(ct+1)|0; ffi(ct|0); rec() } function f() { ct=0; rec() } return f"), null, {ffi});
+//// First find the stack limit:
+//var caught = false;
+//try { f() } catch (e) { caught = true; assertEq(String(e).indexOf("too much recursion") >= 0, true) }
+//assertEq(caught, true);
+//limit = maxct;
+//print("Setting limit");
+//var caught = false;
+//try { f() } catch (e) { caught = true; print("caught"); assertEq(String(e).indexOf("too much recursion") >= 0, true) }
+//var stacks = disableSingleStepProfiling();
+//assertEq(String(stacks).indexOf("rec") >= 0, true);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/asm.js/testTimeout5.js
@@ -0,0 +1,12 @@
+// |jit-test| exitstatus: 6;
+
+load(libdir + "asm.js");
+
+enableSPSProfiling();
+
+var f = asmLink(asmCompile('glob', 'ffis', 'buf', USE_ASM + "function f() { var i=0; while (1) { i=(i+1)|0 } } return f"));
+timeout(1);
+if (getBuildConfiguration()["arm-simulator"])
+    enableSingleStepProfiling();
+f();
+assertEq(true, false);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/asm.js/testTimeout6.js
@@ -0,0 +1,12 @@
+// |jit-test| exitstatus: 6;
+
+load(libdir + "asm.js");
+
+enableSPSProfiling();
+
+var f = asmLink(asmCompile('glob', 'ffis', 'buf', USE_ASM + "function g() { var i=0; while (1) { i=(i+1)|0 } } function f() { g() } return f"));
+timeout(1);
+if (getBuildConfiguration()["arm-simulator"])
+    enableSingleStepProfiling();
+f();
+assertEq(true, false);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-trackingAllocationSites-01.js
@@ -0,0 +1,37 @@
+// Test that we can track allocation sites by setting
+// Debugger.Memory.prototype.trackingAllocationSites to true and then get the
+// allocation site via Debugger.Object.prototype.allocationSite.
+
+const root = newGlobal();
+
+const dbg = new Debugger();
+const wrappedRoot = dbg.addDebuggee(root);
+
+assertEq(dbg.memory.trackingAllocationSites, false);
+dbg.memory.trackingAllocationSites = true;
+assertEq(dbg.memory.trackingAllocationSites, true);
+
+root.eval("(" + function immediate() {
+  this.tests = [
+    { name: "object literal",  object: ({}),                  line: Error().lineNumber },
+    { name: "array literal",   object: [],                    line: Error().lineNumber },
+    { name: "regexp literal",  object: /(two|2)\s*problems/,  line: Error().lineNumber },
+    { name: "new constructor", object: new function Ctor(){}, line: Error().lineNumber },
+    { name: "new Object",      object: new Object(),          line: Error().lineNumber },
+    { name: "new Array",       object: new Array(),           line: Error().lineNumber },
+    { name: "new Date",        object: new Date(),            line: Error().lineNumber }
+  ];
+} + "());");
+
+dbg.memory.trackingAllocationSites = false;
+assertEq(dbg.memory.trackingAllocationSites, false);
+
+for (let { name, object, line } of root.tests) {
+  print("Entering test: " + name);
+
+  let wrappedObject = wrappedRoot.makeDebuggeeValue(object);
+  let allocationSite = wrappedObject.allocationSite;
+  print("Allocation site: " + allocationSite);
+
+  assertEq(allocationSite.line, line);
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-trackingAllocationSites-02.js
@@ -0,0 +1,19 @@
+// Test that we don't get allocation sites when nobody has asked for them.
+
+const root = newGlobal();
+
+const dbg = new Debugger();
+const wrappedRoot = dbg.addDebuggee(root);
+
+dbg.memory.trackingAllocationSites = true;
+root.eval("this.obj = {};");
+dbg.memory.trackingAllocationSites = false;
+root.eval("this.obj2 = {};");
+
+let wrappedObj = wrappedRoot.makeDebuggeeValue(root.obj);
+let allocationSite = wrappedObj.allocationSite;
+assertEq(allocationSite != null && typeof allocationSite == "object", true);
+
+let wrappedObj2 = wrappedRoot.makeDebuggeeValue(root.obj2);
+let allocationSite2 = wrappedObj2.allocationSite;
+assertEq(allocationSite2, null);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-trackingAllocationSites-03.js
@@ -0,0 +1,64 @@
+// Test that multiple Debuggers behave reasonably. Since we're not keeping a
+// per-compartment count of how many Debuggers have requested allocation
+// tracking, assert that attempts to request allocation tracking from multiple
+// debuggers throws.
+
+load(libdir + "asserts.js");
+
+let root1 = newGlobal();
+let root2 = newGlobal();
+
+let dbg1 = new Debugger();
+let dbg2 = new Debugger();
+
+let d1r1 = dbg1.addDebuggee(root1);
+let d2r1 = dbg2.addDebuggee(root1);
+
+let wrappedObj, allocationSite;
+
+function isTrackingAllocations(global, dbgObj) {
+  const site = dbgObj.makeDebuggeeValue(global.eval("({})")).allocationSite;
+  if (site) {
+    assertEq(typeof site, "object");
+  }
+  return !!site;
+}
+
+// Can't track allocations if a different debugger is already tracking them.
+dbg1.memory.trackingAllocationSites = true;
+assertThrowsInstanceOf(() => dbg2.memory.trackingAllocationSites = true,
+                       Error);
+
+// Removing root as a debuggee from dbg1 should disable the allocation hook.
+dbg1.removeDebuggee(root1);
+assertEq(isTrackingAllocations(root1, d1r1), false);
+
+// Tracking allocations in dbg2 should work now that dbg1 isn't debugging root1.
+dbg2.memory.trackingAllocationSites = true;
+assertEq(isTrackingAllocations(root1, d2r1), true);
+
+// Adding root back as a debuggee in dbg1 should fail now because it will
+// attempt to track allocations in root, but dbg2 is already doing that.
+assertThrowsInstanceOf(() => dbg1.addDebuggee(root1),
+                       Error);
+assertEq(dbg1.hasDebuggee(root1), false);
+
+// Adding a new debuggee to a debugger that is tracking allocations should
+// enable the hook for the new debuggee.
+dbg2.removeDebuggee(root1);
+d1r1 = dbg1.addDebuggee(root1);
+assertEq(isTrackingAllocations(root1, d1r1), true);
+
+// Setting trackingAllocationSites to true should throw if the debugger cannot
+// install the allocation hooks for *every* debuggee.
+dbg1.memory.trackingAllocationSites = true;
+dbg1.addDebuggee(root1);
+dbg2.memory.trackingAllocationSites = false;
+let d2r2 = dbg2.addDebuggee(root2);
+dbg2.addDebuggee(root1);
+assertThrowsInstanceOf(() => dbg2.memory.trackingAllocationSites = true,
+                       Error);
+
+// And after it throws, its trackingAllocationSites accessor should reflect that
+// allocation site tracking is still disabled in that Debugger.
+assertEq(isTrackingAllocations(root2, d2r2), false);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/bug1006899.js
@@ -0,0 +1,16 @@
+
+this.__defineGetter__("x",
+  function() {
+    return this;
+  }
+);
+function callback(obj) {}
+setObjectMetadataCallback(callback);
+evaluate("\
+var { ArrayType, StructType, uint32 } = TypedObject;\
+  var L = 1024;\
+  var Matrix = uint32.array(L, 2);\
+  var matrix = new Matrix();\
+  for (var i = 0; i < L; i++)\
+    matrix[i][0] = x;\
+", { compileAndGo : true });
--- a/js/src/jit-test/tests/latin1/asm.js
+++ b/js/src/jit-test/tests/latin1/asm.js
@@ -1,17 +1,16 @@
 load(libdir + "asm.js");
 
 if (!isAsmJSCompilationAvailable() || !isCachingEnabled())
     quit();
 
 // Test Latin1 and TwoByte PropertyName serialization.
 
 // Latin1
-toLatin1("funName"); // Latin1 atom.
 var body1 = "'use asm'; function funName() { return 42 } return funName";
 var m = new Function(body1);
 assertEq(isAsmJSModule(m), true);
 assertEq(m()(), 42);
 var m = new Function(body1);
 assertEq(isAsmJSModuleLoadedFromCache(m), true);
 assertEq(m()(), 42);
 
--- a/js/src/jit-test/tests/latin1/assorted.js
+++ b/js/src/jit-test/tests/latin1/assorted.js
@@ -1,11 +1,12 @@
 // Reflect.parse Latin1
-var ast = Reflect.parse(toLatin1("function f() { return 3; }"));
+var ast = Reflect.parse("function f() { return 3; }");
 assertEq(ast.body[0].id.name, "f");
+assertEq(isLatin1(ast.body[0].id.name), true);
 
 // Reflect.parse TwoByte
 var ast = Reflect.parse("function f\u1200() { return 3; }");
 assertEq(ast.body[0].id.name, "f\u1200");
 
 // obj.toSource Latin1
 var o = {};
 Object.defineProperty(o, "prop", {get: function() { return 1; },
@@ -23,15 +24,14 @@ var ff = function() { return 10; };
 ff.toSource = function() { return "((11))"; }
 Object.defineProperty(o, "prop", {get: ff, set: ff, enumerable: true});
 assertEq(o.toSource(), "({prop:((11)), prop:((11))})");
 
 // XDR
 load(libdir + 'bytecode-cache.js');
 
 // Latin1 string constant
-toLatin1("string123");
 test = "'string123';";
 evalWithCache(test, { assertEqBytecode: true, assertEqResult : true });
 
 // TwoByte string constant
 test = "'string\u1234';";
 evalWithCache(test, { assertEqBytecode: true, assertEqResult : true });
--- a/js/src/jit-test/tests/latin1/basic.js
+++ b/js/src/jit-test/tests/latin1/basic.js
@@ -1,11 +1,11 @@
 assertEq(isLatin1("Foo123\u1200"), false);
 
-s = toLatin1("Foo123");
+s = "Foo123";
 assertEq(isLatin1(s), true);
 
 function testEq(s) {
     assertEq(isLatin1(s), true);
     assertEq(s === "foo02", false);
     assertEq(s == "foo02", false);
 
     // Non-atomized to force char comparison.
@@ -18,38 +18,37 @@ function testEq(s) {
 
     nonAtomized = "\u1234foo02".substr(1);
     assertEq(isLatin1(nonAtomized), false);
     assertEq(s === nonAtomized, false);
     assertEq(nonAtomized == s, false);
 }
 
 s = "foo01\u00c7";
-s = toLatin1(s);
 testEq(s);
 testEq(s);
 
 function testConcat() {
     function concat(s1, s2) {
 	return s1 + s2;
     }
 
     // Following tests create fat inline strings.
     assertEq(concat("abc", "def"), "abcdef");
-    var s1 = toLatin1("ABC");
-    var s2 = toLatin1("DEF");
+    var s1 = "ABC";
+    var s2 = "DEF";
     assertEq(concat(s1, s2), "ABCDEF");
     assertEq(concat(s1, "GHI\u0580"), "ABCGHI\u0580");
     assertEq(concat("GHI\u0580", s2), "GHI\u0580DEF");
     assertEq(concat(concat("GHI\u0580", s2), s1), "GHI\u0580DEFABC");
     assertEq(isLatin1(s1), true);
     assertEq(isLatin1(s2), true);
 
     // Create a Latin1 rope.
-    var s3 = toLatin1("0123456789012345678901234567890123456789");
+    var s3 = "0123456789012345678901234567890123456789";
     var rope = concat(s1, s3);
     assertEq(isLatin1(rope), true);
     assertEq(rope, "ABC0123456789012345678901234567890123456789");
     assertEq(isLatin1(rope), true); // Still Latin1 after flattening.
 
     // Latin1 + TwoByte => TwoByte rope.
     assertEq(isLatin1(s3), true);
     rope = concat(s3, "someTwoByteString\u0580");
@@ -59,17 +58,17 @@ function testConcat() {
 
     assertEq(isLatin1(s3), true);
     rope = concat("twoByteString\u0580", concat(s3, "otherTwoByte\u0580"));
     assertEq(isLatin1(rope), false);
     assertEq(rope, "twoByteString\u05800123456789012345678901234567890123456789otherTwoByte\u0580");
     assertEq(isLatin1(rope), false);
 
     // Build a Latin1 rope with left-most string an extensible string.
-    var s4 = toLatin1("adsfasdfjkasdfkjasdfasasdfasdf");
+    var s4 = "adsfasdfjkasdfkjasdfasasdfasdf";
     for (var i=0; i<5; i++) {
 	s4 = concat(s4, s1);
 	assertEq(s4 === ".".repeat(s4.length), false); // Flatten rope.
     }
 
     assertEq(isLatin1(s4), true);
 
     // Appending a TwoByte string must inflate.
@@ -79,18 +78,18 @@ function testConcat() {
 testConcat();
 
 function testFlattenDependent() {
     function concat(s1, s2) {
 	return s1 + s2;
     }
 
     // Create some latin1 strings.
-    var s1 = toLatin1("Foo0123456789bar012345---");
-    var s2 = toLatin1("Foo0123456789bar012345+++");
+    var s1 = "Foo0123456789bar012345---";
+    var s2 = "Foo0123456789bar012345+++";
     assertEq(isLatin1(s1), true);
     assertEq(isLatin1(s2), true);
 
     // And some ropes.
     var rope1 = concat(s1, s1);
     assertEq(isLatin1(rope1), true);
 
     var rope2 = concat(rope1, s2);
--- a/js/src/jit-test/tests/latin1/compare.js
+++ b/js/src/jit-test/tests/latin1/compare.js
@@ -1,13 +1,13 @@
 function test() {
     var arr = [
-	toLatin1("abc"),
-	toLatin1("abcd"),
-	toLatin1("123\u00ff")
+	"abc",
+	"abcd",
+	"123\u00ff"
     ];
     for (var i = 0; i < arr.length; i++) {
 	for (var j = 0; j < arr.length; j++) {
 	    var s1 = arr[i];
 	    var s2 = arr[j];
 	    var s1tb = "\u1200" + s1;
 	    var s2tb = "\u1200" + s2;
 	    assertEq(s1 < s2, s1tb < s2tb);
--- a/js/src/jit-test/tests/latin1/date.js
+++ b/js/src/jit-test/tests/latin1/date.js
@@ -1,10 +1,10 @@
-function test(s) {
-    var lat1 = toLatin1(s);
+function test(lat1) {
+    assertEq(isLatin1(lat1), true);
 
     var twoByte = "\u1200" + lat1;
     twoByte.indexOf("X"); // Flatten.
     twoByte = twoByte.substr(1);
 
     assertEq(isLatin1(lat1), true);
     assertEq(isLatin1(twoByte), false);
 
--- a/js/src/jit-test/tests/latin1/decompiler.js
+++ b/js/src/jit-test/tests/latin1/decompiler.js
@@ -1,10 +1,8 @@
-var s = toLatin1("someName");
-
 // Latin1
 function f(someName) {
     someName();
 }
 try {
     f(3);
 } catch(e) {
     assertEq(e.message.contains("someName"), true);
--- a/js/src/jit-test/tests/latin1/dependent.js
+++ b/js/src/jit-test/tests/latin1/dependent.js
@@ -1,10 +1,10 @@
 function testSubstrLatin1() {
-    var s1 = toLatin1("abcdefghijklmnopqrstuvwxyz12345678900000a");
+    var s1 = "abcdefghijklmnopqrstuvwxyz12345678900000a";
 
     // Static strings.
     assertEq(s1.substr(s1.length - 1), "a");
     assertEq(s1.substr(s1.length - 2), "0a");
     assertEq(s1.substr(26, 3), "123");
 
     // (Fat) inline strings.
     var s2 = s1.substr(3, 5);
@@ -46,42 +46,43 @@ function testSubstrTwoByte() {
 
     s2 = s2.substr(2).substr(1);
     assertEq(isLatin1(s2), false);
     assertEq(s2, "fghijklmnopqrstuvwxyz12345678900000a\u1480");
 }
 testSubstrTwoByte();
 
 function testSubstring() {
-    var s1 = toLatin1("abcdefghijklmnopqrstuvwxyz123456789000ab");
+    var s1 = "abcdefghijklmnopqrstuvwxyz123456789000ab";
     var s2 = s1.substring(1, 8);
     assertEq(isLatin1(s2), true);
     assertEq(s2, "bcdefgh");
     s2 = s1.substring(0, s1.length - 1);
     assertEq(isLatin1(s2), true);
     assertEq(s2, "abcdefghijklmnopqrstuvwxyz123456789000a");
 }
 testSubstring();
 
 function testSlice() {
-    var s1 = toLatin1("abcdefghijklmnopqrstuvwxyz123456789000ABC");
+    var s1 = "abcdefghijklmnopqrstuvwxyz123456789000ABC";
     var s2 = s1.slice(1, 8);
     assertEq(isLatin1(s2), true);
     assertEq(s2, "bcdefgh");
     s2 = s1.slice(0, -2);
     assertEq(isLatin1(s2), true);
     assertEq(s2, "abcdefghijklmnopqrstuvwxyz123456789000A");
 }
 testSlice();
 
 function testUndepend() {
     // Latin1
     var s = "abcdefg".repeat(7);
     s.indexOf("def"); // flatten
-    s = toLatin1(s);
+    assertEq(isLatin1(s), true);
+
     var dep = s.substr(7);
     var res = dep.replace(/abcdef/g, ""); // StrReplaceRegexpRemove undepends.
     assertEq(res, "gggggg");
 
     // TwoByte
     var s = "abcdefg\u1200".repeat(6);
     s.indexOf("def"); // flatten
     var dep = s.substr(8);
--- a/js/src/jit-test/tests/latin1/encode-decode.js
+++ b/js/src/jit-test/tests/latin1/encode-decode.js
@@ -1,41 +1,57 @@
 // Latin1
-s = toLatin1("a%2b%20def%00A0");
-assertEq(decodeURI(s), "a%2b def\x00A0");
-assertEq(decodeURIComponent(s), "a+ def\x00A0");
+s = "a%2b%20def%00A0";
+
+res = decodeURI(s);
+assertEq(res, "a%2b def\x00A0");
+assertEq(isLatin1(res), true);
+
+res = decodeURIComponent(s);
+assertEq(res, "a+ def\x00A0");
+assertEq(isLatin1(res), true);
 
 // TwoByte
 s += "\u1200";
 assertEq(decodeURI(s), "a%2b def\x00A0\u1200");
 assertEq(decodeURIComponent(s), "a+ def\x00A0\u1200");
 
 // Latin1 malformed
 try {
-    decodeURI(toLatin1("abc%80"));
+    decodeURI("abc%80");
     assertEq(0, 1);
 } catch(e) {
     assertEq(e instanceof URIError, true);
 }
 
 // TwoByte malformed
 try {
     decodeURI("abc%80\u1200");
     assertEq(0, 1);
 } catch(e) {
     assertEq(e instanceof URIError, true);
 }
 
 // Latin1
-assertEq(encodeURI(toLatin1("a%2b def\x00A0")), "a%252b%20def%00A0");
-assertEq(encodeURIComponent(toLatin1("a+ def\x00A0")), "a%2B%20def%00A0");
+res = encodeURI("a%2b def\x00A0");
+assertEq(res, "a%252b%20def%00A0");
+assertEq(isLatin1(res), true);
+
+res = encodeURIComponent("a+ def\x00A0");
+assertEq(res, "a%2B%20def%00A0");
+assertEq(isLatin1(res), true);
 
 // TwoByte
-assertEq(encodeURI("a%2b def\x00A0\u1200"), "a%252b%20def%00A0%E1%88%80");
-assertEq(encodeURIComponent("a+ def\x00A0\u1200"), "a%2B%20def%00A0%E1%88%80");
+res = encodeURI("a%2b def\x00A0\u1200");
+assertEq(res, "a%252b%20def%00A0%E1%88%80");
+assertEq(isLatin1(res), true);
+
+res = encodeURIComponent("a+ def\x00A0\u1200");
+assertEq(res, "a%2B%20def%00A0%E1%88%80");
+assertEq(isLatin1(res), true);
 
 // TwoByte malformed
 try {
     encodeURI("a\uDB00");
     assertEq(0, 1);
 } catch(e) {
     assertEq(e instanceof URIError, true);
 }
--- a/js/src/jit-test/tests/latin1/escape-unescape.js
+++ b/js/src/jit-test/tests/latin1/escape-unescape.js
@@ -1,18 +1,24 @@
 // Latin1
-s = toLatin1("a%2b%20def%00A0");
-assertEq(unescape(s), "a+ def\x00A0");
+s = "a%2b%20def%00A0";
+res = unescape(s);
+assertEq(res, "a+ def\x00A0");
+assertEq(isLatin1(res), true);
 
-s = toLatin1("a%2b%20def%00A0%u1200");
+s = "a%2b%20def%00A0%u1200";
 assertEq(unescape(s), "a+ def\x00A0\u1200");
 
 // TwoByte
 s += "\u1200";
 assertEq(unescape(s), "a+ def\x00A0\u1200\u1200");
 
 // Latin1
-s = toLatin1("abc \u00ff");
-assertEq(escape(s), "abc%20%FF");
+s = "abc \u00ff";
+res = escape(s);
+assertEq(res, "abc%20%FF");
+assertEq(isLatin1(res), true);
 
 // TwoByte
 s += "\u1200";
-assertEq(escape(s), "abc%20%FF%u1200");
+res = escape(s);
+assertEq(res, "abc%20%FF%u1200");
+assertEq(isLatin1(res), true);
--- a/js/src/jit-test/tests/latin1/eval.js
+++ b/js/src/jit-test/tests/latin1/eval.js
@@ -1,10 +1,10 @@
 function f(s) {
     var x = 3, y = 5;
     var z = eval(s);
     assertEq(z, 8);
 }
-var s = toLatin1("x + y");
+var s = "x + y";
 f(s); // Latin1
 f(s);
 f("x + y;/*\u1200*/"); // TwoByte
 f("x + y;/*\u1200*/");
--- a/js/src/jit-test/tests/latin1/function.js
+++ b/js/src/jit-test/tests/latin1/function.js
@@ -1,13 +1,13 @@
 function test() {
     var arg1TwoByte = "arg1\u1200";
-    var arg2Latin1 = toLatin1("arg2");
+    var arg2Latin1 = "arg2";
 
-    var bodyLatin1 = toLatin1("return arg2 * 3");
+    var bodyLatin1 = "return arg2 * 3";
 
     var f = Function(arg1TwoByte, arg2Latin1, bodyLatin1);
     assertEq(f(10, 20), 60);
     assertEq(f.toSource().contains("arg1\u1200, arg2"), true);
 
     var bodyTwoByte = "return arg1\u1200 + arg2;";
     f = Function(arg1TwoByte, arg2Latin1, bodyTwoByte);
     assertEq(f(30, 40), 70);
--- a/js/src/jit-test/tests/latin1/index.js
+++ b/js/src/jit-test/tests/latin1/index.js
@@ -1,26 +1,26 @@
 function test() {
     var arr = new Int8Array(400);
-    var idx = toLatin1("384");
+    var idx = "384";
 
     arr[idx] = 9;
     assertEq(arr[idx], 9);
     arr[idx] = 10;
     assertEq(arr[384], 10);
 
-    idx = toLatin1("512");
+    idx = "512";
     assertEq(arr[idx], undefined);
-    assertEq(arr[toLatin1("byteLength")], 400);
+    assertEq(arr[(() => "byteLength")()], 400);
 
     var o = {};
     Object.defineProperty(o, idx, {value: 123});
     assertEq(o[512], 123);
 
-    var propLatin1 = toLatin1("foobar");
+    var propLatin1 = "foobar";
     o[propLatin1] = 3;
     assertEq(o.foobar, 3);
 
     var propTwoByte = "foobar\u1200";
     o[propTwoByte] = 4;
     assertEq(o.foobar\u1200, 4);
 }
 test();
--- a/js/src/jit-test/tests/latin1/indexOf.js
+++ b/js/src/jit-test/tests/latin1/indexOf.js
@@ -1,8 +1,12 @@
+function toLatin1(s) {
+    assertEq(isLatin1(s), true);
+    return s;
+}
 function testLastIndexOf() {
     var s1 = toLatin1("abcdefgh123456\u0081defg");
     var s2 = toLatin1("456\u0081de");
 
     // Latin1 + Latin1
     assertEq(s1.lastIndexOf(s1), 0);
     assertEq(s1.lastIndexOf(s2), 11);
     assertEq(s1.lastIndexOf(s2, 10), -1);
--- a/js/src/jit-test/tests/latin1/indexing.js
+++ b/js/src/jit-test/tests/latin1/indexing.js
@@ -1,32 +1,32 @@
 function testCharCodeAt() {
-    var s = toLatin1("abcdefghijklm1234567891000");
+    var s = "abcdefghijklm1234567891000";
     for (var i=0; i<10; i++)
 	assertEq(s.charCodeAt(i), 97 + i);
 
-    var rope = s + toLatin1("blah");
+    var rope = s + "blah";
     assertEq(rope.charCodeAt(s.length + 3), 104);
 
     rope = s + "Foo987";
     assertEq(rope.charCodeAt(s.length + 4), 56);
 
     rope = "twoByte\u0580" + s;
     assertEq(rope.charCodeAt(7), 0x580);
     assertEq(rope.charCodeAt(14), 103);
 }
 testCharCodeAt();
 
 function testCharAt() {
-    var s = toLatin1("abcdefghijklm100000002345");
+    var s = "abcdefghijklm100000002345";
     assertEq(s.charAt(0), "a");
     assertEq(s.charAt(s.length-1), "5");
     assertEq(s.charAt(s.length), "");
 
-    var rope = s + toLatin1("abcZYX");
+    var rope = s + "abcZYX";
     assertEq(rope.charAt(s.length + 3), "Z");
 
     rope = s + "Foo987";
     assertEq(rope.charAt(s.length + 4), "8");
 
     rope = "twoByte\u0580" + s;
     assertEq(rope.charAt(7), "\u0580");
     assertEq(rope.charAt(14), "g");
@@ -36,12 +36,12 @@ testCharAt();
 function testIndex(s) {
     assertEq(s[0], "a");
     assertEq(s[s.length-1], "6");
 
     rope = "twoByte\u0512" + s
     assertEq(rope[7], "\u0512");
 }
 
-var s = toLatin1("abcdefghijklm123456");
+var s = "abcdefghijklm123456";
 testIndex(s);
 testIndex(s);
 testIndex(s);
--- a/js/src/jit-test/tests/latin1/join.js
+++ b/js/src/jit-test/tests/latin1/join.js
@@ -1,14 +1,24 @@
+function toLatin1(s) {
+    assertEq(isLatin1(s), true);
+    return s;
+}
+
 var arrLatin1 = [toLatin1("abc1"), toLatin1("abc\u00A0")];
-assertEq(arrLatin1.join(toLatin1("sep\u00ff")), "abc1sep\xFFabc\xA0");
+var res = arrLatin1.join(toLatin1("sep\u00ff"));
+assertEq(res, "abc1sep\xFFabc\xA0");
+assertEq(isLatin1(res), true);
 
 var arrTwoByte = [toLatin1("abc2"), "def\u1200"];
 assertEq(arrTwoByte.join(toLatin1("sep\u00fe")), "abc2sep\xFEdef\u1200");
 
-assertEq(arrLatin1.join(toLatin1("-")), "abc1-abc\xA0");
+res = arrLatin1.join(toLatin1("-"));
+assertEq(res, "abc1-abc\xA0");
+assertEq(isLatin1(res), true);
+
 assertEq(arrTwoByte.join(toLatin1("7")), "abc27def\u1200");
 
 assertEq(arrLatin1.join("\u1200"), "abc1\u1200abc\xA0");
 assertEq(arrTwoByte.join("\u1200"), "abc2\u1200def\u1200");
 
 assertEq(arrLatin1.join("---\u1200"), "abc1---\u1200abc\xA0");
 assertEq(arrTwoByte.join("---\u1200"), "abc2---\u1200def\u1200");
--- a/js/src/jit-test/tests/latin1/json.js
+++ b/js/src/jit-test/tests/latin1/json.js
@@ -1,24 +1,25 @@
 function testBasic() {
     // Latin1
-    var s = toLatin1('[1, 2, "foo", "bar\\r\\n", {"xyz": 3}, [1, 2, 3]]');
+    var s = '[1, 2, "foo", "bar\\r\\n", {"xyz": 3}, [1, 2, 3]]';
+    assertEq(isLatin1(s), true);
     assertEq(JSON.stringify(JSON.parse(s)), '[1,2,"foo","bar\\r\\n",{"xyz":3},[1,2,3]]');
 
     // TwoByte
     s = '[1, 2, "foo\u1200", "bar\\r\\n", {"xyz": 3}, [1, 2, 3]]';
     assertEq(JSON.stringify(JSON.parse(s)), '[1,2,"foo\u1200","bar\\r\\n",{"xyz":3},[1,2,3]]');
 }
 testBasic();
 
 function testErrorPos() {
     // Make sure the error location is calculated correctly.
 
     // Latin1
-    var s = toLatin1('[1, \n2,');
+    var s = '[1, \n2,';
     try {
 	JSON.parse(s);
 	assertEq(0, 1);
     } catch(e) {
 	assertEq(e instanceof SyntaxError, true);
 	assertEq(e.toString().contains("line 2 column 3"), true);
     }
 
@@ -30,28 +31,29 @@ function testErrorPos() {
 	assertEq(e instanceof SyntaxError, true);
 	assertEq(e.toString().contains("line 2 column 3"), true);
     }
 }
 testErrorPos();
 
 function testEvalHack() {
     // Latin1
-    var arr = eval(toLatin1("[1, 2, 3, \"abc\"]"));
+    var arr = eval("[1, 2, 3, \"abc\"]");
     assertEq(JSON.stringify(arr), '[1,2,3,"abc"]');
+    assertEq(isLatin1(JSON.stringify(arr)), true);
 
     // TwoByte
     arr = eval("[1, 2, 3, \"abc\u1200\"]");
     assertEq(JSON.stringify(arr), '[1,2,3,"abc\u1200"]');
 }
 testEvalHack();
 
 function testEvalHackNotJSON() {
     // Latin1
-    var arr = eval(toLatin1("[]; var q; [1, 2, 3, \"abc\"]"));
+    var arr = eval("[]; var q; [1, 2, 3, \"abc\"]");
     assertEq(JSON.stringify(arr), '[1,2,3,"abc"]');
 
     // TwoByte
     arr = eval("[]; var z; [1, 2, 3, \"abc\u1200\"]");
     assertEq(JSON.stringify(arr), '[1,2,3,"abc\u1200"]');
 
     try {
 	eval("[1, 2, 3, \"abc\u2028\"]");
@@ -60,16 +62,18 @@ function testEvalHackNotJSON() {
 	assertEq(e instanceof SyntaxError, true,
 		 "should have thrown a SyntaxError, instead got " + e);
     }
 }
 testEvalHackNotJSON();
 
 function testQuote() {
     // Latin1
-    var s = toLatin1("abc--\x05-'\"-\n-\u00ff++");
-    assertEq(JSON.stringify(s), '"abc--\\u0005-\'\\"-\\n-\xFF++"');
+    var s = "abc--\x05-'\"-\n-\u00ff++";
+    var res = JSON.stringify(s);
+    assertEq(res, '"abc--\\u0005-\'\\"-\\n-\xFF++"');
+    assertEq(isLatin1(res), true);
 
     // TwoByte
     s += "\uAAAA";
     assertEq(JSON.stringify(s), '"abc--\\u0005-\'\\"-\\n-\xFF++\uAAAA"');
 }
 testQuote();
--- a/js/src/jit-test/tests/latin1/latin1.js
+++ b/js/src/jit-test/tests/latin1/latin1.js
@@ -1,11 +1,8 @@
-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");
--- a/js/src/jit-test/tests/latin1/other.js
+++ b/js/src/jit-test/tests/latin1/other.js
@@ -1,10 +1,13 @@
-var s1 = toLatin1("abcdefg12345");
-var s2 = toLatin1('foo"bar');
+var s1 = "abcdefg12345";
+var s2 = 'foo"bar';
+
+assertEq(isLatin1(s1), true);
+assertEq(isLatin1(s2), true);
 
 function test() {
     assertEq(s1.valueOf(), s1);
 
     assertEq(s1.bold(), "<b>abcdefg12345</b>");
     assertEq(s1.fontsize("twoByte\u1400"), '<font size="twoByte\u1400">abcdefg12345</font>');
     assertEq(s1.anchor(s1), '<a name="abcdefg12345">abcdefg12345</a>');
     assertEq(s1.link(s2), '<a href="foo&quot;bar">abcdefg12345</a>');
--- a/js/src/jit-test/tests/latin1/parseInt-parseFloat.js
+++ b/js/src/jit-test/tests/latin1/parseInt-parseFloat.js
@@ -1,8 +1,12 @@
+function toLatin1(s) {
+    assertEq(isLatin1(s), true);
+    return s;
+}
 function testParseInt() {
     // Latin1
     assertEq(parseInt(toLatin1("12345abc")), 12345);
     assertEq(parseInt(toLatin1("0x5")), 0x5);
     assertEq(parseInt(toLatin1("-123")), -123);
     assertEq(parseInt(toLatin1("xyz")), NaN);
     assertEq(parseInt(toLatin1("1234GHI"), 17), 94298);
     assertEq(parseInt(toLatin1("9007199254749999")), 9007199254750000);
--- a/js/src/jit-test/tests/latin1/quote.js
+++ b/js/src/jit-test/tests/latin1/quote.js
@@ -1,7 +1,11 @@
-var s = toLatin1('Abc1234"\'\t987\u00ff');
+var s = 'Abc1234"\'\t987\u00ff';
+assertEq(isLatin1(s), true);
 assertEq(s.toSource(), '(new String("Abc1234\\"\'\\t987\\xFF"))');
-assertEq(s.quote(), '"Abc1234\\"\'\\t987\\xFF"');
+var res = s.quote();
+assertEq(res, '"Abc1234\\"\'\\t987\\xFF"');
+assertEq(isLatin1(res), true);
 
 s = 'Abc1234"\'\t\u1200987\u00ff';
+assertEq(isLatin1(s), false);
 assertEq(s.toSource(), '(new String("Abc1234\\"\'\\t\\u1200987\\xFF"))');
 assertEq(s.quote(), '"Abc1234\\"\'\\t\\u1200987\\xFF"');
--- a/js/src/jit-test/tests/latin1/regexp.js
+++ b/js/src/jit-test/tests/latin1/regexp.js
@@ -1,8 +1,13 @@
+function toLatin1(s) {
+    assertEq(isLatin1(s), true);
+    return s;
+}
+
 // Latin1
 var re = new RegExp(toLatin1("foo[bB]a\\r"), toLatin1("im"));
 assertEq(isLatin1(re.source), true);
 assertEq(re.source, "foo[bB]a\\r");
 assertEq(re.multiline, true);
 assertEq(re.ignoreCase, true);
 assertEq(re.sticky, false);
 
--- a/js/src/jit-test/tests/latin1/replace.js
+++ b/js/src/jit-test/tests/latin1/replace.js
@@ -1,17 +1,22 @@
+function toLatin1(s) {
+    assertEq(isLatin1(s), true);
+    return s;
+}
 function testDollarReplacement() {
     // Latin1 input, pat and replacement
     var s = toLatin1("Foobarbaz123");
     var pat = toLatin1("bar");
     assertEq(s.replace(pat, toLatin1("AA")), "FooAAbaz123");
     assertEq(s.replace(pat, toLatin1("A$$A")), "FooA$Abaz123");
     assertEq(s.replace(pat, toLatin1("A$`A")), "FooAFooAbaz123");
     assertEq(s.replace(pat, toLatin1("A$&A")), "FooAbarAbaz123");
     assertEq(s.replace(pat, toLatin1("A$'A")), "FooAbaz123Abaz123");
+    assertEq(isLatin1(s.replace(pat, "A$'A")), true);
 
     // Latin1 input and pat, TwoByte replacement
     assertEq(s.replace(pat, "A\u1200"), "FooA\u1200baz123");
     assertEq(s.replace(pat, "A$$\u1200"), "FooA$\u1200baz123");
     assertEq(s.replace(pat, "A$`\u1200"), "FooAFoo\u1200baz123");
     assertEq(s.replace(pat, "A$&\u1200"), "FooAbar\u1200baz123");
     assertEq(s.replace(pat, "A$'\u1200"), "FooAbaz123\u1200baz123");
 
@@ -38,23 +43,30 @@ function testDollarReplacement() {
     assertEq(s.replace(pat, "A$`\u1300"), "FooAFoo\u1300baz123");
     assertEq(s.replace(pat, "A$&\u1300"), "FooAbar\u1200\u1300baz123");
     assertEq(s.replace(pat, "A$'\u1300"), "FooAbaz123\u1300baz123");
 }
 testDollarReplacement();
 
 function testRegExp() {
     var s = toLatin1("Foobar123bar234");
-    assertEq(s.replace(/bar\d\d/, "456"), "Foo4563bar234");
+    var res = s.replace(/bar\d\d/, "456");
+    assertEq(res, "Foo4563bar234");
+    assertEq(isLatin1(res), true);
 
     // Latin1 input and replacement
     var re1 = /bar\d\d/;
+    res = s.replace(re1, toLatin1("789"));
+    assertEq(res, "Foo7893bar234");
+    assertEq(isLatin1(res), true);
+
     var re2 = /bar\d\d/g;
-    assertEq(s.replace(re1, toLatin1("789")), "Foo7893bar234");
-    assertEq(s.replace(re2, toLatin1("789\u00ff")), "Foo789\u00ff3789\u00ff4");
+    res = s.replace(re2, toLatin1("789\u00ff"));
+    assertEq(res, "Foo789\u00ff3789\u00ff4");
+    assertEq(isLatin1(res), true);
 
     // Latin1 input, TwoByte replacement
     assertEq(s.replace(re1, "789\u1200"), "Foo789\u12003bar234");
     assertEq(s.replace(re2, "789\u1200"), "Foo789\u12003789\u12004");
 
     // TwoByte input, Latin1 replacement
     s += "\u1200";
     assertEq(s.replace(re1, toLatin1("7890")), "Foo78903bar234\u1200");
@@ -70,16 +82,17 @@ function testRegExpDollar() {
     var s = toLatin1("Foobar123bar2345");
 
     // Latin1 input and replacement
     var re1 = /bar\d\d/;
     var re2 = /bar(\d\d)/g;
     assertEq(s.replace(re1, toLatin1("--$&--")), "Foo--bar12--3bar2345");
     assertEq(s.replace(re2, toLatin1("--$'\u00ff--")), "Foo--3bar2345\xFF--3--45\xFF--45");
     assertEq(s.replace(re2, toLatin1("--$`--")), "Foo--Foo--3--Foobar123--45");
+    assertEq(isLatin1(s.replace(re2, toLatin1("--$`--"))), true);
 
     // Latin1 input, TwoByte replacement
     assertEq(s.replace(re1, "\u1200$$"), "Foo\u1200$3bar2345");
     assertEq(s.replace(re2, "\u1200$1"), "Foo\u1200123\u12002345");
     assertEq(s.replace(re2, "\u1200$'"), "Foo\u12003bar23453\u12004545");
 
     // TwoByte input, Latin1 replacement
     s += "\u1200";
@@ -95,31 +108,37 @@ function testRegExpDollar() {
     assertEq(s.replace(re2, "\ueeee**$'$$**"), "Foo\ueeee**3bar2345\u1200$**3\ueeee**45\u1200$**45\u1200");
 }
 testRegExpDollar();
 
 function testFlattenPattern() {
     var s = "abcdef[g]abc";
 
     // Latin1 pattern
-    assertEq(s.replace(toLatin1("def[g]"), "--$&--", "gi"), "abc--def[g]--abc");
+    var res = s.replace(toLatin1("def[g]"), "--$&--", "gi");
+    assertEq(res, "abc--def[g]--abc");
+    assertEq(isLatin1(res), true);
 
     // TwoByte pattern
     s = "abcdef[g]\u1200abc";
     assertEq(s.replace("def[g]\u1200", "++$&++", "gi"), "abc++def[g]\u1200++abc");
 }
 testFlattenPattern();
 
 function testReplaceEmpty() {
     // Latin1
     var s = toLatin1("--abcdefghijkl--abcdefghijkl--abcdefghijkl--abcdefghijkl");
-    assertEq(s.replace(/abcd[eE]/g, ""), "--fghijkl--fghijkl--fghijkl--fghijkl");
+    var res = s.replace(/abcd[eE]/g, "");
+    assertEq(res, "--fghijkl--fghijkl--fghijkl--fghijkl");
+    assertEq(isLatin1(res), true);
 
     s = "--abcdEf--";
-    assertEq(s.replace(/abcd[eE]/g, ""), "--f--");
+    res = s.replace(/abcd[eE]/g, "");
+    assertEq(res, "--f--");
+    assertEq(isLatin1(res), true);
 
     // TwoByte
     s = "--abcdefghijkl--abcdefghijkl--abcdefghijkl--abcdefghijkl\u1200";
     assertEq(s.replace(/abcd[eE]/g, ""), "--fghijkl--fghijkl--fghijkl--fghijkl\u1200");
 
     s = "--abcdEf--\u1200";
     assertEq(s.replace(/abcd[eE]/g, ""), "--f--\u1200");
 }
--- a/js/src/jit-test/tests/latin1/search.js
+++ b/js/src/jit-test/tests/latin1/search.js
@@ -1,8 +1,12 @@
+function toLatin1(s) {
+    assertEq(isLatin1(s), true);
+    return s;
+}
 function testSearchFlat() {
     var s1 = toLatin1("fooBar12345");
     var s2 = toLatin1("Bar1");
 
     // Latin1 + Latin1
     assertEq(s1.search(s2), 3);
     assertEq(s2.search(s1), -1);
     assertEq(s1.search(s1), 0);
--- a/js/src/jit-test/tests/latin1/split.js
+++ b/js/src/jit-test/tests/latin1/split.js
@@ -1,24 +1,27 @@
 // Latin1
-var s = toLatin1("abcdef,g,,");
-var res = s.split(toLatin1(","));
+var s = "abcdef,g,,";
+var res = s.split(",");
 assertEq(res[0], "abcdef");
+assertEq(isLatin1(res[0]), true);
 assertEq(res[1], "g");
 assertEq(res[2], "");
 assertEq(res[3], "");
 
-s = toLatin1("abcdef,gh,,");
+s = "abcdef,gh,,";
 res = s.split("\u1200");
 assertEq(res[0], "abcdef,gh,,");
+assertEq(isLatin1(res[0]), true);
 
 // TwoByte
 s = "abcdef\u1200\u1200,g,,";
 res = s.split(",");
 assertEq(res[0], "abcdef\u1200\u1200");
+assertEq(isLatin1(res[0]), false);
 assertEq(res[1], "g");
 assertEq(res[2], "");
 assertEq(res[3], "");
 
 res = s.split("\u1200");
 assertEq(res[0], "abcdef");
 assertEq(res[1], "");
 assertEq(res[2], ",g,,");
--- a/js/src/jit-test/tests/latin1/startsWith-endsWith.js
+++ b/js/src/jit-test/tests/latin1/startsWith-endsWith.js
@@ -1,8 +1,12 @@
+function toLatin1(s) {
+    assertEq(isLatin1(s), true);
+    return s;
+}
 function testStartsWith() {
     var s1 = toLatin1("abc\u0099def");
     var s2 = toLatin1("abc\u0099d");
     var s3 = toLatin1("abc\u0098d");
     var s4 = toLatin1("bc\u0099");
 
     // Latin1 + Latin1
     assertEq(s1.startsWith(s2), true);
--- a/js/src/jit-test/tests/latin1/structured-clone.js
+++ b/js/src/jit-test/tests/latin1/structured-clone.js
@@ -1,14 +1,14 @@
 // Latin1
-var s = deserialize(serialize(toLatin1("foo123\u00EE")));
+var s = deserialize(serialize("foo123\u00EE"));
 assertEq(s, "foo123\u00EE");
 assertEq(isLatin1(s), true);
 
-var o = deserialize(serialize(new String(toLatin1("foo\u00EE"))));
+var o = deserialize(serialize(new String("foo\u00EE")));
 assertEq(typeof o, "object");
 assertEq(o.valueOf(), "foo\u00EE");
 assertEq(isLatin1(o.valueOf()), true);
 
 // TwoByte
 var s = deserialize(serialize("foo123\u00FF\u1234"));
 assertEq(s, "foo123\u00FF\u1234");
 assertEq(isLatin1(s), false);
--- a/js/src/jit-test/tests/latin1/toLowerCase-toUpperCase.js
+++ b/js/src/jit-test/tests/latin1/toLowerCase-toUpperCase.js
@@ -1,10 +1,11 @@
 function testToLowerCase() {
-    var s1 = toLatin1("abcdefgABCDEFGH 123456");
+    var s1 = "abcdefgABCDEFGH 123456";
+    assertEq(isLatin1(s1), true);
 
     // Latin1
     var s2 = s1.toLowerCase();
     assertEq(isLatin1(s2), true);
     assertEq(s2, "abcdefgabcdefgh 123456");
 
     s2 = s1.toLocaleLowerCase();
     assertEq(isLatin1(s2), true);
@@ -21,17 +22,18 @@ function testToLowerCase() {
     for (var i=0; i <= 0xff; i++) {
 	var s = "\u1200\u11AA" + String.fromCharCode(i);
 	assertEq(s.toLowerCase().charCodeAt(2) <= 0xff, true);
     }
 }
 testToLowerCase();
 
 function testToUpperCase() {
-    var s1 = toLatin1("abcdefgABCDEFGH 12345");
+    var s1 = "abcdefgABCDEFGH 12345";
+    assertEq(isLatin1(s1), true);
 
     // Latin1
     var s2 = s1.toUpperCase();
     assertEq(s2, "ABCDEFGABCDEFGH 12345");
 
     s2 = s1.toLocaleUpperCase();
     assertEq(s2, "ABCDEFGABCDEFGH 12345");
 
@@ -39,15 +41,15 @@ function testToUpperCase() {
     s2 = "abcdefg\u1200ABCDEFGH 12345\u1E0F".toUpperCase();
     assertEq(s2, "ABCDEFG\u1200ABCDEFGH 12345\u1E0E");
 
     s2 = "abcdefg\u1200ABCDEFGH 12345\u1E0F".toLocaleUpperCase();
     assertEq(s2, "ABCDEFG\u1200ABCDEFGH 12345\u1E0E");
 
     // Tricky case: Latin1 character \u00FF maps to \u0178, a
     // non-Latin1 character.
-    s1 = toLatin1("ABC\u00FF");
+    s1 = "ABC\u00FF";
     assertEq(isLatin1(s1), true);
     s2 = s1.toUpperCase();
     assertEq(isLatin1(s2), false);
     assertEq(s2, "ABC\u0178");
 }
 testToUpperCase();
--- a/js/src/jit-test/tests/latin1/toNumber.js
+++ b/js/src/jit-test/tests/latin1/toNumber.js
@@ -1,8 +1,12 @@
+function toLatin1(s) {
+    assertEq(isLatin1(s), true);
+    return s;
+}
 function testToNumber() {
     // Latin1
     assertEq(+toLatin1("12345.6"), 12345.6);
     assertEq(+toLatin1("+123"), 123);
     assertEq(+toLatin1("0xABC"), 0xABC);
     assertEq(+toLatin1("112."), 112);
     assertEq(+toLatin1("112.A"), NaN);
     assertEq(+toLatin1("-Infinity"), -Infinity);
--- a/js/src/jit-test/tests/latin1/trim.js
+++ b/js/src/jit-test/tests/latin1/trim.js
@@ -1,25 +1,26 @@
 function test() {
     // Latin1
-    var s = toLatin1("  \r\t\n\u00A0foo 123\t \r\n\u00A0");
+    var s = "  \r\t\n\u00A0foo 123\t \r\n\u00A0";
+    assertEq(isLatin1(s), true);
 
     var res = s.trim();
     assertEq(isLatin1(res), true);
     assertEq(res, "foo 123");
 
     res = s.trimLeft();
     assertEq(isLatin1(res), true);
     assertEq(res, "foo 123\t \r\n\u00A0");
 
     res = s.trimRight();
     assertEq(isLatin1(res), true);
     assertEq(res, "  \r\t\n\u00A0foo 123");
 
-    res = toLatin1("foo 1234").trim();
+    res = "foo 1234".trim();
     assertEq(isLatin1(res), true);
     assertEq(res, "foo 1234");
 
     // TwoByte
     s = "  \r\t\n\u00A0\u2000foo\u1200123\t \r\n\u00A0\u2009";
     assertEq(s.trim(), "foo\u1200123");
     assertEq(s.trimLeft(), "foo\u1200123\t \r\n\u00A0\u2009");
     assertEq(s.trimRight(), "  \r\t\n\u00A0\u2000foo\u1200123");
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/stacks-are-frozen.js
@@ -0,0 +1,17 @@
+// Test that SavedFrame instances are frozen and can't be messed with.
+
+// Strict mode so that mutating frozen objects doesn't silently fail.
+"use strict";
+
+const s = saveStack();
+
+load(libdir + 'asserts.js');
+
+assertThrowsInstanceOf(() => s.source = "fake.url",
+                       TypeError);
+
+assertThrowsInstanceOf(() => {
+  Object.defineProperty(s.__proto__, "line", {
+    get: () => 0
+  })
+}, TypeError);
--- a/js/src/jit/AsmJS.cpp
+++ b/js/src/jit/AsmJS.cpp
@@ -2,16 +2,17 @@
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * 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 "jit/AsmJS.h"
 
 #include "mozilla/Move.h"
+#include "mozilla/UniquePtr.h"
 
 #ifdef MOZ_VTUNE
 # include "vtune/VTuneWrapper.h"
 #endif
 
 #include "jsmath.h"
 #include "jsprf.h"
 #include "prmjtime.h"
@@ -45,16 +46,17 @@ using mozilla::ArrayLength;
 using mozilla::CountLeadingZeroes32;
 using mozilla::DebugOnly;
 using mozilla::HashGeneric;
 using mozilla::IsNaN;
 using mozilla::IsNegativeZero;
 using mozilla::Maybe;
 using mozilla::Move;
 using mozilla::PositiveInfinity;
+using mozilla::UniquePtr;
 using JS::GenericNaN;
 
 static const size_t LIFO_ALLOC_PRIMARY_CHUNK_SIZE = 1 << 12;
 
 /*****************************************************************************/
 // ParseNode utilities
 
 static inline ParseNode *
@@ -812,25 +814,25 @@ typedef Vector<MBasicBlock*,8> BlockVect
 // non-JSAtom pointers, or this will break!
 class MOZ_STACK_CLASS ModuleCompiler
 {
   public:
     class Func
     {
         Signature sig_;
         PropertyName *name_;
-        Label *code_;
+        Label *entry_;
         uint32_t srcBegin_;
         uint32_t srcEnd_;
         uint32_t compileTime_;
         bool defined_;
 
       public:
-        Func(PropertyName *name, Signature &&sig, Label *code)
-          : sig_(Move(sig)), name_(name), code_(code), srcBegin_(0), srcEnd_(0),
+        Func(PropertyName *name, Signature &&sig, Label *entry)
+          : sig_(Move(sig)), name_(name), entry_(entry), srcBegin_(0), srcEnd_(0),
             compileTime_(0), defined_(false)
         {}
 
         PropertyName *name() const { return name_; }
         bool defined() const { return defined_; }
 
         void define(ModuleCompiler &m, ParseNode *fn) {
             JS_ASSERT(!defined_);
@@ -843,17 +845,17 @@ class MOZ_STACK_CLASS ModuleCompiler
             srcBegin_ = fn->pn_pos.begin - m.srcStart();
             srcEnd_ = fn->pn_pos.end - m.srcStart();
         }
 
         uint32_t srcBegin() const { JS_ASSERT(defined_); return srcBegin_; }
         uint32_t srcEnd() const { JS_ASSERT(defined_); return srcEnd_; }
         Signature &sig() { return sig_; }
         const Signature &sig() const { return sig_; }
-        Label *code() const { return code_; }
+        Label &entry() const { return *entry_; }
         uint32_t compileTime() const { return compileTime_; }
         void accumulateCompileTime(uint32_t ms) { compileTime_ += ms; }
     };
 
     class Global
     {
       public:
         enum Which {
@@ -966,16 +968,19 @@ class MOZ_STACK_CLASS ModuleCompiler
         Signature sig_;
 
       public:
         ExitDescriptor(PropertyName *name, Signature &&sig)
           : name_(name), sig_(Move(sig)) {}
         ExitDescriptor(ExitDescriptor &&rhs)
           : name_(rhs.name_), sig_(Move(rhs.sig_))
         {}
+        PropertyName *name() const {
+            return name_;
+        }
         const Signature &sig() const {
             return sig_;
         }
 
         // ExitDescriptor is a HashPolicy:
         typedef ExitDescriptor Lookup;
         static HashNumber hash(const ExitDescriptor &d) {
             HashNumber hn = HashGeneric(d.name_, d.sig_.retType().which());
@@ -1039,17 +1044,17 @@ class MOZ_STACK_CLASS ModuleCompiler
     FuncVector                     functions_;
     FuncPtrTableVector             funcPtrTables_;
     ExitMap                        exits_;
     MathNameMap                    standardLibraryMathNames_;
     NonAssertingLabel              stackOverflowLabel_;
     NonAssertingLabel              asyncInterruptLabel_;
     NonAssertingLabel              syncInterruptLabel_;
 
-    char *                         errorString_;
+    UniquePtr<char[], JS::FreePolicy> errorString_;
     uint32_t                       errorOffset_;
     bool                           errorOverRecursed_;
 
     int64_t                        usecBefore_;
     SlowFunctionVector             slowFunctions_;
 
     DebugOnly<bool>                finishedFunctionBodies_;
 
@@ -1091,18 +1096,17 @@ class MOZ_STACK_CLASS ModuleCompiler
         JS_ASSERT(moduleFunctionNode_->pn_funbox == parser.pc->sc->asFunctionBox());
     }
 
     ~ModuleCompiler() {
         if (errorString_) {
             JS_ASSERT(errorOffset_ != UINT32_MAX);
             tokenStream().reportAsmJSError(errorOffset_,
                                            JSMSG_USE_ASM_TYPE_FAIL,
-                                           errorString_);
-            js_free(errorString_);
+                                           errorString_.get());
         }
         if (errorOverRecursed_)
             js_ReportOverRecursed(cx_);
     }
 
     bool init() {
         if (!globals_.init() || !exits_.init())
             return false;
@@ -1153,17 +1157,17 @@ class MOZ_STACK_CLASS ModuleCompiler
         return true;
     }
 
     bool failOffset(uint32_t offset, const char *str) {
         JS_ASSERT(!errorString_);
         JS_ASSERT(errorOffset_ == UINT32_MAX);
         JS_ASSERT(str);
         errorOffset_ = offset;
-        errorString_ = js_strdup(cx_, str);
+        errorString_ = DuplicateString(cx_, str);
         return false;
     }
 
     bool fail(ParseNode *pn, const char *str) {
         if (pn)
             return failOffset(pn->pn_pos.begin, str);
 
         // The exact rooting static analysis does not perform dataflow analysis, so it believes
@@ -1174,17 +1178,17 @@ class MOZ_STACK_CLASS ModuleCompiler
         return failOffset(tokenStream().peekTokenPos().begin, str);
     }
 
     bool failfVA(ParseNode *pn, const char *fmt, va_list ap) {
         JS_ASSERT(!errorString_);
         JS_ASSERT(errorOffset_ == UINT32_MAX);
         JS_ASSERT(fmt);
         errorOffset_ = pn ? pn->pn_pos.begin : tokenStream().currentToken().pos.end;
-        errorString_ = JS_vsmprintf(fmt, ap);
+        errorString_.reset(JS_vsmprintf(fmt, ap));
         return false;
     }
 
     bool failf(ParseNode *pn, const char *fmt, ...) {
         va_list ap;
         va_start(ap, fmt);
         failfVA(pn, fmt, ap);
         va_end(ap);
@@ -1200,28 +1204,16 @@ class MOZ_STACK_CLASS ModuleCompiler
         return false;
     }
 
     bool failOverRecursed() {
         errorOverRecursed_ = true;
         return false;
     }
 
-    static const unsigned SLOW_FUNCTION_THRESHOLD_MS = 250;
-
-    bool maybeReportCompileTime(const Func &func) {
-        if (func.compileTime() < SLOW_FUNCTION_THRESHOLD_MS)
-            return true;
-        SlowFunction sf;
-        sf.name = func.name();
-        sf.ms = func.compileTime();
-        tokenStream().srcCoords.lineNumAndColumnIndex(func.srcBegin(), &sf.line, &sf.column);
-        return slowFunctions_.append(sf);
-    }
-
     /*************************************************** Read-only interface */
 
     ExclusiveContext *cx() const { return cx_; }
     AsmJSParser &parser() const { return parser_; }
     TokenStream &tokenStream() const { return parser_.tokenStream; }
     MacroAssembler &masm() { return masm_; }
     Label &stackOverflowLabel() { return stackOverflowLabel_; }
     Label &asyncInterruptLabel() { return asyncInterruptLabel_; }
@@ -1313,20 +1305,20 @@ class MOZ_STACK_CLASS ModuleCompiler
     bool addFunction(PropertyName *name, Signature &&sig, Func **func) {
         JS_ASSERT(!finishedFunctionBodies_);
         Global *global = moduleLifo_.new_<Global>(Global::Function);
         if (!global)
             return false;
         global->u.funcIndex_ = functions_.length();
         if (!globals_.putNew(name, global))
             return false;
-        Label *code = moduleLifo_.new_<Label>();
-        if (!code)
+        Label *entry = moduleLifo_.new_<Label>();
+        if (!entry)
             return false;
-        *func = moduleLifo_.new_<Func>(name, Move(sig), code);
+        *func = moduleLifo_.new_<Func>(name, Move(sig), entry);
         if (!*func)
             return false;
         return functions_.append(*func);
     }
     bool addFuncPtrTable(PropertyName *name, Signature &&sig, uint32_t mask, FuncPtrTable **table) {
         Global *global = moduleLifo_.new_<Global>(Global::FuncPtrTable);
         if (!global)
             return false;
@@ -1423,82 +1415,104 @@ class MOZ_STACK_CLASS ModuleCompiler
     }
     LifoAlloc &lifo() {
         return moduleLifo_;
     }
 
     void startFunctionBodies() {
         module_->startFunctionBodies();
     }
-
-    void startGeneratingFunction(Func &func, MIRGenerator &mir) {
-        // A single MacroAssembler is reused for all function compilations so
-        // that there is a single linear code segment for each module. To avoid
-        // spiking memory, a LifoAllocScope in the caller frees all MIR/LIR
-        // after each function is compiled. This method is responsible for cleaning
-        // out any dangling pointers that the MacroAssembler may have kept.
-        masm_.resetForNewCodeGenerator(mir.alloc());
-        masm_.align(CodeAlignment);
-        masm_.bind(func.code());
-    }
-
-    bool finishGeneratingFunction(Func &func, MIRGenerator &mir, CodeGenerator &codegen) {
-        JS_ASSERT(func.defined() && func.code()->bound());
-
-        PropertyName *name = func.name();
-        uint32_t codeBegin = func.code()->offset();
-        uint32_t codeEnd = masm_.currentOffset();
-        if (!module_->addFunctionCodeRange(name, codeBegin, codeEnd))
+    bool finishGeneratingFunction(Func &func, CodeGenerator &codegen,
+                                  const AsmJSFunctionLabels &labels)
+    {
+        if (!module_->addFunctionCodeRange(func.name(), labels))
             return false;
 
         jit::IonScriptCounts *counts = codegen.extractScriptCounts();
         if (counts && !module_->addFunctionCounts(counts)) {
             js_delete(counts);
             return false;
         }
 
+        if (func.compileTime() >= 250) {
+            SlowFunction sf;
+            sf.name = func.name();
+            sf.ms = func.compileTime();
+            tokenStream().srcCoords.lineNumAndColumnIndex(func.srcBegin(), &sf.line, &sf.column);
+            if (!slowFunctions_.append(sf))
+                return false;
+        }
+
 #if defined(MOZ_VTUNE) || defined(JS_ION_PERF)
-        unsigned line = 0, column = 0;
+        uint32_t line, column;
         tokenStream().srcCoords.lineNumAndColumnIndex(func.srcBegin(), &line, &column);
-        if (!module_->addProfiledFunction(name, codeBegin, codeEnd, line, column))
+        unsigned begin = labels.begin.offset();
+        unsigned end = labels.end.offset();
+        if (!module_->addProfiledFunction(func.name(), begin, end, line, column))
             return false;
 # ifdef JS_ION_PERF
         // Per-block profiling info uses significantly more memory so only store
         // this information if it is actively requested.
         if (PerfBlockEnabled()) {
-            AsmJSPerfSpewer &ps = mir.perfSpewer();
+            AsmJSPerfSpewer &ps = codegen.mirGen().perfSpewer();
             ps.noteBlocksOffsets();
             unsigned inlineEnd = ps.endInlineCode.offset();
-            if (!module_->addProfiledBlocks(name, codeBegin, inlineEnd, codeEnd, ps.basicBlocks()))
+            if (!module_->addProfiledBlocks(func.name(), begin, inlineEnd, end, ps.basicBlocks()))
                 return false;
         }
 # endif
 #endif
         return true;
     }
-
     void finishFunctionBodies() {
+        // When an interrupt is triggered, all function code is mprotected and,
+        // for sanity, stub code (particularly the interrupt stub) is not.
+        // Protection works at page granularity, so we need to ensure that no
+        // stub code gets into the function code pages.
         JS_ASSERT(!finishedFunctionBodies_);
         masm_.align(AsmJSPageSize);
+        module_->finishFunctionBodies(masm_.currentOffset());
         finishedFunctionBodies_ = true;
-        module_->finishFunctionBodies(masm_.currentOffset());
-    }
-
-    void startGeneratingEntry(unsigned exportIndex) {
-        module_->exportedFunction(exportIndex).initCodeOffset(masm_.currentOffset());
-    }
-    bool finishGeneratingEntry(unsigned exportIndex) {
-        return module_->addEntryCodeRange(exportIndex, masm_.currentOffset());
-    }
-
-    void setInterpExitOffset(unsigned exitIndex) {
-        module_->exit(exitIndex).initInterpOffset(masm_.currentOffset());
-    }
-    void setIonExitOffset(unsigned exitIndex) {
-        module_->exit(exitIndex).initIonOffset(masm_.currentOffset());
+    }
+
+    bool finishGeneratingEntry(unsigned exportIndex, Label *begin) {
+        JS_ASSERT(finishedFunctionBodies_);
+        module_->exportedFunction(exportIndex).initCodeOffset(begin->offset());
+        uint32_t end = masm_.currentOffset();
+        return module_->addEntryCodeRange(begin->offset(), end);
+    }
+    bool finishGeneratingFFI(Label *begin, Label *profilingReturn) {
+        JS_ASSERT(finishedFunctionBodies_);
+        uint32_t end = masm_.currentOffset();
+        return module_->addFFICodeRange(begin->offset(), profilingReturn->offset(), end);
+    }
+    bool finishGeneratingInterpExit(unsigned exitIndex, Label *begin, Label *profilingReturn) {
+        JS_ASSERT(finishedFunctionBodies_);
+        module_->exit(exitIndex).initInterpOffset(begin->offset());
+        return finishGeneratingFFI(begin, profilingReturn);
+    }
+    bool finishGeneratingIonExit(unsigned exitIndex, Label *begin, Label *profilingReturn) {
+        JS_ASSERT(finishedFunctionBodies_);
+        module_->exit(exitIndex).initIonOffset(begin->offset());
+        return finishGeneratingFFI(begin, profilingReturn);
+    }
+    bool finishGeneratingInterrupt(Label *begin, Label *profilingReturn) {
+        JS_ASSERT(finishedFunctionBodies_);
+        uint32_t end = masm_.currentOffset();
+        return module_->addInterruptCodeRange(begin->offset(), profilingReturn->offset(), end);
+    }
+    bool finishGeneratingInlineStub(Label *begin) {
+        JS_ASSERT(finishedFunctionBodies_);
+        uint32_t end = masm_.currentOffset();
+        return module_->addInlineCodeRange(begin->offset(), end);
+    }
+    bool finishGeneratingBuiltinThunk(AsmJSExit::BuiltinKind builtin, Label *begin, Label *pret) {
+        JS_ASSERT(finishedFunctionBodies_);
+        uint32_t end = masm_.currentOffset();
+        return module_->addBuiltinThunkCodeRange(builtin, begin->offset(), pret->offset(), end);
     }
 
     void buildCompilationTimeReport(bool storedInCache, ScopedJSFreePtr<char> *out) {
         ScopedJSFreePtr<char> slowFuns;
 #ifndef JS_MORE_DETERMINISTIC
         int64_t usecAfter = PRMJ_Now();
         int msTotal = (usecAfter - usecBefore_) / PRMJ_USEC_PER_MSEC;
         if (!slowFunctions_.empty()) {
@@ -1536,17 +1550,17 @@ class MOZ_STACK_CLASS ModuleCompiler
         // Finally, convert all the function-pointer table elements into
         // RelativeLinks that will be patched by AsmJSModule::staticallyLink.
         for (unsigned tableIndex = 0; tableIndex < funcPtrTables_.length(); tableIndex++) {
             FuncPtrTable &table = funcPtrTables_[tableIndex];
             unsigned tableBaseOffset = module_->offsetOfGlobalData() + table.globalDataOffset();
             for (unsigned elemIndex = 0; elemIndex < table.numElems(); elemIndex++) {
                 AsmJSModule::RelativeLink link(AsmJSModule::RelativeLink::RawPointer);
                 link.patchAtOffset = tableBaseOffset + elemIndex * sizeof(uint8_t*);
-                link.targetOffset = masm_.actualOffset(table.elem(elemIndex).code()->offset());
+                link.targetOffset = masm_.actualOffset(table.elem(elemIndex).entry().offset());
                 if (!module_->addRelativeLink(link))
                     return false;
             }
         }
 
         *module = module_.forget();
         return true;
     }
@@ -2173,17 +2187,17 @@ class FunctionCompiler
         if (inDeadCode())
             return;
 
         if (m().usesSignalHandlersForInterrupt())
             return;
 
         unsigned lineno = 0, column = 0;
         m().tokenStream().srcCoords.lineNumAndColumnIndex(pn->pn_pos.begin, &lineno, &column);
-        CallSiteDesc callDesc(lineno, column);
+        CallSiteDesc callDesc(lineno, column, CallSiteDesc::Relative);
         curBlock_->add(MAsmJSInterruptCheck::New(alloc(), &m().syncInterruptLabel(), callDesc));
     }
 
     /***************************************************************** Calls */
 
     // The IonMonkey backend maintains a single stack offset (from the stack
     // pointer to the base of the frame) by adding the total amount of spill
     // space required plus the maximum stack required for argument passing.
@@ -2290,32 +2304,38 @@ class FunctionCompiler
         if (inDeadCode()) {
             *def = nullptr;
             return true;
         }
 
         uint32_t line, column;
         m_.tokenStream().srcCoords.lineNumAndColumnIndex(call.node_->pn_pos.begin, &line, &column);
 
-        CallSiteDesc desc(line, column);
-        MAsmJSCall *ins = MAsmJSCall::New(alloc(), desc, callee, call.regArgs_, returnType,
-                                          call.spIncrement_);
+        CallSiteDesc::Kind kind;
+        switch (callee.which()) {
+          case MAsmJSCall::Callee::Internal: kind = CallSiteDesc::Relative; break;
+          case MAsmJSCall::Callee::Dynamic:  kind = CallSiteDesc::Register; break;
+          case MAsmJSCall::Callee::Builtin:  kind = CallSiteDesc::Register; break;
+        }
+
+        MAsmJSCall *ins = MAsmJSCall::New(alloc(), CallSiteDesc(line, column, kind), callee,
+                                          call.regArgs_, returnType, call.spIncrement_);
         if (!ins)
             return false;
 
         curBlock_->add(ins);
         *def = ins;
         return true;
     }
 
   public:
     bool internalCall(const ModuleCompiler::Func &func, const Call &call, MDefinition **def)
     {
         MIRType returnType = func.sig().retType().toMIRType();
-        return callPrivate(MAsmJSCall::Callee(func.code()), call, returnType, def);
+        return callPrivate(MAsmJSCall::Callee(&func.entry()), call, returnType, def);
     }
 
     bool funcPtrCall(const ModuleCompiler::FuncPtrTable &table, MDefinition *index,
                      const Call &call, MDefinition **def)
     {
         if (inDeadCode()) {
             *def = nullptr;
             return true;
@@ -4028,30 +4048,30 @@ CheckMathBuiltinCall(FunctionCompiler &f
       case AsmJSMathBuiltin_imul:   return CheckMathIMul(f, callNode, retType, def, type);
       case AsmJSMathBuiltin_abs:    return CheckMathAbs(f, callNode, retType, def, type);
       case AsmJSMathBuiltin_sqrt:   return CheckMathSqrt(f, callNode, retType, def, type);
       case AsmJSMathBuiltin_fround: return CheckMathFRound(f, callNode, retType, def, type);
       case AsmJSMathBuiltin_min:    return CheckMathMinMax(f, callNode, retType, def, type, /* isMax = */ false);
       case AsmJSMathBuiltin_max:    return CheckMathMinMax(f, callNode, retType, def, type, /* isMax = */ true);
       case AsmJSMathBuiltin_ceil:   arity = 1; doubleCallee = AsmJSImm_CeilD;  floatCallee = AsmJSImm_CeilF;   break;
       case AsmJSMathBuiltin_floor:  arity = 1; doubleCallee = AsmJSImm_FloorD; floatCallee = AsmJSImm_FloorF;  break;
-      case AsmJSMathBuiltin_sin:    arity = 1; doubleCallee = AsmJSImm_SinD;   floatCallee = AsmJSImm_Invalid; break;
-      case AsmJSMathBuiltin_cos:    arity = 1; doubleCallee = AsmJSImm_CosD;   floatCallee = AsmJSImm_Invalid; break;
-      case AsmJSMathBuiltin_tan:    arity = 1; doubleCallee = AsmJSImm_TanD;   floatCallee = AsmJSImm_Invalid; break;
-      case AsmJSMathBuiltin_asin:   arity = 1; doubleCallee = AsmJSImm_ASinD;  floatCallee = AsmJSImm_Invalid; break;
-      case AsmJSMathBuiltin_acos:   arity = 1; doubleCallee = AsmJSImm_ACosD;  floatCallee = AsmJSImm_Invalid; break;
-      case AsmJSMathBuiltin_atan:   arity = 1; doubleCallee = AsmJSImm_ATanD;  floatCallee = AsmJSImm_Invalid; break;
-      case AsmJSMathBuiltin_exp:    arity = 1; doubleCallee = AsmJSImm_ExpD;   floatCallee = AsmJSImm_Invalid; break;
-      case AsmJSMathBuiltin_log:    arity = 1; doubleCallee = AsmJSImm_LogD;   floatCallee = AsmJSImm_Invalid; break;
-      case AsmJSMathBuiltin_pow:    arity = 2; doubleCallee = AsmJSImm_PowD;   floatCallee = AsmJSImm_Invalid; break;
-      case AsmJSMathBuiltin_atan2:  arity = 2; doubleCallee = AsmJSImm_ATan2D; floatCallee = AsmJSImm_Invalid; break;
+      case AsmJSMathBuiltin_sin:    arity = 1; doubleCallee = AsmJSImm_SinD;   floatCallee = AsmJSImm_Limit; break;
+      case AsmJSMathBuiltin_cos:    arity = 1; doubleCallee = AsmJSImm_CosD;   floatCallee = AsmJSImm_Limit; break;
+      case AsmJSMathBuiltin_tan:    arity = 1; doubleCallee = AsmJSImm_TanD;   floatCallee = AsmJSImm_Limit; break;
+      case AsmJSMathBuiltin_asin:   arity = 1; doubleCallee = AsmJSImm_ASinD;  floatCallee = AsmJSImm_Limit; break;
+      case AsmJSMathBuiltin_acos:   arity = 1; doubleCallee = AsmJSImm_ACosD;  floatCallee = AsmJSImm_Limit; break;
+      case AsmJSMathBuiltin_atan:   arity = 1; doubleCallee = AsmJSImm_ATanD;  floatCallee = AsmJSImm_Limit; break;
+      case AsmJSMathBuiltin_exp:    arity = 1; doubleCallee = AsmJSImm_ExpD;   floatCallee = AsmJSImm_Limit; break;
+      case AsmJSMathBuiltin_log:    arity = 1; doubleCallee = AsmJSImm_LogD;   floatCallee = AsmJSImm_Limit; break;
+      case AsmJSMathBuiltin_pow:    arity = 2; doubleCallee = AsmJSImm_PowD;   floatCallee = AsmJSImm_Limit; break;
+      case AsmJSMathBuiltin_atan2:  arity = 2; doubleCallee = AsmJSImm_ATan2D; floatCallee = AsmJSImm_Limit; break;
       default: MOZ_ASSUME_UNREACHABLE("unexpected mathBuiltin function");
     }
 
-    if (retType == RetType::Float && floatCallee == AsmJSImm_Invalid)
+    if (retType == RetType::Float && floatCallee == AsmJSImm_Limit)
         return f.fail(callNode, "math builtin cannot be used as float");
     if (retType != RetType::Double && retType != RetType::Float)
         return f.failf(callNode, "return type of math function is double or float, used as %s", retType.toType().toChars());
 
     FunctionCompiler::Call call(f, callNode, retType);
     if (retType == RetType::Float && !CheckCallArgs(f, callNode, CheckIsMaybeFloat, &call))
         return false;
     if (retType == RetType::Double && !CheckCallArgs(f, callNode, CheckIsMaybeDouble, &call))
@@ -5381,41 +5401,48 @@ CheckFunction(ModuleCompiler &m, LifoAll
     return true;
 }
 
 static bool
 GenerateCode(ModuleCompiler &m, ModuleCompiler::Func &func, MIRGenerator &mir, LIRGraph &lir)
 {
     int64_t before = PRMJ_Now();
 
-    m.startGeneratingFunction(func, mir);
+    // A single MacroAssembler is reused for all function compilations so
+    // that there is a single linear code segment for each module. To avoid
+    // spiking memory, a LifoAllocScope in the caller frees all MIR/LIR
+    // after each function is compiled. This method is responsible for cleaning
+    // out any dangling pointers that the MacroAssembler may have kept.
+    m.masm().resetForNewCodeGenerator(mir.alloc());
 
     ScopedJSDeletePtr<CodeGenerator> codegen(js_new<CodeGenerator>(&mir, &lir, &m.masm()));
-    if (!codegen || !codegen->generateAsmJS(&m.stackOverflowLabel()))
-        return m.fail(nullptr, "internal codegen failure (probably out of memory)");
-
-    if (!m.finishGeneratingFunction(func, mir, *codegen))
+    if (!codegen)
+        return false;
+
+    AsmJSFunctionLabels labels(func.entry(), m.stackOverflowLabel());
+    if (!codegen->generateAsmJS(&labels))
         return false;
 
     func.accumulateCompileTime((PRMJ_Now() - before) / PRMJ_USEC_PER_MSEC);
-    if (!m.maybeReportCompileTime(func))
-        return false;
-
-    // Unlike regular IonMonkey which links and generates a new JitCode for
+
+    if (!m.finishGeneratingFunction(func, *codegen, labels))
+        return false;
+
+    // Unlike regular IonMonkey, which links and generates a new JitCode for
     // every function, we accumulate all the functions in the module in a
     // single MacroAssembler and link at end. Linking asm.js doesn't require a
     // CodeGenerator so we can destroy it now (via ScopedJSDeletePtr).
     return true;
 }
 
 static bool
 CheckAllFunctionsDefined(ModuleCompiler &m)
 {
     for (unsigned i = 0; i < m.numFunctions(); i++) {
-        if (!m.function(i).code()->bound())
+        if (!m.function(i).entry().bound())
             return m.failName(nullptr, "missing definition of function %s", m.function(i).name());
     }
 
     return true;
 }
 
 static bool
 CheckFunctionsSequential(ModuleCompiler &m)
@@ -5862,62 +5889,38 @@ CheckModuleReturn(ModuleCompiler &m)
     // (since cx->tempLifoAlloc is marked/released after each function
     // statement) and thus all the identifiers in the return statement will be
     // mistaken as free variables and added to lexdeps. Clear these now.
     m.parser().pc->lexdeps->clear();
     return true;
 }
 
 static void
-LoadAsmJSActivationIntoRegister(MacroAssembler &masm, Register reg)
-{
-    masm.movePtr(AsmJSImmPtr(AsmJSImm_Runtime), reg);
-    size_t offset = offsetof(JSRuntime, mainThread) +
-                    PerThreadData::offsetOfAsmJSActivationStackReadOnly();
-    masm.loadPtr(Address(reg, offset), reg);
-}
-
-static void
-LoadJSContextFromActivation(MacroAssembler &masm, Register activation, Register dest)
-{
-    masm.loadPtr(Address(activation, AsmJSActivation::offsetOfContext()), dest);
-}
-
-static void
 AssertStackAlignment(MacroAssembler &masm)
 {
-    JS_ASSERT((AsmJSFrameSize + masm.framePushed()) % StackAlignment == 0);
-#ifdef DEBUG
-    Label ok;
-    JS_ASSERT(IsPowerOfTwo(StackAlignment));
-    masm.branchTestPtr(Assembler::Zero, StackPointer, Imm32(StackAlignment - 1), &ok);
-    masm.breakpoint();
-    masm.bind(&ok);
-#endif
+    JS_ASSERT((sizeof(AsmJSFrame) + masm.framePushed()) % StackAlignment == 0);
+    masm.assertStackAlignment();
+}
+
+static unsigned
+StackDecrementForCall(MacroAssembler &masm, unsigned bytesToPush)
+{
+    return StackDecrementForCall(sizeof(AsmJSFrame) + masm.framePushed(), bytesToPush);
 }
 
 template <class VectorT>
 static unsigned
 StackArgBytes(const VectorT &argTypes)
 {
     ABIArgIter<VectorT> iter(argTypes);
     while (!iter.done())
         iter++;
     return iter.stackBytesConsumedSoFar();
 }
 
-static unsigned
-StackDecrementForCall(MacroAssembler &masm, unsigned bytesToPush)
-{
-    // Include extra padding so that, after pushing the bytesToPush,
-    // the stack is aligned for a call instruction.
-    unsigned alreadyPushed = AsmJSFrameSize + masm.framePushed();
-    return AlignBytes(alreadyPushed + bytesToPush, StackAlignment) - alreadyPushed;
-}
-
 template <class VectorT>
 static unsigned
 StackDecrementForCall(MacroAssembler &masm, const VectorT &argTypes, unsigned extraBytes = 0)
 {
     return StackDecrementForCall(masm, StackArgBytes(argTypes) + extraBytes);
 }
 
 #if defined(JS_CODEGEN_ARM)
@@ -5940,73 +5943,65 @@ static const unsigned FramePushedAfterSa
                                              NonVolatileRegs.fpus().size() * sizeof(double) +
                                              sizeof(double);
 #else
 static const unsigned FramePushedAfterSave = NonVolatileRegs.gprs().size() * sizeof(intptr_t) +
                                              NonVolatileRegs.fpus().getPushSizeInBytes();
 #endif
 
 static bool
-GenerateEntry(ModuleCompiler &m, const AsmJSModule::ExportedFunction &exportedFunc)
-{
+GenerateEntry(ModuleCompiler &m, unsigned exportIndex)
+{
+    PropertyName *funcName = m.module().exportedFunction(exportIndex).name();
+    const ModuleCompiler::Func &func = *m.lookupFunction(funcName);
+
     MacroAssembler &masm = m.masm();
 
+    Label begin;
+    GenerateAsmJSEntryPrologue(masm, &begin);
+
     // In constrast to the system ABI, the Ion convention is that all registers
     // are clobbered by calls. Thus, we must save the caller's non-volatile
     // registers.
-    //
-    // NB: GenerateExits assumes that masm.framePushed() == 0 before
-    // PushRegsInMask(NonVolatileRegs).
-    masm.setFramePushed(0);
-
-    // See AsmJSFrameSize comment in Assembler-shared.h.
-#if defined(JS_CODEGEN_ARM)
-    masm.push(lr);
-#endif // JS_CODEGEN_ARM
-#if defined(JS_CODEGEN_MIPS)
-    masm.push(ra);
-#endif
-
     masm.PushRegsInMask(NonVolatileRegs);
     JS_ASSERT(masm.framePushed() == FramePushedAfterSave);
 
-    // Remember the stack pointer in the current AsmJSActivation. This will be
-    // used by error exit paths to set the stack pointer back to what it was
-    // right after the (C++) caller's non-volatile registers were saved so that
-    // they can be restored.
-    Register activation = ABIArgGenerator::NonArgReturnVolatileReg0;
-    LoadAsmJSActivationIntoRegister(masm, activation);
-    masm.storePtr(StackPointer, Address(activation, AsmJSActivation::offsetOfErrorRejoinSP()));
-
     // ARM and MIPS have a globally-pinned GlobalReg (x64 uses RIP-relative
     // addressing, x86 uses immediates in effective addresses) and NaN register
     // (used as part of the out-of-bounds handling in heap loads/stores).
 #if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
     masm.movePtr(IntArgReg1, GlobalReg);
     masm.loadConstantDouble(GenericNaN(), NANReg);
 #endif
 
     // ARM, MIPS and x64 have a globally-pinned HeapReg (x86 uses immediates in
     // effective addresses).
 #if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
     masm.loadPtr(Address(IntArgReg1, AsmJSModule::heapGlobalDataOffset()), HeapReg);
 #endif
 
+    // Remember the stack pointer in the current AsmJSActivation. This will be
+    // used by error exit paths to set the stack pointer back to what it was
+    // right after the (C++) caller's non-volatile registers were saved so that
+    // they can be restored.
+    Register activation = ABIArgGenerator::NonArgReturnVolatileReg0;
+    masm.loadAsmJSActivation(activation);
+    masm.storePtr(StackPointer, Address(activation, AsmJSActivation::offsetOfErrorRejoinSP()));
+
     // Get 'argv' into a non-arg register and save it on the stack.
     Register argv = ABIArgGenerator::NonArgReturnVolatileReg0;
     Register scratch = ABIArgGenerator::NonArgReturnVolatileReg1;
 #if defined(JS_CODEGEN_X86)
-    masm.loadPtr(Address(StackPointer, AsmJSFrameSize + masm.framePushed()), argv);
+    masm.loadPtr(Address(StackPointer, sizeof(AsmJSFrame) + masm.framePushed()), argv);
 #else
     masm.movePtr(IntArgReg0, argv);
 #endif
     masm.Push(argv);
 
     // Bump the stack for the call.
-    const ModuleCompiler::Func &func = *m.lookupFunction(exportedFunc.name());
     unsigned stackDec = StackDecrementForCall(masm, func.sig().args());
     masm.reserveStack(stackDec);
 
     // Copy parameters out of argv and into the registers/stack-slots specified by
     // the system ABI.
     for (ABIArgTypeIter iter(func.sig().args()); !iter.done(); iter++) {
         unsigned argOffset = iter.index() * sizeof(uint64_t);
         Address src(argv, argOffset);
@@ -6027,17 +6022,17 @@ GenerateEntry(ModuleCompiler &m, const A
                 masm.storeDouble(ScratchDoubleReg, Address(StackPointer, iter->offsetFromArgBase()));
             }
             break;
         }
     }
 
     // Call into the real function.
     AssertStackAlignment(masm);
-    masm.call(func.code());
+    masm.call(CallSiteDesc(CallSiteDesc::Relative), &func.entry());
 
     // Pop the stack and recover the original 'argv' argument passed to the
     // trampoline (which was pushed on the stack).
     masm.freeStack(stackDec);
     masm.Pop(argv);
 
     // Store the return value in argv[0]
     switch (func.sig().retType().which()) {
@@ -6052,138 +6047,41 @@ GenerateEntry(ModuleCompiler &m, const A
       case RetType::Double:
         masm.canonicalizeDouble(ReturnDoubleReg);
         masm.storeDouble(ReturnDoubleReg, Address(argv, 0));
         break;
     }
 
     // Restore clobbered non-volatile registers of the caller.
     masm.PopRegsInMask(NonVolatileRegs);
-
     JS_ASSERT(masm.framePushed() == 0);
 
     masm.move32(Imm32(true), ReturnReg);
-    masm.ret();
-    return true;
-}
-
-// This function and InvokeFromAsmJS* functions all return int32_t rather than
-// bool to prevent the compiler from optimizing bits higher than what's
-// actually needed for a bool (as the result is tested in asm.js generated code
-// which the compiler isn't aware of).
-static inline int32_t
-TryEnablingIon(JSContext *cx, AsmJSModule &module, HandleFunction fun, uint32_t exitIndex,
-               int32_t argc, Value *argv)
-{
-    if (!fun->hasScript())
-        return true;
-
-    // Test if the function is Ion compiled
-    JSScript *script = fun->nonLazyScript();
-    if (!script->hasIonScript())
-        return true;
-
-    // Currently we can't rectify arguments. Therefore disabling if argc is too low.
-    if (fun->nargs() > size_t(argc))
-        return true;
-
-    // Normally the types should corresond, since we just ran with those types,
-    // but there are reports this is asserting. Therefore doing it as a check, instead of DEBUG only.
-    if (!types::TypeScript::ThisTypes(script)->hasType(types::Type::UndefinedType()))
-        return true;
-    for(uint32_t i = 0; i < fun->nargs(); i++) {
-        types::StackTypeSet *typeset = types::TypeScript::ArgTypes(script, i);
-        types::Type type = types::Type::DoubleType();
-        if (!argv[i].isDouble())
-            type = types::Type::PrimitiveType(argv[i].extractNonDoubleType());
-        if (!typeset->hasType(type))
-            return true;
-    }
-
-    // Enable
-    IonScript *ionScript = script->ionScript();
-    if (!ionScript->addDependentAsmJSModule(cx, DependentAsmJSModuleExit(&module, exitIndex)))
-        return false;
-
-    module.exitIndexToGlobalDatum(exitIndex).exit = module.ionExitTrampoline(module.exit(exitIndex));
-    return true;
-}
-
-namespace js {
-
-// See comment above TryEnablingIon.
-int32_t
-InvokeFromAsmJS(JSContext *cx, int32_t exitIndex, int32_t argc, Value *argv,
-                MutableHandleValue rval)
-{
-    AsmJSModule &module = cx->mainThread().asmJSActivationStackFromOwnerThread()->module();
-
-    RootedFunction fun(cx, module.exitIndexToGlobalDatum(exitIndex).fun);
-    RootedValue fval(cx, ObjectValue(*fun));
-    if (!Invoke(cx, UndefinedValue(), fval, argc, argv, rval))
-        return false;
-
-    return TryEnablingIon(cx, module, fun, exitIndex, argc, argv);
-}
-
-int32_t
-InvokeFromAsmJS_Ignore(JSContext *cx, int32_t exitIndex, int32_t argc, Value *argv)
-{
-    RootedValue rval(cx);
-    return InvokeFromAsmJS(cx, exitIndex, argc, argv, &rval);
-}
-
-int32_t
-InvokeFromAsmJS_ToInt32(JSContext *cx, int32_t exitIndex, int32_t argc, Value *argv)
-{
-    RootedValue rval(cx);
-    if (!InvokeFromAsmJS(cx, exitIndex, argc, argv, &rval))
-        return false;
-
-    int32_t i32;
-    if (!ToInt32(cx, rval, &i32))
-        return false;
-    argv[0] = Int32Value(i32);
-    return true;
-}
-
-int32_t
-InvokeFromAsmJS_ToNumber(JSContext *cx, int32_t exitIndex, int32_t argc, Value *argv)
-{
-    RootedValue rval(cx);
-    if (!InvokeFromAsmJS(cx, exitIndex, argc, argv, &rval))
-        return false;
-
-    double dbl;
-    if (!ToNumber(cx, rval, &dbl))
-        return false;
-    argv[0] = DoubleValue(dbl);
-    return true;
-}
-
-}  // namespace js
+
+    GenerateAsmJSEntryEpilogue(masm);
+    return m.finishGeneratingEntry(exportIndex, &begin) && !masm.oom();
+}
 
 static void
 FillArgumentArray(ModuleCompiler &m, const VarTypeVector &argTypes,
                   unsigned offsetToArgs, unsigned offsetToCallerStackArgs,
                   Register scratch)
 {
     MacroAssembler &masm = m.masm();
 
     for (ABIArgTypeIter i(argTypes); !i.done(); i++) {
-        Address dstAddr = Address(StackPointer, offsetToArgs + i.index() * sizeof(Value));
+        Address dstAddr(StackPointer, offsetToArgs + i.index() * sizeof(Value));
         switch (i->kind()) {
           case ABIArg::GPR:
             masm.storeValue(JSVAL_TYPE_INT32, i->gpr(), dstAddr);
             break;
-          case ABIArg::FPU: {
-              masm.canonicalizeDouble(i->fpu());
-              masm.storeDouble(i->fpu(), dstAddr);
-              break;
-          }
+          case ABIArg::FPU:
+            masm.canonicalizeDouble(i->fpu());
+            masm.storeDouble(i->fpu(), dstAddr);
+            break;
           case ABIArg::Stack:
             if (i.mirType() == MIRType_Int32) {
                 Address src(StackPointer, offsetToCallerStackArgs + i->offsetFromArgBase());
 #if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
                 masm.load32(src, scratch);
                 masm.storeValue(JSVAL_TYPE_INT32, scratch, dstAddr);
 #else
                 masm.memIntToValue(src, dstAddr);
@@ -6195,86 +6093,65 @@ FillArgumentArray(ModuleCompiler &m, con
                 masm.canonicalizeDouble(ScratchDoubleReg);
                 masm.storeDouble(ScratchDoubleReg, dstAddr);
             }
             break;
         }
     }
 }
 
-static void
-GenerateFFIInterpreterExit(ModuleCompiler &m, const ModuleCompiler::ExitDescriptor &exit,
-                           unsigned exitIndex, Label *throwLabel)
+static bool
+GenerateFFIInterpExit(ModuleCompiler &m, const ModuleCompiler::ExitDescriptor &exit,
+                      unsigned exitIndex, Label *throwLabel)
 {
     MacroAssembler &masm = m.masm();
-    masm.align(CodeAlignment);
-    m.setInterpExitOffset(exitIndex);
-    masm.setFramePushed(0);
-
-    // See AsmJSFrameSize comment in Assembler-shared.h.
-#if defined(JS_CODEGEN_ARM)
-    masm.push(lr);
-#elif defined(JS_CODEGEN_MIPS)
-    masm.push(ra);
-#endif
-
-    // Store the frame pointer in AsmJSActivation::exitFP for stack unwinding.
-    Register activation = ABIArgGenerator::NonArgReturnVolatileReg0;
-    LoadAsmJSActivationIntoRegister(masm, activation);
-    masm.storePtr(StackPointer, Address(activation, AsmJSActivation::offsetOfExitFP()));
-
-    MIRType typeArray[] = { MIRType_Pointer,   // cx
-                            MIRType_Pointer,   // exitDatum
+    JS_ASSERT(masm.framePushed() == 0);
+
+    // Argument types for InvokeFromAsmJS_*:
+    MIRType typeArray[] = { MIRType_Pointer,   // exitDatum
                             MIRType_Int32,     // argc
                             MIRType_Pointer }; // argv
     MIRTypeVector invokeArgTypes(m.cx());
     invokeArgTypes.infallibleAppend(typeArray, ArrayLength(typeArray));
 
     // At the point of the call, the stack layout shall be (sp grows to the left):
     //   | stack args | padding | Value argv[] | padding | retaddr | caller stack args |
-    // The padding between stack args and argv ensures that sp is aligned. The
-    // padding between argv and retaddr ensures that argv is aligned.
-    unsigned offsetToArgv = AlignBytes(StackArgBytes(invokeArgTypes), StackAlignment);
+    // The padding between stack args and argv ensures that argv is aligned. The
+    // padding between argv and retaddr ensures that sp is aligned.
+    unsigned offsetToArgv = AlignBytes(StackArgBytes(invokeArgTypes), sizeof(double));
     unsigned argvBytes = Max<size_t>(1, exit.sig().args().length()) * sizeof(Value);
-    unsigned stackDec = StackDecrementForCall(masm, offsetToArgv + argvBytes);
-    masm.reserveStack(stackDec);
+    unsigned framePushed = StackDecrementForCall(masm, offsetToArgv + argvBytes);
+
+    Label begin;
+    GenerateAsmJSExitPrologue(masm, framePushed, AsmJSExit::FFI, &begin);
 
     // Fill the argument array.
-    unsigned offsetToCallerStackArgs = AsmJSFrameSize + masm.framePushed();
-    Register scratch = ABIArgGenerator::NonArgReturnVolatileReg1;
+    unsigned offsetToCallerStackArgs = sizeof(AsmJSFrame) + masm.framePushed();
+    Register scratch = ABIArgGenerator::NonArgReturnVolatileReg0;
     FillArgumentArray(m, exit.sig().args(), offsetToArgv, offsetToCallerStackArgs, scratch);
 
     // Prepare the arguments for the call to InvokeFromAsmJS_*.
     ABIArgMIRTypeIter i(invokeArgTypes);
 
-    // argument 0: cx
-    if (i->kind() == ABIArg::GPR) {
-        LoadJSContextFromActivation(masm, activation, i->gpr());
-    } else {
-        LoadJSContextFromActivation(masm, activation, scratch);
-        masm.storePtr(scratch, Address(StackPointer, i->offsetFromArgBase()));
-    }
-    i++;
-
-    // argument 1: exitIndex
+    // argument 0: exitIndex
     if (i->kind() == ABIArg::GPR)
         masm.mov(ImmWord(exitIndex), i->gpr());
     else
         masm.store32(Imm32(exitIndex), Address(StackPointer, i->offsetFromArgBase()));
     i++;
 
-    // argument 2: argc
+    // argument 1: argc
     unsigned argc = exit.sig().args().length();
     if (i->kind() == ABIArg::GPR)
         masm.mov(ImmWord(argc), i->gpr());
     else
         masm.store32(Imm32(argc), Address(StackPointer, i->offsetFromArgBase()));
     i++;
 
-    // argument 3: argv
+    // argument 2: argv
     Address argv(StackPointer, offsetToArgv);
     if (i->kind() == ABIArg::GPR) {
         masm.computeEffectiveAddress(argv, i->gpr());
     } else {
         masm.computeEffectiveAddress(argv, scratch);
         masm.storePtr(scratch, Address(StackPointer, i->offsetFromArgBase()));
     }
     i++;
@@ -6297,61 +6174,36 @@ GenerateFFIInterpreterExit(ModuleCompile
         masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
         masm.loadDouble(argv, ReturnDoubleReg);
         break;
       case RetType::Float:
         MOZ_ASSUME_UNREACHABLE("Float32 shouldn't be returned from a FFI");
         break;
     }
 
-    // Note: the caller is IonMonkey code which means there are no non-volatile
-    // registers to restore.
-    masm.freeStack(stackDec);
-
-    // Clear exitFP before the frame is destroyed.
-    LoadAsmJSActivationIntoRegister(masm, activation);
-    masm.storePtr(ImmWord(0), Address(activation, AsmJSActivation::offsetOfExitFP()));
-
-    masm.ret();
+    Label profilingReturn;
+    GenerateAsmJSExitEpilogue(masm, framePushed, AsmJSExit::FFI, &profilingReturn);
+    return m.finishGeneratingInterpExit(exitIndex, &begin, &profilingReturn) && !masm.oom();
 }
 
 // On ARM/MIPS, we need to include an extra word of space at the top of the
 // stack so we can explicitly store the return address before making the call
 // to C++ or Ion. On x86/x64, this isn't necessary since the call instruction
 // pushes the return address.
 #if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
 static const unsigned MaybeRetAddr = sizeof(void*);
 #else
 static const unsigned MaybeRetAddr = 0;
 #endif
 
-static void
+static bool
 GenerateFFIIonExit(ModuleCompiler &m, const ModuleCompiler::ExitDescriptor &exit,
-                         unsigned exitIndex, Label *throwLabel)
+                   unsigned exitIndex, Label *throwLabel)
 {
     MacroAssembler &masm = m.masm();
-    masm.align(CodeAlignment);
-    m.setIonExitOffset(exitIndex);
-    masm.setFramePushed(0);
-
-    // See AsmJSFrameSize comment in Assembler-shared.h.
-#if defined(JS_CODEGEN_ARM)
-    masm.push(lr);
-#elif defined(JS_CODEGEN_MIPS)
-    masm.push(ra);
-#endif
-
-    // 'callee' stays live throughout much of the Ion exit and 'scratch' is
-    // constantly clobbered.
-    Register callee = ABIArgGenerator::NonArgReturnVolatileReg0;
-    Register scratch = ABIArgGenerator::NonArgReturnVolatileReg1;
-
-    // Store the frame pointer in AsmJSActivation::exitFP for stack unwinding.
-    LoadAsmJSActivationIntoRegister(masm, scratch);
-    masm.storePtr(StackPointer, Address(scratch, AsmJSActivation::offsetOfExitFP()));
 
     // Even though the caller has saved volatile registers, we still need to
     // save/restore globally-pinned asm.js registers at Ion calls since Ion does
     // not preserve non-volatile registers.
 #if defined(JS_CODEGEN_X64)
     unsigned savedRegBytes = 1 * sizeof(void*);  // HeapReg
 #elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
     unsigned savedRegBytes = 2 * sizeof(void*);  // HeapReg, GlobalReg
@@ -6361,40 +6213,44 @@ GenerateFFIIonExit(ModuleCompiler &m, co
 
     // The same stack frame is used for the call into Ion and (possibly) a call
     // into C++ to coerce the return type. To do this, we compute the amount of
     // space required for both calls and take the maximum. In both cases,
     // include space for savedRegBytes, since these go below the Ion/coerce.
 
     // Ion calls use the following stack layout (sp grows to the left):
     //   | return address | descriptor | callee | argc | this | arg1 | arg2 | ...
-    unsigned offsetToArgs = MaybeRetAddr;
-    unsigned argBytes = 3 * sizeof(size_t) + (1 + exit.sig().args().length()) * sizeof(Value);
-    unsigned totalIonBytes = offsetToArgs + argBytes + savedRegBytes;
+    unsigned offsetToIonArgs = MaybeRetAddr;
+    unsigned ionArgBytes = 3 * sizeof(size_t) + (1 + exit.sig().args().length()) * sizeof(Value);
+    unsigned totalIonBytes = offsetToIonArgs + ionArgBytes + savedRegBytes;
     unsigned ionFrameSize = StackDecrementForCall(masm, totalIonBytes);
 
     // Coercion calls use the following stack layout (sp grows to the left):
-    //   | stack args | Value argv[1] | ...
+    //   | stack args | padding | Value argv[1] | ...
+    // The padding between args and argv ensures that argv is aligned.
     MIRTypeVector coerceArgTypes(m.cx());
-    coerceArgTypes.infallibleAppend(MIRType_Pointer); // cx
     coerceArgTypes.infallibleAppend(MIRType_Pointer); // argv
-    unsigned bytesAfterArgs = sizeof(Value) + savedRegBytes;
-    unsigned coerceFrameSize = StackDecrementForCall(masm, coerceArgTypes, bytesAfterArgs);
-
-    // Allocate a frame large enough for both of the above calls.
+    unsigned offsetToCoerceArgv = AlignBytes(StackArgBytes(coerceArgTypes), sizeof(double));
+    unsigned totalCoerceBytes = offsetToCoerceArgv + sizeof(Value) + savedRegBytes;
+    unsigned coerceFrameSize = StackDecrementForCall(masm, totalCoerceBytes);
+
     unsigned framePushed = Max(ionFrameSize, coerceFrameSize);
-    masm.reserveStack(framePushed);
+
+    Label begin;
+    GenerateAsmJSExitPrologue(masm, framePushed, AsmJSExit::FFI, &begin);
 
     // 1. Descriptor
-    size_t argOffset = offsetToArgs;
+    size_t argOffset = offsetToIonArgs;
     uint32_t descriptor = MakeFrameDescriptor(framePushed, JitFrame_Entry);
     masm.storePtr(ImmWord(uintptr_t(descriptor)), Address(StackPointer, argOffset));
     argOffset += sizeof(size_t);
 
     // 2. Callee
+    Register callee = ABIArgGenerator::NonArgReturnVolatileReg0;   // live until call
+    Register scratch = ABIArgGenerator::NonArgReturnVolatileReg1;  // clobbered
 
     // 2.1. Get ExitDatum
     unsigned globalDataOffset = m.module().exitIndexToGlobalDataOffset(exitIndex);
 #if defined(JS_CODEGEN_X64)
     m.masm().append(AsmJSGlobalAccess(masm.leaRipRelative(callee), globalDataOffset));
 #elif defined(JS_CODEGEN_X86)
     m.masm().append(AsmJSGlobalAccess(masm.movlWithPatch(Imm32(0), callee), globalDataOffset));
 #else
@@ -6417,20 +6273,20 @@ GenerateFFIIonExit(ModuleCompiler &m, co
     masm.storePtr(ImmWord(uintptr_t(argc)), Address(StackPointer, argOffset));
     argOffset += sizeof(size_t);
 
     // 4. |this| value
     masm.storeValue(UndefinedValue(), Address(StackPointer, argOffset));
     argOffset += sizeof(Value);
 
     // 5. Fill the arguments
-    unsigned offsetToCallerStackArgs = framePushed + AsmJSFrameSize;
+    unsigned offsetToCallerStackArgs = framePushed + sizeof(AsmJSFrame);
     FillArgumentArray(m, exit.sig().args(), argOffset, offsetToCallerStackArgs, scratch);
     argOffset += exit.sig().args().length() * sizeof(Value);
-    JS_ASSERT(argOffset == offsetToArgs + argBytes);
+    JS_ASSERT(argOffset == offsetToIonArgs + ionArgBytes);
 
     // 6. Store asm.js pinned registers
 #if defined(JS_CODEGEN_X64)
     unsigned savedHeapOffset = framePushed - sizeof(void*);
     masm.storePtr(HeapReg, Address(StackPointer, savedHeapOffset));
 #elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
     unsigned savedHeapOffset = framePushed - 1 * sizeof(void*);
     unsigned savedGlobalOffset = framePushed - 2 * sizeof(void*);
@@ -6457,17 +6313,17 @@ GenerateFFIIonExit(ModuleCompiler &m, co
         //   act.prevJitJSContext_ = cx->mainThread().jitJSContext;
         //   cx->mainThread().jitJSContext = cx;
         // On the ARM store8() uses the secondScratchReg (lr) as a temp.
         size_t offsetOfActivation = offsetof(JSRuntime, mainThread) +
                                     PerThreadData::offsetOfActivation();
         size_t offsetOfJitTop = offsetof(JSRuntime, mainThread) + offsetof(PerThreadData, jitTop);
         size_t offsetOfJitJSContext = offsetof(JSRuntime, mainThread) +
                                       offsetof(PerThreadData, jitJSContext);
-        LoadAsmJSActivationIntoRegister(masm, reg0);
+        masm.loadAsmJSActivation(reg0);
         masm.loadPtr(Address(reg0, AsmJSActivation::offsetOfContext()), reg3);
         masm.loadPtr(Address(reg3, JSContext::offsetOfRuntime()), reg0);
         masm.loadPtr(Address(reg0, offsetOfActivation), reg1);
         masm.store8(Imm32(1), Address(reg1, JitActivation::offsetOfActiveUint8()));
         masm.loadPtr(Address(reg0, offsetOfJitTop), reg2);
         masm.storePtr(reg2, Address(reg1, JitActivation::offsetOfPrevJitTop()));
         masm.loadPtr(Address(reg0, offsetOfJitJSContext), reg2);
         masm.storePtr(reg2, Address(reg1, JitActivation::offsetOfPrevJitJSContext()));
@@ -6533,139 +6389,165 @@ GenerateFFIIonExit(ModuleCompiler &m, co
     JS_ASSERT(masm.framePushed() == framePushed);
 #if defined(JS_CODEGEN_X64)
     masm.loadPtr(Address(StackPointer, savedHeapOffset), HeapReg);
 #elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
     masm.loadPtr(Address(StackPointer, savedHeapOffset), HeapReg);
     masm.loadPtr(Address(StackPointer, savedGlobalOffset), GlobalReg);
 #endif
 
-    masm.freeStack(framePushed);
-
-    // Clear exitFP before the frame is destroyed.
-    LoadAsmJSActivationIntoRegister(masm, scratch);
-    masm.storePtr(ImmWord(0), Address(scratch, AsmJSActivation::offsetOfExitFP()));
-
-    masm.ret();
+    Label profilingReturn;
+    GenerateAsmJSExitEpilogue(masm, framePushed, AsmJSExit::FFI, &profilingReturn);
 
     if (oolConvert.used()) {
         masm.bind(&oolConvert);
         masm.setFramePushed(framePushed);
 
         // Store return value into argv[0]
-        unsigned offsetToArgv = StackArgBytes(coerceArgTypes);
-        JS_ASSERT(offsetToArgv % sizeof(Value) == 0);
-        masm.storeValue(JSReturnOperand, Address(StackPointer, offsetToArgv));
-
+        masm.storeValue(JSReturnOperand, Address(StackPointer, offsetToCoerceArgv));
+
+        // argument 0: argv
         ABIArgMIRTypeIter i(coerceArgTypes);
-
-        // argument 0: cx
-        LoadAsmJSActivationIntoRegister(masm, scratch);
-        if (i->kind() == ABIArg::GPR) {
-            LoadJSContextFromActivation(masm, scratch, i->gpr());
-        } else {
-            LoadJSContextFromActivation(masm, scratch, scratch);
-            masm.storePtr(scratch, Address(StackPointer, i->offsetFromArgBase()));
-        }
-        i++;
-
-        // argument 1: argv
-        Address argv(StackPointer, offsetToArgv);
+        Address argv(StackPointer, offsetToCoerceArgv);
         if (i->kind() == ABIArg::GPR) {
             masm.computeEffectiveAddress(argv, i->gpr());
         } else {
             masm.computeEffectiveAddress(argv, scratch);
             masm.storePtr(scratch, Address(StackPointer, i->offsetFromArgBase()));
         }
         i++;
         JS_ASSERT(i.done());
 
         // Call coercion function
         AssertStackAlignment(masm);
         switch (exit.sig().retType().which()) {
           case RetType::Signed:
             masm.call(AsmJSImmPtr(AsmJSImm_CoerceInPlace_ToInt32));
             masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
-            masm.unboxInt32(Address(StackPointer, offsetToArgv), ReturnReg);
+            masm.unboxInt32(Address(StackPointer, offsetToCoerceArgv), ReturnReg);
             break;
           case RetType::Double:
             masm.call(AsmJSImmPtr(AsmJSImm_CoerceInPlace_ToNumber));
             masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
-            masm.loadDouble(Address(StackPointer, offsetToArgv), ReturnDoubleReg);
+            masm.loadDouble(Address(StackPointer, offsetToCoerceArgv), ReturnDoubleReg);
             break;
           default:
             MOZ_ASSUME_UNREACHABLE("Unsupported convert type");
         }
 
         masm.jump(&done);
         masm.setFramePushed(0);
     }
 
     JS_ASSERT(masm.framePushed() == 0);
+
+    return m.finishGeneratingIonExit(exitIndex, &begin, &profilingReturn) && !masm.oom();
 }
 
 // See "asm.js FFI calls" comment above.
-static void
-GenerateFFIExit(ModuleCompiler &m, const ModuleCompiler::ExitDescriptor &exit, unsigned exitIndex,
-                Label *throwLabel)
+static bool
+GenerateFFIExits(ModuleCompiler &m, const ModuleCompiler::ExitDescriptor &exit, unsigned exitIndex,
+                 Label *throwLabel)
 {
     // Generate the slow path through the interpreter
-    GenerateFFIInterpreterExit(m, exit, exitIndex, throwLabel);
+    if (!GenerateFFIInterpExit(m, exit, exitIndex, throwLabel))
+        return false;
 
     // Generate the fast path
-    GenerateFFIIonExit(m, exit, exitIndex, throwLabel);
-}
-
-// The stack-overflow exit is called when the stack limit has definitely been
-// exceeded. In this case, we can clobber everything since we are about to pop
-// all the frames.
+    if (!GenerateFFIIonExit(m, exit, exitIndex, throwLabel))
+        return false;
+
+    return true;
+}
+
+// Generate a thunk that updates fp before calling the given builtin so that
+// both the builtin and the calling function show up in profiler stacks. (This
+// thunk is dynamically patched in when profiling is enabled.) Since the thunk
+// pushes an AsmJSFrame on the stack, that means we must rebuild the stack
+// frame. Fortunately, these are low arity functions and everything is passed in
+// regs on everything but x86 anyhow.
+static bool
+GenerateBuiltinThunk(ModuleCompiler &m, AsmJSExit::BuiltinKind builtin)
+{
+    MacroAssembler &masm = m.masm();
+    JS_ASSERT(masm.framePushed() == 0);
+
+    MIRTypeVector argTypes(m.cx());
+    switch (builtin) {
+      case AsmJSExit::Builtin_ToInt32:
+        argTypes.infallibleAppend(MIRType_Int32);
+        break;
+#if defined(JS_CODEGEN_ARM)
+      case AsmJSExit::Builtin_IDivMod:
+      case AsmJSExit::Builtin_UDivMod:
+        argTypes.infallibleAppend(MIRType_Int32);