Merge m-c to b2g-inbound. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Wed, 23 Apr 2014 14:00:16 -0700
changeset 179798 33ca5e3210463f5601c97f5af8be96dde46cd86a
parent 179797 d1cabfe507ef62dba8f2c1eb9146d1849c73f346 (current diff)
parent 179780 b5395baf5883f4c3a3d6276e8fec1cf20db96a0b (diff)
child 179799 190a19291d8cafa60cb6e16c2192fa1e00073708
child 179806 35ed12caf75fdc91b8dc05ad3d502604d1ade19e
child 179840 fa5e77def52e812b926ef925f4fa7957c9df6224
push id26641
push userryanvm@gmail.com
push dateWed, 23 Apr 2014 21:11:14 +0000
treeherdermozilla-central@33ca5e321046 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone31.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to b2g-inbound. a=merge
content/media/webaudio/test/test_AudioParam.html
js/src/jit/IonFrameIterator-inl.h
js/src/jit/IonFrameIterator.h
layout/mathml/mathfontAsanaMath.properties
layout/mathml/mathfontSTIXSize1.properties
mobile/android/base/RemoteTabs.java
mobile/android/base/home/HomeConfigInvalidator.java
mobile/android/base/home/PanelManager.java
--- a/b2g/config/flame/config.json
+++ b/b2g/config/flame/config.json
@@ -8,17 +8,18 @@
     "upload_files": [
         "{objdir}/dist/b2g-*.crashreporter-symbols.zip",
         "{objdir}/dist/b2g-*.tar.gz",
         "{workdir}/sources.xml"
     ],
     "public_upload_files": [
         "{objdir}/dist/b2g-*.crashreporter-symbols.zip",
         "{objdir}/dist/b2g-*.tar.gz",
-        "{workdir}/sources.xml"
+        "{workdir}/sources.xml",
+        "{objdir}/dist/b2g-update/b2g-gecko-update.mar"
     ],
     "zip_files": [
         ["{workdir}/out/target/product/flame/*.img", "out/target/product/flame/"],
         ["{workdir}/boot.img", "out/target/product/flame/"],
         "{workdir}/flash.sh",
         "{workdir}/load-config.sh",
         "{workdir}/.config",
         "{workdir}/sources.xml"
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1538,15 +1538,15 @@ pref("browser.cache.auto_delete_cache_ve
 // Play with different values of the decay time and get telemetry,
 // 0 means to randomize (and persist) the experiment value in users' profiles,
 // -1 means no experiment is run and we use the preferred value for frecency (6h)
 pref("browser.cache.frecency_experiment", 0);
 
 pref("browser.translation.detectLanguage", false);
 
 // Telemetry experiments settings.
-pref("experiments.enabled", false);
+pref("experiments.enabled", true);
 pref("experiments.manifest.fetchIntervalSeconds", 86400);
 pref("experiments.manifest.uri", "https://telemetry-experiment.cdn.mozilla.net/manifest/v1/firefox/%VERSION%/%CHANNEL%");
 pref("experiments.manifest.certs.1.commonName", "*.cdn.mozilla.net");
 pref("experiments.manifest.certs.1.issuerName", "CN=Cybertrust Public SureServer SV CA,O=Cybertrust Inc");
 // Whether experiments are supported by the current application profile.
 pref("experiments.supported", true);
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1120,16 +1120,21 @@
                 let prompts = promptBox.listPrompts();
                 // NB: This code assumes that the beforeunload prompt
                 //     is the top-most prompt on the tab.
                 promptBox.removePrompt(prompts[prompts.length - 1]);
               }
 
               // Adjust focus
               oldBrowser._urlbarFocused = (gURLBar && gURLBar.focused);
+              if (this.isFindBarInitialized(oldTab)) {
+                let findBar = this.getFindBar(oldTab);
+                oldTab._findBarFocused = (!findBar.hidden &&
+                  findBar._findField.getAttribute("focused") == "true");
+              }
               do {
                 // When focus is in the tab bar, retain it there.
                 if (document.activeElement == oldTab) {
                   // We need to explicitly focus the new tab, because
                   // tabbox.xml does this only in some cases.
                   this.mCurrentTab.focus();
                   break;
                 }
@@ -1155,18 +1160,19 @@
                     gURLBar.focus();
                     break;
                   } else if (isTabEmpty(this.mCurrentTab)) {
                     focusAndSelectUrlBar();
                     break;
                   }
                 }
 
-                // If the find bar is open, focus it.
-                if (gFindBarInitialized && !gFindBar.hidden) {
+                // Focus the find bar if it was previously focused for that tab.
+                if (gFindBarInitialized && !gFindBar.hidden &&
+                    this.selectedTab._findBarFocused) {
                   gFindBar._findField.focus();
                   break;
                 }
 
                 // Otherwise, focus the content area.
                 let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
                 let focusFlags = fm.FLAG_NOSCROLL;
 
--- a/browser/base/content/test/general/browser_bug537013.js
+++ b/browser/base/content/test/general/browser_bug537013.js
@@ -81,25 +81,29 @@ function continueTests2() {
   ok(!gFindBar.getElement("highlight").checked, "Highlight button reset!");
   gFindBar.close();
   ok(gFindBar.hidden, "First tab doesn't show find bar!");
   gBrowser.selectedTab = tabs[1];
   ok(!gFindBar.hidden, "Second tab shows find bar!");
   // Test for bug 892384
   is(gFindBar._findField.getAttribute("focused"), "true",
      "Open findbar refocused on tab change!");
+  gURLBar.focus();
   gBrowser.selectedTab = tabs[0];
   ok(gFindBar.hidden, "First tab doesn't show find bar!");
 
   // Set up a third tab, no tests here
   gBrowser.selectedTab = tabs[2];
   setFindString(texts[2]);
 
   // Now we jump to the second, then first, and then fourth
   gBrowser.selectedTab = tabs[1];
+  // Test for bug 892384
+  ok(!gFindBar._findField.hasAttribute("focused"),
+     "Open findbar not refocused on tab change!");
   gBrowser.selectedTab = tabs[0];
   gBrowser.selectedTab = tabs[3];
   ok(gFindBar.hidden, "Fourth tab doesn't show find bar!");
   is(gFindBar, gBrowser.getFindBar(), "Find bar is right one!");
   gFindBar.open();
   // Disabled the following assertion due to intermittent failure on OSX 10.6 Debug.
   if (!HasFindClipboard) {
     is(gFindBar._findField.value, texts[1],
--- a/browser/components/customizableui/test/browser.ini
+++ b/browser/components/customizableui/test/browser.ini
@@ -97,14 +97,15 @@ skip-if = os == "linux"
 [browser_984455_bookmarks_items_reparenting.js]
 skip-if = os == "linux"
 
 [browser_985815_propagate_setToolbarVisibility.js]
 [browser_981305_separator_insertion.js]
 [browser_987177_destroyWidget_xul.js]
 [browser_987177_xul_wrapper_updating.js]
 [browser_987492_window_api.js]
+[browser_987640_charEncoding.js]
 [browser_992747_toggle_noncustomizable_toolbar.js]
 [browser_993322_widget_notoolbar.js]
 [browser_995164_registerArea_during_customize_mode.js]
 [browser_996364_registerArea_different_properties.js]
 [browser_bootstrapped_custom_toolbar.js]
 [browser_panel_toggle.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/customizableui/test/browser_987640_charEncoding.js
@@ -0,0 +1,62 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const TEST_PAGE = "http://mochi.test:8888/browser/browser/components/customizableui/test/support/test_967000_charEncoding_page.html";
+let newTab = null;
+
+add_task(function() {
+  info("Check Character Encoding panel functionality");
+
+  // add the Character Encoding button to the panel
+  CustomizableUI.addWidgetToArea("characterencoding-button",
+                                  CustomizableUI.AREA_PANEL);
+
+  newTab = gBrowser.addTab(TEST_PAGE);
+  yield promiseTabLoadEvent(gBrowser.selectedTab, TEST_PAGE);
+
+  yield PanelUI.show();
+  let charEncodingButton = document.getElementById("characterencoding-button");
+  charEncodingButton.click();
+
+  let characterEncodingView = document.getElementById("PanelUI-characterEncodingView");
+  let checkedButtons = characterEncodingView.querySelectorAll("toolbarbutton[checked='true']");
+  let initialEncoding = checkedButtons[0];
+  is(initialEncoding.getAttribute("label"), "Unicode", "The unicode encoding is initially selected");
+
+  // change the encoding
+  let encodings = characterEncodingView.querySelectorAll("toolbarbutton");
+  let newEncoding = encodings[0].hasAttribute("checked") ? encodings[1] : encodings[0];
+  let tabLoadPromise = promiseTabLoadEvent(gBrowser.selectedTab, TEST_PAGE);
+  newEncoding.click();
+  yield tabLoadPromise;
+
+  // check that the new encodng is applied
+  yield PanelUI.show();
+  charEncodingButton.click();
+  checkedButtons = characterEncodingView.querySelectorAll("toolbarbutton[checked='true']");
+  let selectedEncodingName = checkedButtons[0].getAttribute("label");
+  ok(selectedEncodingName != "Unicode", "The encoding was changed to " + selectedEncodingName);
+
+  // reset the initial encoding
+  yield PanelUI.show();
+  charEncodingButton.click();
+  tabLoadPromise = promiseTabLoadEvent(gBrowser.selectedTab, TEST_PAGE);
+  initialEncoding.click();
+  yield tabLoadPromise;
+  yield PanelUI.show();
+  charEncodingButton.click();
+  let checkedButtons = characterEncodingView.querySelectorAll("toolbarbutton[checked='true']");
+  is(checkedButtons[0].getAttribute("label"), "Unicode", "The encoding was reset to Unicode");
+});
+
+add_task(function asyncCleanup() {
+  // reset the panel to the default state
+  yield resetCustomization();
+  ok(CustomizableUI.inDefaultState, "The UI is in default state again.");
+
+  // remove the added tab
+  gBrowser.removeTab(newTab);
+});
--- a/browser/experiments/Experiments.jsm
+++ b/browser/experiments/Experiments.jsm
@@ -19,16 +19,18 @@ Cu.import("resource://gre/modules/osfile
 Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/AsyncShutdown.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
                                   "resource://gre/modules/UpdateChannel.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                   "resource://gre/modules/AddonManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate",
+                                  "resource://gre/modules/AddonManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryPing",
                                   "resource://gre/modules/TelemetryPing.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryLog",
                                   "resource://gre/modules/TelemetryLog.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils",
                                   "resource://services-common/utils.js");
 XPCOMUtils.defineLazyModuleGetter(this, "Metrics",
                                   "resource://gre/modules/Metrics.jsm");
@@ -65,16 +67,19 @@ const PREF_MANIFEST_CHECKCERT   = "manif
 const PREF_MANIFEST_REQUIREBUILTIN = "manifest.cert.requireBuiltin"; // experiments.manifest.cert.requireBuiltin
 const PREF_FORCE_SAMPLE = "force-sample-value"; // experiments.force-sample-value
 
 const PREF_HEALTHREPORT_ENABLED = "datareporting.healthreport.service.enabled";
 
 const PREF_BRANCH_TELEMETRY     = "toolkit.telemetry.";
 const PREF_TELEMETRY_ENABLED    = "enabled";
 
+const URI_EXTENSION_STRINGS     = "chrome://mozapps/locale/extensions/extensions.properties";
+const STRING_TYPE_NAME          = "type.%ID%.name";
+
 const TELEMETRY_LOG = {
   // log(key, [kind, experimentId, details])
   ACTIVATION_KEY: "EXPERIMENT_ACTIVATION",
   ACTIVATION: {
     // Successfully activated.
     ACTIVATED: "ACTIVATED",
     // Failed to install the add-on.
     INSTALL_FAILURE: "INSTALL_FAILURE",
@@ -98,16 +103,17 @@ const TELEMETRY_LOG = {
     // details will be provided.
     RECHECK: "RECHECK",
   },
 };
 
 const gPrefs = new Preferences(PREF_BRANCH);
 const gPrefsTelemetry = new Preferences(PREF_BRANCH_TELEMETRY);
 let gExperimentsEnabled = false;
+let gAddonProvider = null;
 let gExperiments = null;
 let gLogAppenderDump = null;
 let gPolicyCounter = 0;
 let gExperimentsCounter = 0;
 let gExperimentEntryCounter = 0;
 
 // Tracks active AddonInstall we know about so we can deny external
 // installs.
@@ -195,18 +201,19 @@ function addonInstallForURL(url, hash) {
                                 "application/x-xpinstall", hash);
   return deferred.promise;
 }
 
 // Returns a promise that is resolved with an Array<Addon> of the installed
 // experiment addons.
 function installedExperimentAddons() {
   let deferred = Promise.defer();
-  AddonManager.getAddonsByTypes(["experiment"],
-                                addons => deferred.resolve(addons));
+  AddonManager.getAddonsByTypes(["experiment"], (addons) => {
+    deferred.resolve([a for (a of addons) if (!a.appDisabled)]);
+  });
   return deferred.promise;
 }
 
 // Takes an Array<Addon> and returns a promise that is resolved when the
 // addons are uninstalled.
 function uninstallAddons(addons) {
   let ids = new Set([a.id for (a of addons)]);
   let deferred = Promise.defer();
@@ -457,19 +464,43 @@ Experiments.Experiments.prototype = {
     this._log.info("Completed uninitialization.");
   }),
 
   _registerWithAddonManager: function () {
     this._log.trace("Registering instance with Addon Manager.");
 
     AddonManager.addAddonListener(this);
     AddonManager.addInstallListener(this);
+
+    if (!gAddonProvider) {
+      // The properties of this AddonType should be kept in sync with the
+      // experiment AddonType registered in XPIProvider.
+      this._log.trace("Registering previous experiment add-on provider.");
+      gAddonProvider = new Experiments.PreviousExperimentProvider(this, [
+          new AddonManagerPrivate.AddonType("experiment",
+                                            URI_EXTENSION_STRINGS,
+                                            STRING_TYPE_NAME,
+                                            AddonManager.VIEW_TYPE_LIST,
+                                            11000,
+                                            AddonManager.TYPE_UI_HIDE_EMPTY),
+        ]);
+      AddonManagerPrivate.registerProvider(gAddonProvider);
+    }
+
   },
 
   _unregisterWithAddonManager: function () {
+    this._log.trace("Unregistering instance with Addon Manager.");
+
+    if (gAddonProvider) {
+      this._log.trace("Unregistering previous experiment add-on provider.");
+      AddonManagerPrivate.unregisterProvider(gAddonProvider);
+      gAddonProvider = null;
+    }
+
     AddonManager.removeInstallListener(this);
     AddonManager.removeAddonListener(this);
   },
 
   /**
    * Throws an exception if we've already shut down.
    */
   _checkForShutdown: function() {
@@ -557,16 +588,38 @@ Experiments.Experiments.prototype = {
 
       // Sort chronologically, descending.
       list.sort((a, b) => b.endDate - a.endDate);
       return list;
     }.bind(this));
   },
 
   /**
+   * Returns the ExperimentInfo for the active experiment, or null
+   * if there is none.
+   */
+  getActiveExperiment: function () {
+    let experiment = this._getActiveExperiment();
+    if (!experiment) {
+      return null;
+    }
+
+    let info = {
+      id: experiment.id,
+      name: experiment._name,
+      description: experiment._description,
+      active: experiment.enabled,
+      endDate: experiment.endDate.getTime(),
+      detailURL: experiment._homepageURL,
+    };
+
+    return info;
+  },
+
+  /**
    * Determine whether another date has the same UTC day as now().
    */
   _dateIsTodayUTC: function (d) {
     let now = this._policy.now();
 
     return stripDateToMidnight(now).getTime() == stripDateToMidnight(d).getTime();
   },
 
@@ -706,16 +759,22 @@ Experiments.Experiments.prototype = {
     this.disableExperiment(TELEMETRY_LOG.TERMINATION.ADDON_UNINSTALLED);
   },
 
   onInstallStarted: function (install) {
     if (install.addon.type != "experiment") {
       return;
     }
 
+    this._log.trace("onInstallStarted() - " + install.addon.id);
+    if (install.addon.appDisabled) {
+      // This is a PreviousExperiment
+      return;
+    }
+
     // We want to be in control of all experiment add-ons: reject installs
     // for add-ons that we don't know about.
 
     // We have a race condition of sorts to worry about here. We have 2
     // onInstallStarted listeners. This one (the global one) and the one
     // created as part of ExperimentEntry._installAddon. Because of the order
     // they are registered in, this one likely executes first. Unfortunately,
     // this means that the add-on ID is not yet set on the ExperimentEntry.
@@ -1749,17 +1808,24 @@ Experiments.ExperimentEntry.prototype = 
    */
   _getAddon: function () {
     if (!this._addonId) {
       return Promise.resolve(null);
     }
 
     let deferred = Promise.defer();
 
-    AddonManager.getAddonByID(this._addonId, deferred.resolve);
+    AddonManager.getAddonByID(this._addonId, (addon) => {
+      if (addon && addon.appDisabled) {
+        // Don't return PreviousExperiments.
+        addon = null;
+      }
+
+      deferred.resolve(addon);
+    });
 
     return deferred.promise;
   },
 
   _logTermination: function (terminationKind, terminationReason) {
     if (terminationKind === undefined) {
       return;
     }
@@ -1952,8 +2018,175 @@ ExperimentsProvider.prototype = Object.f
 
         this._log.info("Recording last active experiment: " + todayActive.id);
         yield m.setDailyLastText("lastActive", todayActive.id,
                                  this._experiments._policy.now());
       }.bind(this));
     });
   },
 });
+
+/**
+ * An Add-ons Manager provider that knows about old experiments.
+ *
+ * This provider exposes read-only add-ons corresponding to previously-active
+ * experiments. The existence of this provider (and the add-ons it knows about)
+ * facilitates the display of old experiments in the Add-ons Manager UI with
+ * very little custom code in that component.
+ */
+this.Experiments.PreviousExperimentProvider = function (experiments) {
+  this._experiments = experiments;
+}
+
+this.Experiments.PreviousExperimentProvider.prototype = Object.freeze({
+  startup: function () {},
+  shutdown: function () {},
+
+  getAddonByID: function (id, cb) {
+    this._getPreviousExperiments().then((experiments) => {
+      for (let experiment of experiments) {
+        if (experiment.id == id) {
+          cb(new PreviousExperimentAddon(experiment));
+          return;
+        }
+      }
+
+      cb(null);
+    },
+    (error) => {
+      cb(null);
+    });
+  },
+
+  getAddonsByTypes: function (types, cb) {
+    if (types && types.length > 0 && types.indexOf("experiment") == -1) {
+      cb([]);
+      return;
+    }
+
+    this._getPreviousExperiments().then((experiments) => {
+      cb([new PreviousExperimentAddon(e) for (e of experiments)]);
+    },
+    (error) => {
+      cb([]);
+    });
+  },
+
+  _getPreviousExperiments: function () {
+    return this._experiments.getExperiments().then((experiments) => {
+      return Promise.resolve([e for (e of experiments) if (!e.active)]);
+    });
+  },
+});
+
+/**
+ * An add-on that represents a previously-installed experiment.
+ */
+function PreviousExperimentAddon(experiment) {
+  this._id = experiment.id;
+  this._name = experiment.name;
+  this._endDate = experiment.endDate;
+  this._description = experiment.description;
+}
+
+PreviousExperimentAddon.prototype = Object.freeze({
+  // BEGIN REQUIRED ADDON PROPERTIES
+
+  get appDisabled() {
+    return true;
+  },
+
+  get blocklistState() {
+    Ci.nsIBlocklistService.STATE_NOT_BLOCKED
+  },
+
+  get creator() {
+    return new AddonManagerPrivate.AddonAuthor("");
+  },
+
+  get foreignInstall() {
+    return false;
+  },
+
+  get id() {
+    return this._id;
+  },
+
+  get isActive() {
+    return false;
+  },
+
+  get isCompatible() {
+    return true;
+  },
+
+  get isPlatformCompatible() {
+    return true;
+  },
+
+  get name() {
+    return this._name;
+  },
+
+  get pendingOperations() {
+    return AddonManager.PENDING_NONE;
+  },
+
+  get permissions() {
+    return 0;
+  },
+
+  get providesUpdatesSecurely() {
+    return true;
+  },
+
+  get scope() {
+    return AddonManager.SCOPE_PROFILE;
+  },
+
+  get type() {
+    return "experiment";
+  },
+
+  get userDisabled() {
+    return true;
+  },
+
+  get version() {
+    return null;
+  },
+
+  // END REQUIRED PROPERTIES
+
+  // BEGIN OPTIONAL PROPERTIES
+
+  get description() {
+    return this._description;
+  },
+
+  get updateDate() {
+    return new Date(this._endDate);
+  },
+
+  // END OPTIONAL PROPERTIES
+
+  // BEGIN REQUIRED METHODS
+
+  isCompatibleWith: function (appVersion, platformVersion) {
+    return true;
+  },
+
+  findUpdates: function (listener, reason, appVersion, platformVersion) {
+    AddonManagerPrivate.callNoUpdateListeners(this, listener, reason,
+                                              appVersion, platformVersion);
+  },
+
+  // END REQUIRED METHODS
+
+  /**
+   * The end-date of the experiment, required for the Addon Manager UI.
+   */
+
+   get endDate() {
+     return this._endDate;
+   },
+
+});
--- a/browser/experiments/test/xpcshell/head.js
+++ b/browser/experiments/test/xpcshell/head.js
@@ -150,20 +150,26 @@ function loadAddonManager() {
   let head = "../../../../toolkit/mozapps/extensions/test/xpcshell/head_addons.js";
   let file = do_get_file(head);
   let uri = ns.Services.io.newFileURI(file);
   ns.Services.scriptloader.loadSubScript(uri.spec, gGlobalScope);
   createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
   startupManager();
 }
 
-function getExperimentAddons() {
+function getExperimentAddons(previous=false) {
   let deferred = Promise.defer();
 
-  AddonManager.getAddonsByTypes(["experiment"], deferred.resolve);
+  AddonManager.getAddonsByTypes(["experiment"], (addons) => {
+    if (previous) {
+      deferred.resolve(addons);
+    } else {
+      deferred.resolve([a for (a of addons) if (!a.appDisabled)]);
+    }
+  });
 
   return deferred.promise;
 }
 
 function createAppInfo(optionsIn) {
   const XULAPPINFO_CONTRACTID = "@mozilla.org/xre/app-info;1";
   const XULAPPINFO_CID = Components.ID("{c763b610-9d49-455a-bbd2-ede71682a1ac}");
 
new file mode 100644
--- /dev/null
+++ b/browser/experiments/test/xpcshell/test_previous_provider.js
@@ -0,0 +1,177 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource:///modules/experiments/Experiments.jsm");
+Cu.import("resource://testing-common/httpd.js");
+
+let gDataRoot;
+let gHttpServer;
+let gManifestObject;
+
+function run_test() {
+  run_next_test();
+}
+
+add_task(function test_setup() {
+  loadAddonManager();
+  do_get_profile();
+
+  gHttpServer = new HttpServer();
+  gHttpServer.start(-1);
+  let httpRoot = "http://localhost:" + gHttpServer.identity.primaryPort + "/";
+  gDataRoot = httpRoot + "data/";
+  gHttpServer.registerDirectory("/data/", do_get_cwd());
+  gHttpServer.registerPathHandler("/manifests/handler", (req, res) => {
+    res.setStatusLine(null, 200, "OK");
+    res.write(JSON.stringify(gManifestObject));
+    res.processAsync();
+    res.finish();
+  });
+  do_register_cleanup(() => gHttpServer.stop(() => {}));
+
+  Services.prefs.setBoolPref("experiments.enabled", true);
+  Services.prefs.setCharPref("experiments.manifest.uri",
+                             httpRoot + "manifests/handler");
+  Services.prefs.setBoolPref("experiments.logging.dump", true);
+  Services.prefs.setCharPref("experiments.logging.level", "Trace");
+  disableCertificateChecks();
+});
+
+add_task(function* test_provider_basic() {
+  let e = Experiments.instance();
+
+  let provider = new Experiments.PreviousExperimentProvider(e);
+  let deferred = Promise.defer();
+  provider.getAddonsByTypes(["experiment"], (addons) => {
+    deferred.resolve(addons);
+  });
+  let addons = yield deferred.promise;
+  Assert.ok(Array.isArray(addons), "getAddonsByTypes returns an Array.");
+  Assert.equal(addons.length, 0, "No previous add-ons returned.");
+
+  gManifestObject = {
+    version: 1,
+    experiments: [
+      {
+        id: EXPERIMENT1_ID,
+        xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
+        xpiHash: EXPERIMENT1_XPI_SHA1,
+        startTime: Date.now() / 1000 - 60,
+        endTime: Date.now() / 1000 + 60,
+        maxActiveSeconds: 60,
+        appName: ["XPCShell"],
+        channel: [e._policy.updatechannel()],
+      },
+    ],
+  };
+
+  yield e.updateManifest();
+
+  deferred = Promise.defer();
+  provider.getAddonsByTypes(["experiment"], (addons) => {
+    deferred.resolve(addons);
+  });
+  addons = yield deferred.promise;
+  Assert.equal(addons.length, 0, "Still no previous experiment.");
+
+  let experiments = yield e.getExperiments();
+  Assert.equal(experiments.length, 1, "1 experiment present.");
+  Assert.ok(experiments[0].active, "It is active.");
+
+  // Deactivate it.
+  defineNow(e._policy, new Date(gManifestObject.experiments[0].endTime * 1000 + 1000));
+  yield e.updateManifest();
+
+  experiments = yield e.getExperiments();
+  Assert.equal(experiments.length, 1, "1 experiment present.");
+  Assert.equal(experiments[0].active, false, "It isn't active.");
+
+  deferred = Promise.defer();
+  provider.getAddonsByTypes(["experiment"], (addons) => {
+    deferred.resolve(addons);
+  });
+  addons = yield deferred.promise;
+  Assert.equal(addons.length, 1, "1 previous add-on known.");
+  Assert.equal(addons[0].id, EXPERIMENT1_ID, "ID matches expected.");
+
+  deferred = Promise.defer();
+  provider.getAddonByID(EXPERIMENT1_ID, (addon) => {
+    deferred.resolve(addon);
+  });
+  let addon = yield deferred.promise;
+  Assert.ok(addon, "We got an add-on from its ID.");
+  Assert.equal(addon.id, EXPERIMENT1_ID, "ID matches expected.");
+  Assert.ok(addon.appDisabled, "Add-on is a previous experiment.");
+  Assert.ok(addon.userDisabled, "Add-on is disabled.");
+  Assert.equal(addon.type, "experiment", "Add-on is an experiment.");
+  Assert.equal(addon.isActive, false, "Add-on is not active.");
+  Assert.equal(addon.permissions, 0, "Add-on has no permissions.");
+
+  deferred = Promise.defer();
+  AddonManager.getAddonsByTypes(["experiment"], (addons) => {
+    deferred.resolve(addons);
+  });
+  addons = yield deferred.promise;
+  Assert.equal(addons.length, 1, "Got 1 experiment from add-on manager.");
+  Assert.equal(addons[0].id, EXPERIMENT1_ID, "ID matches expected.");
+  Assert.ok(addons[0].appDisabled, "It is a previous experiment add-on.");
+});
+
+add_task(function* test_active_and_previous() {
+  // Building on the previous test, activate experiment 2.
+  let e = Experiments.instance();
+  let provider = new Experiments.PreviousExperimentProvider(e);
+
+  gManifestObject = {
+    version: 1,
+    experiments: [
+      {
+        id: EXPERIMENT2_ID,
+        xpiURL: gDataRoot + EXPERIMENT2_XPI_NAME,
+        xpiHash: EXPERIMENT2_XPI_SHA1,
+        startTime: Date.now() / 1000 - 60,
+        endTime: Date.now() / 1000 + 60,
+        maxActiveSeconds: 60,
+        appName: ["XPCShell"],
+        channel: [e._policy.updatechannel()],
+      },
+    ],
+  };
+
+  defineNow(e._policy, new Date());
+  yield e.updateManifest();
+
+  let experiments = yield e.getExperiments();
+  Assert.equal(experiments.length, 2, "2 experiments known.");
+
+  let deferred = Promise.defer();
+  provider.getAddonsByTypes(["experiment"], (addons) => {
+    deferred.resolve(addons);
+  });
+  let addons = yield deferred.promise;
+  Assert.equal(addons.length, 1, "1 previous experiment.");
+
+  deferred = Promise.defer();
+  AddonManager.getAddonsByTypes(["experiment"], (addons) => {
+    deferred.resolve(addons);
+  });
+  addons = yield deferred.promise;
+  Assert.equal(addons.length, 2, "2 experiment add-ons known.");
+
+  for (let addon of addons) {
+    if (addon.id == EXPERIMENT1_ID) {
+      Assert.equal(addon.isActive, false, "Add-on is not active.");
+      Assert.ok(addon.appDisabled, "Should be a previous experiment.");
+    }
+    else if (addon.id == EXPERIMENT2_ID) {
+      Assert.ok(addon.isActive, "Add-on is active.");
+      Assert.ok(!addon.appDisabled, "Should not be a previous experiment.");
+    }
+    else {
+      throw new Error("Unexpected add-on ID: " + addon.id);
+    }
+  }
+});
--- a/browser/experiments/test/xpcshell/xpcshell.ini
+++ b/browser/experiments/test/xpcshell/xpcshell.ini
@@ -15,8 +15,9 @@ generated-files =
 [test_activate.js]
 [test_api.js]
 [test_cache.js]
 [test_conditions.js]
 [test_disableExperiments.js]
 [test_fetch.js]
 [test_telemetry.js]
 [test_healthreport.js]
+[test_previous_provider.js]
--- a/content/base/src/nsDOMFile.cpp
+++ b/content/base/src/nsDOMFile.cpp
@@ -15,17 +15,16 @@
 #include "nsIClassInfo.h"
 #include "nsIConverterInputStream.h"
 #include "nsIDocument.h"
 #include "nsIFileStreams.h"
 #include "nsIInputStream.h"
 #include "nsIIPCSerializableInputStream.h"
 #include "nsIMemoryReporter.h"
 #include "nsIMIMEService.h"
-#include "nsIPlatformCharset.h"
 #include "nsISeekableStream.h"
 #include "nsIUnicharInputStream.h"
 #include "nsIUnicodeDecoder.h"
 #include "nsNetCID.h"
 #include "nsNetUtil.h"
 #include "nsIUUIDGenerator.h"
 #include "nsHostObjectProtocolHandler.h"
 #include "nsStringStream.h"
--- a/content/media/MediaDecoder.cpp
+++ b/content/media/MediaDecoder.cpp
@@ -43,21 +43,25 @@ static const uint32_t STALL_MS = 3000;
 // Number of estimated seconds worth of data we need to have buffered
 // ahead of the current playback position before we allow the media decoder
 // to report that it can play through the entire media without the decode
 // catching up with the download. Having this margin make the
 // MediaDecoder::CanPlayThrough() calculation more stable in the case of
 // fluctuating bitrates.
 static const int64_t CAN_PLAY_THROUGH_MARGIN = 1;
 
+// avoid redefined macro in unified build
+#undef DECODER_LOG
+
 #ifdef PR_LOGGING
 PRLogModuleInfo* gMediaDecoderLog;
-#define DECODER_LOG(type, msg) PR_LOG(gMediaDecoderLog, type, msg)
+#define DECODER_LOG(type, msg, ...) \
+  PR_LOG(gMediaDecoderLog, type, ("Decoder=%p " msg, this, ##__VA_ARGS__))
 #else
-#define DECODER_LOG(type, msg)
+#define DECODER_LOG(type, msg, ...)
 #endif
 
 class MediaMemoryTracker : public nsIMemoryReporter
 {
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIMEMORYREPORTER
 
   MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf);
@@ -309,18 +313,17 @@ void MediaDecoder::UpdateStreamBlockingF
     }
   }
 }
 
 void MediaDecoder::RecreateDecodedStream(int64_t aStartTimeUSecs)
 {
   MOZ_ASSERT(NS_IsMainThread());
   GetReentrantMonitor().AssertCurrentThreadIn();
-  DECODER_LOG(PR_LOG_DEBUG, ("MediaDecoder::RecreateDecodedStream this=%p aStartTimeUSecs=%lld!",
-                             this, (long long)aStartTimeUSecs));
+  DECODER_LOG(PR_LOG_DEBUG, "RecreateDecodedStream aStartTimeUSecs=%lld!", aStartTimeUSecs);
 
   DestroyDecodedStream();
 
   mDecodedStream = new DecodedStreamData(this, aStartTimeUSecs,
     MediaStreamGraph::GetInstance()->CreateSourceStream(nullptr));
 
   // Note that the delay between removing ports in DestroyDecodedStream
   // and adding new ones won't cause a glitch since all graph operations
@@ -342,18 +345,17 @@ void MediaDecoder::RecreateDecodedStream
     mDecodedStream->mStream->ChangeExplicitBlockerCount(1);
   }
 }
 
 void MediaDecoder::AddOutputStream(ProcessedMediaStream* aStream,
                                    bool aFinishWhenEnded)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  DECODER_LOG(PR_LOG_DEBUG, ("MediaDecoder::AddOutputStream this=%p aStream=%p!",
-                             this, aStream));
+  DECODER_LOG(PR_LOG_DEBUG, "AddOutputStream aStream=%p!", aStream);
 
   {
     ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
     if (!mDecodedStream) {
       RecreateDecodedStream(mDecoderStateMachine ?
           int64_t(mDecoderStateMachine->GetCurrentTime()*USECS_PER_S) : 0);
     }
     OutputStreamData* os = mOutputStreams.AppendElement();
@@ -504,49 +506,49 @@ nsresult MediaDecoder::OpenResource(nsIS
   {
     // Hold the lock while we do this to set proper lock ordering
     // expectations for dynamic deadlock detectors: decoder lock(s)
     // should be grabbed before the cache lock
     ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
 
     nsresult rv = mResource->Open(aStreamListener);
     if (NS_FAILED(rv)) {
-      DECODER_LOG(PR_LOG_DEBUG, ("%p Failed to open stream!", this));
+      DECODER_LOG(PR_LOG_WARNING, "Failed to open stream!");
       return rv;
     }
   }
   return NS_OK;
 }
 
 nsresult MediaDecoder::Load(nsIStreamListener** aStreamListener,
                             MediaDecoder* aCloneDonor)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsresult rv = OpenResource(aStreamListener);
   NS_ENSURE_SUCCESS(rv, rv);
 
   mDecoderStateMachine = CreateStateMachine();
   if (!mDecoderStateMachine) {
-    DECODER_LOG(PR_LOG_DEBUG, ("%p Failed to create state machine!", this));
+    DECODER_LOG(PR_LOG_WARNING, "Failed to create state machine!");
     return NS_ERROR_FAILURE;
   }
 
   return InitializeStateMachine(aCloneDonor);
 }
 
 nsresult MediaDecoder::InitializeStateMachine(MediaDecoder* aCloneDonor)
 {
   MOZ_ASSERT(NS_IsMainThread());
   NS_ASSERTION(mDecoderStateMachine, "Cannot initialize null state machine!");
 
   MediaDecoder* cloneDonor = static_cast<MediaDecoder*>(aCloneDonor);
   if (NS_FAILED(mDecoderStateMachine->Init(cloneDonor ?
                                            cloneDonor->mDecoderStateMachine : nullptr))) {
-    DECODER_LOG(PR_LOG_DEBUG, ("%p Failed to init state machine!", this));
+    DECODER_LOG(PR_LOG_WARNING, "Failed to init state machine!");
     return NS_ERROR_FAILURE;
   }
   {
     ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
     mDecoderStateMachine->SetTransportSeekable(mTransportSeekable);
     mDecoderStateMachine->SetMediaSeekable(mMediaSeekable);
     mDecoderStateMachine->SetDuration(mDuration);
     mDecoderStateMachine->SetVolume(mInitialVolume);
@@ -1228,17 +1230,17 @@ void MediaDecoder::DurationChanged()
   int64_t oldDuration = mDuration;
   mDuration = mDecoderStateMachine ? mDecoderStateMachine->GetDuration() : -1;
   // Duration has changed so we should recompute playback rate
   UpdatePlaybackRate();
 
   SetInfinite(mDuration == -1);
 
   if (mOwner && oldDuration != mDuration && !IsInfinite()) {
-    DECODER_LOG(PR_LOG_DEBUG, ("%p duration changed to %lld", this, mDuration));
+    DECODER_LOG(PR_LOG_DEBUG, "Duration changed to %lld", mDuration);
     mOwner->DispatchEvent(NS_LITERAL_STRING("durationchange"));
   }
 }
 
 void MediaDecoder::SetDuration(double aDuration)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mozilla::IsInfinite(aDuration)) {
@@ -1794,8 +1796,10 @@ MediaMemoryTracker::InitMemoryReporter()
 
 MediaMemoryTracker::~MediaMemoryTracker()
 {
   UnregisterWeakMemoryReporter(this);
 }
 
 } // namespace mozilla
 
+// avoid redefined macro in unified build
+#undef DECODER_LOG
--- a/content/media/MediaDecoderStateMachine.cpp
+++ b/content/media/MediaDecoderStateMachine.cpp
@@ -38,21 +38,33 @@
 #include <algorithm>
 
 namespace mozilla {
 
 using namespace mozilla::layers;
 using namespace mozilla::dom;
 using namespace mozilla::gfx;
 
+// avoid redefined macro in unified build
+#undef DECODER_LOG
+#undef VERBOSE_LOG
+
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* gMediaDecoderLog;
-#define DECODER_LOG(type, msg) PR_LOG(gMediaDecoderLog, type, msg)
+#define DECODER_LOG(type, msg, ...) \
+  PR_LOG(gMediaDecoderLog, type, ("Decoder=%p " msg, mDecoder.get(), ##__VA_ARGS__))
+#define VERBOSE_LOG(msg, ...)                          \
+    PR_BEGIN_MACRO                                     \
+      if (!PR_GetEnv("MOZ_QUIET")) {                   \
+        DECODER_LOG(PR_LOG_DEBUG, msg, ##__VA_ARGS__); \
+      }                                                \
+    PR_END_MACRO
 #else
-#define DECODER_LOG(type, msg)
+#define DECODER_LOG(type, msg, ...)
+#define VERBOSE_LOG(msg, ...)
 #endif
 
 // GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
 // GetTickCount() and conflicts with MediaDecoderStateMachine::GetCurrentTime
 // implementation.  With unified builds, putting this in headers is not enough.
 #ifdef GetCurrentTime
 #undef GetCurrentTime
 #endif
@@ -190,17 +202,18 @@ MediaDecoderStateMachine::MediaDecoderSt
   mAudioCompleted(false),
   mGotDurationFromMetaData(false),
   mDispatchedEventToDecode(false),
   mStopAudioThread(true),
   mQuickBuffering(false),
   mMinimizePreroll(false),
   mDecodeThreadWaiting(false),
   mRealTime(aRealTime),
-  mLastFrameStatus(MediaDecoderOwner::NEXT_FRAME_UNINITIALIZED)
+  mLastFrameStatus(MediaDecoderOwner::NEXT_FRAME_UNINITIALIZED),
+  mTimerId(0)
 {
   MOZ_COUNT_CTOR(MediaDecoderStateMachine);
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
 
   // Only enable realtime mode when "media.realtime_decoder.enabled" is true.
   if (Preferences::GetBool("media.realtime_decoder.enabled", false) == false)
     mRealTime = false;
 
@@ -230,19 +243,18 @@ MediaDecoderStateMachine::~MediaDecoderS
   NS_ASSERTION(!mPendingWakeDecoder.get(),
                "WakeDecoder should have been revoked already");
 
   if (mDecodeTaskQueue) {
     mDecodeTaskQueue->Shutdown();
     mDecodeTaskQueue = nullptr;
   }
 
-  if (mTimer) {
-    mTimer->Cancel();
-  }
+  // No need to cancel the timer here for we've done that in
+  // TimeoutExpired() triggered by Shutdown()
   mTimer = nullptr;
   mReader = nullptr;
 
 #ifdef XP_WIN
   timeEndPeriod(1);
 #endif
 }
 
@@ -294,18 +306,18 @@ void MediaDecoderStateMachine::SendStrea
   // the exact same silences
   CheckedInt64 audioWrittenOffset = UsecsToFrames(mInfo.mAudio.mRate,
       aStream->mInitialTime + mStartTime) + aStream->mAudioFramesWritten;
   CheckedInt64 frameOffset = UsecsToFrames(mInfo.mAudio.mRate, aAudio->mTime);
   if (!audioWrittenOffset.isValid() || !frameOffset.isValid())
     return;
   if (audioWrittenOffset.value() < frameOffset.value()) {
     // Write silence to catch up
-    DECODER_LOG(PR_LOG_DEBUG, ("%p Decoder writing %d frames of silence to MediaStream",
-                               mDecoder.get(), int32_t(frameOffset.value() - audioWrittenOffset.value())));
+    VERBOSE_LOG("writing %d frames of silence to MediaStream",
+                int32_t(frameOffset.value() - audioWrittenOffset.value()));
     AudioSegment silence;
     silence.InsertNullDataAtStart(frameOffset.value() - audioWrittenOffset.value());
     aStream->mAudioFramesWritten += silence.GetDuration();
     aOutput->AppendFrom(&silence);
   }
 
   int64_t offset;
   if (aStream->mAudioFramesWritten == 0) {
@@ -324,18 +336,18 @@ void MediaDecoderStateMachine::SendStrea
   aAudio->EnsureAudioBuffer();
   nsRefPtr<SharedBuffer> buffer = aAudio->mAudioBuffer;
   AudioDataValue* bufferData = static_cast<AudioDataValue*>(buffer->Data());
   nsAutoTArray<const AudioDataValue*,2> channels;
   for (uint32_t i = 0; i < aAudio->mChannels; ++i) {
     channels.AppendElement(bufferData + i*aAudio->mFrames + offset);
   }
   aOutput->AppendFrames(buffer.forget(), channels, aAudio->mFrames);
-  DECODER_LOG(PR_LOG_DEBUG, ("%p Decoder writing %d frames of data to MediaStream for AudioData at %lld",
-                             mDecoder.get(), aAudio->mFrames - int32_t(offset), aAudio->mTime));
+  VERBOSE_LOG("writing %d frames of data to MediaStream for AudioData at %lld",
+              aAudio->mFrames - int32_t(offset), aAudio->mTime);
   aStream->mAudioFramesWritten += aAudio->mFrames - int32_t(offset);
 }
 
 static void WriteVideoToMediaStream(layers::Image* aImage,
                                     int64_t aDuration,
                                     const IntSize& aIntrinsicSize,
                                     VideoSegment* aOutput)
 {
@@ -416,39 +428,37 @@ void MediaDecoderStateMachine::SendStrea
       nsAutoTArray<VideoData*,10> video;
       // It's OK to hold references to the VideoData only the decoder thread
       // pops from the queue.
       mReader->VideoQueue().GetElementsAfter(stream->mNextVideoTime, &video);
       VideoSegment output;
       for (uint32_t i = 0; i < video.Length(); ++i) {
         VideoData* v = video[i];
         if (stream->mNextVideoTime < v->mTime) {
-          DECODER_LOG(PR_LOG_DEBUG, ("%p Decoder writing last video to MediaStream %p for %lldus",
-                                     mDecoder.get(), mediaStream,
-                                     v->mTime - stream->mNextVideoTime));
+          VERBOSE_LOG("writing last video to MediaStream %p for %lldus",
+                      mediaStream, v->mTime - stream->mNextVideoTime);
           // Write last video frame to catch up. mLastVideoImage can be null here
           // which is fine, it just means there's no video.
           WriteVideoToMediaStream(stream->mLastVideoImage,
             v->mTime - stream->mNextVideoTime, stream->mLastVideoImageDisplaySize,
               &output);
           stream->mNextVideoTime = v->mTime;
         }
         if (stream->mNextVideoTime < v->GetEndTime()) {
-          DECODER_LOG(PR_LOG_DEBUG, ("%p Decoder writing video frame %lldus to MediaStream %p for %lldus",
-                                     mDecoder.get(), v->mTime, mediaStream,
-                                     v->GetEndTime() - stream->mNextVideoTime));
+          VERBOSE_LOG("writing video frame %lldus to MediaStream %p for %lldus",
+                      v->mTime, mediaStream, v->GetEndTime() - stream->mNextVideoTime);
           WriteVideoToMediaStream(v->mImage,
               v->GetEndTime() - stream->mNextVideoTime, v->mDisplay,
               &output);
           stream->mNextVideoTime = v->GetEndTime();
           stream->mLastVideoImage = v->mImage;
           stream->mLastVideoImageDisplaySize = v->mDisplay;
         } else {
-          DECODER_LOG(PR_LOG_DEBUG, ("%p Decoder skipping writing video frame %lldus (end %lldus) to MediaStream",
-                                     mDecoder.get(), v->mTime, v->GetEndTime()));
+          VERBOSE_LOG("skipping writing video frame %lldus (end %lldus) to MediaStream",
+                      v->mTime, v->GetEndTime());
         }
       }
       if (output.GetDuration() > 0) {
         mediaStream->AppendToTrack(TRACK_VIDEO, &output);
       }
       if (mReader->VideoQueue().IsFinished() && !stream->mHaveSentFinishVideo) {
         mediaStream->EndTrack(TRACK_VIDEO);
         stream->mHaveSentFinishVideo = true;
@@ -593,17 +603,17 @@ MediaDecoderStateMachine::DecodeVideo()
          // don't skip frame when |clock time| <= |mVideoFrameEndTime| for
          // we are still in the safe range without underrunning video frames
          GetClock() > mVideoFrameEndTime &&
         (static_cast<uint32_t>(mReader->VideoQueue().GetSize())
           < LOW_VIDEO_FRAMES * mPlaybackRate))) &&
       !HasLowUndecodedData())
   {
     mSkipToNextKeyFrame = true;
-    DECODER_LOG(PR_LOG_DEBUG, ("%p Skipping video decode to the next keyframe", mDecoder.get()));
+    DECODER_LOG(PR_LOG_DEBUG, "Skipping video decode to the next keyframe");
   }
 
   // Time the video decode, so that if it's slow, we can increase our low
   // audio threshold to reduce the chance of an audio underrun while we're
   // waiting for a video decode to complete.
   TimeDuration decodeTime;
   {
     int64_t currentTime = GetMediaTime();
@@ -620,19 +630,18 @@ MediaDecoderStateMachine::DecodeVideo()
 
   if (THRESHOLD_FACTOR * DurationToUsecs(decodeTime) > mLowAudioThresholdUsecs &&
       !HasLowUndecodedData())
   {
     mLowAudioThresholdUsecs =
       std::min(THRESHOLD_FACTOR * DurationToUsecs(decodeTime), AMPLE_AUDIO_USECS);
     mAmpleAudioThresholdUsecs = std::max(THRESHOLD_FACTOR * mLowAudioThresholdUsecs,
                                           mAmpleAudioThresholdUsecs);
-    DECODER_LOG(PR_LOG_DEBUG,
-                ("Slow video decode, set mLowAudioThresholdUsecs=%lld mAmpleAudioThresholdUsecs=%lld",
-                mLowAudioThresholdUsecs, mAmpleAudioThresholdUsecs));
+    DECODER_LOG(PR_LOG_DEBUG, "Slow video decode, set mLowAudioThresholdUsecs=%lld mAmpleAudioThresholdUsecs=%lld",
+                mLowAudioThresholdUsecs, mAmpleAudioThresholdUsecs);
   }
 
   SendStreamData();
 
   // The ready state can change when we've decoded data, so update the
   // ready state, so that DOM events can fire.
   UpdateReadyState();
 
@@ -710,19 +719,18 @@ MediaDecoderStateMachine::CheckIfDecodeC
   MOZ_ASSERT(!mReader->VideoQueue().IsFinished() || !mIsVideoDecoding);
   if (!mIsVideoDecoding && !mIsAudioDecoding) {
     // We've finished decoding all active streams,
     // so move to COMPLETED state.
     mState = DECODER_STATE_COMPLETED;
     DispatchDecodeTasksIfNeeded();
     ScheduleStateMachine();
   }
-  DECODER_LOG(PR_LOG_DEBUG,
-    ("%p CheckIfDecodeComplete %scompleted", mDecoder.get(),
-    ((mState == DECODER_STATE_COMPLETED) ? "" : "NOT ")));
+  DECODER_LOG(PR_LOG_DEBUG, "CheckIfDecodeComplete %scompleted",
+              ((mState == DECODER_STATE_COMPLETED) ? "" : "NOT "));
 }
 
 bool MediaDecoderStateMachine::IsPlaying()
 {
   AssertCurrentThreadInMonitor();
 
   return !mPlayStartTime.IsNull();
 }
@@ -748,17 +756,17 @@ static void WriteSilence(AudioStream* aS
   aStream->Write(buf.Elements(), aFrames);
 
   StartAudioStreamPlaybackIfNeeded(aStream);
 }
 
 void MediaDecoderStateMachine::AudioLoop()
 {
   NS_ASSERTION(OnAudioThread(), "Should be on audio thread.");
-  DECODER_LOG(PR_LOG_DEBUG, ("%p Begun audio thread/loop", mDecoder.get()));
+  DECODER_LOG(PR_LOG_DEBUG, "Begun audio thread/loop");
   int64_t audioDuration = 0;
   int64_t audioStartTime = -1;
   uint32_t channels, rate;
   double volume = -1;
   bool setVolume;
   double playbackRate = -1;
   bool setPlaybackRate;
   bool preservesPitch;
@@ -886,18 +894,17 @@ void MediaDecoderStateMachine::AudioLoop
 
     int64_t framesWritten = 0;
     if (missingFrames.value() > 0) {
       // The next audio chunk begins some time after the end of the last chunk
       // we pushed to the audio hardware. We must push silence into the audio
       // hardware so that the next audio chunk begins playback at the correct
       // time.
       missingFrames = std::min<int64_t>(UINT32_MAX, missingFrames.value());
-      DECODER_LOG(PR_LOG_DEBUG, ("%p Decoder playing %d frames of silence",
-                                 mDecoder.get(), int32_t(missingFrames.value())));
+      VERBOSE_LOG("playing %d frames of silence", int32_t(missingFrames.value()));
       framesWritten = PlaySilence(static_cast<uint32_t>(missingFrames.value()),
                                   channels, playedFrames.value());
     } else {
       framesWritten = PlayFromAudioQueue(sampleTime.value(), channels);
     }
     audioDuration += framesWritten;
     {
       ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
@@ -940,32 +947,32 @@ void MediaDecoderStateMachine::AudioLoop
       if (!seeking && !mAudioStream->IsPaused()) {
         {
           ReentrantMonitorAutoExit exit(mDecoder->GetReentrantMonitor());
           mAudioStream->Drain();
         }
       }
     }
   }
-  DECODER_LOG(PR_LOG_DEBUG, ("%p Reached audio stream end.", mDecoder.get()));
+  DECODER_LOG(PR_LOG_DEBUG, "Reached audio stream end.");
   {
     // Must hold lock while shutting down and anulling the audio stream to prevent
     // state machine thread trying to use it while we're destroying it.
     ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
     mAudioStream->Shutdown();
     mAudioStream = nullptr;
     if (!mAudioCaptured) {
       mAudioCompleted = true;
       UpdateReadyState();
       // Kick the decode thread; it may be sleeping waiting for this to finish.
       mDecoder->GetReentrantMonitor().NotifyAll();
     }
   }
 
-  DECODER_LOG(PR_LOG_DEBUG, ("%p Audio stream finished playing, audio thread exit", mDecoder.get()));
+  DECODER_LOG(PR_LOG_DEBUG, "Audio stream finished playing, audio thread exit");
 }
 
 uint32_t MediaDecoderStateMachine::PlaySilence(uint32_t aFrames,
                                                    uint32_t aChannels,
                                                    uint64_t aFrameOffset)
 
 {
   NS_ASSERTION(OnAudioThread(), "Only call on audio thread.");
@@ -986,20 +993,18 @@ uint32_t MediaDecoderStateMachine::PlayF
     ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
     NS_WARN_IF_FALSE(IsPlaying(), "Should be playing");
     // Awaken the decode loop if it's waiting for space to free up in the
     // audio queue.
     mDecoder->GetReentrantMonitor().NotifyAll();
   }
   int64_t offset = -1;
   uint32_t frames = 0;
-  if (!PR_GetEnv("MOZ_QUIET")) {
-    DECODER_LOG(PR_LOG_DEBUG, ("%p Decoder playing %d frames of data to stream for AudioData at %lld",
-                               mDecoder.get(), audio->mFrames, audio->mTime));
-  }
+  VERBOSE_LOG("playing %d frames of data to stream for AudioData at %lld",
+              audio->mFrames, audio->mTime);
   mAudioStream->Write(audio->mAudioData,
                       audio->mFrames);
 
   aChannels = mAudioStream->GetOutChannels();
 
   StartAudioStreamPlaybackIfNeeded(mAudioStream);
 
   offset = audio->mOffset;
@@ -1040,17 +1045,17 @@ nsresult MediaDecoderStateMachine::Init(
   rv = mTimer->SetTarget(GetStateMachineThread());
   NS_ENSURE_SUCCESS(rv, rv);
 
   return mReader->Init(cloneReader);
 }
 
 void MediaDecoderStateMachine::StopPlayback()
 {
-  DECODER_LOG(PR_LOG_DEBUG, ("%p StopPlayback()", mDecoder.get()));
+  DECODER_LOG(PR_LOG_DEBUG, "StopPlayback()");
 
   AssertCurrentThreadInMonitor();
 
   mDecoder->NotifyPlaybackStopped();
 
   if (IsPlaying()) {
     mPlayDuration = GetClock();
     mPlayStartTime = TimeStamp();
@@ -1083,17 +1088,17 @@ int64_t MediaDecoderStateMachine::GetCur
   NS_ASSERTION(mSyncPointInDecodedStream >= 0, "Should have set up sync point");
   DecodedStreamData* stream = mDecoder->GetDecodedStream();
   StreamTime streamDelta = stream->GetLastOutputTime() - mSyncPointInMediaStream;
   return mSyncPointInDecodedStream + MediaTimeToMicroseconds(streamDelta);
 }
 
 void MediaDecoderStateMachine::StartPlayback()
 {
-  DECODER_LOG(PR_LOG_DEBUG, ("%p StartPlayback()", mDecoder.get()));
+  DECODER_LOG(PR_LOG_DEBUG, "StartPlayback()");
 
   NS_ASSERTION(!IsPlaying(), "Shouldn't be playing when StartPlayback() is called");
   AssertCurrentThreadInMonitor();
 
   mDecoder->NotifyPlaybackStarted();
   mPlayStartTime = TimeStamp::Now();
 
   NS_ASSERTION(IsPlaying(), "Should report playing by end of StartPlayback()");
@@ -1295,17 +1300,17 @@ void MediaDecoderStateMachine::Shutdown(
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
 
   // Once we've entered the shutdown state here there's no going back.
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
   // Change state before issuing shutdown request to threads so those
   // threads can start exiting cleanly during the Shutdown call.
-  DECODER_LOG(PR_LOG_DEBUG, ("%p Changed state to SHUTDOWN", mDecoder.get()));
+  DECODER_LOG(PR_LOG_DEBUG, "Changed state to SHUTDOWN");
   ScheduleStateMachine();
   mState = DECODER_STATE_SHUTDOWN;
   mDecoder->GetReentrantMonitor().NotifyAll();
 }
 
 void MediaDecoderStateMachine::StartDecoding()
 {
   NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
@@ -1363,17 +1368,17 @@ void MediaDecoderStateMachine::NotifyWai
 void MediaDecoderStateMachine::Play()
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   // When asked to play, switch to decoding state only if
   // we are currently buffering. In other cases, we'll start playing anyway
   // when the state machine notices the decoder's state change to PLAYING.
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   if (mState == DECODER_STATE_BUFFERING) {
-    DECODER_LOG(PR_LOG_DEBUG, ("%p Changed state from BUFFERING to DECODING", mDecoder.get()));
+    DECODER_LOG(PR_LOG_DEBUG, "Changed state from BUFFERING to DECODING");
     mState = DECODER_STATE_DECODING;
     mDecodeStartTime = TimeStamp::Now();
   }
   // Once we start playing, we don't want to minimize our prerolling, as we
   // assume the user is likely to want to keep playing in future.
   mMinimizePreroll = false;
   ScheduleStateMachine();
 }
@@ -1437,17 +1442,17 @@ void MediaDecoderStateMachine::Seek(cons
   int64_t seekTime = aTarget.mTime + mStartTime;
   seekTime = std::min(seekTime, mEndTime);
   seekTime = std::max(mStartTime, seekTime);
   NS_ASSERTION(seekTime >= mStartTime && seekTime <= mEndTime,
                "Can only seek in range [0,duration]");
   mSeekTarget = SeekTarget(seekTime, aTarget.mType);
 
   mBasePosition = seekTime - mStartTime;
-  DECODER_LOG(PR_LOG_DEBUG, ("%p Changed state to SEEKING (to %ld)", mDecoder.get(), mSeekTarget.mTime));
+  DECODER_LOG(PR_LOG_DEBUG, "Changed state to SEEKING (to %ld)", mSeekTarget.mTime);
   mState = DECODER_STATE_SEEKING;
   if (mDecoder->GetDecodedStream()) {
     mDecoder->RecreateDecodedStream(seekTime - mStartTime);
   }
   ScheduleStateMachine();
 }
 
 void MediaDecoderStateMachine::StopAudioThread()
@@ -1459,17 +1464,17 @@ void MediaDecoderStateMachine::StopAudio
   if (mStopAudioThread) {
     // Nothing to do, since the thread is already stopping
     return;
   }
 
   mStopAudioThread = true;
   mDecoder->GetReentrantMonitor().NotifyAll();
   if (mAudioThread) {
-    DECODER_LOG(PR_LOG_DEBUG, ("%p Shutdown audio thread", mDecoder.get()));
+    DECODER_LOG(PR_LOG_DEBUG, "Shutdown audio thread");
     {
       ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
       mAudioThread->Shutdown();
     }
     mAudioThread = nullptr;
     // Now that the audio thread is dead, try sending data to our MediaStream(s).
     // That may have been waiting for the audio thread to stop.
     SendStreamData();
@@ -1507,30 +1512,29 @@ MediaDecoderStateMachine::EnsureActive()
 }
 
 void
 MediaDecoderStateMachine::SetReaderIdle()
 {
 #ifdef PR_LOGGING
   {
     ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-    DECODER_LOG(PR_LOG_DEBUG, ("%p SetReaderIdle() audioQueue=%lld videoQueue=%lld",
-                               mDecoder.get(),
-                               GetDecodedAudioDuration(),
-                               mReader->VideoQueue().Duration()));
+    DECODER_LOG(PR_LOG_DEBUG, "SetReaderIdle() audioQueue=%lld videoQueue=%lld",
+                GetDecodedAudioDuration(),
+                mReader->VideoQueue().Duration());
   }
 #endif
   MOZ_ASSERT(OnDecodeThread());
   mReader->SetIdle();
 }
 
 void
 MediaDecoderStateMachine::SetReaderActive()
 {
-  DECODER_LOG(PR_LOG_DEBUG, ("%p SetReaderActive()", mDecoder.get()));
+  DECODER_LOG(PR_LOG_DEBUG, "SetReaderActive()");
   MOZ_ASSERT(OnDecodeThread());
   mReader->SetActive();
 }
 
 void
 MediaDecoderStateMachine::DispatchDecodeTasksIfNeeded()
 {
   AssertCurrentThreadInMonitor();
@@ -1694,17 +1698,17 @@ MediaDecoderStateMachine::StartAudioThre
 
   mStopAudioThread = false;
   if (HasAudio() && !mAudioThread) {
     nsresult rv = NS_NewNamedThread("Media Audio",
                                     getter_AddRefs(mAudioThread),
                                     nullptr,
                                     MEDIA_THREAD_STACK_SIZE);
     if (NS_FAILED(rv)) {
-      DECODER_LOG(PR_LOG_DEBUG, ("%p Changed state to SHUTDOWN because failed to create audio thread", mDecoder.get()));
+      DECODER_LOG(PR_LOG_WARNING, "Changed state to SHUTDOWN because failed to create audio thread");
       mState = DECODER_STATE_SHUTDOWN;
       return rv;
     }
 
     nsCOMPtr<nsIRunnable> event =
       NS_NewRunnableMethod(this, &MediaDecoderStateMachine::AudioLoop);
     mAudioThread->Dispatch(event, NS_DISPATCH_NORMAL);
   }
@@ -1771,17 +1775,17 @@ void
 MediaDecoderStateMachine::DecodeError()
 {
   AssertCurrentThreadInMonitor();
   NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
 
   // Change state to shutdown before sending error report to MediaDecoder
   // and the HTMLMediaElement, so that our pipeline can start exiting
   // cleanly during the sync dispatch below.
-  DECODER_LOG(PR_LOG_DEBUG, ("%p Changed state to SHUTDOWN", mDecoder.get()));
+  DECODER_LOG(PR_LOG_WARNING, "Decode error, changed state to SHUTDOWN");
   ScheduleStateMachine();
   mState = DECODER_STATE_SHUTDOWN;
   mDecoder->GetReentrantMonitor().NotifyAll();
 
   // Dispatch the event to call DecodeError synchronously. This ensures
   // we're in shutdown state by the time we exit the decode thread.
   // If we just moved to shutdown state here on the decode thread, we may
   // cause the state machine to shutdown/free memory without closing its
@@ -1798,26 +1802,26 @@ MediaDecoderStateMachine::DecodeError()
 void
 MediaDecoderStateMachine::CallDecodeMetadata()
 {
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   if (mState != DECODER_STATE_DECODING_METADATA) {
     return;
   }
   if (NS_FAILED(DecodeMetadata())) {
-    DECODER_LOG(PR_LOG_DEBUG, ("Decode metadata failed, shutting down decoder"));
+    DECODER_LOG(PR_LOG_WARNING, "Decode metadata failed, shutting down decoder");
     DecodeError();
   }
 }
 
 nsresult MediaDecoderStateMachine::DecodeMetadata()
 {
   AssertCurrentThreadInMonitor();
   NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
-  DECODER_LOG(PR_LOG_DEBUG, ("%p Decoding Media Headers", mDecoder.get()));
+  DECODER_LOG(PR_LOG_DEBUG, "Decoding Media Headers");
   if (mState != DECODER_STATE_DECODING_METADATA) {
     return NS_ERROR_FAILURE;
   }
   EnsureActive();
 
   nsresult res;
   MediaInfo info;
   MetadataTags* tags;
@@ -1852,20 +1856,19 @@ nsresult MediaDecoderStateMachine::Decod
   }
 
   NS_ASSERTION(mStartTime != -1, "Must have start time");
   MOZ_ASSERT((!HasVideo() && !HasAudio()) ||
               !(mMediaSeekable && mTransportSeekable) || mEndTime != -1,
               "Active seekable media should have end time");
   MOZ_ASSERT(!(mMediaSeekable && mTransportSeekable) ||
              GetDuration() != -1, "Seekable media should have duration");
-  DECODER_LOG(PR_LOG_DEBUG, ("%p Media goes from %lld to %lld (duration %lld)"
-                             " transportSeekable=%d, mediaSeekable=%d",
-                             mDecoder.get(), mStartTime, mEndTime, GetDuration(),
-                             mTransportSeekable, mMediaSeekable));
+  DECODER_LOG(PR_LOG_DEBUG, "Media goes from %lld to %lld (duration %lld) "
+              "transportSeekable=%d, mediaSeekable=%d",
+              mStartTime, mEndTime, GetDuration(), mTransportSeekable, mMediaSeekable);
 
   if (HasAudio() && !HasVideo()) {
     // We're playing audio only. We don't need to worry about slow video
     // decodes causing audio underruns, so don't buffer so much audio in
     // order to reduce memory usage.
     mAmpleAudioThresholdUsecs /= NO_VIDEO_AMPLE_AUDIO_DIVISOR;
     mLowAudioThresholdUsecs /= NO_VIDEO_AMPLE_AUDIO_DIVISOR;
   }
@@ -1887,17 +1890,17 @@ nsresult MediaDecoderStateMachine::Decod
   }
   if (HasVideo()) {
     RefPtr<nsIRunnable> decodeTask(
       NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DispatchVideoDecodeTaskIfNeeded));
     mReader->VideoQueue().AddPopListener(decodeTask, mDecodeTaskQueue);
   }
 
   if (mState == DECODER_STATE_DECODING_METADATA) {
-    DECODER_LOG(PR_LOG_DEBUG, ("%p Changed state from DECODING_METADATA to DECODING", mDecoder.get()));
+    DECODER_LOG(PR_LOG_DEBUG, "Changed state from DECODING_METADATA to DECODING");
     StartDecoding();
   }
 
   // For very short media FindStartTime() can decode the entire media.
   // So we need to check if this has occurred, else our decode pipeline won't
   // run (since it doesn't need to) and we won't detect end of stream.
   CheckIfDecodeComplete();
 
@@ -2020,42 +2023,39 @@ void MediaDecoderStateMachine::DecodeSee
   // if we need to seek again.
 
   nsCOMPtr<nsIRunnable> stopEvent;
   bool isLiveStream = mDecoder->GetResource()->GetLength() == -1;
   if (GetMediaTime() == mEndTime && !isLiveStream) {
     // Seeked to end of media, move to COMPLETED state. Note we don't do
     // this if we're playing a live stream, since the end of media will advance
     // once we download more data!
-    DECODER_LOG(PR_LOG_DEBUG, ("%p Changed state from SEEKING (to %lld) to COMPLETED",
-                               mDecoder.get(), seekTime));
+    DECODER_LOG(PR_LOG_DEBUG, "Changed state from SEEKING (to %lld) to COMPLETED", seekTime);
     stopEvent = NS_NewRunnableMethod(mDecoder, &MediaDecoder::SeekingStoppedAtEnd);
     // Explicitly set our state so we don't decode further, and so
     // we report playback ended to the media element.
     mState = DECODER_STATE_COMPLETED;
     mIsAudioDecoding = false;
     mIsVideoDecoding = false;
     DispatchDecodeTasksIfNeeded();
   } else {
-    DECODER_LOG(PR_LOG_DEBUG, ("%p Changed state from SEEKING (to %lld) to DECODING",
-                               mDecoder.get(), seekTime));
+    DECODER_LOG(PR_LOG_DEBUG, "Changed state from SEEKING (to %lld) to DECODING", seekTime);
     stopEvent = NS_NewRunnableMethod(mDecoder, &MediaDecoder::SeekingStopped);
     StartDecoding();
   }
 
   if (newCurrentTime != mediaTime) {
     UpdatePlaybackPositionInternal(newCurrentTime);
     if (mDecoder->GetDecodedStream()) {
       SetSyncPointForMediaStream();
     }
   }
 
   // Try to decode another frame to detect if we're at the end...
-  DECODER_LOG(PR_LOG_DEBUG, ("%p Seek completed, mCurrentFrameTime=%lld\n",
-              mDecoder.get(), mCurrentFrameTime));
+  DECODER_LOG(PR_LOG_DEBUG, "Seek completed, mCurrentFrameTime=%lld", mCurrentFrameTime);
 
   {
     ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
     NS_DispatchToMainThread(stopEvent, NS_DISPATCH_SYNC);
   }
 
   // Reset quick buffering status. This ensures that if we began the
   // seek while quick-buffering, we won't bypass quick buffering mode
@@ -2221,29 +2221,24 @@ nsresult MediaDecoderStateMachine::RunSt
       bool isLiveStream = resource->GetLength() == -1;
       if ((isLiveStream || !mDecoder->CanPlayThrough()) &&
             elapsed < TimeDuration::FromSeconds(mBufferingWait * mPlaybackRate) &&
             (mQuickBuffering ? HasLowDecodedData(QUICK_BUFFERING_LOW_DATA_USECS)
                             : HasLowUndecodedData(mBufferingWait * USECS_PER_S)) &&
             !mDecoder->IsDataCachedToEndOfResource() &&
             !resource->IsSuspended())
       {
-        DECODER_LOG(PR_LOG_DEBUG,
-                    ("%p Buffering: wait %ds, timeout in %.3lfs %s",
-                      mDecoder.get(),
-                      mBufferingWait,
-                      mBufferingWait - elapsed.ToSeconds(),
-                      (mQuickBuffering ? "(quick exit)" : "")));
+        DECODER_LOG(PR_LOG_DEBUG, "Buffering: wait %ds, timeout in %.3lfs %s",
+                    mBufferingWait, mBufferingWait - elapsed.ToSeconds(),
+                    (mQuickBuffering ? "(quick exit)" : ""));
         ScheduleStateMachine(USECS_PER_S);
         return NS_OK;
       } else {
-        DECODER_LOG(PR_LOG_DEBUG, ("%p Changed state from BUFFERING to DECODING", mDecoder.get()));
-        DECODER_LOG(PR_LOG_DEBUG, ("%p Buffered for %.3lfs",
-                                   mDecoder.get(),
-                                   (now - mBufferingStart).ToSeconds()));
+        DECODER_LOG(PR_LOG_DEBUG, "Changed state from BUFFERING to DECODING");
+        DECODER_LOG(PR_LOG_DEBUG, "Buffered for %.3lfs", (now - mBufferingStart).ToSeconds());
         StartDecoding();
       }
 
       // Notify to allow blocked decoder thread to continue
       mDecoder->GetReentrantMonitor().NotifyAll();
       UpdateReadyState();
       if (mDecoder->GetState() == MediaDecoder::PLAY_STATE_PLAYING &&
           !IsPlaying())
@@ -2319,20 +2314,17 @@ void MediaDecoderStateMachine::RenderVid
   NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
                "Should be on state machine or decode thread.");
   mDecoder->GetReentrantMonitor().AssertNotCurrentThreadIn();
 
   if (aData->mDuplicate) {
     return;
   }
 
-  if (!PR_GetEnv("MOZ_QUIET")) {
-    DECODER_LOG(PR_LOG_DEBUG, ("%p Decoder playing video frame %lld",
-                               mDecoder.get(), aData->mTime));
-  }
+  VERBOSE_LOG("playing video frame %lld", aData->mTime);
 
   VideoFrameContainer* container = mDecoder->GetVideoFrameContainer();
   if (container) {
     container->SetCurrentFrame(ThebesIntSize(aData->mDisplay), aData->mImage,
                                aTarget);
   }
 }
 
@@ -2433,22 +2425,19 @@ void MediaDecoderStateMachine::AdvanceFr
   int32_t droppedFrames = 0;
 #endif
   if (mReader->VideoQueue().GetSize() > 0) {
     VideoData* frame = mReader->VideoQueue().PeekFront();
     while (mRealTime || clock_time >= frame->mTime) {
       mVideoFrameEndTime = frame->GetEndTime();
       currentFrame = frame;
 #ifdef PR_LOGGING
-      if (!PR_GetEnv("MOZ_QUIET")) {
-        DECODER_LOG(PR_LOG_DEBUG, ("%p Decoder discarding video frame %lld", mDecoder.get(), frame->mTime));
-        if (droppedFrames++) {
-          DECODER_LOG(PR_LOG_DEBUG, ("%p Decoder discarding video frame %lld (%d so far)",
-                      mDecoder.get(), frame->mTime, droppedFrames - 1));
-        }
+      VERBOSE_LOG("discarding video frame %lld", frame->mTime);
+      if (droppedFrames++) {
+        VERBOSE_LOG("discarding video frame %lld (%d so far)", frame->mTime, droppedFrames-1);
       }
 #endif
       mReader->VideoQueue().PopFront();
       // Notify the decode thread that the video queue's buffers may have
       // free'd up space for more frames.
       mDecoder->GetReentrantMonitor().NotifyAll();
       mDecoder->UpdatePlaybackOffset(frame->mOffset);
       if (mReader->VideoQueue().GetSize() == 0)
@@ -2576,17 +2565,17 @@ VideoData* MediaDecoderStateMachine::Fin
       // duration.
       mEndTime = mStartTime + mEndTime;
     }
   }
   // Set the audio start time to be start of media. If this lies before the
   // first actual audio frame we have, we'll inject silence during playback
   // to ensure the audio starts at the correct time.
   mAudioStartTime = mStartTime;
-  DECODER_LOG(PR_LOG_DEBUG, ("%p Media start time is %lld", mDecoder.get(), mStartTime));
+  DECODER_LOG(PR_LOG_DEBUG, "Media start time is %lld", mStartTime);
   return v;
 }
 
 void MediaDecoderStateMachine::UpdateReadyState() {
   AssertCurrentThreadInMonitor();
 
   MediaDecoderOwner::NextFrameStatus nextFrameStatus = GetNextFrameStatus();
   if (nextFrameStatus == mLastFrameStatus) {
@@ -2646,24 +2635,23 @@ void MediaDecoderStateMachine::StartBuff
   // there might be pending main-thread events, such as "data
   // received" notifications, that mean we're not actually still
   // buffering by the time this runnable executes. So instead
   // we just trigger UpdateReadyStateForData; when it runs, it
   // will check the current state and decide whether to tell
   // the element we're buffering or not.
   UpdateReadyState();
   mState = DECODER_STATE_BUFFERING;
-  DECODER_LOG(PR_LOG_DEBUG, ("%p Changed state from DECODING to BUFFERING, decoded for %.3lfs",
-                             mDecoder.get(), decodeDuration.ToSeconds()));
+  DECODER_LOG(PR_LOG_DEBUG, "Changed state from DECODING to BUFFERING, decoded for %.3lfs",
+              decodeDuration.ToSeconds());
 #ifdef PR_LOGGING
   MediaDecoder::Statistics stats = mDecoder->GetStatistics();
-  DECODER_LOG(PR_LOG_DEBUG, ("%p Playback rate: %.1lfKB/s%s download rate: %.1lfKB/s%s",
-              mDecoder.get(),
+  DECODER_LOG(PR_LOG_DEBUG, "Playback rate: %.1lfKB/s%s download rate: %.1lfKB/s%s",
               stats.mPlaybackRate/1024, stats.mPlaybackRateReliable ? "" : " (unreliable)",
-              stats.mDownloadRate/1024, stats.mDownloadRateReliable ? "" : " (unreliable)"));
+              stats.mDownloadRate/1024, stats.mDownloadRateReliable ? "" : " (unreliable)");
 #endif
 }
 
 nsresult MediaDecoderStateMachine::GetBuffered(dom::TimeRanges* aBuffered) {
   MediaResource* resource = mDecoder->GetResource();
   NS_ENSURE_TRUE(resource, NS_ERROR_FAILURE);
   resource->Pin();
   nsresult res = mReader->GetBuffered(aBuffered, mStartTime);
@@ -2684,36 +2672,54 @@ nsresult MediaDecoderStateMachine::CallR
   MOZ_ASSERT(!mInRunningStateMachine, "State machine cycles must run in sequence!");
   mTimeout = TimeStamp();
   mInRunningStateMachine = true;
   nsresult res = RunStateMachine();
   mInRunningStateMachine = false;
   return res;
 }
 
-static void TimeoutExpired(nsITimer *aTimer, void *aClosure) {
-  MediaDecoderStateMachine *machine =
-    static_cast<MediaDecoderStateMachine*>(aClosure);
-  NS_ASSERTION(machine, "Must have been passed state machine");
-  machine->TimeoutExpired();
-}
-
-void MediaDecoderStateMachine::TimeoutExpired()
+nsresult MediaDecoderStateMachine::TimeoutExpired(int aTimerId)
 {
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   NS_ASSERTION(OnStateMachineThread(), "Must be on state machine thread");
-  CallRunStateMachine();
+  mTimer->Cancel();
+  if (mTimerId == aTimerId) {
+    return CallRunStateMachine();
+  } else {
+    return NS_OK;
+  }
 }
 
 void MediaDecoderStateMachine::ScheduleStateMachineWithLockAndWakeDecoder() {
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   DispatchAudioDecodeTaskIfNeeded();
   DispatchVideoDecodeTaskIfNeeded();
 }
 
+class TimerEvent : public nsITimerCallback, public nsRunnable {
+  NS_DECL_THREADSAFE_ISUPPORTS
+public:
+  TimerEvent(MediaDecoderStateMachine* aStateMachine, int aTimerId)
+    : mStateMachine(aStateMachine), mTimerId(aTimerId) {}
+
+  NS_IMETHOD Run() MOZ_OVERRIDE {
+    return mStateMachine->TimeoutExpired(mTimerId);
+  }
+
+  NS_IMETHOD Notify(nsITimer* aTimer) {
+    return mStateMachine->TimeoutExpired(mTimerId);
+  }
+private:
+  const nsRefPtr<MediaDecoderStateMachine> mStateMachine;
+  int mTimerId;
+};
+
+NS_IMPL_ISUPPORTS2(TimerEvent, nsITimerCallback, nsIRunnable);
+
 nsresult MediaDecoderStateMachine::ScheduleStateMachine(int64_t aUsecs) {
   AssertCurrentThreadInMonitor();
   NS_ABORT_IF_FALSE(GetStateMachineThread(),
     "Must have a state machine thread to schedule");
 
   if (mState == DECODER_STATE_SHUTDOWN) {
     return NS_ERROR_FAILURE;
   }
@@ -2725,25 +2731,42 @@ nsresult MediaDecoderStateMachine::Sched
     // or have an event dispatched to run the state machine.
     return NS_OK;
   }
 
   uint32_t ms = static_cast<uint32_t>((aUsecs / USECS_PER_MS) & 0xFFFFFFFF);
   if (mRealTime && ms > 40) {
     ms = 40;
   }
-  mTimeout = timeout;
-  // Cancel existing timer if any since we are going to schedule a new one.
-  mTimer->Cancel();
-  nsresult rv = mTimer->InitWithFuncCallback(mozilla::TimeoutExpired,
-                                             this,
-                                             ms,
-                                             nsITimer::TYPE_ONE_SHOT);
-  NS_ENSURE_SUCCESS(rv, rv);
-  return NS_OK;
+
+  // Don't cancel the timer here for this function will be called from
+  // different threads.
+
+  nsresult rv = NS_ERROR_FAILURE;
+  nsRefPtr<TimerEvent> event = new TimerEvent(this, mTimerId+1);
+
+  if (ms == 0) {
+    // Dispatch a runnable to the state machine thread when delay is 0.
+    // It will has less latency than dispatching a runnable to the state
+    // machine thread which will then schedule a zero-delay timer.
+    rv = GetStateMachineThread()->Dispatch(event, NS_DISPATCH_NORMAL);
+  } else if (OnStateMachineThread()) {
+    rv = mTimer->InitWithCallback(event, ms, nsITimer::TYPE_ONE_SHOT);
+  } else {
+    MOZ_ASSERT(false, "non-zero delay timer should be only scheduled in state machine thread");
+  }
+
+  if (NS_SUCCEEDED(rv)) {
+    mTimeout = timeout;
+    ++mTimerId;
+  } else {
+    NS_WARNING("Failed to schedule state machine");
+  }
+
+  return rv;
 }
 
 bool MediaDecoderStateMachine::OnDecodeThread() const
 {
   return mDecodeTaskQueue->IsCurrentThreadIn();
 }
 
 bool MediaDecoderStateMachine::OnStateMachineThread() const
@@ -2822,8 +2845,11 @@ void MediaDecoderStateMachine::QueueMeta
   metadata->mHasAudio = aHasAudio;
   metadata->mHasVideo = aHasVideo;
   metadata->mTags = aTags;
   mMetadataManager.QueueMetadata(metadata);
 }
 
 } // namespace mozilla
 
+// avoid redefined macro in unified build
+#undef DECODER_LOG
+#undef VERBOSE_LOG
--- a/content/media/MediaDecoderStateMachine.h
+++ b/content/media/MediaDecoderStateMachine.h
@@ -302,17 +302,17 @@ public:
   void ScheduleStateMachineWithLockAndWakeDecoder();
 
   // Schedules the shared state machine thread to run the state machine
   // in aUsecs microseconds from now, if it's not already scheduled to run
   // earlier, in which case the request is discarded.
   nsresult ScheduleStateMachine(int64_t aUsecs = 0);
 
   // Timer function to implement ScheduleStateMachine(aUsecs).
-  void TimeoutExpired();
+  nsresult TimeoutExpired(int aGeneration);
 
   // Set the media fragment end time. aEndTime is in microseconds.
   void SetFragmentEndTime(int64_t aEndTime);
 
   // Drop reference to decoder.  Only called during shutdown dance.
   void ReleaseDecoder() {
     MOZ_ASSERT(mReader);
     if (mReader) {
@@ -932,12 +932,15 @@ private:
 
   // Stores presentation info required for playback. The decoder monitor
   // must be held when accessing this.
   MediaInfo mInfo;
 
   mozilla::MediaMetadataManager mMetadataManager;
 
   MediaDecoderOwner::NextFrameStatus mLastFrameStatus;
+
+  // The id of timer tasks, used to ignore tasks that are scheduled previously.
+  int mTimerId;
 };
 
 } // namespace mozilla;
 #endif
--- a/content/media/webaudio/AudioBuffer.cpp
+++ b/content/media/webaudio/AudioBuffer.cpp
@@ -223,43 +223,16 @@ AudioBuffer::GetThreadSharedChannelsForR
 
     mSharedChannels =
       StealJSArrayDataIntoThreadSharedFloatArrayBufferList(aJSContext, mJSChannels);
   }
 
   return mSharedChannels;
 }
 
-void
-AudioBuffer::MixToMono(JSContext* aJSContext)
-{
-  if (mJSChannels.Length() == 1) {
-    // The buffer is already mono
-    return;
-  }
-
-  // Prepare the input channels
-  nsAutoTArray<const void*, GUESS_AUDIO_CHANNELS> channels;
-  channels.SetLength(mJSChannels.Length());
-  for (uint32_t i = 0; i < mJSChannels.Length(); ++i) {
-    channels[i] = JS_GetFloat32ArrayData(mJSChannels[i]);
-  }
-
-  // Prepare the output channels
-  float* downmixBuffer = new float[mLength];
-
-  // Perform the down-mix
-  AudioChannelsDownMix(channels, &downmixBuffer, 1, mLength);
-
-  // Truncate the shared channels and copy the downmixed data over
-  mJSChannels.SetLength(1);
-  SetRawChannelContents(aJSContext, 0, downmixBuffer);
-  delete[] downmixBuffer;
-}
-
 size_t
 AudioBuffer::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
 {
   size_t amount = aMallocSizeOf(this);
   amount += mJSChannels.SizeOfExcludingThis(aMallocSizeOf);
   if (mSharedChannels) {
     amount += mSharedChannels->SizeOfExcludingThis(aMallocSizeOf);
   }
--- a/content/media/webaudio/AudioBuffer.h
+++ b/content/media/webaudio/AudioBuffer.h
@@ -96,18 +96,16 @@ public:
   // This replaces the contents of the JS array for the given channel.
   // This function needs to be called on an AudioBuffer which has not been
   // handed off to the content yet, and right after the object has been
   // initialized.
   void SetRawChannelContents(JSContext* aJSContext,
                              uint32_t aChannel,
                              float* aContents);
 
-  void MixToMono(JSContext* aJSContext);
-
 protected:
   bool RestoreJSChannelData(JSContext* aJSContext);
   void ClearJSChannels();
 
   nsRefPtr<AudioContext> mContext;
   // Float32Arrays
   AutoFallibleTArray<JS::Heap<JSObject*>, 2> mJSChannels;
 
--- a/content/media/webaudio/AudioBufferSourceNode.h
+++ b/content/media/webaudio/AudioBufferSourceNode.h
@@ -39,32 +39,17 @@ public:
   }
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(AudioBufferSourceNode, AudioNode)
 
   virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
   void Start(double aWhen, double aOffset,
              const Optional<double>& aDuration, ErrorResult& aRv);
-  void NoteOn(double aWhen, ErrorResult& aRv)
-  {
-    Start(aWhen, 0.0, Optional<double>(), aRv);
-  }
-  void NoteGrainOn(double aWhen, double aOffset,
-                   double aDuration, ErrorResult& aRv)
-  {
-    Optional<double> duration;
-    duration.Construct(aDuration);
-    Start(aWhen, aOffset, duration, aRv);
-  }
   void Stop(double aWhen, ErrorResult& aRv);
-  void NoteOff(double aWhen, ErrorResult& aRv)
-  {
-    Stop(aWhen, aRv);
-  }
 
   AudioBuffer* GetBuffer(JSContext* aCx) const
   {
     return mBuffer;
   }
   void SetBuffer(JSContext* aCx, AudioBuffer* aBuffer)
   {
     mBuffer = aBuffer;
--- a/content/media/webaudio/AudioContext.cpp
+++ b/content/media/webaudio/AudioContext.cpp
@@ -199,49 +199,16 @@ AudioContext::CreateBuffer(JSContext* aJ
   if (!buffer->InitializeBuffers(aNumberOfChannels, aJSContext)) {
     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return nullptr;
   }
 
   return buffer.forget();
 }
 
-already_AddRefed<AudioBuffer>
-AudioContext::CreateBuffer(JSContext* aJSContext, const ArrayBuffer& aBuffer,
-                          bool aMixToMono, ErrorResult& aRv)
-{
-  // Do not accept this method unless the legacy pref has been set.
-  if (!Preferences::GetBool("media.webaudio.legacy.AudioContext")) {
-    aRv.ThrowNotEnoughArgsError();
-    return nullptr;
-  }
-
-  // Sniff the content of the media.
-  // Failed type sniffing will be handled by SyncDecodeMedia.
-  nsAutoCString contentType;
-  NS_SniffContent(NS_DATA_SNIFFER_CATEGORY, nullptr,
-                  aBuffer.Data(), aBuffer.Length(),
-                  contentType);
-
-  nsRefPtr<WebAudioDecodeJob> job =
-    new WebAudioDecodeJob(contentType, this, aBuffer);
-
-  if (mDecoder.SyncDecodeMedia(contentType.get(),
-                               aBuffer.Data(), aBuffer.Length(), *job) &&
-      job->mOutput) {
-    nsRefPtr<AudioBuffer> buffer = job->mOutput.forget();
-    if (aMixToMono) {
-      buffer->MixToMono(aJSContext);
-    }
-    return buffer.forget();
-  }
-
-  return nullptr;
-}
-
 namespace {
 
 bool IsValidBufferSize(uint32_t aBufferSize) {
   switch (aBufferSize) {
   case 0:       // let the implementation choose the buffer size
   case 256:
   case 512:
   case 1024:
--- a/content/media/webaudio/AudioContext.h
+++ b/content/media/webaudio/AudioContext.h
@@ -121,68 +121,42 @@ public:
 
   already_AddRefed<AudioBufferSourceNode> CreateBufferSource();
 
   already_AddRefed<AudioBuffer>
   CreateBuffer(JSContext* aJSContext, uint32_t aNumberOfChannels,
                uint32_t aLength, float aSampleRate,
                ErrorResult& aRv);
 
-  already_AddRefed<AudioBuffer>
-  CreateBuffer(JSContext* aJSContext, const ArrayBuffer& aBuffer,
-               bool aMixToMono, ErrorResult& aRv);
-
   already_AddRefed<MediaStreamAudioDestinationNode>
   CreateMediaStreamDestination(ErrorResult& aRv);
 
   already_AddRefed<ScriptProcessorNode>
   CreateScriptProcessor(uint32_t aBufferSize,
                         uint32_t aNumberOfInputChannels,
                         uint32_t aNumberOfOutputChannels,
                         ErrorResult& aRv);
 
-  already_AddRefed<ScriptProcessorNode>
-  CreateJavaScriptNode(uint32_t aBufferSize,
-                       uint32_t aNumberOfInputChannels,
-                       uint32_t aNumberOfOutputChannels,
-                       ErrorResult& aRv)
-  {
-    return CreateScriptProcessor(aBufferSize, aNumberOfInputChannels,
-                                 aNumberOfOutputChannels, aRv);
-  }
-
   already_AddRefed<AnalyserNode>
   CreateAnalyser();
 
   already_AddRefed<GainNode>
   CreateGain();
 
   already_AddRefed<WaveShaperNode>
   CreateWaveShaper();
 
-  already_AddRefed<GainNode>
-  CreateGainNode()
-  {
-    return CreateGain();
-  }
-
   already_AddRefed<MediaElementAudioSourceNode>
   CreateMediaElementSource(HTMLMediaElement& aMediaElement, ErrorResult& aRv);
   already_AddRefed<MediaStreamAudioSourceNode>
   CreateMediaStreamSource(DOMMediaStream& aMediaStream, ErrorResult& aRv);
 
   already_AddRefed<DelayNode>
   CreateDelay(double aMaxDelayTime, ErrorResult& aRv);
 
-  already_AddRefed<DelayNode>
-  CreateDelayNode(double aMaxDelayTime, ErrorResult& aRv)
-  {
-    return CreateDelay(aMaxDelayTime, aRv);
-  }
-
   already_AddRefed<PannerNode>
   CreatePanner();
 
   already_AddRefed<ConvolverNode>
   CreateConvolver();
 
   already_AddRefed<ChannelSplitterNode>
   CreateChannelSplitter(uint32_t aNumberOfOutputs, ErrorResult& aRv);
--- a/content/media/webaudio/AudioParam.h
+++ b/content/media/webaudio/AudioParam.h
@@ -104,20 +104,16 @@ public:
     if (!WebAudioUtils::IsTimeValid(aStartTime) ||
         !WebAudioUtils::IsTimeValid(aTimeConstant)) {
       aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
       return;
     }
     AudioParamTimeline::SetTargetAtTime(aTarget, DOMTimeToStreamTime(aStartTime), aTimeConstant, aRv);
     mCallback(mNode);
   }
-  void SetTargetValueAtTime(float aTarget, double aStartTime, double aTimeConstant, ErrorResult& aRv)
-  {
-    SetTargetAtTime(aTarget, aStartTime, aTimeConstant, aRv);
-  }
   void CancelScheduledValues(double aStartTime, ErrorResult& aRv)
   {
     if (!WebAudioUtils::IsTimeValid(aStartTime)) {
       aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
       return;
     }
     AudioParamTimeline::CancelScheduledValues(DOMTimeToStreamTime(aStartTime));
     mCallback(mNode);
--- a/content/media/webaudio/MediaBufferDecoder.cpp
+++ b/content/media/webaudio/MediaBufferDecoder.cpp
@@ -127,18 +127,16 @@ private:
                               NS_DISPATCH_NORMAL);
 
       nsCOMPtr<nsIRunnable> event =
         new ReportResultTask(mDecodeJob, &WebAudioDecodeJob::OnFailure, aErrorCode);
       NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
     }
   }
 
-  void RunNextPhase();
-
   void Decode();
   void AllocateBuffer();
   void CallbackTheResult();
 
   void Cleanup()
   {
     MOZ_ASSERT(NS_IsMainThread());
     // MediaDecoderReader expects that BufferDecoder is alive.
@@ -202,40 +200,16 @@ MediaDecodeTask::CreateReader()
   nsresult rv = mDecoderReader->Init(nullptr);
   if (NS_FAILED(rv)) {
     return false;
   }
 
   return true;
 }
 
-void
-MediaDecodeTask::RunNextPhase()
-{
-  // This takes care of handling the logic of where to run the next phase.
-  // If we were invoked synchronously, we do not have a thread pool and
-  // everything happens on the main thread. Just invoke Run() in that case.
-  // Otherwise, some things happen on the main thread and others are run
-  // in the thread pool.
-  if (!mThreadPool) {
-    Run();
-    return;
-  }
-
-  switch (mPhase) {
-  case PhaseEnum::AllocateBuffer:
-    MOZ_ASSERT(!NS_IsMainThread());
-    NS_DispatchToMainThread(this);
-    break;
-  case PhaseEnum::Decode:
-  case PhaseEnum::Done:
-    MOZ_CRASH("Invalid phase Decode");
-  }
-}
-
 class AutoResampler {
 public:
   AutoResampler()
     : mResampler(nullptr)
   {}
   ~AutoResampler()
   {
     if (mResampler) {
@@ -254,18 +228,17 @@ public:
 
 private:
   SpeexResamplerState* mResampler;
 };
 
 void
 MediaDecodeTask::Decode()
 {
-  MOZ_ASSERT(!mThreadPool == NS_IsMainThread(),
-             "We should be on the main thread only if we don't have a thread pool");
+  MOZ_ASSERT(!NS_IsMainThread());
 
   mBufferDecoder->BeginDecoding(NS_GetCurrentThread());
 
   // Tell the decoder reader that we are not going to play the data directly,
   // and that we should not reject files with more channels than the audio
   // bakend support.
   mDecoderReader->SetIgnoreAudioOutputFormat();
 
@@ -390,17 +363,17 @@ MediaDecodeTask::Decode()
         mDecodeJob.mWriteIndex += outSamples;
         MOZ_ASSERT(mDecodeJob.mWriteIndex <= resampledFrames);
         MOZ_ASSERT(inSamples == inputLatency);
       }
     }
   }
 
   mPhase = PhaseEnum::AllocateBuffer;
-  RunNextPhase();
+  NS_DispatchToMainThread(this);
 }
 
 void
 MediaDecodeTask::AllocateBuffer()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (!mDecodeJob.AllocateBuffer()) {
@@ -485,39 +458,16 @@ MediaBufferDecoder::AsyncDecodeMedia(con
                            WebAudioDecodeJob::UnknownError);
     NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
   } else {
     mThreadPool->Dispatch(task, nsIThreadPool::DISPATCH_NORMAL);
   }
 }
 
 bool
-MediaBufferDecoder::SyncDecodeMedia(const char* aContentType, uint8_t* aBuffer,
-                                    uint32_t aLength,
-                                    WebAudioDecodeJob& aDecodeJob)
-{
-  // Do not attempt to decode the media if we were not successful at sniffing
-  // the content type.
-  if (!*aContentType ||
-      strcmp(aContentType, APPLICATION_OCTET_STREAM) == 0) {
-    return false;
-  }
-
-  nsRefPtr<MediaDecodeTask> task =
-    new MediaDecodeTask(aContentType, aBuffer, aLength, aDecodeJob, nullptr);
-  if (!task->CreateReader()) {
-    return false;
-  }
-
-  task->Run();
-  return true;
-}
-
-
-bool
 MediaBufferDecoder::EnsureThreadPoolInitialized()
 {
   if (!mThreadPool) {
     mThreadPool = SharedThreadPool::Get(NS_LITERAL_CSTRING("MediaBufferDecoder"));
     if (!mThreadPool) {
       return false;
     }
   }
@@ -531,25 +481,22 @@ WebAudioDecodeJob::WebAudioDecodeJob(con
                                      DecodeErrorCallback* aFailureCallback)
   : mContentType(aContentType)
   , mWriteIndex(0)
   , mContext(aContext)
   , mSuccessCallback(aSuccessCallback)
   , mFailureCallback(aFailureCallback)
 {
   MOZ_ASSERT(aContext);
+  MOZ_ASSERT(aSuccessCallback);
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_COUNT_CTOR(WebAudioDecodeJob);
 
   mArrayBuffer = aBuffer.Obj();
 
-  MOZ_ASSERT(aSuccessCallback ||
-             (!aSuccessCallback && !aFailureCallback),
-             "If a success callback is not passed, no failure callback should be passed either");
-
   mozilla::HoldJSObjects(this);
 }
 
 WebAudioDecodeJob::~WebAudioDecodeJob()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_COUNT_DTOR(WebAudioDecodeJob);
   mArrayBuffer = nullptr;
@@ -559,20 +506,18 @@ WebAudioDecodeJob::~WebAudioDecodeJob()
 void
 WebAudioDecodeJob::OnSuccess(ErrorCode aErrorCode)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aErrorCode == NoError);
 
   // Ignore errors in calling the callback, since there is not much that we can
   // do about it here.
-  if (mSuccessCallback) {
-    ErrorResult rv;
-    mSuccessCallback->Call(*mOutput, rv);
-  }
+  ErrorResult rv;
+  mSuccessCallback->Call(*mOutput, rv);
 
   mContext->RemoveFromDecodeQueue(this);
 }
 
 void
 WebAudioDecodeJob::OnFailure(ErrorCode aErrorCode)
 {
   MOZ_ASSERT(NS_IsMainThread());
--- a/content/media/webaudio/MediaBufferDecoder.h
+++ b/content/media/webaudio/MediaBufferDecoder.h
@@ -75,19 +75,16 @@ struct WebAudioDecodeJob MOZ_FINAL
  * thread-pool) and provides a clean external interface.
  */
 class MediaBufferDecoder
 {
 public:
   void AsyncDecodeMedia(const char* aContentType, uint8_t* aBuffer,
                         uint32_t aLength, WebAudioDecodeJob& aDecodeJob);
 
-  bool SyncDecodeMedia(const char* aContentType, uint8_t* aBuffer,
-                       uint32_t aLength, WebAudioDecodeJob& aDecodeJob);
-
   size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
   {
     return 0;
   }
 
 private:
   bool EnsureThreadPoolInitialized();
 
--- a/content/media/webaudio/test/mochitest.ini
+++ b/content/media/webaudio/test/mochitest.ini
@@ -21,17 +21,16 @@ support-files =
   ting-44.1k-2ch.wav
   ting-48k-1ch.wav
   ting-48k-2ch.wav
   webaudio.js
 
 [test_AudioBuffer.html]
 [test_AudioContext.html]
 [test_AudioListener.html]
-[test_AudioParam.html]
 [test_OfflineAudioContext.html]
 [test_analyserNode.html]
 [test_audioBufferSourceNode.html]
 [test_audioBufferSourceNodeEnded.html]
 [test_audioBufferSourceNodeLazyLoopParam.html]
 [test_audioBufferSourceNodeLoop.html]
 [test_audioBufferSourceNodeLoopStartEnd.html]
 [test_audioBufferSourceNodeLoopStartEndSame.html]
deleted file mode 100644
--- a/content/media/webaudio/test/test_AudioParam.html
+++ /dev/null
@@ -1,36 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Test AudioParam</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="webaudio.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-<pre id="test">
-<script class="testbody" type="text/javascript">
-
-var context = new AudioContext();
-var gain = context.createGain().gain;
-
-ok("value" in gain, "The value attr must exist");
-gain.value = 0.5;
-ok("defaultValue" in gain, "The defaultValue attr must exist");
-(function() {
-  "use strict"; // in order to get the readOnly setter to throw
-  expectTypeError(function() {
-    gain.defaultValue = 0.5;
-  });
-})();
-
-gain.setValueAtTime(1, 0.25);
-gain.linearRampToValueAtTime(0.75, 0.5);
-gain.exponentialRampToValueAtTime(0.1, 0.75);
-gain.setTargetAtTime(0.2, 1, 0.5);
-gain.setTargetValueAtTime(0.3, 1.25, 0.5);
-gain.cancelScheduledValues(1.5);
-
-</script>
-</pre>
-</body>
-</html>
--- a/content/media/webaudio/test/test_delayNode.html
+++ b/content/media/webaudio/test/test_delayNode.html
@@ -18,40 +18,30 @@ var gTest = {
     for (var i = 0; i < 2048; ++i) {
       buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
     }
 
     var source = context.createBufferSource();
 
     var delay = context.createDelay();
 
-    var delay2 = context.createDelayNode();
-    isnot(delay, delay2, "createDelayNode should create a different delay node");
-
     source.buffer = buffer;
 
     source.connect(delay);
 
     ok(delay.delayTime, "The audioparam member must exist");
     is(delay.delayTime.value, 0, "Correct initial value");
     is(delay.delayTime.defaultValue, 0, "Correct default value");
     delay.delayTime.value = 0.5;
     is(delay.delayTime.value, 0.5, "Correct initial value");
     is(delay.delayTime.defaultValue, 0, "Correct default value");
     is(delay.channelCount, 2, "delay node has 2 input channels by default");
     is(delay.channelCountMode, "max", "Correct channelCountMode for the delay node");
     is(delay.channelInterpretation, "speakers", "Correct channelCountInterpretation for the delay node");
 
-    var delay2 = context.createDelay(2);
-    is(delay2.delayTime.value, 0, "Correct initial value");
-    is(delay2.delayTime.defaultValue, 0, "Correct default value");
-    delay2.delayTime.value = 0.5;
-    is(delay2.delayTime.value, 0.5, "Correct initial value");
-    is(delay2.delayTime.defaultValue, 0, "Correct default value");
-
     expectException(function() {
       context.createDelay(0);
     }, DOMException.NOT_SUPPORTED_ERR);
     expectException(function() {
       context.createDelay(180);
     }, DOMException.NOT_SUPPORTED_ERR);
     expectTypeError(function() {
       context.createDelay(NaN);
--- a/content/media/webaudio/test/test_gainNode.html
+++ b/content/media/webaudio/test/test_gainNode.html
@@ -18,19 +18,16 @@ var gTest = {
     for (var i = 0; i < 2048; ++i) {
       buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
     }
 
     var source = context.createBufferSource();
 
     var gain = context.createGain();
 
-    var gain2 = context.createGainNode();
-    isnot(gain, gain2, "createGainNode should create a different gain node");
-
     source.buffer = buffer;
 
     source.connect(gain);
 
     ok(gain.gain, "The audioparam member must exist");
     is(gain.gain.value, 1.0, "Correct initial value");
     is(gain.gain.defaultValue, 1.0, "Correct default value");
     gain.gain.value = 0.5;
--- a/content/media/webaudio/test/test_mediaDecoding.html
+++ b/content/media/webaudio/test/test_mediaDecoding.html
@@ -297,36 +297,32 @@ function checkResampledBuffer(buffer, te
   cx.startRendering();
 }
 
 function runResampling(test, response, callback) {
   var sampleRate = test.sampleRate == 44100 ? 48000 : 44100;
   var cx = new OfflineAudioContext(1, 1, sampleRate);
   cx.decodeAudioData(response, function onSuccess(asyncResult) {
     is(asyncResult.sampleRate, sampleRate, "Correct sample rate");
-    syncResult = cx.createBuffer(response, false);
-    compareBuffers(syncResult, asyncResult);
 
     checkResampledBuffer(asyncResult, test, callback);
   }, function onFailure() {
     ok(false, "Expected successful decode with resample");
     callback();
   });
 }
 
 function runTest(test, response, callback) {
   var expectCallback = false;
   var cx = new OfflineAudioContext(test.numberOfChannels || 1,
                                    test.frames || 1, test.sampleRate);
   cx.decodeAudioData(response, function onSuccess(asyncResult) {
     ok(expectCallback, "Success callback should fire asynchronously");
     ok(test.valid, "Did expect success for test " + test.url);
 
-    syncResult = cx.createBuffer(response, false);
-    compareBuffers(syncResult, asyncResult);
     checkAudioBuffer(asyncResult, test);
 
     test.expectedBuffer = asyncResult;
     test.nativeContext = cx;
     runResampling(test, response, callback);
   }, function onFailure() {
     ok(expectCallback, "Failure callback should fire asynchronously");
     ok(!test.valid, "Did expect failure for test " + test.url);
--- a/content/media/webaudio/test/test_scriptProcessorNode.html
+++ b/content/media/webaudio/test/test_scriptProcessorNode.html
@@ -15,17 +15,17 @@
 // not be easy to map to OfflineAudioContext, as ScriptProcessorNodes
 // can experience delays.
 
 SimpleTest.waitForExplicitFinish();
 addLoadEvent(function() {
   var context = new AudioContext();
   var buffer = null;
 
-  var sourceSP = context.createJavaScriptNode(2048);
+  var sourceSP = context.createScriptProcessor(2048);
   sourceSP.addEventListener("audioprocess", function(e) {
     // generate the audio
     for (var i = 0; i < 2048; ++i) {
       // Make sure our first sample won't be zero
       e.outputBuffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * (i + 1) / context.sampleRate);
       e.outputBuffer.getChannelData(0)[i] = Math.sin(880 * 2 * Math.PI * (i + 1) / context.sampleRate);
     }
     // Remember our generated audio
--- a/content/media/webaudio/test/test_singleSourceDest.html
+++ b/content/media/webaudio/test/test_singleSourceDest.html
@@ -23,64 +23,16 @@ addLoadEvent(function() {
   is(destination.context, context, "Destination node has proper context");
   is(destination.numberOfInputs, 1, "Destination node has 1 inputs");
   is(destination.numberOfOutputs, 0, "Destination node has 0 outputs");
   is(destination.channelCount, 2, "Destination node has 2 input channels by default");
   is(destination.channelCountMode, "explicit", "Correct channelCountMode for the destination node");
   is(destination.channelInterpretation, "speakers", "Correct channelCountInterpretation for the destination node");
   ok(destination instanceof EventTarget, "AudioNodes must be EventTargets");
 
-  testWith(context, buffer, destination, function(source) {
-    source.start(0);
-  }, function(source) {
-    source.stop();
-  }, function() {
-    testWith(context, buffer, destination, function(source) {
-      source.start(0, 1);
-    }, function(source) {
-      expectTypeError(function() {
-        source.noteOff();
-      });
-      source.noteOff(0);
-    }, function() {
-      testWith(context, buffer, destination, function(source) {
-        source.start(0, 1, 0.5);
-      }, function(source) {
-        source.stop(0);
-      }, function() {
-        testWith(context, buffer, destination, function(source) {
-          source.noteOn(0);
-        }, function(source) {
-          source.noteOff(0);
-        }, function() {
-          testWith(context, buffer, destination, function(source) {
-            source.noteGrainOn(0, 1, 0.5);
-          }, function(source) {
-            source.stop();
-          }, function() {
-            SimpleTest.finish();
-          });
-        });
-      });
-    });
-  });
-});
-
-function testWith(context, buffer, destination, start, stop, callback)
-{
-  var source = createNode(context, buffer, destination);
-  start(source);
-  SimpleTest.executeSoon(function() {
-    stop(source);
-    callback();
-    source.disconnect();
-  });
-}
-
-function createNode(context, buffer, destination) {
   var source = context.createBufferSource();
   is(source.context, context, "Source node has proper context");
   is(source.numberOfInputs, 0, "Source node has 0 inputs");
   is(source.numberOfOutputs, 1, "Source node has 1 outputs");
   is(source.loop, false, "Source node is not looping");
   is(source.loopStart, 0, "Correct default value for loopStart");
   is(source.loopEnd, 0, "Correct default value for loopEnd");
   ok(!source.buffer, "Source node should not have a buffer when it's created");
@@ -97,15 +49,22 @@ function createNode(context, buffer, des
 
   source.connect(destination);
 
   is(source.numberOfInputs, 0, "Source node has 0 inputs");
   is(source.numberOfOutputs, 1, "Source node has 0 outputs");
   is(destination.numberOfInputs, 1, "Destination node has 0 inputs");
   is(destination.numberOfOutputs, 0, "Destination node has 0 outputs");
 
-  return source;
-}
+  source.start(0);
+  SimpleTest.executeSoon(function() {
+    source.stop(0);
+    source.disconnect();
+
+    SpecialPowers.clearUserPref("media.webaudio.enabled");
+    SimpleTest.finish();
+  });
+});
 
 </script>
 </pre>
 </body>
 </html>
--- a/content/media/webm/EbmlComposer.cpp
+++ b/content/media/webm/EbmlComposer.cpp
@@ -26,17 +26,18 @@ void EbmlComposer::GenerateHeader()
   ebml.buf = buffer.get();
   ebml.offset = 0;
   writeHeader(&ebml);
   {
     EbmlLoc segEbmlLoc, ebmlLocseg, ebmlLoc;
     Ebml_StartSubElement(&ebml, &segEbmlLoc, Segment);
     {
       Ebml_StartSubElement(&ebml, &ebmlLocseg, SeekHead);
-      // Todo: We don't know the exact sizes of encoded data and ignore this section.
+      // Todo: We don't know the exact sizes of encoded data and
+      // ignore this section.
       Ebml_EndSubElement(&ebml, &ebmlLocseg);
       writeSegmentInformation(&ebml, &ebmlLoc, TIME_CODE_SCALE, 0);
       {
         EbmlLoc trackLoc;
         Ebml_StartSubElement(&ebml, &trackLoc, Tracks);
         {
           // Video
           if (mWidth > 0 && mHeight > 0) {
@@ -49,82 +50,106 @@ void EbmlComposer::GenerateHeader()
             writeAudioTrack(&ebml, 0x2, 0x0, "A_VORBIS", mSampleFreq,
                             mChannels, mCodecPrivateData.Elements(),
                             mCodecPrivateData.Length());
           }
         }
         Ebml_EndSubElement(&ebml, &trackLoc);
       }
     }
-    // The Recording length is unknow and ignore write the whole Segment element size
+    // The Recording length is unknown and
+    // ignore write the whole Segment element size
   }
   MOZ_ASSERT(ebml.offset <= DEFAULT_HEADER_SIZE + mCodecPrivateData.Length(),
              "write more data > EBML_BUFFER_SIZE");
-  mClusterBuffs.AppendElement();
-  mClusterBuffs.LastElement().SetLength(ebml.offset);
-  memcpy(mClusterBuffs.LastElement().Elements(), ebml.buf, ebml.offset);
+  auto block = mClusterBuffs.AppendElement();
+  block->SetLength(ebml.offset);
+  memcpy(block->Elements(), ebml.buf, ebml.offset);
+  mFlushState |= FLUSH_METADATA;
+}
+
+void EbmlComposer::FinishMetadata()
+{
+  if (mFlushState & FLUSH_METADATA) {
+    // We don't remove the first element of mClusterBuffs because the
+    // |mClusterHeaderIndex| may have value.
+    mClusterCanFlushBuffs.AppendElement()->SwapElements(mClusterBuffs[0]);
+    mFlushState &= ~FLUSH_METADATA;
+  }
 }
 
 void EbmlComposer::FinishCluster()
 {
-  MOZ_ASSERT(mClusterLengthLoc > 0 );
-  MOZ_ASSERT(mClusterHeaderIndex > 0);
-  for (uint32_t i = 0; i < mClusterBuffs.Length(); i ++ ) {
-    mClusterCanFlushBuffs.AppendElement()->SwapElements(mClusterBuffs[i]);
+  FinishMetadata();
+  if (!(mFlushState & FLUSH_CLUSTER)) {
+    // No completed cluster available.
+    return;
   }
-  mClusterBuffs.Clear();
+
+  MOZ_ASSERT(mClusterLengthLoc > 0);
   EbmlGlobal ebml;
   EbmlLoc ebmlLoc;
   ebmlLoc.offset = mClusterLengthLoc;
-  ebml.offset = mClusterCanFlushBuffs[mClusterHeaderIndex].Length();
-  ebml.buf = mClusterCanFlushBuffs[mClusterHeaderIndex].Elements();
+  ebml.offset = mClusterBuffs[mClusterHeaderIndex].Length();
+  ebml.buf = mClusterBuffs[mClusterHeaderIndex].Elements();
   Ebml_EndSubElement(&ebml, &ebmlLoc);
+  // Move the mClusterBuffs data from mClusterHeaderIndex that we can skip
+  // the metadata and the rest P-frames after ContainerWriter::FLUSH_NEEDED.
+  for (uint32_t i = mClusterHeaderIndex; i < mClusterBuffs.Length(); i++) {
+    mClusterCanFlushBuffs.AppendElement()->SwapElements(mClusterBuffs[i]);
+  }
+
   mClusterHeaderIndex = 0;
   mClusterLengthLoc = 0;
+  mClusterBuffs.Clear();
+  mFlushState &= ~FLUSH_CLUSTER;
 }
 
 void
 EbmlComposer::WriteSimpleBlock(EncodedFrame* aFrame)
 {
   EbmlGlobal ebml;
   ebml.offset = 0;
 
-  if (aFrame->GetFrameType() == EncodedFrame::FrameType::VP8_I_FRAME && mClusterHeaderIndex > 0) {
+  if (aFrame->GetFrameType() == EncodedFrame::FrameType::VP8_I_FRAME) {
     FinishCluster();
   }
 
-  mClusterBuffs.AppendElement();
-  mClusterBuffs.LastElement().SetLength(aFrame->GetFrameData().Length() + DEFAULT_HEADER_SIZE);
-  ebml.buf = mClusterBuffs.LastElement().Elements();
+  auto block = mClusterBuffs.AppendElement();
+  block->SetLength(aFrame->GetFrameData().Length() + DEFAULT_HEADER_SIZE);
+  ebml.buf = block->Elements();
 
   if (aFrame->GetFrameType() == EncodedFrame::FrameType::VP8_I_FRAME) {
     EbmlLoc ebmlLoc;
     Ebml_StartSubElement(&ebml, &ebmlLoc, Cluster);
-    mClusterHeaderIndex = mClusterBuffs.Length() - 1; // current cluster header array index
+    MOZ_ASSERT(mClusterBuffs.Length() > 0);
+    // current cluster header array index
+    mClusterHeaderIndex = mClusterBuffs.Length() - 1;
     mClusterLengthLoc = ebmlLoc.offset;
-    if (aFrame->GetFrameType() != EncodedFrame::FrameType::VORBIS_AUDIO_FRAME) {
-      mClusterTimecode = aFrame->GetTimeStamp() / PR_USEC_PER_MSEC;
-    }
+    mClusterTimecode = aFrame->GetTimeStamp() / PR_USEC_PER_MSEC;
     Ebml_SerializeUnsigned(&ebml, Timecode, mClusterTimecode);
+    mFlushState |= FLUSH_CLUSTER;
   }
 
   if (aFrame->GetFrameType() != EncodedFrame::FrameType::VORBIS_AUDIO_FRAME) {
-    short timeCode = aFrame->GetTimeStamp() / PR_USEC_PER_MSEC - mClusterTimecode;
+    short timeCode = aFrame->GetTimeStamp() / PR_USEC_PER_MSEC
+                     - mClusterTimecode;
     writeSimpleBlock(&ebml, 0x1, timeCode, aFrame->GetFrameType() ==
                      EncodedFrame::FrameType::VP8_I_FRAME,
                      0, 0, (unsigned char*)aFrame->GetFrameData().Elements(),
                      aFrame->GetFrameData().Length());
   } else {
     writeSimpleBlock(&ebml, 0x2, 0, false,
                      0, 0, (unsigned char*)aFrame->GetFrameData().Elements(),
                      aFrame->GetFrameData().Length());
   }
-  MOZ_ASSERT(ebml.offset <= DEFAULT_HEADER_SIZE + aFrame->GetFrameData().Length(),
+  MOZ_ASSERT(ebml.offset <= DEFAULT_HEADER_SIZE +
+             aFrame->GetFrameData().Length(),
              "write more data > EBML_BUFFER_SIZE");
-  mClusterBuffs.LastElement().SetLength(ebml.offset);
+  block->SetLength(ebml.offset);
 }
 
 void
 EbmlComposer::SetVideoConfig(uint32_t aWidth, uint32_t aHeight,
                              uint32_t aDisplayWidth, uint32_t aDisplayHeight,
                              float aFrameRate)
 {
   MOZ_ASSERT(aWidth > 0, "Width should > 0");
@@ -150,28 +175,35 @@ EbmlComposer::SetAudioConfig(uint32_t aS
   mBitDepth = aBitDepth;
   mChannels = aChannels;
 }
 
 void
 EbmlComposer::ExtractBuffer(nsTArray<nsTArray<uint8_t> >* aDestBufs,
                             uint32_t aFlag)
 {
-  if ((aFlag & ContainerWriter::FLUSH_NEEDED) && mClusterHeaderIndex > 0) {
+  if ((aFlag & ContainerWriter::FLUSH_NEEDED) ||
+      (aFlag & ContainerWriter::GET_HEADER))
+  {
+    FinishMetadata();
+  }
+  if (aFlag & ContainerWriter::FLUSH_NEEDED)
+  {
     FinishCluster();
   }
   // aDestBufs may have some element
-  for (uint32_t i = 0; i < mClusterCanFlushBuffs.Length(); i ++ ) {
+  for (uint32_t i = 0; i < mClusterCanFlushBuffs.Length(); i++) {
     aDestBufs->AppendElement()->SwapElements(mClusterCanFlushBuffs[i]);
   }
   mClusterCanFlushBuffs.Clear();
 }
 
 EbmlComposer::EbmlComposer()
-  : mClusterHeaderIndex(0)
+  : mFlushState(FLUSH_NONE)
+  , mClusterHeaderIndex(0)
   , mClusterLengthLoc(0)
   , mClusterTimecode(0)
   , mWidth(0)
   , mHeight(0)
   , mFrameRate(0)
   , mSampleFreq(0)
   , mBitDepth(0)
   , mChannels(0)
--- a/content/media/webm/EbmlComposer.h
+++ b/content/media/webm/EbmlComposer.h
@@ -41,23 +41,33 @@ public:
    */
   void WriteSimpleBlock(EncodedFrame* aFrame);
   /*
    * Get valid cluster data.
    */
   void ExtractBuffer(nsTArray<nsTArray<uint8_t> >* aDestBufs,
                      uint32_t aFlag = 0);
 private:
+  // Move the metadata data to mClusterCanFlushBuffs.
+  void FinishMetadata();
   // Close current cluster and move data to mClusterCanFlushBuffs.
   void FinishCluster();
   // The temporary storage for cluster data.
   nsTArray<nsTArray<uint8_t> > mClusterBuffs;
   // The storage which contain valid cluster data.
   nsTArray<nsTArray<uint8_t> > mClusterCanFlushBuffs;
-  // Indicate the header index in mClusterBuffs.
+
+  // Indicate the data types in mClusterBuffs.
+  enum {
+    FLUSH_NONE = 0,
+    FLUSH_METADATA = 1 << 0,
+    FLUSH_CLUSTER = 1 << 1
+  };
+  uint32_t mFlushState;
+  // Indicate the cluster header index in mClusterBuffs.
   uint32_t mClusterHeaderIndex;
   // The cluster length position.
   uint64_t mClusterLengthLoc;
   // Audio codec specific header data.
   nsTArray<uint8_t> mCodecPrivateData;
 
   // The timecode of the cluster.
   uint64_t mClusterTimecode;
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -477,17 +477,17 @@ public:
             sameOrigin = false;
           }
         }
 
         NS_NAMED_LITERAL_STRING(xoriginMsg, "Script error.");
         if (sameOrigin) {
           init.mMessage = mErrorMsg;
           init.mLineno = mLineNumber;
-          init.mColumn = mColumn;
+          init.mColno = mColumn;
           init.mError = mError;
         } else {
           NS_WARNING("Not same origin error!");
           init.mMessage = xoriginMsg;
           init.mLineno = 0;
         }
 
         nsRefPtr<ErrorEvent> event =
--- a/dom/events/JSEventHandler.cpp
+++ b/dom/events/JSEventHandler.cpp
@@ -137,17 +137,17 @@ JSEventHandler::HandleEvent(nsIDOMEvent*
 
       scriptEvent->GetFilename(file);
       fileName = &file;
 
       lineNumber.Construct();
       lineNumber.Value() = scriptEvent->Lineno();
 
       columnNumber.Construct();
-      columnNumber.Value() = scriptEvent->Column();
+      columnNumber.Value() = scriptEvent->Colno();
 
       ThreadsafeAutoJSContext cx;
       error.Construct(cx);
       error.Value() = scriptEvent->Error(cx);
     } else {
       msgOrEvent.SetAsEvent() = aEvent->InternalDOMEvent();
     }
 
--- a/dom/events/test/test_error_events.html
+++ b/dom/events/test/test_error_events.html
@@ -32,17 +32,17 @@
     [ "Event filename", errorEvent.filename, location.href ],
     [ "Callback filename", file, location.href ],
     [ "Event line number", errorEvent.lineno, 27 ],
     [ "Callback line number", line, 27 ],
     [ "Event message", errorEvent.message, "Error: hello" ],
     [ "Callback message", msg, "Error: hello" ],
     [ "Event error-object", errorEvent.error, thrown],
     [ "Callback error-object", error, thrown ],
-    [ "Event column", errorEvent.column, 0 ], // Sadly not correct right now
+    [ "Event column", errorEvent.colno, 0 ], // Sadly not correct right now
     [ "Callback column", column, 0 ]
   ]);
 </script>
 <script>
   var workerLocation = location.protocol + "//" + location.host +
     location.pathname.replace("test_error_events.html", "error_event_worker.js");
   var eventFileTest = async_test("Worker event filename");
   var eventLineTest = async_test("Worker event line number");
--- a/dom/file/ArchiveZipEvent.cpp
+++ b/dom/file/ArchiveZipEvent.cpp
@@ -3,18 +3,16 @@
 /* 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 "ArchiveZipEvent.h"
 #include "ArchiveZipFile.h"
 
 #include "nsContentUtils.h"
-#include "nsIPlatformCharset.h"
-#include "nsNativeCharsetUtils.h"
 #include "nsCExternalHandlerService.h"
 
 using namespace mozilla::dom;
 
 USING_FILE_NAMESPACE
 
 #ifndef PATH_MAX
 #  define PATH_MAX 65536 // The filename length is stored in 2 bytes
--- a/dom/identity/nsDOMIdentity.js
+++ b/dom/identity/nsDOMIdentity.js
@@ -164,24 +164,23 @@ nsDOMIdentity.prototype = {
     }
 
     let util = this._window.QueryInterface(Ci.nsIInterfaceRequestor)
                            .getInterface(Ci.nsIDOMWindowUtils);
 
     let message = this.DOMIdentityMessage(aOptions);
 
     // We permit calling of request() outside of a user input handler only when
-    // we are handling the (deprecated) get() or getVerifiedEmail() calls,
-    // which make use of an RP context marked as _internal, or when a certified
-    // app is calling.
-    //
-    // XXX Bug 982460 - grant the same privilege to packaged apps
+    // a certified or privileged app is calling, or when we are handling the
+    // (deprecated) get() or getVerifiedEmail() calls, which make use of an RP
+    // context marked as _internal.
 
     if (!aOptions._internal &&
-        this._appStatus !== Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
+        this._appStatus !== Ci.nsIPrincipal.APP_STATUS_CERTIFIED &&
+        this._appStatus !== Ci.nsIPrincipal.APP_STATUS_PRIVILEGED) {
 
       // If the caller is not special in one of those ways, see if the user has
       // preffed on 'syntheticEventsOk' (useful for testing); otherwise, if
       // this is a non-native event, reject it.
       let util = this._window.QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsIDOMWindowUtils);
 
       if (!util.isHandlingUserInput && this.nativeEventsRequired) {
--- a/dom/identity/tests/mochitest/file_syntheticEvents.html
+++ b/dom/identity/tests/mochitest/file_syntheticEvents.html
@@ -2,17 +2,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/. */
  -->
 <!DOCTYPE html>
 <html>
   <!--
   Certified and privileged apps can call mozId outside an event handler
-  https://bugzilla.mozilla.org/show_bug.cgi?id=971379
+  Certified apps:  https://bugzilla.mozilla.org/show_bug.cgi?id=971379
+  Privileged apps: https://bugzilla.mozilla.org/show_bug.cgi?id=982460
   -->
 <head>
   <meta charset="utf-8">
   <title>Test app for bug 971379</title>
 </head>
 
 <body>
     <div id='test'>
--- a/dom/identity/tests/mochitest/test_syntheticEvents.html
+++ b/dom/identity/tests/mochitest/test_syntheticEvents.html
@@ -89,26 +89,23 @@ let apps = [
     expected: {
       success: false,
       errors: [
         "ERROR_NOT_AUTHORIZED_FOR_FIREFOX_ACCOUNTS",
       ],
     },
   },
   {
-    title: "a privileged app, which may not use synthetic events (until bug 982460 lands)",
+    title: "a privileged app, which may use synthetic events",
     manifest: "https://example.com/manifest_priv.webapp",
     origin: "https://example.com",
     uri: "https://example.com/chrome/dom/identity/tests/mochitest/file_syntheticEvents.html",
     wantIssuer: "firefox-accounts",
     expected: {
-      success: false,
-      errors: [
-        "ERROR_REQUEST_WHILE_NOT_HANDLING_USER_INPUT",
-      ],
+      success: true,
     },
   },
   {
     title: "a certified app, which may use synthetic events",
     manifest: "https://example.com/manifest_cert.webapp",
     origin: "https://example.com",
     uri: "https://example.com/chrome/dom/identity/tests/mochitest/file_syntheticEvents.html",
     wantIssuer: "firefox-accounts",
--- a/dom/webidl/AudioBufferSourceNode.webidl
+++ b/dom/webidl/AudioBufferSourceNode.webidl
@@ -23,25 +23,8 @@ interface AudioBufferSourceNode : AudioN
     [Throws]
     void start(optional double when = 0, optional double grainOffset = 0,
                optional double grainDuration);
     [Throws]
     void stop(optional double when = 0);
 
     attribute EventHandler onended;
 };
-
-/*
- * The origin of this IDL file is
- * https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html#AlternateNames
- */
-partial interface AudioBufferSourceNode {
-    // Same as start()
-    [Throws,Pref="media.webaudio.legacy.AudioBufferSourceNode"]
-    void noteOn(double when);
-    [Throws,Pref="media.webaudio.legacy.AudioBufferSourceNode"]
-    void noteGrainOn(double when, double grainOffset, double grainDuration);
-    
-    [Throws,Pref="media.webaudio.legacy.AudioBufferSourceNode"]
-    // Same as stop()
-    void noteOff(double when);
-};
-
--- a/dom/webidl/AudioContext.webidl
+++ b/dom/webidl/AudioContext.webidl
@@ -69,37 +69,14 @@ interface AudioContext : EventTarget {
 
     [NewObject]
     OscillatorNode createOscillator();
     [NewObject, Throws]
     PeriodicWave createPeriodicWave(Float32Array real, Float32Array imag);
 
 };
 
-/*
- * The origin of this IDL file is
- * https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html#AlternateNames
- */
-partial interface AudioContext {
-    [NewObject, Throws]
-    AudioBuffer? createBuffer(ArrayBuffer buffer, boolean mixToMono);
-
-    // Same as createGain()
-    [NewObject,Pref="media.webaudio.legacy.AudioContext"]
-    GainNode createGainNode();
-
-    // Same as createDelay()
-    [NewObject, Throws, Pref="media.webaudio.legacy.AudioContext"]
-    DelayNode createDelayNode(optional double maxDelayTime = 1);
-
-    // Same as createScriptProcessor()
-    [NewObject, Throws, Pref="media.webaudio.legacy.AudioContext"]
-    ScriptProcessorNode createJavaScriptNode(optional unsigned long bufferSize = 0,
-                                             optional unsigned long numberOfInputChannels = 2,
-                                             optional unsigned long numberOfOutputChannels = 2);
-};
-
 // Mozilla extensions
 partial interface AudioContext {
   // Read AudioChannel.webidl for more information about this attribute.
   [Pref="media.useAudioChannelService", SetterThrows]
   attribute AudioChannel mozAudioChannelType;
 };
--- a/dom/webidl/AudioParam.webidl
+++ b/dom/webidl/AudioParam.webidl
@@ -32,19 +32,8 @@ interface AudioParam {
     [Throws]
     void setValueCurveAtTime(Float32Array values, double startTime, double duration);
 
     // Cancels all scheduled parameter changes with times greater than or equal to startTime. 
     [Throws]
     void cancelScheduledValues(double startTime);
 
 };
-
-/*
- * The origin of this IDL file is
- * https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html#AlternateNames
- */
-partial interface AudioParam {
-    // Same as setTargetAtTime()
-    [Throws,Pref="media.webaudio.legacy.AudioParam"]
-    void setTargetValueAtTime(float target, double startTime, double timeConstant);
-};
-
--- a/dom/webidl/ErrorEvent.webidl
+++ b/dom/webidl/ErrorEvent.webidl
@@ -4,20 +4,20 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 [Constructor(DOMString type, optional ErrorEventInit eventInitDict)]
 interface ErrorEvent : Event
 {
   readonly attribute DOMString message;
   readonly attribute DOMString filename;
   readonly attribute unsigned long lineno;
-  readonly attribute unsigned long column;
+  readonly attribute unsigned long colno;
   readonly attribute any error;
 };
 
 dictionary ErrorEventInit : EventInit
 {
   DOMString message = "";
   DOMString filename = "";
   unsigned long lineno = 0;
-  unsigned long column = 0;
+  unsigned long colno = 0;
   any error = null;
 };
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -7,17 +7,16 @@
 #include "RuntimeService.h"
 
 #include "nsIChannel.h"
 #include "nsIContentSecurityPolicy.h"
 #include "nsIDocument.h"
 #include "nsIDOMChromeWindow.h"
 #include "nsIEffectiveTLDService.h"
 #include "nsIObserverService.h"
-#include "nsIPlatformCharset.h"
 #include "nsIPrincipal.h"
 #include "nsIScriptContext.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsISupportsPriority.h"
 #include "nsITimer.h"
 #include "nsIURI.h"
 #include "nsPIDOMWindow.h"
 
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -3142,17 +3142,17 @@ WorkerPrivateParent<Derived>::BroadcastE
     }
 
     RootedDictionary<ErrorEventInit> errorInit(aCx);
     errorInit.mBubbles = false;
     errorInit.mCancelable = true;
     errorInit.mMessage = aMessage;
     errorInit.mFilename = aFilename;
     errorInit.mLineno = aLineNumber;
-    errorInit.mColumn = aColumnNumber;
+    errorInit.mColno = aColumnNumber;
 
     nsRefPtr<ErrorEvent> errorEvent =
       ErrorEvent::Constructor(sharedWorker, NS_LITERAL_STRING("error"),
                               errorInit);
     if (!errorEvent) {
       Throw(cx, NS_ERROR_UNEXPECTED);
       JS_ReportPendingException(cx);
       continue;
--- a/dom/workers/test/test_sharedWorker.html
+++ b/dom/workers/test/test_sharedWorker.html
@@ -52,17 +52,17 @@
             receivedMessage = event.data;
           };
 
           worker.onerror = function(event) {
             ok(event instanceof ErrorEvent, "Got an ErrorEvent");
             is(event.message, "Error: " + sentMessage, "Got correct error");
             is(event.filename, errorFilename, "Got correct filename");
             is(event.lineno, errorLine, "Got correct lineno");
-            is(event.column, errorColumn, "Got correct column");
+            is(event.colno, errorColumn, "Got correct column");
             ok(receivedMessage !== undefined, "Got message already");
             ok(receivedError === undefined, "Haven't gotten error yet");
             receivedError = event.message;
             event.preventDefault();
             SimpleTest.finish();
           };
 
           worker.port.postMessage(sentMessage);
--- a/gfx/layers/apz/src/APZCTreeManager.h
+++ b/gfx/layers/apz/src/APZCTreeManager.h
@@ -27,18 +27,19 @@ namespace mozilla {
 class InputData;
 
 namespace layers {
 
 enum AllowedTouchBehavior {
   NONE =               0,
   VERTICAL_PAN =       1 << 0,
   HORIZONTAL_PAN =     1 << 1,
-  ZOOM =               1 << 2,
-  UNKNOWN =            1 << 3
+  PINCH_ZOOM =         1 << 2,
+  DOUBLE_TAP_ZOOM =    1 << 3,
+  UNKNOWN =            1 << 4
 };
 
 class Layer;
 class AsyncPanZoomController;
 class CompositorParent;
 
 /**
  * ****************** NOTE ON LOCK ORDERING IN APZ **************************
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -228,17 +228,18 @@ typedef GeckoContentController::APZState
  * documentation for the skate size multipliers above).
  */
 
 /**
  * Default touch behavior (is used when not touch behavior is set).
  */
 static const uint32_t DefaultTouchBehavior = AllowedTouchBehavior::VERTICAL_PAN |
                                              AllowedTouchBehavior::HORIZONTAL_PAN |
-                                             AllowedTouchBehavior::ZOOM;
+                                             AllowedTouchBehavior::PINCH_ZOOM |
+                                             AllowedTouchBehavior::DOUBLE_TAP_ZOOM;
 
 /**
  * Angle from axis within which we stay axis-locked
  */
 static const double AXIS_LOCK_ANGLE = M_PI / 6.0; // 30 degrees
 
 /**
  * The distance in inches the user must pan before axis lock can be broken
@@ -776,17 +777,17 @@ nsEventStatus AsyncPanZoomController::On
   OnTouchEndOrCancel();
   SetState(NOTHING);
   return nsEventStatus_eConsumeNoDefault;
 }
 
 nsEventStatus AsyncPanZoomController::OnScaleBegin(const PinchGestureInput& aEvent) {
   APZC_LOG("%p got a scale-begin in state %d\n", this, mState);
 
-  if (!TouchActionAllowZoom()) {
+  if (!TouchActionAllowPinchZoom()) {
     return nsEventStatus_eIgnore;
   }
 
   if (!mZoomConstraints.mAllowZoom) {
     return nsEventStatus_eConsumeNoDefault;
   }
 
   SetState(PINCHING);
@@ -967,39 +968,38 @@ void AsyncPanZoomController::OnTouchEndO
         GetGuid(), APZStateChange::EndTouch, mTouchBlockState.mSingleTapOccurred);
   }
 }
 
 nsEventStatus AsyncPanZoomController::OnSingleTapUp(const TapGestureInput& aEvent) {
   APZC_LOG("%p got a single-tap-up in state %d\n", this, mState);
   // If mZoomConstraints.mAllowDoubleTapZoom is true we wait for a call to OnSingleTapConfirmed before
   // sending event to content
-  if (!mZoomConstraints.mAllowDoubleTapZoom) {
+  if (!(mZoomConstraints.mAllowDoubleTapZoom && TouchActionAllowDoubleTapZoom())) {
     return GenerateSingleTap(aEvent.mPoint, aEvent.modifiers);
   }
   return nsEventStatus_eIgnore;
 }
 
 nsEventStatus AsyncPanZoomController::OnSingleTapConfirmed(const TapGestureInput& aEvent) {
   APZC_LOG("%p got a single-tap-confirmed in state %d\n", this, mState);
   return GenerateSingleTap(aEvent.mPoint, aEvent.modifiers);
 }
 
 nsEventStatus AsyncPanZoomController::OnDoubleTap(const TapGestureInput& aEvent) {
   APZC_LOG("%p got a double-tap in state %d\n", this, mState);
   nsRefPtr<GeckoContentController> controller = GetGeckoContentController();
   if (controller) {
-    if (mZoomConstraints.mAllowDoubleTapZoom) {
+    if (mZoomConstraints.mAllowDoubleTapZoom && TouchActionAllowDoubleTapZoom()) {
       int32_t modifiers = WidgetModifiersToDOMModifiers(aEvent.modifiers);
       CSSPoint geckoScreenPoint;
       if (ConvertToGecko(aEvent.mPoint, &geckoScreenPoint)) {
         controller->HandleDoubleTap(geckoScreenPoint, modifiers, GetGuid());
       }
     }
-
     return nsEventStatus_eConsumeNoDefault;
   }
   return nsEventStatus_eIgnore;
 }
 
 nsEventStatus AsyncPanZoomController::OnCancelTap(const TapGestureInput& aEvent) {
   APZC_LOG("%p got a cancel-tap in state %d\n", this, mState);
   // XXX: Implement this.
@@ -1956,29 +1956,39 @@ void AsyncPanZoomController::CheckConten
 
       mTouchQueue.RemoveElementAt(0);
     }
 
     mHandlingTouchQueue = false;
   }
 }
 
-bool AsyncPanZoomController::TouchActionAllowZoom() {
+bool AsyncPanZoomController::TouchActionAllowPinchZoom() {
   if (!mTouchActionPropertyEnabled) {
     return true;
   }
-
   // Pointer events specification implies all touch points to allow zoom
   // to perform it.
   for (size_t i = 0; i < mTouchBlockState.mAllowedTouchBehaviors.Length(); i++) {
-    if (!(mTouchBlockState.mAllowedTouchBehaviors[i] & AllowedTouchBehavior::ZOOM)) {
+    if (!(mTouchBlockState.mAllowedTouchBehaviors[i] & AllowedTouchBehavior::PINCH_ZOOM)) {
       return false;
     }
   }
+  return true;
+}
 
+bool AsyncPanZoomController::TouchActionAllowDoubleTapZoom() {
+  if (!mTouchActionPropertyEnabled) {
+    return true;
+  }
+  for (size_t i = 0; i < mTouchBlockState.mAllowedTouchBehaviors.Length(); i++) {
+    if (!(mTouchBlockState.mAllowedTouchBehaviors[i] & AllowedTouchBehavior::DOUBLE_TAP_ZOOM)) {
+      return false;
+    }
+  }
   return true;
 }
 
 AsyncPanZoomController::TouchBehaviorFlags
 AsyncPanZoomController::GetTouchBehavior(uint32_t touchIndex) {
   if (touchIndex < mTouchBlockState.mAllowedTouchBehaviors.Length()) {
     return mTouchBlockState.mAllowedTouchBehaviors[touchIndex];
   }
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -590,19 +590,24 @@ private:
     // Specifies whether mPreventDefault property is set for this touch events block.
     bool mPreventDefaultSet;
 
     // Specifies whether a single tap event was generated during this touch block.
     bool mSingleTapOccurred;
   };
 
   /*
-   * Returns whether current touch behavior values allow zooming.
+   * Returns whether current touch behavior values allow pinch-zooming.
    */
-  bool TouchActionAllowZoom();
+  bool TouchActionAllowPinchZoom();
+
+  /*
+   * Returns whether current touch behavior values allow double-tap-zooming.
+   */
+  bool TouchActionAllowDoubleTapZoom();
 
   /*
    * Returns allowed touch behavior from the mAllowedTouchBehavior array.
    * In case apzc didn't receive touch behavior values within the timeout
    * it returns default value.
    */
   TouchBehaviorFlags GetTouchBehavior(uint32_t touchIndex);
 
--- a/gfx/layers/client/ClientTiledThebesLayer.cpp
+++ b/gfx/layers/client/ClientTiledThebesLayer.cpp
@@ -81,16 +81,42 @@ ClientTiledThebesLayer::BeginPaint()
     //       CSS transforms.
     //
     //       Because of this, there may be unintended behaviour when setting
     //       2d CSS translations on the children of scrollable displayport
     //       layers.
     return;
   }
 
+#ifdef MOZ_WIDGET_ANDROID
+  // Subframes on Fennec are not async scrollable because they have no displayport.
+  // However, the code in RenderLayer() picks up a displayport from the nearest
+  // scrollable ancestor container layer anyway, which is incorrect for Fennec. This
+  // behaviour results in the subframe getting clipped improperly and perma-blank areas
+  // while scrolling the subframe. To work around this, we detect if this layer is
+  // the primary scrollable layer and disable the tiling behaviour if it is not.
+  bool isPrimaryScrollableThebesLayer = false;
+  if (Layer* scrollable = ClientManager()->GetPrimaryScrollableLayer()) {
+    if (GetParent() == scrollable) {
+      for (Layer* child = scrollable->GetFirstChild(); child; child = child->GetNextSibling()) {
+        if (child->GetType() == Layer::TYPE_THEBES) {
+          if (child == this) {
+            // |this| is the first thebes layer child of the GetPrimaryScrollableLayer()
+            isPrimaryScrollableThebesLayer = true;
+          }
+          break;
+        }
+      }
+    }
+  }
+  if (!isPrimaryScrollableThebesLayer) {
+    return;
+  }
+#endif
+
   // Get the metrics of the nearest scrollable layer and the nearest layer
   // with a displayport.
   ContainerLayer* displayPortParent = nullptr;
   ContainerLayer* scrollParent = nullptr;
   for (ContainerLayer* parent = GetParent(); parent; parent = parent->GetParent()) {
     const FrameMetrics& metrics = parent->GetFrameMetrics();
     if (!scrollParent && metrics.GetScrollId() != FrameMetrics::NULL_SCROLL_ID) {
       scrollParent = parent;
--- a/gfx/layers/composite/ContentHost.h
+++ b/gfx/layers/composite/ContentHost.h
@@ -147,16 +147,19 @@ public:
   virtual void PrintInfo(nsACString& aTo, const char* aPrefix) MOZ_OVERRIDE;
 
   virtual void UseTextureHost(TextureHost* aTexture) MOZ_OVERRIDE;
   virtual void UseComponentAlphaTextures(TextureHost* aTextureOnBlack,
                                          TextureHost* aTextureOnWhite) MOZ_OVERRIDE;
 
   virtual bool Lock() {
     MOZ_ASSERT(!mLocked);
+    if (!mTextureHost) {
+      return false;
+    }
     if (!mTextureHost->Lock()) {
       return false;
     }
 
     if (mTextureHostOnWhite && !mTextureHostOnWhite->Lock()) {
       return false;
     }
 
--- a/gfx/tests/gtest/TestAsyncPanZoomController.cpp
+++ b/gfx/tests/gtest/TestAsyncPanZoomController.cpp
@@ -410,17 +410,17 @@ TEST_F(AsyncPanZoomControllerTester, Pin
 
   // Apzc's OnScaleEnd method calls once SendAsyncScrollDOMEvent and RequestContentRepaint methods,
   // therefore we're setting these specific values.
   EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(AtMost(1));
   EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtMost(1));
 
   nsTArray<uint32_t> values;
   values.AppendElement(mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN);
-  values.AppendElement(mozilla::layers::AllowedTouchBehavior::ZOOM);
+  values.AppendElement(mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM);
   apzc->SetTouchActionEnabled(true);
 
   apzc->SetAllowedTouchBehavior(values);
   ApzcPinch(apzc, 250, 300, 1.25);
 
   // The frame metrics should stay the same since touch-action:none makes
   // apzc ignore pinch gestures.
   fm = apzc->GetFrameMetrics();
@@ -901,27 +901,27 @@ DoLongPressPreventDefaultTest(bool aShou
 
 TEST_F(AsyncPanZoomControllerTester, LongPress) {
   DoLongPressTest(false, mozilla::layers::AllowedTouchBehavior::NONE);
 }
 
 TEST_F(AsyncPanZoomControllerTester, LongPressWithTouchAction) {
   DoLongPressTest(true, mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN
                       | mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN
-                      | mozilla::layers::AllowedTouchBehavior::ZOOM);
+                      | mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM);
 }
 
 TEST_F(AsyncPanZoomControllerTester, LongPressPreventDefault) {
   DoLongPressPreventDefaultTest(false, mozilla::layers::AllowedTouchBehavior::NONE);
 }
 
 TEST_F(AsyncPanZoomControllerTester, LongPressPreventDefaultWithTouchAction) {
   DoLongPressPreventDefaultTest(true, mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN
                                     | mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN
-                                    | mozilla::layers::AllowedTouchBehavior::ZOOM);
+                                    | mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM);
 }
 
 // Layer tree for HitTesting1
 static already_AddRefed<mozilla::layers::Layer>
 CreateTestLayerTree1(nsRefPtr<LayerManager>& aLayerManager, nsTArray<nsRefPtr<Layer> >& aLayers) {
   const char* layerTreeSyntax = "c(ttcc)";
   // LayerID                     0 1234
   nsIntRegion layerVisibleRegion[] = {
new file mode 100644
--- /dev/null
+++ b/gfx/thebes/MathTableStructures.h
@@ -0,0 +1,121 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This file contains the structures described in Microsoft's document
+// "The MATH table and OpenType Features for Math Processing" (not yet public).
+//
+// Arrays of varying size are indicated in comments. Typically, gfxMathTable
+// will read the header of the structure first, verify that there is enough
+// space for the specified arrays and then use a pointer to browse these arrays.
+
+#ifndef MATH_TABLE_STRUCTURE_H
+#define MATH_TABLE_STRUCTURE_H
+
+#include "gfxFontUtils.h"
+
+typedef mozilla::AutoSwap_PRUint16 Count16;
+typedef mozilla::AutoSwap_PRUint16 GlyphID;
+typedef mozilla::AutoSwap_PRUint16 Offset;
+
+struct MathValueRecord {
+  mozilla::AutoSwap_PRInt16  mValue;
+  Offset                     mDeviceTable;
+};
+
+struct RangeRecord {
+  GlyphID                    mStart;
+  GlyphID                    mEnd;
+  mozilla::AutoSwap_PRUint16 mStartCoverageIndex;
+};
+
+struct Coverage {
+  mozilla::AutoSwap_PRUint16 mFormat;
+};
+
+struct CoverageFormat1 {
+  mozilla::AutoSwap_PRUint16 mFormat;
+  Count16                    mGlyphCount;
+  // GlyphID                 mGlyphArray[mGlyphCount]
+};
+
+struct CoverageFormat2 {
+  mozilla::AutoSwap_PRUint16 mFormat;
+  Count16                    mRangeCount;
+  // RangeRecord             mRangeArray[mRangeCount];
+};
+
+struct MATHTableHeader {
+  mozilla::AutoSwap_PRUint32 mVersion;
+  Offset                     mMathConstants;
+  Offset                     mMathGlyphInfo;
+  Offset                     mMathVariants;
+};
+
+struct MathConstants {
+  mozilla::AutoSwap_PRInt16  mInt16[gfxFontEntry::ScriptScriptPercentScaleDown -
+                                    gfxFontEntry::ScriptPercentScaleDown + 1];
+  mozilla::AutoSwap_PRUint16 mUint16[gfxFontEntry::DisplayOperatorMinHeight -
+                                     gfxFontEntry::
+                                     DelimitedSubFormulaMinHeight + 1];
+  MathValueRecord            mMathValues[gfxFontEntry::RadicalKernAfterDegree -
+                                         gfxFontEntry::MathLeading + 1];
+  mozilla::AutoSwap_PRUint16 mRadicalDegreeBottomRaisePercent;
+};
+
+struct MathGlyphInfo {
+  Offset mMathItalicsCorrectionInfo;
+  Offset mMathTopAccentAttachment;
+  Offset mExtendedShapeCoverage;
+  Offset mMathKernInfo;
+};
+
+struct MathItalicsCorrectionInfo {
+  Offset  mCoverage;
+  Count16 mItalicsCorrectionCount;
+  // MathValueRecord mItalicsCorrection[mItalicsCorrectionCount]
+};
+
+struct MathVariants {
+  mozilla::AutoSwap_PRUint16 mMinConnectorOverlap;
+  Offset                     mVertGlyphCoverage;
+  Offset                     mHorizGlyphCoverage;
+  Count16                    mVertGlyphCount;
+  Count16                    mHorizGlyphCount;
+  // Offset                  mVertGlyphConstruction[mVertGlyphCount];
+  // Offset                  mHorizGlyphConstruction[mHorizGlyphCount];
+};
+
+struct MathGlyphVariantRecord {
+  GlyphID                    mVariantGlyph;
+  mozilla::AutoSwap_PRUint16 mAdvanceMeasurement;
+};
+
+struct MathGlyphConstruction {
+  Offset                    mGlyphAssembly;
+  Count16                   mVariantCount;
+  // MathGlyphVariantRecord mMathGlyphVariantRecord[mVariantCount]
+};
+
+struct GlyphPartRecord {
+  GlyphID	              mGlyph;
+  mozilla::AutoSwap_PRUint16 mStartConnectorLength;
+  mozilla::AutoSwap_PRUint16 mEndConnectorLength;
+  mozilla::AutoSwap_PRUint16 mFullAdvance;
+  mozilla::AutoSwap_PRUint16 mPartFlags;
+};
+
+// PartFlags enumeration currently uses only one bit:
+// 0x0001 If set, the part can be skipped or repeated.
+// 0xFFFE Reserved.
+enum {
+  PART_FLAG_EXTENDER = 0x01
+};
+
+struct GlyphAssembly {
+  MathValueRecord    mItalicsCorrection;
+  Count16            mPartCount;
+  // GlyphPartRecord mPartRecords[mPartCount]
+};
+
+#endif
--- a/gfx/thebes/gfxFT2FontBase.cpp
+++ b/gfx/thebes/gfxFT2FontBase.cpp
@@ -112,17 +112,19 @@ gfxFT2FontBase::GetMetrics()
 {
     if (mHasMetrics)
         return mMetrics;
 
     if (MOZ_UNLIKELY(GetStyle()->size <= 0.0)) {
         new(&mMetrics) gfxFont::Metrics(); // zero initialize
         mSpaceGlyph = 0;
     } else {
-        gfxFT2LockedFace(this).GetMetrics(&mMetrics, &mSpaceGlyph);
+        gfxFT2LockedFace face(this);
+        mFUnitsConvFactor = face.XScale();
+        face.GetMetrics(&mMetrics, &mSpaceGlyph);
     }
 
     SanitizeMetrics(&mMetrics, false);
 
 #if 0
     //    printf("font name: %s %f\n", NS_ConvertUTF16toUTF8(GetName()).get(), GetStyle()->size);
     //    printf ("pango font %s\n", pango_font_description_to_string (pango_font_describe (font)));
 
--- a/gfx/thebes/gfxFT2Fonts.cpp
+++ b/gfx/thebes/gfxFT2Fonts.cpp
@@ -421,19 +421,16 @@ gfxFT2Font::ShapeText(gfxContext      *a
             ok = mGraphiteShaper->ShapeText(aContext, aText,
                                             aOffset, aLength,
                                             aScript, aShapedText);
         }
     }
 
     if (!ok && gfxPlatform::GetPlatform()->UseHarfBuzzForScript(aScript)) {
         if (!mHarfBuzzShaper) {
-            gfxFT2LockedFace face(this);
-            mFUnitsConvFactor = face.XScale();
-
             mHarfBuzzShaper = new gfxHarfBuzzShaper(this);
         }
         ok = mHarfBuzzShaper->ShapeText(aContext, aText,
                                         aOffset, aLength,
                                         aScript, aShapedText);
     }
 
     if (!ok) {
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -18,31 +18,33 @@
 
 #include "gfxFont.h"
 #include "gfxPlatform.h"
 #include "nsGkAtoms.h"
 
 #include "gfxTypes.h"
 #include "gfxContext.h"
 #include "gfxFontMissingGlyphs.h"
+#include "gfxHarfBuzzShaper.h"
 #include "gfxUserFontSet.h"
 #include "gfxPlatformFontList.h"
 #include "gfxScriptItemizer.h"
 #include "nsUnicodeProperties.h"
 #include "nsMathUtils.h"
 #include "nsBidiUtils.h"
 #include "nsUnicodeRange.h"
 #include "nsStyleConsts.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"
 #include "gfx2DGlue.h"
 
 #if defined(XP_MACOSX)
 #include "nsCocoaFeatures.h"
 #endif
 
 #include "cairo.h"
 #include "gfxFontTest.h"
@@ -107,16 +109,17 @@ gfxFontEntry::gfxFontEntry() :
     mIsBadUnderlineFont(false),
     mIsUserFont(false),
     mIsLocalUserFont(false),
     mStandardFace(false),
     mSymbolFont(false),
     mIgnoreGDEF(false),
     mIgnoreGSUB(false),
     mSVGInitialized(false),
+    mMathInitialized(false),
     mHasSpaceFeaturesInitialized(false),
     mHasSpaceFeatures(false),
     mHasSpaceFeaturesKerning(false),
     mHasSpaceFeaturesNonKerning(false),
     mSkipDefaultFeatureSpaceCheck(false),
     mCheckedForGraphiteTables(false),
     mHasCmapTable(false),
     mGrFaceInitialized(false),
@@ -136,16 +139,17 @@ gfxFontEntry::gfxFontEntry(const nsAStri
     mName(aName), mItalic(false), mFixedPitch(false),
     mIsProxy(false), mIsValid(true),
     mIsBadUnderlineFont(false), mIsUserFont(false),
     mIsLocalUserFont(false), mStandardFace(aIsStandardFace),
     mSymbolFont(false),
     mIgnoreGDEF(false),
     mIgnoreGSUB(false),
     mSVGInitialized(false),
+    mMathInitialized(false),
     mHasSpaceFeaturesInitialized(false),
     mHasSpaceFeatures(false),
     mHasSpaceFeaturesKerning(false),
     mHasSpaceFeaturesNonKerning(false),
     mSkipDefaultFeatureSpaceCheck(false),
     mCheckedForGraphiteTables(false),
     mHasCmapTable(false),
     mGrFaceInitialized(false),
@@ -384,16 +388,88 @@ void
 gfxFontEntry::NotifyGlyphsChanged()
 {
     for (uint32_t i = 0, count = mFontsUsingSVGGlyphs.Length(); i < count; ++i) {
         gfxFont* font = mFontsUsingSVGGlyphs[i];
         font->NotifyGlyphsChanged();
     }
 }
 
+bool
+gfxFontEntry::TryGetMathTable(gfxFont* aFont)
+{
+    if (!mMathInitialized) {
+        mMathInitialized = true;
+
+        // If UnitsPerEm is not known/valid, we can't use MATH table
+        if (UnitsPerEm() == kInvalidUPEM) {
+            return false;
+        }
+
+        // We don't use AutoTable here because we'll pass ownership of this
+        // blob to the gfxMathTable, once we've confirmed the table exists
+        hb_blob_t *mathTable = GetFontTable(TRUETYPE_TAG('M','A','T','H'));
+        if (!mathTable) {
+            return false;
+        }
+
+        // gfxMathTable will hb_blob_destroy() the table when it is finished
+        // with it.
+        mMathTable = new gfxMathTable(mathTable);
+        if (!mMathTable->HasValidHeaders()) {
+            mMathTable = nullptr;
+            return false;
+        }
+    }
+
+    return !!mMathTable;
+}
+
+gfxFloat
+gfxFontEntry::GetMathConstant(gfxFontEntry::MathConstant aConstant)
+{
+    NS_ASSERTION(mMathTable, "Math data has not yet been loaded. TryGetMathData() first.");
+    gfxFloat value = mMathTable->GetMathConstant(aConstant);
+    if (aConstant == gfxFontEntry::ScriptPercentScaleDown ||
+        aConstant == gfxFontEntry::ScriptScriptPercentScaleDown ||
+        aConstant == gfxFontEntry::RadicalDegreeBottomRaisePercent) {
+        return value / 100.0;
+    }
+    return value / mUnitsPerEm;
+}
+
+bool
+gfxFontEntry::GetMathItalicsCorrection(uint32_t aGlyphID,
+                                       gfxFloat* aItalicCorrection)
+{
+    NS_ASSERTION(mMathTable, "Math data has not yet been loaded. TryGetMathData() first.");
+    int16_t italicCorrection;
+    if (!mMathTable->GetMathItalicsCorrection(aGlyphID, &italicCorrection)) {
+        return false;
+    }
+    *aItalicCorrection = gfxFloat(italicCorrection) / mUnitsPerEm;
+    return true;
+}
+
+uint32_t
+gfxFontEntry::GetMathVariantsSize(uint32_t aGlyphID, bool aVertical,
+                                  uint16_t aSize)
+{
+    NS_ASSERTION(mMathTable, "Math data has not yet been loaded. TryGetMathData() first.");
+    return mMathTable->GetMathVariantsSize(aGlyphID, aVertical, aSize);
+}
+
+bool
+gfxFontEntry::GetMathVariantsParts(uint32_t aGlyphID, bool aVertical,
+                                   uint32_t aGlyphs[4])
+{
+    NS_ASSERTION(mMathTable, "Math data has not yet been loaded. TryGetMathData() first.");
+    return mMathTable->GetMathVariantsParts(aGlyphID, aVertical, aGlyphs);
+}
+
 /**
  * FontTableBlobData
  *
  * See FontTableHashEntry for the general strategy.
  */
 
 class gfxFontEntry::FontTableBlobData {
 public:
@@ -1947,16 +2023,38 @@ gfxFont::~gfxFont()
 
     mFontEntry->NotifyFontDestroyed(this);
 
     if (mGlyphChangeObservers) {
         mGlyphChangeObservers->EnumerateEntries(NotifyFontDestroyed, nullptr);
     }
 }
 
+gfxFloat
+gfxFont::GetGlyphHAdvance(gfxContext *aCtx, uint16_t aGID)
+{
+    if (ProvidesGlyphWidths()) {
+        return GetGlyphWidth(aCtx, aGID) / 65536.0;
+    }
+    if (mFUnitsConvFactor == 0.0f) {
+        GetMetrics();
+    }
+    NS_ASSERTION(mFUnitsConvFactor > 0.0f,
+                 "missing font unit conversion factor");
+    if (!mHarfBuzzShaper) {
+        mHarfBuzzShaper = new gfxHarfBuzzShaper(this);
+    }
+    gfxHarfBuzzShaper* shaper =
+        static_cast<gfxHarfBuzzShaper*>(mHarfBuzzShaper.get());
+    if (!shaper->Initialize() || !SetupCairoFont(aCtx)) {
+        return 0;
+    }
+    return shaper->GetGlyphHAdvance(aCtx, aGID) / 65536.0;
+}
+
 /*static*/
 PLDHashOperator
 gfxFont::AgeCacheEntry(CacheHashEntry *aEntry, void *aUserData)
 {
     if (!aEntry->mShapedWord) {
         NS_ASSERTION(aEntry->mShapedWord, "cache entry has no gfxShapedWord!");
         return PL_DHASH_REMOVE;
     }
--- a/gfx/thebes/gfxFont.h
+++ b/gfx/thebes/gfxFont.h
@@ -38,21 +38,24 @@ typedef struct gr_face            gr_fac
 #include <stdio.h>
 #endif
 
 class gfxContext;
 class gfxTextRun;
 class gfxFont;
 class gfxFontFamily;
 class gfxFontGroup;
+class gfxGraphiteShaper;
+class gfxHarfBuzzShaper;
 class gfxUserFontSet;
 class gfxUserFontData;
 class gfxShapedText;
 class gfxShapedWord;
 class gfxSVGGlyphs;
+class gfxMathTable;
 class gfxTextContextPaint;
 class FontInfoData;
 
 class nsILanguageAtomService;
 
 #define FONT_MAX_SIZE                  2000.0
 
 #define NO_FONT_LANGUAGE_OVERRIDE      0
@@ -306,16 +309,89 @@ public:
     bool GetSVGGlyphExtents(gfxContext *aContext, uint32_t aGlyphId,
                             gfxRect *aResult);
     bool RenderSVGGlyph(gfxContext *aContext, uint32_t aGlyphId, int aDrawMode,
                         gfxTextContextPaint *aContextPaint);
     // Call this when glyph geometry or rendering has changed
     // (e.g. animated SVG glyphs)
     void NotifyGlyphsChanged();
 
+    enum MathConstant {
+        // The order of the constants must match the order of the fields
+        // defined in the MATH table.
+        ScriptPercentScaleDown,
+        ScriptScriptPercentScaleDown,
+        DelimitedSubFormulaMinHeight,
+        DisplayOperatorMinHeight,
+        MathLeading,
+        AxisHeight,
+        AccentBaseHeight,
+        FlattenedAccentBaseHeight,
+        SubscriptShiftDown,
+        SubscriptTopMax,
+        SubscriptBaselineDropMin,
+        SuperscriptShiftUp,
+        SuperscriptShiftUpCramped,
+        SuperscriptBottomMin,
+        SuperscriptBaselineDropMax,
+        SubSuperscriptGapMin,
+        SuperscriptBottomMaxWithSubscript,
+        SpaceAfterScript,
+        UpperLimitGapMin,
+        UpperLimitBaselineRiseMin,
+        LowerLimitGapMin,
+        LowerLimitBaselineDropMin,
+        StackTopShiftUp,
+        StackTopDisplayStyleShiftUp,
+        StackBottomShiftDown,
+        StackBottomDisplayStyleShiftDown,
+        StackGapMin,
+        StackDisplayStyleGapMin,
+        StretchStackTopShiftUp,
+        StretchStackBottomShiftDown,
+        StretchStackGapAboveMin,
+        StretchStackGapBelowMin,
+        FractionNumeratorShiftUp,
+        FractionNumeratorDisplayStyleShiftUp,
+        FractionDenominatorShiftDown,
+        FractionDenominatorDisplayStyleShiftDown,
+        FractionNumeratorGapMin,
+        FractionNumDisplayStyleGapMin,
+        FractionRuleThickness,
+        FractionDenominatorGapMin,
+        FractionDenomDisplayStyleGapMin,
+        SkewedFractionHorizontalGap,
+        SkewedFractionVerticalGap,
+        OverbarVerticalGap,
+        OverbarRuleThickness,
+        OverbarExtraAscender,
+        UnderbarVerticalGap,
+        UnderbarRuleThickness,
+        UnderbarExtraDescender,
+        RadicalVerticalGap,
+        RadicalDisplayStyleVerticalGap,
+        RadicalRuleThickness,
+        RadicalExtraAscender,
+        RadicalKernBeforeDegree,
+        RadicalKernAfterDegree,
+        RadicalDegreeBottomRaisePercent
+    };
+
+    // Call TryGetMathTable to try to load the Open Type MATH table. The other
+    // functions forward the call to the gfxMathTable class. The GetMath...()
+    // functions MUST NOT be called unless TryGetMathTable() has returned true.
+    bool     TryGetMathTable(gfxFont* aFont);
+    gfxFloat GetMathConstant(MathConstant aConstant);
+    bool     GetMathItalicsCorrection(uint32_t aGlyphID,
+                                      gfxFloat* aItalicCorrection);
+    uint32_t GetMathVariantsSize(uint32_t aGlyphID, bool aVertical,
+                                 uint16_t aSize);
+    bool     GetMathVariantsParts(uint32_t aGlyphID, bool aVertical,
+                                  uint32_t aGlyphs[4]);
+
     virtual bool MatchesGenericFamily(const nsACString& aGeneric) const {
         return true;
     }
     virtual bool SupportsLangGroup(nsIAtom *aLangGroup) const {
         return true;
     }
 
     // Access to raw font table data (needed for Harfbuzz):
@@ -428,16 +504,17 @@ public:
     bool             mIsBadUnderlineFont : 1;
     bool             mIsUserFont  : 1;
     bool             mIsLocalUserFont  : 1;
     bool             mStandardFace : 1;
     bool             mSymbolFont  : 1;
     bool             mIgnoreGDEF  : 1;
     bool             mIgnoreGSUB  : 1;
     bool             mSVGInitialized : 1;
+    bool             mMathInitialized : 1;
     bool             mHasSpaceFeaturesInitialized : 1;
     bool             mHasSpaceFeatures : 1;
     bool             mHasSpaceFeaturesKerning : 1;
     bool             mHasSpaceFeaturesNonKerning : 1;
     bool             mSkipDefaultFeatureSpaceCheck : 1;
     bool             mHasGraphiteTables : 1;
     bool             mCheckedForGraphiteTables : 1;
     bool             mHasCmapTable : 1;
@@ -453,16 +530,17 @@ public:
 
     nsRefPtr<gfxCharacterMap> mCharacterMap;
     uint32_t         mUVSOffset;
     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;
     uint32_t         mLanguageOverride;
 
 protected:
     friend class gfxPlatformFontList;
     friend class gfxMacPlatformFontList;
     friend class gfxUserFcFontEntry;
     friend class gfxFontFamily;
@@ -1376,16 +1454,20 @@ public:
 
 protected:
     // the font this shaper is working with
     gfxFont * mFont;
 };
 
 /* a SPECIFIC single font family */
 class gfxFont {
+
+    friend class gfxHarfBuzzShaper;
+    friend class gfxGraphiteShaper;
+
 public:
     nsrefcnt AddRef(void) {
         NS_PRECONDITION(int32_t(mRefCnt) >= 0, "illegal refcnt");
         if (mExpirationState.IsTracked()) {
             gfxFontCache::GetCache()->RemoveObject(this);
         }
         ++mRefCnt;
         NS_LOG_ADDREF(this, mRefCnt, "gfxFont", sizeof(*this));
@@ -1503,29 +1585,18 @@ public:
     virtual bool ProvidesGetGlyph() const {
         return false;
     }
     // Map unicode character to glyph ID.
     // Only used if ProvidesGetGlyph() returns true.
     virtual uint32_t GetGlyph(uint32_t unicode, uint32_t variation_selector) {
         return 0;
     }
-
-    // subclasses may provide (possibly hinted) glyph widths (in font units);
-    // if they do not override this, harfbuzz will use unhinted widths
-    // derived from the font tables
-    virtual bool ProvidesGlyphWidths() {
-        return false;
-    }
-
-    // The return value is interpreted as a horizontal advance in 16.16 fixed
-    // point format.
-    virtual int32_t GetGlyphWidth(gfxContext *aCtx, uint16_t aGID) {
-        return -1;
-    }
+    // Return the horizontal advance of a glyph.
+    gfxFloat GetGlyphHAdvance(gfxContext *aCtx, uint16_t aGID);
 
     // Return Azure GlyphRenderingOptions for drawing this font.
     virtual mozilla::TemporaryRef<mozilla::gfx::GlyphRenderingOptions>
       GetGlyphRenderingOptions() { return nullptr; }
 
     gfxFloat SynthesizeSpaceWidth(uint32_t aCh);
 
     // Font metrics
@@ -1811,16 +1882,29 @@ public:
     }
 
     static void DestroySingletons() {
         delete sScriptTagToCode;
         delete sDefaultFeatures;
     }
 
 protected:
+    // subclasses may provide (possibly hinted) glyph widths (in font units);
+    // if they do not override this, harfbuzz will use unhinted widths
+    // derived from the font tables
+    virtual bool ProvidesGlyphWidths() {
+        return false;
+    }
+
+    // The return value is interpreted as a horizontal advance in 16.16 fixed
+    // point format.
+    virtual int32_t GetGlyphWidth(gfxContext *aCtx, uint16_t aGID) {
+        return -1;
+    }
+
     void AddGlyphChangeObserver(GlyphChangeObserver *aObserver);
     void RemoveGlyphChangeObserver(GlyphChangeObserver *aObserver);
 
     // whether font contains substitution lookups containing spaces
     bool HasSubstitutionRulesWithSpaceLookups(int32_t aRunScript);
 
     // do spaces participate in shaping rules? if so, can't used word cache
     bool SpaceMayParticipateInShaping(int32_t aRunScript);
--- a/gfx/thebes/gfxHarfBuzzShaper.cpp
+++ b/gfx/thebes/gfxHarfBuzzShaper.cpp
@@ -183,20 +183,16 @@ struct HMetrics {
 // the variable-length metrics[] array is immediately followed by:
 //  AutoSwap_PRUint16    leftSideBearing[];
 };
 
 hb_position_t
 gfxHarfBuzzShaper::GetGlyphHAdvance(gfxContext *aContext,
                                     hb_codepoint_t glyph) const
 {
-    if (mUseFontGlyphWidths) {
-        return mFont->GetGlyphWidth(aContext, glyph);
-    }
-
     // font did not implement GetHintedGlyphWidth, so get an unhinted value
     // directly from the font tables
 
     NS_ASSERTION((mNumLongMetrics > 0) && mHmtxTable != nullptr,
                  "font is lacking metrics, we shouldn't be here");
 
     if (glyph >= uint32_t(mNumLongMetrics)) {
         glyph = mNumLongMetrics - 1;
@@ -206,23 +202,29 @@ gfxHarfBuzzShaper::GetGlyphHAdvance(gfxC
     // that mNumLongMetrics is > 0, and that the hmtx table is large enough
     // to contain mNumLongMetrics records
     const HMetrics* hmtx =
         reinterpret_cast<const HMetrics*>(hb_blob_get_data(mHmtxTable, nullptr));
     return FloatToFixed(mFont->FUnitsToDevUnitsFactor() *
                         uint16_t(hmtx->metrics[glyph].advanceWidth));
 }
 
-static hb_position_t
-HBGetGlyphHAdvance(hb_font_t *font, void *font_data,
-                   hb_codepoint_t glyph, void *user_data)
+/* static */
+hb_position_t
+gfxHarfBuzzShaper::HBGetGlyphHAdvance(hb_font_t *font, void *font_data,
+                                      hb_codepoint_t glyph, void *user_data)
 {
     const gfxHarfBuzzShaper::FontCallbackData *fcd =
         static_cast<const gfxHarfBuzzShaper::FontCallbackData*>(font_data);
-    return fcd->mShaper->GetGlyphHAdvance(fcd->mContext, glyph);
+    gfxFont *gfxfont = fcd->mShaper->GetFont();
+    if (gfxfont->ProvidesGlyphWidths()) {
+        return gfxfont->GetGlyphWidth(fcd->mContext, glyph);
+    } else {
+        return fcd->mShaper->GetGlyphHAdvance(fcd->mContext, glyph);
+    }
 }
 
 static hb_bool_t
 HBGetContourPoint(hb_font_t *font, void *font_data,
                   unsigned int point_index, hb_codepoint_t glyph,
                   hb_position_t *x, hb_position_t *y,
                   void *user_data)
 {
@@ -813,143 +815,155 @@ AddOpenTypeFeature(const uint32_t& aTag,
  */
 
 static hb_font_funcs_t * sHBFontFuncs = nullptr;
 static hb_unicode_funcs_t * sHBUnicodeFuncs = nullptr;
 static const hb_script_t sMathScript =
     hb_ot_tag_to_script(HB_TAG('m','a','t','h'));
 
 bool
+gfxHarfBuzzShaper::Initialize()
+{
+    if (mInitialized) {
+        return mHBFont != nullptr;
+    }
+    mInitialized = true;
+    mCallbackData.mShaper = this;
+
+    mUseFontGlyphWidths = mFont->ProvidesGlyphWidths();
+
+    if (!sHBFontFuncs) {
+        // static function callback pointers, initialized by the first
+        // harfbuzz shaper used
+        sHBFontFuncs = hb_font_funcs_create();
+        hb_font_funcs_set_glyph_func(sHBFontFuncs, HBGetGlyph,
+                                     nullptr, nullptr);
+        hb_font_funcs_set_glyph_h_advance_func(sHBFontFuncs,
+                                               HBGetGlyphHAdvance,
+                                               nullptr, nullptr);
+        hb_font_funcs_set_glyph_contour_point_func(sHBFontFuncs,
+                                                   HBGetContourPoint,
+                                                   nullptr, nullptr);
+        hb_font_funcs_set_glyph_h_kerning_func(sHBFontFuncs,
+                                               HBGetHKerning,
+                                               nullptr, nullptr);
+
+        sHBUnicodeFuncs =
+            hb_unicode_funcs_create(hb_unicode_funcs_get_empty());
+        hb_unicode_funcs_set_mirroring_func(sHBUnicodeFuncs,
+                                            HBGetMirroring,
+                                            nullptr, nullptr);
+        hb_unicode_funcs_set_script_func(sHBUnicodeFuncs, HBGetScript,
+                                         nullptr, nullptr);
+        hb_unicode_funcs_set_general_category_func(sHBUnicodeFuncs,
+                                                   HBGetGeneralCategory,
+                                                   nullptr, nullptr);
+        hb_unicode_funcs_set_combining_class_func(sHBUnicodeFuncs,
+                                                  HBGetCombiningClass,
+                                                  nullptr, nullptr);
+        hb_unicode_funcs_set_eastasian_width_func(sHBUnicodeFuncs,
+                                                  HBGetEastAsianWidth,
+                                                  nullptr, nullptr);
+        hb_unicode_funcs_set_compose_func(sHBUnicodeFuncs,
+                                          HBUnicodeCompose,
+                                          nullptr, nullptr);
+        hb_unicode_funcs_set_decompose_func(sHBUnicodeFuncs,
+                                            HBUnicodeDecompose,
+                                            nullptr, nullptr);
+    }
+
+    gfxFontEntry *entry = mFont->GetFontEntry();
+    if (!mUseFontGetGlyph) {
+        // get the cmap table and find offset to our subtable
+        mCmapTable = entry->GetFontTable(TRUETYPE_TAG('c','m','a','p'));
+        if (!mCmapTable) {
+            NS_WARNING("failed to load cmap, glyphs will be missing");
+            return false;
+        }
+        uint32_t len;
+        const uint8_t* data = (const uint8_t*)hb_blob_get_data(mCmapTable, &len);
+        bool symbol;
+        mCmapFormat = gfxFontUtils::
+            FindPreferredSubtable(data, len,
+                                  &mSubtableOffset, &mUVSTableOffset,
+                                  &symbol);
+        if (mCmapFormat <= 0) {
+            return false;
+        }
+    }
+
+    if (!mUseFontGlyphWidths) {
+        // if font doesn't implement GetGlyphWidth, we will be reading
+        // the hmtx table directly;
+        // read mNumLongMetrics from hhea table without caching its blob,
+        // and preload/cache the hmtx table
+        gfxFontEntry::AutoTable hheaTable(entry, TRUETYPE_TAG('h','h','e','a'));
+        if (hheaTable) {
+            uint32_t len;
+            const HMetricsHeader* hhea =
+                reinterpret_cast<const HMetricsHeader*>
+                (hb_blob_get_data(hheaTable, &len));
+            if (len >= sizeof(HMetricsHeader)) {
+                mNumLongMetrics = hhea->numberOfHMetrics;
+                if (mNumLongMetrics > 0 &&
+                    int16_t(hhea->metricDataFormat) == 0) {
+                    // no point reading hmtx if number of entries is zero!
+                    // in that case, we won't be able to use this font
+                    // (this method will return FALSE below if mHmtx is null)
+                    mHmtxTable =
+                        entry->GetFontTable(TRUETYPE_TAG('h','m','t','x'));
+                    if (hb_blob_get_length(mHmtxTable) <
+                        mNumLongMetrics * sizeof(HLongMetric)) {
+                        // hmtx table is not large enough for the claimed
+                        // number of entries: invalid, do not use.
+                        hb_blob_destroy(mHmtxTable);
+                        mHmtxTable = nullptr;
+                    }
+                }
+            }
+        }
+        if (!mHmtxTable) {
+            return false;
+        }
+    }
+
+    mHBFont = hb_font_create(mHBFace);
+    hb_font_set_funcs(mHBFont, sHBFontFuncs, &mCallbackData, nullptr);
+    hb_font_set_ppem(mHBFont, mFont->GetAdjustedSize(), mFont->GetAdjustedSize());
+    uint32_t scale = FloatToFixed(mFont->GetAdjustedSize()); // 16.16 fixed-point
+    hb_font_set_scale(mHBFont, scale, scale);
+
+    return true;
+}
+
+bool
 gfxHarfBuzzShaper::ShapeText(gfxContext      *aContext,
                              const char16_t *aText,
                              uint32_t         aOffset,
                              uint32_t         aLength,
                              int32_t          aScript,
                              gfxShapedText   *aShapedText)
 {
     // some font back-ends require this in order to get proper hinted metrics
     if (!mFont->SetupCairoFont(aContext)) {
         return false;
     }
 
     mCallbackData.mContext = aContext;
-    gfxFontEntry *entry = mFont->GetFontEntry();
 
-    if (!mInitialized) {
-        mInitialized = true;
-        mCallbackData.mShaper = this;
-
-        mUseFontGlyphWidths = mFont->ProvidesGlyphWidths();
-
-        if (!sHBFontFuncs) {
-            // static function callback pointers, initialized by the first
-            // harfbuzz shaper used
-            sHBFontFuncs = hb_font_funcs_create();
-            hb_font_funcs_set_glyph_func(sHBFontFuncs, HBGetGlyph,
-                                         nullptr, nullptr);
-            hb_font_funcs_set_glyph_h_advance_func(sHBFontFuncs,
-                                                   HBGetGlyphHAdvance,
-                                                   nullptr, nullptr);
-            hb_font_funcs_set_glyph_contour_point_func(sHBFontFuncs,
-                                                       HBGetContourPoint,
-                                                       nullptr, nullptr);
-            hb_font_funcs_set_glyph_h_kerning_func(sHBFontFuncs,
-                                                   HBGetHKerning,
-                                                   nullptr, nullptr);
-
-            sHBUnicodeFuncs =
-                hb_unicode_funcs_create(hb_unicode_funcs_get_empty());
-            hb_unicode_funcs_set_mirroring_func(sHBUnicodeFuncs,
-                                                HBGetMirroring,
-                                                nullptr, nullptr);
-            hb_unicode_funcs_set_script_func(sHBUnicodeFuncs, HBGetScript,
-                                             nullptr, nullptr);
-            hb_unicode_funcs_set_general_category_func(sHBUnicodeFuncs,
-                                                       HBGetGeneralCategory,
-                                                       nullptr, nullptr);
-            hb_unicode_funcs_set_combining_class_func(sHBUnicodeFuncs,
-                                                      HBGetCombiningClass,
-                                                      nullptr, nullptr);
-            hb_unicode_funcs_set_eastasian_width_func(sHBUnicodeFuncs,
-                                                      HBGetEastAsianWidth,
-                                                      nullptr, nullptr);
-            hb_unicode_funcs_set_compose_func(sHBUnicodeFuncs,
-                                              HBUnicodeCompose,
-                                              nullptr, nullptr);
-            hb_unicode_funcs_set_decompose_func(sHBUnicodeFuncs,
-                                                HBUnicodeDecompose,
-                                                nullptr, nullptr);
-        }
-
-        if (!mUseFontGetGlyph) {
-            // get the cmap table and find offset to our subtable
-            mCmapTable = entry->GetFontTable(TRUETYPE_TAG('c','m','a','p'));
-            if (!mCmapTable) {
-                NS_WARNING("failed to load cmap, glyphs will be missing");
-                return false;
-            }
-            uint32_t len;
-            const uint8_t* data = (const uint8_t*)hb_blob_get_data(mCmapTable, &len);
-            bool symbol;
-            mCmapFormat = gfxFontUtils::
-                FindPreferredSubtable(data, len,
-                                      &mSubtableOffset, &mUVSTableOffset,
-                                      &symbol);
-        }
-
-        if (!mUseFontGlyphWidths) {
-            // if font doesn't implement GetGlyphWidth, we will be reading
-            // the hmtx table directly;
-            // read mNumLongMetrics from hhea table without caching its blob,
-            // and preload/cache the hmtx table
-            gfxFontEntry::AutoTable hheaTable(entry, TRUETYPE_TAG('h','h','e','a'));
-            if (hheaTable) {
-                uint32_t len;
-                const HMetricsHeader* hhea =
-                    reinterpret_cast<const HMetricsHeader*>
-                        (hb_blob_get_data(hheaTable, &len));
-                if (len >= sizeof(HMetricsHeader)) {
-                    mNumLongMetrics = hhea->numberOfHMetrics;
-                    if (mNumLongMetrics > 0 &&
-                        int16_t(hhea->metricDataFormat) == 0) {
-                        // no point reading hmtx if number of entries is zero!
-                        // in that case, we won't be able to use this font
-                        // (this method will return FALSE below if mHmtx is null)
-                        mHmtxTable =
-                            entry->GetFontTable(TRUETYPE_TAG('h','m','t','x'));
-                        if (hb_blob_get_length(mHmtxTable) <
-                            mNumLongMetrics * sizeof(HLongMetric)) {
-                            // hmtx table is not large enough for the claimed
-                            // number of entries: invalid, do not use.
-                            hb_blob_destroy(mHmtxTable);
-                            mHmtxTable = nullptr;
-                        }
-                    }
-                }
-            }
-        }
-
-        mHBFont = hb_font_create(mHBFace);
-        hb_font_set_funcs(mHBFont, sHBFontFuncs, &mCallbackData, nullptr);
-        hb_font_set_ppem(mHBFont, mFont->GetAdjustedSize(), mFont->GetAdjustedSize());
-        uint32_t scale = FloatToFixed(mFont->GetAdjustedSize()); // 16.16 fixed-point
-        hb_font_set_scale(mHBFont, scale, scale);
-    }
-
-    if ((!mUseFontGetGlyph && mCmapFormat <= 0) ||
-        (!mUseFontGlyphWidths && !mHmtxTable)) {
-        // unable to shape with this font
+    if (!Initialize()) {
         return false;
     }
 
     const gfxFontStyle *style = mFont->GetStyle();
 
     nsAutoTArray<hb_feature_t,20> features;
     nsDataHashtable<nsUint32HashKey,uint32_t> mergedFeatures;
 
+    gfxFontEntry *entry = mFont->GetFontEntry();
     if (MergeFontFeatures(style,
                           entry->mFeatureSettings,
                           aShapedText->DisableLigatures(),
                           entry->FamilyName(),
                           mergedFeatures))
     {
         // enumerate result and insert into hb_feature array
         mergedFeatures.Enumerate(AddOpenTypeFeature, &features);
--- a/gfx/thebes/gfxHarfBuzzShaper.h
+++ b/gfx/thebes/gfxHarfBuzzShaper.h
@@ -19,16 +19,17 @@ public:
      * For HarfBuzz font callback functions, font_data is a ptr to a
      * FontCallbackData struct
      */
     struct FontCallbackData {
         gfxHarfBuzzShaper *mShaper;
         gfxContext        *mContext;
     };
 
+    bool Initialize();
     virtual bool ShapeText(gfxContext      *aContext,
                            const char16_t *aText,
                            uint32_t         aOffset,
                            uint32_t         aLength,
                            int32_t          aScript,
                            gfxShapedText   *aShapedText);
 
     // get a given font table in harfbuzz blob form
@@ -37,16 +38,21 @@ public:
     // map unicode character to glyph ID
     hb_codepoint_t GetGlyph(hb_codepoint_t unicode,
                             hb_codepoint_t variation_selector) const;
 
     // get harfbuzz glyph advance, in font design units
     hb_position_t GetGlyphHAdvance(gfxContext *aContext,
                                    hb_codepoint_t glyph) const;
 
+    // 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;
 
 protected:
     nsresult SetGlyphsFromRun(gfxContext      *aContext,
                               gfxShapedText   *aShapedText,
                               uint32_t         aOffset,
                               uint32_t         aLength,
new file mode 100644
--- /dev/null
+++ b/gfx/thebes/gfxMathTable.cpp
@@ -0,0 +1,459 @@
+/* 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 "gfxMathTable.h"
+
+#include "MathTableStructures.h"
+#include "harfbuzz/hb.h"
+#include <algorithm>
+
+using namespace mozilla;
+
+gfxMathTable::gfxMathTable(hb_blob_t* aMathTable)
+  : mMathTable(aMathTable)
+  , mGlyphConstruction(nullptr)
+  , mGlyphID(-1)
+  , mVertical(false)
+{
+}
+
+gfxMathTable::~gfxMathTable()
+{
+  hb_blob_destroy(mMathTable);
+}
+
+bool
+gfxMathTable::HasValidHeaders()
+{
+  const char* mathData = hb_blob_get_data(mMathTable, nullptr);
+  // Verify the MATH table header.
+  if (!ValidStructure(mathData, sizeof(MATHTableHeader))) {
+    return false;
+  }
+  const MATHTableHeader* header = GetMATHTableHeader();
+  if (uint32_t(header->mVersion) != 0x00010000 ||
+      !ValidOffset(mathData, uint16_t(header->mMathConstants)) ||
+      !ValidOffset(mathData, uint16_t(header->mMathGlyphInfo)) ||
+      !ValidOffset(mathData, uint16_t(header->mMathVariants))) {
+    return false;
+  }
+
+  // Verify the MathConstants header.
+  const MathConstants* mathconstants = GetMathConstants();
+  const char* start = reinterpret_cast<const char*>(mathconstants);
+  if (!ValidStructure(start, sizeof(MathConstants))) {
+    return false;
+  }
+
+  // Verify the MathGlyphInfo header.
+  const MathGlyphInfo* mathglyphinfo = GetMathGlyphInfo();
+  start = reinterpret_cast<const char*>(mathglyphinfo);
+  if (!ValidStructure(start, sizeof(MathGlyphInfo))) {
+    return false;
+  }
+
+  // Verify the MathVariants header.
+  const MathVariants* mathvariants = GetMathVariants();
+  start = reinterpret_cast<const char*>(mathvariants);
+  if (!ValidStructure(start, sizeof(MathVariants)) ||
+      !ValidStructure(start,
+                      sizeof(MathVariants) + sizeof(Offset) *
+                      (uint16_t(mathvariants->mVertGlyphCount) +
+                       uint16_t(mathvariants->mHorizGlyphCount))) ||
+      !ValidOffset(start, uint16_t(mathvariants->mVertGlyphCoverage)) ||
+      !ValidOffset(start, uint16_t(mathvariants->mHorizGlyphCoverage))) {
+    return false;
+  }
+
+  return true;
+}
+
+int32_t
+gfxMathTable::GetMathConstant(gfxFontEntry::MathConstant aConstant)
+{
+  const MathConstants* mathconstants = GetMathConstants();
+
+  if (aConstant <= gfxFontEntry::ScriptScriptPercentScaleDown) {
+    return int16_t(mathconstants->mInt16[aConstant]);
+  }
+
+  if (aConstant <= gfxFontEntry::DisplayOperatorMinHeight) {
+    return
+      uint16_t(mathconstants->
+               mUint16[aConstant - gfxFontEntry::DelimitedSubFormulaMinHeight]);
+  }
+
+  if (aConstant <= gfxFontEntry::RadicalKernAfterDegree) {
+    return int16_t(mathconstants->
+                   mMathValues[aConstant - gfxFontEntry::MathLeading].mValue);
+  }
+
+  return uint16_t(mathconstants->mRadicalDegreeBottomRaisePercent);
+}
+
+bool
+gfxMathTable::GetMathItalicsCorrection(uint32_t aGlyphID,
+                                       int16_t* aItalicCorrection)
+{
+  const MathGlyphInfo* mathglyphinfo = GetMathGlyphInfo();
+
+  // Get the offset of the italic correction and verify whether it is valid.
+  const char* start = reinterpret_cast<const char*>(mathglyphinfo);
+  uint16_t offset = mathglyphinfo->mMathItalicsCorrectionInfo;
+  if (offset == 0 || !ValidOffset(start, offset)) {
+    return false;
+  }
+  start += offset;
+
+  // Verify the validity of the MathItalicsCorrectionInfo and retrieve it.
+  if (!ValidStructure(start, sizeof(MathItalicsCorrectionInfo))) {
+    return false;
+  }
+  const MathItalicsCorrectionInfo* italicsCorrectionInfo =
+    reinterpret_cast<const MathItalicsCorrectionInfo*>(start);
+
+  // Get the coverage index for the glyph.
+  offset = italicsCorrectionInfo->mCoverage;
+  const Coverage* coverage =
+    reinterpret_cast<const Coverage*>(start + offset);
+  int32_t i = GetCoverageIndex(coverage, aGlyphID);
+
+  // Get the ItalicsCorrection.
+  uint16_t count = italicsCorrectionInfo->mItalicsCorrectionCount;
+  if (i < 0 || i >= count) {
+    return false;
+  }
+  start = reinterpret_cast<const char*>(italicsCorrectionInfo + 1);
+  if (!ValidStructure(start, count * sizeof(MathValueRecord))) {
+    return false;
+  }
+  const MathValueRecord* mathValueRecordArray =
+    reinterpret_cast<const MathValueRecord*>(start);
+
+  *aItalicCorrection = int16_t(mathValueRecordArray[i].mValue);
+  return true;
+}
+
+uint32_t
+gfxMathTable::GetMathVariantsSize(uint32_t aGlyphID, bool aVertical,
+                                  uint16_t aSize)
+{
+  // Select the glyph construction.
+  SelectGlyphConstruction(aGlyphID, aVertical);
+  if (!mGlyphConstruction) {
+    return 0;
+  }
+
+  // Verify the validity of the array of the MathGlyphVariantRecord's and
+  // whether there is a variant of the requested size.
+  uint16_t count = mGlyphConstruction->mVariantCount;
+  const char* start = reinterpret_cast<const char*>(mGlyphConstruction + 1);
+  if (aSize >= count ||
+      !ValidStructure(start, count * sizeof(MathGlyphVariantRecord))) {
+    return 0;
+  }
+
+  // Return the glyph index of the requested size variant.
+  const MathGlyphVariantRecord* recordArray =
+    reinterpret_cast<const MathGlyphVariantRecord*>(start);
+  return uint32_t(recordArray[aSize].mVariantGlyph);
+}
+
+bool
+gfxMathTable::GetMathVariantsParts(uint32_t aGlyphID, bool aVertical,
+                                   uint32_t aGlyphs[4])
+{
+  // Get the glyph assembly corresponding to that (aGlyphID, aVertical) pair.
+  const GlyphAssembly* glyphAssembly = GetGlyphAssembly(aGlyphID, aVertical);
+  if (!glyphAssembly) {
+    return false;
+  }
+
+  // Verify the validity of the array of GlyphPartRecord's and retrieve it.
+  uint16_t count = glyphAssembly->mPartCount;
+  const char* start = reinterpret_cast<const char*>(glyphAssembly + 1);
+  if (!ValidStructure(start, count * sizeof(GlyphPartRecord))) {
+    return false;
+  }
+  const GlyphPartRecord* recordArray =
+    reinterpret_cast<const GlyphPartRecord*>(start);
+
+  // XXXfredw The structure of the Open Type Math table is a bit more general
+  // than the one currently used by the nsMathMLChar code, so we try to fallback
+  // in reasonable way. We use the approach of the copyComponents function in
+  // github.com/mathjax/MathJax-dev/blob/master/fonts/OpenTypeMath/fontUtil.py
+  //
+  // The nsMathMLChar code can use at most 3 non extender pieces (aGlyphs[0],
+  // aGlyphs[1] and aGlyphs[2]) and the extenders between these pieces should
+  // all be the same (aGlyphs[4]). Also, the parts of vertical assembly are
+  // stored from bottom to top in the Open Type MATH table while they are
+  // stored from top to bottom in nsMathMLChar.
+
+  // Count the number of non extender pieces
+  uint16_t nonExtenderCount = 0;
+  for (uint16_t i = 0; i < count; i++) {
+    if (!(uint16_t(recordArray[i].mPartFlags) & PART_FLAG_EXTENDER)) {
+      nonExtenderCount++;
+    }
+  }
+  if (nonExtenderCount > 3) {
+    // Not supported: too many pieces
+    return false;
+  }
+
+  // Now browse the list of pieces
+
+  // 0 = look for a left/bottom glyph
+  // 1 = look for an extender between left/bottom and mid
+  // 2 = look for a middle glyph
+  // 3 = look for an extender between middle and right/top
+  // 4 = look for a right/top glyph
+  // 5 = no more piece expected
+  uint8_t state = 0;
+
+  // First extender char found.
+  uint32_t extenderChar = 0;
+
+  // Clear the aGlyphs table.
+  memset(aGlyphs, 0, sizeof(uint32_t) * 4);
+
+  for (uint16_t i = 0; i < count; i++) {
+
+    bool isExtender = uint16_t(recordArray[i].mPartFlags) & PART_FLAG_EXTENDER;
+    uint32_t glyph = recordArray[i].mGlyph;
+
+    if ((state == 1 || state == 2) && nonExtenderCount < 3) {
+      // do not try to find a middle glyph
+      state += 2;
+    }
+
+    if (isExtender) {
+      if (!extenderChar) {
+        extenderChar = glyph;
+        aGlyphs[3] = extenderChar;
+      } else if (extenderChar != glyph)  {
+        // Not supported: different extenders
+        return false;
+      }
+
+      if (state == 0) { // or state == 1
+        // ignore left/bottom piece and multiple successive extenders
+        state = 1;
+      } else if (state == 2) { // or state == 3
+        // ignore middle piece and multiple successive extenders
+        state = 3;
+      } else if (state >= 4) {
+        // Not supported: unexpected extender
+        return false;
+      }
+
+      continue;
+    }
+
+    if (state == 0) {
+      // copy left/bottom part
+      aGlyphs[mVertical ? 2 : 0] = glyph;
+      state = 1;
+      continue;
+    }
+
+    if (state == 1 || state == 2) {
+      // copy middle part
+      aGlyphs[1] = glyph;
+      state = 3;
+      continue;
+    }
+
+    if (state == 3 || state == 4) {
+      // copy right/top part
+      aGlyphs[mVertical ? 0 : 2] = glyph;
+      state = 5;
+    }
+
+  }
+
+  return true;
+}
+
+bool
+gfxMathTable::ValidStructure(const char* aStart, uint16_t aSize)
+{
+  unsigned int mathDataLength;
+  const char* mathData = hb_blob_get_data(mMathTable, &mathDataLength);
+  return (mathData <= aStart &&
+          aStart + aSize <= mathData + mathDataLength);
+}
+
+bool
+gfxMathTable::ValidOffset(const char* aStart, uint16_t aOffset)
+{
+  unsigned int mathDataLength;
+  const char* mathData = hb_blob_get_data(mMathTable, &mathDataLength);
+  return (mathData <= aStart + aOffset &&
+          aStart + aOffset < mathData + mathDataLength);
+}
+
+const MATHTableHeader*
+gfxMathTable::GetMATHTableHeader()
+{
+  const char* mathData = hb_blob_get_data(mMathTable, nullptr);
+  return reinterpret_cast<const MATHTableHeader*>(mathData);
+}
+
+const MathConstants*
+gfxMathTable::GetMathConstants()
+{
+  const char* mathData = hb_blob_get_data(mMathTable, nullptr);
+  return
+    reinterpret_cast<const MathConstants*>(mathData +
+                                           uint16_t(GetMATHTableHeader()->
+                                                    mMathConstants));
+}
+
+const MathGlyphInfo*
+gfxMathTable::GetMathGlyphInfo()
+{
+  const char* mathData = hb_blob_get_data(mMathTable, nullptr);
+  return
+    reinterpret_cast<const MathGlyphInfo*>(mathData +
+                                           uint16_t(GetMATHTableHeader()->
+                                                    mMathGlyphInfo));
+}
+
+const MathVariants*
+gfxMathTable::GetMathVariants()
+{
+  const char* mathData = hb_blob_get_data(mMathTable, nullptr);
+  return
+    reinterpret_cast<const MathVariants*>(mathData +
+                                          uint16_t(GetMATHTableHeader()->
+                                                   mMathVariants));
+}
+
+const GlyphAssembly*
+gfxMathTable::GetGlyphAssembly(uint32_t aGlyphID, bool aVertical)
+{
+  // Select the glyph construction.
+  SelectGlyphConstruction(aGlyphID, aVertical);
+  if (!mGlyphConstruction) {
+    return nullptr;
+  }
+
+  // Get the offset of the glyph assembly and verify whether it is valid.
+  const char* start = reinterpret_cast<const char*>(mGlyphConstruction);
+  uint16_t offset = mGlyphConstruction->mGlyphAssembly;
+  if (offset == 0 || !ValidOffset(start, offset)) {
+    return nullptr;
+  }
+  start += offset;
+
+  // Verify the validity of the GlyphAssembly and return it.
+  if (!ValidStructure(start, sizeof(GlyphAssembly))) {
+    return nullptr;
+  }
+  return reinterpret_cast<const GlyphAssembly*>(start);
+}
+
+int32_t
+gfxMathTable::GetCoverageIndex(const Coverage* aCoverage, uint32_t aGlyph)
+{
+  if (uint16_t(aCoverage->mFormat) == 1) {
+    // Coverage Format 1: list of individual glyph indices in the glyph set.
+    const CoverageFormat1* table =
+      reinterpret_cast<const CoverageFormat1*>(aCoverage);
+    uint16_t count = table->mGlyphCount;
+    const char* start = reinterpret_cast<const char*>(table + 1);
+    if (ValidStructure(start, count * sizeof(GlyphID))) {
+      const GlyphID* glyphArray =
+        reinterpret_cast<const GlyphID*>(start);
+      uint32_t imin = 0, imax = count;
+      while (imin < imax) {
+        uint32_t imid = (imin + imax) >> 1;
+        uint16_t glyphMid = glyphArray[imid];
+        if (glyphMid == aGlyph) {
+          return imid;
+        }
+        if (glyphMid < aGlyph) {
+          imin = imid + 1;
+        } else {
+          imax = imid;
+        }
+      }
+    }
+  } else if (uint16_t(aCoverage->mFormat) == 2) {
+    // Coverage Format 2: ranges of consecutive indices.
+    const CoverageFormat2* table =
+      reinterpret_cast<const CoverageFormat2*>(aCoverage);
+    uint16_t count = table->mRangeCount;
+    const char* start = reinterpret_cast<const char*>(table + 1);
+    if (ValidStructure(start, count * sizeof(RangeRecord))) {
+      const RangeRecord* rangeArray =
+        reinterpret_cast<const RangeRecord*>(start);
+      uint32_t imin = 0, imax = count;
+      while (imin < imax) {
+        uint32_t imid = (imin + imax) >> 1;
+        uint16_t rStart = rangeArray[imid].mStart;
+        uint16_t rEnd = rangeArray[imid].mEnd;
+        if (rEnd < aGlyph) {
+          imin = imid + 1;
+        } else if (aGlyph < rStart) {
+          imax = imid;
+        } else {
+          return (uint16_t(rangeArray[imid].mStartCoverageIndex) +
+                  aGlyph - rStart);
+        }
+      }
+    }
+  }
+  return -1;
+}
+
+void
+gfxMathTable::SelectGlyphConstruction(uint32_t aGlyphID, bool aVertical)
+{
+  if (mGlyphID == aGlyphID && mVertical == aVertical) {
+    // The (glyph, direction) pair is already selected: nothing to do.
+    return;
+  }
+
+  // Update our cached values.
+  mVertical = aVertical;
+  mGlyphID = aGlyphID;
+  mGlyphConstruction = nullptr;
+
+  // Get the coverage index for the new values.
+  const MathVariants* mathvariants = GetMathVariants();
+  const char* start = reinterpret_cast<const char*>(mathvariants);
+  uint16_t offset = (aVertical ?
+                     mathvariants->mVertGlyphCoverage :
+                     mathvariants->mHorizGlyphCoverage);
+  const Coverage* coverage =
+    reinterpret_cast<const Coverage*>(start + offset);
+  int32_t i = GetCoverageIndex(coverage, aGlyphID);
+
+  // Get the offset to the glyph construction.
+  uint16_t count = (aVertical ?
+                    mathvariants->mVertGlyphCount :
+                    mathvariants->mHorizGlyphCount);
+  start = reinterpret_cast<const char*>(mathvariants + 1);
+  if (i < 0 || i >= count) {
+    return;
+  }
+  if (!aVertical) {
+    start += uint16_t(mathvariants->mVertGlyphCount) * sizeof(Offset);
+  }
+  if (!ValidStructure(start, count * sizeof(Offset))) {
+    return;
+  }
+  const Offset* offsetArray = reinterpret_cast<const Offset*>(start);
+  offset = uint16_t(offsetArray[i]);
+
+  // Make mGlyphConstruction point to the desired glyph construction.
+  start = reinterpret_cast<const char*>(mathvariants);
+  if (!ValidStructure(start + offset, sizeof(MathGlyphConstruction))) {
+    return;
+  }
+  mGlyphConstruction =
+    reinterpret_cast<const MathGlyphConstruction*>(start + offset);
+}
new file mode 100644
--- /dev/null
+++ b/gfx/thebes/gfxMathTable.h
@@ -0,0 +1,122 @@
+/* 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 GFX_MATH_TABLE_H
+#define GFX_MATH_TABLE_H
+
+#include "gfxFont.h"
+
+struct Coverage;
+struct GlyphAssembly;
+struct MATHTableHeader;
+struct MathConstants;
+struct MathGlyphConstruction;
+struct MathGlyphInfo;
+struct MathVariants;
+
+/**
+ * Used by |gfxFontEntry| to represent the MATH table of an OpenType font.
+ * Each |gfxFontEntry| owns at most one |gfxMathTable| instance.
+ */
+class gfxMathTable
+{
+public:
+    /**
+     * @param aMathTable The MATH table from the OpenType font
+     *
+     * The gfxMathTable object takes over ownership of the blob references
+     * that are passed in, and will hb_blob_destroy() them when finished;
+     * the caller should -not- destroy this reference.
+     */
+    gfxMathTable(hb_blob_t* aMathTable);
+
+    /**
+     * Releases our reference to the MATH table and cleans up everything else.
+     */
+    ~gfxMathTable();
+
+    /**
+     * Returns the value of the specified constant from the MATH table.
+     */
+    int32_t GetMathConstant(gfxFontEntry::MathConstant aConstant);
+
+    /**
+     *  If the MATH table contains an italic correction for that glyph, this
+     *  function gets the value and returns true. Otherwise it returns false.
+     */
+    bool
+    GetMathItalicsCorrection(uint32_t aGlyphID, int16_t* aItalicCorrection);
+
+    /**
+     * @param aGlyphID  glyph index of the character we want to stretch
+     * @param aVertical direction of the stretching (vertical/horizontal)
+     * @param aSize     the desired size variant
+     *
+     * Returns the glyph index of the desired size variant or 0 if there is not
+     * any such size variant.
+     */
+    uint32_t GetMathVariantsSize(uint32_t aGlyphID, bool aVertical,
+                                 uint16_t aSize);
+
+    /**
+     * @param aGlyphID  glyph index of the character we want to stretch
+     * @param aVertical direction of the stretching (vertical/horizontal)
+     * @param aGlyphs   pre-allocated buffer of 4 elements where the glyph
+     * indexes (or 0 for absent parts) will be stored. The parts are stored in
+     * the order expected by the nsMathMLChar: Top (or Left), Middle, Bottom
+     * (or Right), Glue.
+     *
+     * Tries to fill-in aGlyphs with the relevant glyph indexes and returns
+     * whether the operation was successful. The function returns false if
+     * there is not any assembly for the character we want to stretch or if
+     * the format is not supported by the nsMathMLChar code.
+     *
+     */
+    bool GetMathVariantsParts(uint32_t aGlyphID, bool aVertical,
+                              uint32_t aGlyphs[4]);
+
+protected:
+    friend class gfxFontEntry;
+    // This allows gfxFontEntry to verify the validity of the main headers
+    // before starting to use the MATH table.
+    bool HasValidHeaders();
+
+private:
+    // HarfBuzz blob where the MATH table is stored.
+    hb_blob_t*    mMathTable;
+
+    // Cached values for the latest (mGlyphID, mVertical) pair that has been
+    // accessed and the corresponding glyph construction. These are verified
+    // by SelectGlyphConstruction and updated if necessary.
+    // mGlyphConstruction will be set to nullptr if no construction is defined
+    // for the glyph. If non-null, its mGlyphAssembly and mVariantCount fields
+    // may be safely read, but no further validation will have been done.
+    const MathGlyphConstruction* mGlyphConstruction;
+    uint32_t mGlyphID;
+    bool     mVertical;
+    void     SelectGlyphConstruction(uint32_t aGlyphID, bool aVertical);
+
+    // Access to some structures of the MATH table.
+    // These accessors just return a pointer, but do NOT themselves check the
+    // validity of anything. Until we've checked that HasValidHeaders (which
+    // does validate them) returns true, they might return pointers that cannot
+    // even safely be dereferenced. GetGlyphAssembly may return nullptr if the
+    // given glyph has no assembly defined.
+    const MATHTableHeader* GetMATHTableHeader();
+    const MathConstants*   GetMathConstants();
+    const MathGlyphInfo*   GetMathGlyphInfo();
+    const MathVariants*    GetMathVariants();
+    const GlyphAssembly*   GetGlyphAssembly(uint32_t aGlyphID, bool aVertical);
+
+    // Verify whether a structure or an offset belongs to the math data and can
+    // be read safely.
+    bool ValidStructure(const char* aStructStart, uint16_t aStructSize);
+    bool ValidOffset(const char* aOffsetStart, uint16_t aOffset);
+
+    // Get the coverage index of a glyph index from an Open Type coverage table
+    // or -1 if the glyph index is not found.
+    int32_t GetCoverageIndex(const Coverage* aCoverage, uint32_t aGlyph);
+};
+
+#endif
--- a/gfx/thebes/gfxPangoFonts.cpp
+++ b/gfx/thebes/gfxPangoFonts.cpp
@@ -1619,20 +1619,17 @@ gfxFcFont::ShapeText(gfxContext      *aC
             }
             ok = mGraphiteShaper->ShapeText(aContext, aText, aOffset, aLength,
                                             aScript, aShapedText);
         }
     }
 
     if (!ok) {
         if (!mHarfBuzzShaper) {
-            gfxFT2LockedFace face(this);
             mHarfBuzzShaper = new gfxHarfBuzzShaper(this);
-            // Used by gfxHarfBuzzShaper, currently only for kerning
-            mFUnitsConvFactor = face.XScale();
         }
         ok = mHarfBuzzShaper->ShapeText(aContext, aText, aOffset, aLength,
                                         aScript, aShapedText);
     }
 
     NS_WARN_IF_FALSE(ok, "shaper failed, expect scrambled or missing text");
 
     PostShapingFixup(aContext, aText, aOffset, aLength, aShapedText);
--- a/gfx/thebes/gfxWindowsPlatform.cpp
+++ b/gfx/thebes/gfxWindowsPlatform.cpp
@@ -799,16 +799,17 @@ gfxWindowsPlatform::GetCommonFallbackFon
             aFontList.AppendElement(kFontMicrosoftUighur);
             break;
         case 0x07:
             aFontList.AppendElement(kFontEstrangeloEdessa);
             aFontList.AppendElement(kFontMVBoli);
             aFontList.AppendElement(kFontEbrima);
             break;
         case 0x09:
+            aFontList.AppendElement(kFontNirmalaUI);
             aFontList.AppendElement(kFontUtsaah);
             aFontList.AppendElement(kFontAparajita);
             break;
         case 0x0e:
             aFontList.AppendElement(kFontLaoUI);
             break;
         case 0x10:
             aFontList.AppendElement(kFontMyanmarText);
--- a/gfx/thebes/moz.build
+++ b/gfx/thebes/moz.build
@@ -21,16 +21,17 @@ EXPORTS += [
     'gfxFontConstants.h',
     'gfxFontFeatures.h',
     'gfxFontInfoLoader.h',
     'gfxFontTest.h',
     'gfxFontUtils.h',
     'gfxGradientCache.h',
     'gfxImageSurface.h',
     'gfxLineSegment.h',
+    'gfxMathTable.h',
     'gfxMatrix.h',
     'gfxPath.h',
     'gfxPattern.h',
     'gfxPlatform.h',
     'gfxPoint.h',
     'gfxPoint3D.h',
     'gfxPointH3D.h',
     'gfxPrefs.h',
@@ -230,16 +231,17 @@ UNIFIED_SOURCES += [
     'gfxFontFeatures.cpp',
     'gfxFontInfoLoader.cpp',
     'gfxFontMissingGlyphs.cpp',
     'gfxFontTest.cpp',
     'gfxGradientCache.cpp',
     'gfxGraphiteShaper.cpp',
     'gfxHarfBuzzShaper.cpp',
     'gfxImageSurface.cpp',
+    'gfxMathTable.cpp',
     'gfxMatrix.cpp',
     'gfxPath.cpp',
     'gfxPattern.cpp',
     'gfxRect.cpp',
     'gfxReusableImageSurfaceWrapper.cpp',
     'gfxReusableSharedImageSurfaceWrapper.cpp',
     'gfxScriptItemizer.cpp',
     'gfxSkipChars.cpp',
--- a/intl/locale/public/nsIPlatformCharset.h
+++ b/intl/locale/public/nsIPlatformCharset.h
@@ -29,16 +29,23 @@ typedef enum {
      kPlatformCharsetSel_Menu = 2,
      kPlatformCharsetSel_4xBookmarkFile = 3,
      kPlatformCharsetSel_KeyboardInput = 4,
      kPlatformCharsetSel_WindowManager = 5,
      kPlatformCharsetSel_4xPrefsJS = 6,
      kPlatformCharsetSel_PlainTextInFile = 7
 } nsPlatformCharsetSel;
 
+/**
+ * DO NOT ADD NEW USES OF THIS INTERFACE!
+ * Removal is https://bugzilla.mozilla.org/show_bug.cgi?id=943272
+ *
+ * Instead, use UTF-16 APIs on Windows and UTF-8 APIs everywhere else.
+ * Assume plain text files are UTF-8.
+ */
 class nsIPlatformCharset : public nsISupports
 {
 public:
  
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_IPLATFORMCHARSET_IID)
 
   NS_IMETHOD GetCharset(nsPlatformCharsetSel selector, nsACString& oResult) = 0;
 
--- a/js/src/gc/Nursery.h
+++ b/js/src/gc/Nursery.h
@@ -134,16 +134,26 @@ class Nursery
     size_t sizeOfHugeSlots(mozilla::MallocSizeOf mallocSizeOf) const {
         size_t total = 0;
         for (HugeSlotsSet::Range r = hugeSlots.all(); !r.empty(); r.popFront())
             total += mallocSizeOf(r.front());
         total += hugeSlots.sizeOfExcludingThis(mallocSizeOf);
         return total;
     }
 
+    MOZ_ALWAYS_INLINE uintptr_t start() const {
+        JS_ASSERT(runtime_);
+        return ((JS::shadow::Runtime *)runtime_)->gcNurseryStart_;
+    }
+
+    MOZ_ALWAYS_INLINE uintptr_t heapEnd() const {
+        JS_ASSERT(runtime_);
+        return ((JS::shadow::Runtime *)runtime_)->gcNurseryEnd_;
+    }
+
   private:
     /*
      * The start and end pointers are stored under the runtime so that we can
      * inline the isInsideNursery check into embedder code. Use the start()
      * and heapEnd() functions to access these values.
      */
     JSRuntime *runtime_;
 
@@ -185,26 +195,16 @@ class Nursery
     static_assert(sizeof(NurseryChunkLayout) == gc::ChunkSize,
                   "Nursery chunk size must match gc::Chunk size.");
     NurseryChunkLayout &chunk(int index) const {
         JS_ASSERT(index < NumNurseryChunks);
         JS_ASSERT(start());
         return reinterpret_cast<NurseryChunkLayout *>(start())[index];
     }
 
-    MOZ_ALWAYS_INLINE uintptr_t start() const {
-        JS_ASSERT(runtime_);
-        return ((JS::shadow::Runtime *)runtime_)->gcNurseryStart_;
-    }
-
-    MOZ_ALWAYS_INLINE uintptr_t heapEnd() const {
-        JS_ASSERT(runtime_);
-        return ((JS::shadow::Runtime *)runtime_)->gcNurseryEnd_;
-    }
-
     MOZ_ALWAYS_INLINE void setCurrentChunk(int chunkno) {
         JS_ASSERT(chunkno < NumNurseryChunks);
         JS_ASSERT(chunkno < numActiveChunks_);
         currentChunk_ = chunkno;
         position_ = chunk(chunkno).start();
         currentEnd_ = chunk(chunkno).end();
         chunk(chunkno).trailer.runtime = runtime();
     }
@@ -309,19 +309,16 @@ class Nursery
         }
     }
 #else
     void enterZealMode() {}
     void leaveZealMode() {}
 #endif
 
     friend class gc::MinorCollectionTracer;
-    friend class jit::CodeGenerator;
     friend class jit::MacroAssembler;
-    friend class jit::ICStubCompiler;
-    friend class jit::BaselineCompiler;
     friend void SetGCZeal(JSRuntime *, uint8_t, uint32_t);
 };
 
 } /* namespace js */
 
 #endif /* JSGC_GENERATIONAL */
 #endif /* gc_Nursery_h */
--- a/js/src/jit/Bailouts.cpp
+++ b/js/src/jit/Bailouts.cpp
@@ -10,34 +10,34 @@
 
 #include "jit/BaselineJIT.h"
 #include "jit/Ion.h"
 #include "jit/IonSpewer.h"
 #include "jit/JitCompartment.h"
 #include "jit/Snapshots.h"
 #include "vm/TraceLogging.h"
 
-#include "jit/IonFrameIterator-inl.h"
+#include "jit/JitFrameIterator-inl.h"
 #include "vm/Stack-inl.h"
 
 using namespace js;
 using namespace js::jit;
 
 // These constructor are exactly the same except for the type of the iterator
 // which is given to the SnapshotIterator constructor. Doing so avoid the
 // creation of virtual functions for the IonIterator but may introduce some
-// weirdness as IonInlineIterator is using an IonFrameIterator reference.
+// weirdness as IonInlineIterator is using a JitFrameIterator reference.
 //
 // If a function relies on ionScript() or to use OsiIndex(), due to the
-// lack of virtual, these functions will use the IonFrameIterator reference
+// lack of virtual, these functions will use the JitFrameIterator reference
 // contained in the InlineFrameIterator and thus are not able to recover
 // correctly the data stored in IonBailoutIterator.
 //
 // Currently, such cases should not happen because our only use case of the
-// IonFrameIterator within InlineFrameIterator is to read the frame content, or
+// JitFrameIterator within InlineFrameIterator is to read the frame content, or
 // to clone it to find the parent scripted frame.  Both use cases are fine and
 // should not cause any issue since the only potential issue is to read the
 // bailed out frame.
 
 SnapshotIterator::SnapshotIterator(const IonBailoutIterator &iter)
   : snapshot_(iter.ionScript()->snapshots(),
               iter.snapshotOffset(),
               iter.ionScript()->snapshotsRVATableSize(),
@@ -58,17 +58,17 @@ IonBailoutIterator::dump() const
         InlineFrameIterator frames(GetJSContextFromJitCode(), this);
         for (;;) {
             frames.dump();
             if (!frames.more())
                 break;
             ++frames;
         }
     } else {
-        IonFrameIterator::dump();
+        JitFrameIterator::dump();
     }
 }
 
 uint32_t
 jit::Bailout(BailoutStack *sp, BaselineBailoutInfo **bailoutInfo)
 {
     JSContext *cx = GetJSContextFromJitCode();
     JS_ASSERT(bailoutInfo);
@@ -146,18 +146,18 @@ jit::InvalidationBailout(InvalidationBai
     }
 
     iter.ionScript()->decref(cx->runtime()->defaultFreeOp());
 
     return retval;
 }
 
 IonBailoutIterator::IonBailoutIterator(const JitActivationIterator &activations,
-                                       const IonFrameIterator &frame)
-  : IonFrameIterator(activations),
+                                       const JitFrameIterator &frame)
+  : JitFrameIterator(activations),
     machine_(frame.machineState())
 {
     returnAddressToFp_ = frame.returnAddressToFp();
     topIonScript_ = frame.ionScript();
     const OsiIndex *osiIndex = frame.osiIndex();
 
     current_ = (uint8_t *) frame.fp();
     type_ = JitFrame_IonJS;
--- a/js/src/jit/Bailouts.h
+++ b/js/src/jit/Bailouts.h
@@ -4,18 +4,18 @@
  * 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 jit_Bailouts_h
 #define jit_Bailouts_h
 
 #include "jstypes.h"
 
-#include "jit/IonFrameIterator.h"
 #include "jit/IonFrames.h"
+#include "jit/JitFrameIterator.h"
 #include "vm/Stack.h"
 
 namespace js {
 namespace jit {
 
 // A "bailout" is a condition in which we need to recover an interpreter frame
 // from an IonFrame. Bailouts can happen for the following reasons:
 //   (1) A deoptimization guard, for example, an add overflows or a type check
@@ -106,44 +106,44 @@ class JitCompartment;
 // bailout handler.
 class BailoutStack;
 class InvalidationBailoutStack;
 
 // Must be implemented by each architecture.
 
 // This iterator is constructed at a time where there is no exit frame at the
 // moment. They must be initialized to the first JS frame instead of the exit
-// frame as usually done with IonFrameIterator.
-class IonBailoutIterator : public IonFrameIterator
+// frame as usually done with JitFrameIterator.
+class IonBailoutIterator : public JitFrameIterator
 {
     MachineState machine_;
     uint32_t snapshotOffset_;
     size_t topFrameSize_;
     IonScript *topIonScript_;
 
   public:
     IonBailoutIterator(const JitActivationIterator &activations, BailoutStack *sp);
     IonBailoutIterator(const JitActivationIterator &activations, InvalidationBailoutStack *sp);
-    IonBailoutIterator(const JitActivationIterator &activations, const IonFrameIterator &frame);
+    IonBailoutIterator(const JitActivationIterator &activations, const JitFrameIterator &frame);
 
     SnapshotOffset snapshotOffset() const {
         JS_ASSERT(topIonScript_);
         return snapshotOffset_;
     }
     const MachineState &machineState() const {
         return machine_;
     }
     size_t topFrameSize() const {
         JS_ASSERT(topIonScript_);
         return topFrameSize_;
     }
     IonScript *ionScript() const {
         if (topIonScript_)
             return topIonScript_;
-        return IonFrameIterator::ionScript();
+        return JitFrameIterator::ionScript();
     }
 
     void dump() const;
 };
 
 bool EnsureHasScopeObjects(JSContext *cx, AbstractFramePtr fp);
 
 struct BaselineBailoutInfo;
--- a/js/src/jit/BaselineBailouts.cpp
+++ b/js/src/jit/BaselineBailouts.cpp
@@ -1533,17 +1533,17 @@ jit::FinishBailoutToBaseline(BaselineBai
         return false;
 
     // Create arguments objects for bailed out frames, to maintain the invariant
     // that script->needsArgsObj() implies frame->hasArgsObj().
     RootedScript innerScript(cx, nullptr);
     RootedScript outerScript(cx, nullptr);
 
     JS_ASSERT(cx->currentlyRunningInJit());
-    IonFrameIterator iter(cx);
+    JitFrameIterator iter(cx);
 
     uint32_t frameno = 0;
     while (frameno < numFrames) {
         JS_ASSERT(!iter.isIonJS());
 
         if (iter.isBaselineJS()) {
             BaselineFrame *frame = iter.baselineFrame();
             MOZ_ASSERT(frame->script()->hasBaselineScript());
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -411,17 +411,16 @@ BaselineCompiler::emitEpilogue()
 
     masm.ret();
     return true;
 }
 
 #ifdef JSGC_GENERATIONAL
 // On input:
 //  R2.scratchReg() contains object being written to.
-//  R1.scratchReg() contains slot index being written to.
 //  Otherwise, baseline stack will be synced, so all other registers are usable as scratch.
 // This calls:
 //    void PostWriteBarrier(JSRuntime *rt, JSObject *obj);
 bool
 BaselineCompiler::emitOutOfLinePostBarrierSlot()
 {
     masm.bind(&postBarrierSlot_);
 
@@ -2096,25 +2095,23 @@ BaselineCompiler::emit_JSOP_SETALIASEDVA
     masm.patchableCallPreBarrier(address, MIRType_Value);
     masm.storeValue(R0, address);
     frame.push(R0);
 
 #ifdef JSGC_GENERATIONAL
     // Fully sync the stack if post-barrier is needed.
     // Scope coordinate object is already in R2.scratchReg().
     frame.syncStack(0);
+    Register temp = R1.scratchReg();
 
     Nursery &nursery = cx->runtime()->gcNursery;
     Label skipBarrier;
-    Label isTenured;
     masm.branchTestObject(Assembler::NotEqual, R0, &skipBarrier);
-    masm.branchPtr(Assembler::Below, objReg, ImmWord(nursery.start()), &isTenured);
-    masm.branchPtr(Assembler::Below, objReg, ImmWord(nursery.heapEnd()), &skipBarrier);
-
-    masm.bind(&isTenured);
+    masm.branchPtrInNurseryRange(objReg, temp, &skipBarrier);
+
     masm.call(&postBarrierSlot_);
 
     masm.bind(&skipBarrier);
 #endif
 
     return true;
 }
 
@@ -2414,28 +2411,26 @@ BaselineCompiler::emitFormalArgAccess(ui
         frame.push(R0);
     } else {
         masm.patchableCallPreBarrier(argAddr, MIRType_Value);
         storeValue(frame.peek(-1), argAddr, R0);
 
 #ifdef JSGC_GENERATIONAL
         // Fully sync the stack if post-barrier is needed.
         frame.syncStack(0);
+        Register temp = R1.scratchReg();
 
         // Reload the arguments object
         Register reg = R2.scratchReg();
         masm.loadPtr(Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfArgsObj()), reg);
 
         Nursery &nursery = cx->runtime()->gcNursery;
         Label skipBarrier;
-        Label isTenured;
-        masm.branchPtr(Assembler::Below, reg, ImmWord(nursery.start()), &isTenured);
-        masm.branchPtr(Assembler::Below, reg, ImmWord(nursery.heapEnd()), &skipBarrier);
-
-        masm.bind(&isTenured);
+        masm.branchPtrInNurseryRange(reg, temp, &skipBarrier);
+
         masm.call(&postBarrierSlot_);
 
         masm.bind(&skipBarrier);
 #endif
     }
 
     masm.bind(&done);
     return true;
--- a/js/src/jit/BaselineFrame.cpp
+++ b/js/src/jit/BaselineFrame.cpp
@@ -23,17 +23,17 @@ MarkLocals(BaselineFrame *frame, JSTrace
     if (start < end) {
         // Stack grows down.
         Value *last = frame->valueSlot(end - 1);
         gc::MarkValueRootRange(trc, end - start, last, "baseline-stack");
     }
 }
 
 void
-BaselineFrame::trace(JSTracer *trc, IonFrameIterator &frameIterator)
+BaselineFrame::trace(JSTracer *trc, JitFrameIterator &frameIterator)
 {
     replaceCalleeToken(MarkCalleeToken(trc, calleeToken()));
 
     gc::MarkValueRoot(trc, &thisValue(), "baseline-this");
 
     // Mark actual and formal args.
     if (isNonEvalFunctionFrame()) {
         unsigned numArgs = js::Max(numActualArgs(), numFormalArgs());
@@ -203,17 +203,17 @@ BaselineFrame::initForOsr(InterpreterFra
     if (cx->compartment()->debugMode()) {
         // In debug mode, update any Debugger.Frame objects for the
         // InterpreterFrame to point to the BaselineFrame.
 
         // The caller pushed a fake return address. ScriptFrameIter, used by the
         // debugger, wants a valid return address, but it's okay to just pick one.
         // In debug mode there's always at least 1 ICEntry (since there are always
         // debug prologue/epilogue calls).
-        IonFrameIterator iter(cx);
+        JitFrameIterator iter(cx);
         JS_ASSERT(iter.returnAddress() == nullptr);
         BaselineScript *baseline = fp->script()->baselineScript();
         iter.current()->setReturnAddress(baseline->returnAddressForIC(baseline->icEntry(0)));
 
         if (!Debugger::handleBaselineOsr(cx, fp, this))
             return false;
     }
 
--- a/js/src/jit/BaselineFrame.h
+++ b/js/src/jit/BaselineFrame.h
@@ -291,17 +291,17 @@ class BaselineFrame
     bool overRecursed() const {
         return flags_ & OVER_RECURSED;
     }
 
     void setOverRecursed() {
         flags_ |= OVER_RECURSED;
     }
 
-    void trace(JSTracer *trc, IonFrameIterator &frame);
+    void trace(JSTracer *trc, JitFrameIterator &frame);
 
     bool isFunctionFrame() const {
         return CalleeTokenIsFunction(calleeToken());
     }
     bool isGlobalFrame() const {
         return !CalleeTokenIsFunction(calleeToken());
     }
      bool isEvalFrame() const {
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -733,20 +733,17 @@ inline bool
 ICStubCompiler::emitPostWriteBarrierSlot(MacroAssembler &masm, Register obj, ValueOperand val,
                                          Register scratch, GeneralRegisterSet saveRegs)
 {
     Nursery &nursery = cx->runtime()->gcNursery;
 
     Label skipBarrier;
     masm.branchTestObject(Assembler::NotEqual, val, &skipBarrier);
 
-    Label isTenured;
-    masm.branchPtr(Assembler::Below, obj, ImmWord(nursery.start()), &isTenured);
-    masm.branchPtr(Assembler::Below, obj, ImmWord(nursery.heapEnd()), &skipBarrier);
-    masm.bind(&isTenured);
+    masm.branchPtrInNurseryRange(obj, scratch, &skipBarrier);
 
     Register valReg = masm.extractObject(val, scratch);
     masm.branchPtr(Assembler::Below, valReg, ImmWord(nursery.start()), &skipBarrier);
     masm.branchPtr(Assembler::AboveOrEqual, valReg, ImmWord(nursery.heapEnd()), &skipBarrier);
 
     // void PostWriteBarrier(JSRuntime *rt, JSObject *obj);
 #ifdef JS_CODEGEN_ARM
     saveRegs.add(BaselineTailCallReg);
@@ -768,17 +765,17 @@ ICStubCompiler::emitPostWriteBarrierSlot
 //
 // UseCount_Fallback
 //
 static bool
 IsTopFrameConstructing(JSContext *cx)
 {
     JS_ASSERT(cx->currentlyRunningInJit());
     JitActivationIterator activations(cx->runtime());
-    IonFrameIterator iter(activations);
+    JitFrameIterator iter(activations);
     JS_ASSERT(iter.type() == JitFrame_Exit);
 
     ++iter;
     JS_ASSERT(iter.type() == JitFrame_BaselineStub);
 
     ++iter;
     JS_ASSERT(iter.isBaselineJS());
 
--- a/js/src/jit/BaselineJIT.cpp
+++ b/js/src/jit/BaselineJIT.cpp
@@ -926,17 +926,17 @@ jit::ToggleBaselineSPS(JSRuntime *runtim
             script->baselineScript()->toggleSPS(enable);
         }
     }
 }
 
 static void
 MarkActiveBaselineScripts(JSRuntime *rt, const JitActivationIterator &activation)
 {
-    for (jit::IonFrameIterator iter(activation); !iter.done(); ++iter) {
+    for (jit::JitFrameIterator iter(activation); !iter.done(); ++iter) {
         switch (iter.type()) {
           case JitFrame_BaselineJS:
             iter.script()->baselineScript()->setActive();
             break;
           case JitFrame_IonJS: {
             // Keep the baseline script around, since bailouts from the ion
             // jitcode might need to re-enter into the baseline jitcode.
             iter.script()->baselineScript()->setActive();
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -1007,19 +1007,22 @@ CodeGenerator::visitLambdaArrow(LLambdaA
         return true;
     }
 
     masm.newGCThing(output, tempReg, info.fun, ool->entry(), gc::DefaultHeap);
     masm.initGCThing(output, tempReg, info.fun);
 
     emitLambdaInit(output, scopeChain, info);
 
-    // Store the lexical |this| value.
+    // Initialize extended slots. Lexical |this| is stored in the first one.
     MOZ_ASSERT(info.flags & JSFunction::EXTENDED);
-    masm.storeValue(thisv, Address(output, FunctionExtended::offsetOfArrowThisSlot()));
+    static_assert(FunctionExtended::NUM_EXTENDED_SLOTS == 2, "All slots must be initialized");
+    static_assert(FunctionExtended::ARROW_THIS_SLOT == 0, "|this| must be stored in first slot");
+    masm.storeValue(thisv, Address(output, FunctionExtended::offsetOfExtendedSlot(0)));
+    masm.storeValue(UndefinedValue(), Address(output, FunctionExtended::offsetOfExtendedSlot(1)));
 
     masm.bind(ool->rejoin());
     return true;
 }
 
 void
 CodeGenerator::emitLambdaInit(Register output, Register scopeChain,
                               const LambdaFunctionInfo &info)
@@ -1823,67 +1826,51 @@ CodeGenerator::visitOutOfLineCallPostWri
 bool
 CodeGenerator::visitPostWriteBarrierO(LPostWriteBarrierO *lir)
 {
 #ifdef JSGC_GENERATIONAL
     OutOfLineCallPostWriteBarrier *ool = new(alloc()) OutOfLineCallPostWriteBarrier(lir, lir->object());
     if (!addOutOfLineCode(ool))
         return false;
 
-    const Nursery &nursery = GetIonContext()->runtime->gcNursery();
-    Register temp = ToRegister(lir->temp());
+    Register temp = ToTempRegisterOrInvalid(lir->temp());
 
     if (lir->object()->isConstant()) {
+        const Nursery &nursery = GetIonContext()->runtime->gcNursery();
         JS_ASSERT(!nursery.isInside(&lir->object()->toConstant()->toObject()));
     } else {
-        Register objreg = ToRegister(lir->object());
-        masm.movePtr(ImmWord(-ptrdiff_t(nursery.start())), temp);
-        masm.addPtr(objreg, temp);
-        masm.branchPtr(Assembler::Below, temp, Imm32(Nursery::NurserySize), ool->rejoin());
-    }
-
-    Register valuereg = ToRegister(lir->value());
-    masm.movePtr(ImmWord(-ptrdiff_t(nursery.start())), temp);
-    masm.addPtr(valuereg, temp);
-    masm.branchPtr(Assembler::Below, temp, Imm32(Nursery::NurserySize), ool->entry());
+        masm.branchPtrInNurseryRange(ToRegister(lir->object()), temp, ool->rejoin());
+    }
+
+    masm.branchPtrInNurseryRange(ToRegister(lir->value()), temp, ool->entry());
 
     masm.bind(ool->rejoin());
 #endif
     return true;
 }
 
 bool
 CodeGenerator::visitPostWriteBarrierV(LPostWriteBarrierV *lir)
 {
 #ifdef JSGC_GENERATIONAL
     OutOfLineCallPostWriteBarrier *ool = new(alloc()) OutOfLineCallPostWriteBarrier(lir, lir->object());
     if (!addOutOfLineCode(ool))
         return false;
 
-    ValueOperand value = ToValue(lir, LPostWriteBarrierV::Input);
-    masm.branchTestObject(Assembler::NotEqual, value, ool->rejoin());
-
-    const Nursery &nursery = GetIonContext()->runtime->gcNursery();
+    Register temp = ToTempRegisterOrInvalid(lir->temp());
 
     if (lir->object()->isConstant()) {
+        const Nursery &nursery = GetIonContext()->runtime->gcNursery();
         JS_ASSERT(!nursery.isInside(&lir->object()->toConstant()->toObject()));
     } else {
-        Register temp = ToRegister(lir->temp());
-        Register objreg = ToRegister(lir->object());
-        masm.movePtr(ImmWord(-ptrdiff_t(nursery.start())), temp);
-        masm.addPtr(objreg, temp);
-        masm.branchPtr(Assembler::Below, temp, Imm32(Nursery::NurserySize), ool->rejoin());
-    }
-
-    // This section is a little different because we mustn't trash the temp
-    // register before we use its contents.
-    Register temp = ToRegister(lir->temp());
-    masm.unboxObject(value, temp);
-    masm.addPtr(ImmWord(-ptrdiff_t(nursery.start())), temp);
-    masm.branchPtr(Assembler::Below, temp, Imm32(Nursery::NurserySize), ool->entry());
+        masm.branchPtrInNurseryRange(ToRegister(lir->object()), temp, ool->rejoin());
+    }
+
+    ValueOperand value = ToValue(lir, LPostWriteBarrierV::Input);
+    masm.branchValueIsNurseryObject(value, temp, ool->entry());
 
     masm.bind(ool->rejoin());
 #endif
     return true;
 }
 
 bool
 CodeGenerator::visitCallNative(LCallNative *call)
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -2509,17 +2509,17 @@ jit::FastInvoke(JSContext *cx, HandleFun
 
 static void
 InvalidateActivation(FreeOp *fop, uint8_t *ionTop, bool invalidateAll)
 {
     IonSpew(IonSpew_Invalidate, "BEGIN invalidating activation");
 
     size_t frameno = 1;
 
-    for (IonFrameIterator it(ionTop, SequentialExecution); !it.done(); ++it, ++frameno) {
+    for (JitFrameIterator it(ionTop, SequentialExecution); !it.done(); ++it, ++frameno) {
         JS_ASSERT_IF(frameno == 1, it.type() == JitFrame_Exit);
 
 #ifdef DEBUG
         switch (it.type()) {
           case JitFrame_Exit:
             IonSpew(IonSpew_Invalidate, "#%d exit frame @ %p", frameno, it.fp());
             break;
           case JitFrame_BaselineJS:
@@ -2875,17 +2875,17 @@ jit::ForbidCompilation(JSContext *cx, JS
             mode, script->filename(), script->lineno());
 
     CancelOffThreadIonCompile(cx->compartment(), script);
 
     switch (mode) {
       case SequentialExecution:
         if (script->hasIonScript()) {
             // It is only safe to modify script->ion if the script is not currently
-            // running, because IonFrameIterator needs to tell what ionScript to
+            // running, because JitFrameIterator needs to tell what ionScript to
             // use (either the one on the JSScript, or the one hidden in the
             // breadcrumbs Invalidation() leaves). Therefore, if invalidation
             // fails, we cannot disable the script.
             if (!Invalidate(cx, script, mode, false))
                 return;
         }
 
         script->setIonScript(ION_DISABLED_SCRIPT);
--- a/js/src/jit/IonFrames-inl.h
+++ b/js/src/jit/IonFrames-inl.h
@@ -6,78 +6,78 @@
 
 #ifndef jit_IonFrames_inl_h
 #define jit_IonFrames_inl_h
 
 #ifdef JS_ION
 
 #include "jit/IonFrames.h"
 
-#include "jit/IonFrameIterator.h"
+#include "jit/JitFrameIterator.h"
 #include "jit/LIR.h"
 #include "vm/ForkJoin.h"
 
-#include "jit/IonFrameIterator-inl.h"
+#include "jit/JitFrameIterator-inl.h"
 
 namespace js {
 namespace jit {
 
 inline void
 SafepointIndex::resolve()
 {
     JS_ASSERT(!resolved);
     safepointOffset_ = safepoint_->offset();
 #ifdef DEBUG
     resolved = true;
 #endif
 }
 
 inline uint8_t *
-IonFrameIterator::returnAddress() const
+JitFrameIterator::returnAddress() const
 {
     IonCommonFrameLayout *current = (IonCommonFrameLayout *) current_;
     return current->returnAddress();
 }
 
 inline size_t
-IonFrameIterator::prevFrameLocalSize() const
+JitFrameIterator::prevFrameLocalSize() const
 {
     IonCommonFrameLayout *current = (IonCommonFrameLayout *) current_;
     return current->prevFrameLocalSize();
 }
 
 inline FrameType
-IonFrameIterator::prevType() const
+JitFrameIterator::prevType() const
 {
     IonCommonFrameLayout *current = (IonCommonFrameLayout *) current_;
     return current->prevType();
 }
 
 inline bool
-IonFrameIterator::isFakeExitFrame() const
+JitFrameIterator::isFakeExitFrame() const
 {
     bool res = (prevType() == JitFrame_Unwound_Rectifier ||
                 prevType() == JitFrame_Unwound_IonJS ||
                 prevType() == JitFrame_Unwound_BaselineStub);
     JS_ASSERT_IF(res, type() == JitFrame_Exit || type() == JitFrame_BaselineJS);
     return res;
 }
 
 inline IonExitFrameLayout *
-IonFrameIterator::exitFrame() const
+JitFrameIterator::exitFrame() const
 {
     JS_ASSERT(type() == JitFrame_Exit);
     JS_ASSERT(!isFakeExitFrame());
     return (IonExitFrameLayout *) fp();
 }
 
 inline BaselineFrame *
 GetTopBaselineFrame(JSContext *cx)
 {
-    IonFrameIterator iter(cx);
+    JitFrameIterator iter(cx);
     JS_ASSERT(iter.type() == JitFrame_Exit);
     ++iter;
     if (iter.isBaselineStub())
         ++iter;
     JS_ASSERT(iter.isBaselineJS());
     return iter.baselineFrame();
 }
 
--- a/js/src/jit/IonFrames.cpp
+++ b/js/src/jit/IonFrames.cpp
@@ -22,17 +22,17 @@
 #include "jit/PcScriptCache.h"
 #include "jit/Recover.h"
 #include "jit/Safepoints.h"
 #include "jit/Snapshots.h"
 #include "jit/VMFunctions.h"
 #include "vm/ForkJoin.h"
 #include "vm/Interpreter.h"
 
-#include "jit/IonFrameIterator-inl.h"
+#include "jit/JitFrameIterator-inl.h"
 #include "vm/Probes-inl.h"
 
 namespace js {
 namespace jit {
 
 // Given a slot index, returns the offset, in bytes, of that slot from an
 // IonJSFrameLayout. Slot distances are uniform across architectures, however,
 // the distance does depend on the size of the frame header.
@@ -67,56 +67,56 @@ ReadFrameInt32Slot(IonJSFrameLayout *fp,
 }
 
 static inline bool
 ReadFrameBooleanSlot(IonJSFrameLayout *fp, int32_t slot)
 {
     return *(bool *)((char *)fp + OffsetOfFrameSlot(slot));
 }
 
-IonFrameIterator::IonFrameIterator(JSContext *cx)
+JitFrameIterator::JitFrameIterator(JSContext *cx)
   : current_(cx->mainThread().ionTop),
     type_(JitFrame_Exit),
     returnAddressToFp_(nullptr),
     frameSize_(0),
     cachedSafepointIndex_(nullptr),
     activation_(nullptr),
     mode_(SequentialExecution)
 {
 }
 
-IonFrameIterator::IonFrameIterator(const ActivationIterator &activations)
+JitFrameIterator::JitFrameIterator(const ActivationIterator &activations)
     : current_(activations.jitTop()),
       type_(JitFrame_Exit),
       returnAddressToFp_(nullptr),
       frameSize_(0),
       cachedSafepointIndex_(nullptr),
       activation_(activations.activation()->asJit()),
       mode_(SequentialExecution)
 {
 }
 
-IonFrameIterator::IonFrameIterator(IonJSFrameLayout *fp, ExecutionMode mode)
+JitFrameIterator::JitFrameIterator(IonJSFrameLayout *fp, ExecutionMode mode)
   : current_((uint8_t *)fp),
     type_(JitFrame_IonJS),
     returnAddressToFp_(fp->returnAddress()),
     frameSize_(fp->prevFrameLocalSize()),
     mode_(mode)
 {
 }
 
 bool
-IonFrameIterator::checkInvalidation() const
+JitFrameIterator::checkInvalidation() const
 {
     IonScript *dummy;
     return checkInvalidation(&dummy);
 }
 
 bool
-IonFrameIterator::checkInvalidation(IonScript **ionScriptOut) const
+JitFrameIterator::checkInvalidation(IonScript **ionScriptOut) const
 {
     uint8_t *returnAddr = returnAddressToFp();
     JSScript *script = this->script();
     // N.B. the current IonScript is not the same as the frame's
     // IonScript if the frame has since been invalidated.
     bool invalidated;
     if (mode_ == ParallelExecution) {
         // Parallel execution does not have invalidating bailouts.
@@ -132,96 +132,96 @@ IonFrameIterator::checkInvalidation(IonS
     uint8_t *ionScriptDataOffset = returnAddr + invalidationDataOffset;
     IonScript *ionScript = (IonScript *) Assembler::getPointer(ionScriptDataOffset);
     JS_ASSERT(ionScript->containsReturnAddress(returnAddr));
     *ionScriptOut = ionScript;
     return true;
 }
 
 CalleeToken
-IonFrameIterator::calleeToken() const
+JitFrameIterator::calleeToken() const
 {
     return ((IonJSFrameLayout *) current_)->calleeToken();
 }
 
 JSFunction *
-IonFrameIterator::callee() const
+JitFrameIterator::callee() const
 {
     JS_ASSERT(isScripted());
     JS_ASSERT(isFunctionFrame());
     return CalleeTokenToFunction(calleeToken());
 }
 
 JSFunction *
-IonFrameIterator::maybeCallee() const
+JitFrameIterator::maybeCallee() const
 {
     if (isScripted() && (isFunctionFrame()))
         return callee();
     return nullptr;
 }
 
 bool
-IonFrameIterator::isNative() const
+JitFrameIterator::isNative() const
 {
     if (type_ != JitFrame_Exit || isFakeExitFrame())
         return false;
     return exitFrame()->footer()->jitCode() == nullptr;
 }
 
 bool
-IonFrameIterator::isOOLNative() const
+JitFrameIterator::isOOLNative() const
 {
     if (type_ != JitFrame_Exit)
         return false;
     return exitFrame()->footer()->jitCode() == ION_FRAME_OOL_NATIVE;
 }
 
 bool
-IonFrameIterator::isOOLPropertyOp() const
+JitFrameIterator::isOOLPropertyOp() const
 {
     if (type_ != JitFrame_Exit)
         return false;
     return exitFrame()->footer()->jitCode() == ION_FRAME_OOL_PROPERTY_OP;
 }
 
 bool
-IonFrameIterator::isOOLProxy() const
+JitFrameIterator::isOOLProxy() const
 {
     if (type_ != JitFrame_Exit)
         return false;
     return exitFrame()->footer()->jitCode() == ION_FRAME_OOL_PROXY;
 }
 
 bool
-IonFrameIterator::isDOMExit() const
+JitFrameIterator::isDOMExit() const
 {
     if (type_ != JitFrame_Exit)
         return false;
     return exitFrame()->isDomExit();
 }
 
 bool
-IonFrameIterator::isFunctionFrame() const
+JitFrameIterator::isFunctionFrame() const
 {
     return CalleeTokenIsFunction(calleeToken());
 }
 
 JSScript *
-IonFrameIterator::script() const
+JitFrameIterator::script() const
 {
     JS_ASSERT(isScripted());
     if (isBaselineJS())
         return baselineFrame()->script();
     JSScript *script = ScriptFromCalleeToken(calleeToken());
     JS_ASSERT(script);
     return script;
 }
 
 void
-IonFrameIterator::baselineScriptAndPc(JSScript **scriptRes, jsbytecode **pcRes) const
+JitFrameIterator::baselineScriptAndPc(JSScript **scriptRes, jsbytecode **pcRes) const
 {
     JS_ASSERT(isBaselineJS());
     JSScript *script = this->script();
     if (scriptRes)
         *scriptRes = script;
     uint8_t *retAddr = returnAddressToFp();
     if (pcRes) {
         // If the return address is into the prologue entry address, then assume start
@@ -241,17 +241,17 @@ IonFrameIterator::baselineScriptAndPc(JS
 
         // If not, the return address _must_ be the start address of an op, which can
         // be computed from the pc mapping table.
         *pcRes = script->baselineScript()->pcForReturnAddress(script, retAddr);
     }
 }
 
 Value *
-IonFrameIterator::actualArgs() const
+JitFrameIterator::actualArgs() const
 {
     return jsFrame()->argv() + 1;
 }
 
 static inline size_t
 SizeOfFramePrefix(FrameType type)
 {
     switch (type) {
@@ -270,33 +270,33 @@ SizeOfFramePrefix(FrameType type)
       case JitFrame_Exit:
         return IonExitFrameLayout::Size();
       default:
         MOZ_ASSUME_UNREACHABLE("unknown frame type");
     }
 }
 
 uint8_t *
-IonFrameIterator::prevFp() const
+JitFrameIterator::prevFp() const
 {
     size_t currentSize = SizeOfFramePrefix(type_);
     // This quick fix must be removed as soon as bug 717297 land.  This is
     // needed because the descriptor size of JS-to-JS frame which is just after
     // a Rectifier frame should not change. (cf EnsureExitFrame function)
     if (isFakeExitFrame()) {
         JS_ASSERT(SizeOfFramePrefix(JitFrame_BaselineJS) ==
                   SizeOfFramePrefix(JitFrame_IonJS));
         currentSize = SizeOfFramePrefix(JitFrame_IonJS);
     }
     currentSize += current()->prevFrameLocalSize();
     return current_ + currentSize;
 }
 
-IonFrameIterator &
-IonFrameIterator::operator++()
+JitFrameIterator &
+JitFrameIterator::operator++()
 {
     JS_ASSERT(type_ != JitFrame_Entry);
 
     frameSize_ = prevFrameLocalSize();
     cachedSafepointIndex_ = nullptr;
 
     // If the next frame is the entry frame, just exit. Don't update current_,
     // since the entry and first frames overlap.
@@ -314,27 +314,27 @@ IonFrameIterator::operator++()
     else if (type_ == JitFrame_Unwound_BaselineStub)
         type_ = JitFrame_BaselineStub;
     returnAddressToFp_ = current()->returnAddress();
     current_ = prev;
     return *this;
 }
 
 uintptr_t *
-IonFrameIterator::spillBase() const
+JitFrameIterator::spillBase() const
 {
     // Get the base address to where safepoint registers are spilled.
     // Out-of-line calls do not unwind the extra padding space used to
     // aggregate bailout tables, so we use frameSize instead of frameLocals,
     // which would only account for local stack slots.
     return reinterpret_cast<uintptr_t *>(fp() - ionScript()->frameSize());
 }
 
 MachineState
-IonFrameIterator::machineState() const
+JitFrameIterator::machineState() const
 {
     SafepointReader reader(ionScript(), safepoint());
     uintptr_t *spill = spillBase();
 
     MachineState machine;
     for (GeneralRegisterBackwardIterator iter(reader.allGprSpills()); iter.more(); iter++)
         machine.setRegisterLocation(*iter, --spill);
 
@@ -441,17 +441,17 @@ HandleExceptionIon(JSContext *cx, const 
 
           default:
             MOZ_ASSUME_UNREACHABLE("Unexpected try note");
         }
     }
 }
 
 static void
-HandleExceptionBaseline(JSContext *cx, const IonFrameIterator &frame, ResumeFromException *rfe,
+HandleExceptionBaseline(JSContext *cx, const JitFrameIterator &frame, ResumeFromException *rfe,
                         bool *calledDebugEpilogue)
 {
     JS_ASSERT(frame.isBaselineJS());
     JS_ASSERT(!*calledDebugEpilogue);
 
     RootedScript script(cx);
     jsbytecode *pc;
     frame.baselineScriptAndPc(script.address(), &pc);
@@ -578,17 +578,17 @@ HandleException(ResumeFromException *rfe
 
     // Clear any Ion return override that's been set.
     // This may happen if a callVM function causes an invalidation (setting the
     // override), and then fails, bypassing the bailout handlers that would
     // otherwise clear the return override.
     if (cx->runtime()->hasIonReturnOverride())
         cx->runtime()->takeIonReturnOverride();
 
-    IonFrameIterator iter(cx);
+    JitFrameIterator iter(cx);
     while (!iter.isEntry()) {
         bool overrecursed = false;
         if (iter.isIonJS()) {
             // Search each inlined frame for live iterator objects, and close
             // them.
             InlineFrameIterator frames(cx, &iter);
 
             // Invalidation state will be the same for all inlined scripts in the frame.
@@ -688,17 +688,17 @@ HandleException(ResumeFromException *rfe
 
     rfe->stackPointer = iter.fp();
 }
 
 void
 HandleParallelFailure(ResumeFromException *rfe)
 {
     ForkJoinContext *cx = ForkJoinContext::current();
-    IonFrameIterator iter(cx->perThreadData->ionTop, ParallelExecution);
+    JitFrameIterator iter(cx->perThreadData->ionTop, ParallelExecution);
 
     parallel::Spew(parallel::SpewBailouts, "Bailing from VM reentry");
 
     while (!iter.isEntry()) {
         if (iter.isScripted()) {
             cx->bailoutRecord->updateCause(ParallelBailoutUnsupportedVM,
                                            iter.script(), iter.script(), nullptr);
             break;
@@ -769,49 +769,49 @@ MarkCalleeToken(JSTracer *trc, CalleeTok
       }
       default:
         MOZ_ASSUME_UNREACHABLE("unknown callee token type");
     }
 }
 
 #ifdef JS_NUNBOX32
 static inline uintptr_t
-ReadAllocation(const IonFrameIterator &frame, const LAllocation *a)
+ReadAllocation(const JitFrameIterator &frame, const LAllocation *a)
 {
     if (a->isGeneralReg()) {
         Register reg = a->toGeneralReg()->reg();
         return frame.machineState().read(reg);
     }
     if (a->isStackSlot()) {
         uint32_t slot = a->toStackSlot()->slot();
         return *frame.jsFrame()->slotRef(slot);
     }
     uint32_t index = a->toArgument()->index();
     uint8_t *argv = reinterpret_cast<uint8_t *>(frame.jsFrame()->argv());
     return *reinterpret_cast<uintptr_t *>(argv + index);
 }
 #endif
 
 static void
-MarkActualArguments(JSTracer *trc, const IonFrameIterator &frame)
+MarkActualArguments(JSTracer *trc, const JitFrameIterator &frame)
 {
     IonJSFrameLayout *layout = frame.jsFrame();
     JS_ASSERT(CalleeTokenIsFunction(layout->calleeToken()));
 
     size_t nargs = frame.numActualArgs();
 
     // Trace function arguments. Note + 1 for thisv.
     Value *argv = layout->argv();
     for (size_t i = 0; i < nargs + 1; i++)
         gc::MarkValueRoot(trc, &argv[i], "ion-argv");
 }
 
 #ifdef JS_NUNBOX32
 static inline void
-WriteAllocation(const IonFrameIterator &frame, const LAllocation *a, uintptr_t value)
+WriteAllocation(const JitFrameIterator &frame, const LAllocation *a, uintptr_t value)
 {
     if (a->isGeneralReg()) {
         Register reg = a->toGeneralReg()->reg();
         frame.machineState().write(reg, value);
         return;
     }
     if (a->isStackSlot()) {
         uint32_t slot = a->toStackSlot()->slot();
@@ -820,17 +820,17 @@ WriteAllocation(const IonFrameIterator &
     }
     uint32_t index = a->toArgument()->index();
     uint8_t *argv = reinterpret_cast<uint8_t *>(frame.jsFrame()->argv());
     *reinterpret_cast<uintptr_t *>(argv + index) = value;
 }
 #endif
 
 static void
-MarkIonJSFrame(JSTracer *trc, const IonFrameIterator &frame)
+MarkIonJSFrame(JSTracer *trc, const JitFrameIterator &frame)
 {
     IonJSFrameLayout *layout = (IonJSFrameLayout *)frame.fp();
 
     layout->replaceCalleeToken(MarkCalleeToken(trc, layout->calleeToken()));
 
     IonScript *ionScript = nullptr;
     if (frame.checkInvalidation(&ionScript)) {
         // This frame has been invalidated, meaning that its IonScript is no
@@ -890,17 +890,17 @@ MarkIonJSFrame(JSTracer *trc, const IonF
             WriteAllocation(frame, &payload, layout.s.payload.uintptr);
         }
     }
 #endif
 }
 
 #ifdef JSGC_GENERATIONAL
 static void
-UpdateIonJSFrameForMinorGC(JSTracer *trc, const IonFrameIterator &frame)
+UpdateIonJSFrameForMinorGC(JSTracer *trc, const JitFrameIterator &frame)
 {
     // Minor GCs may move slots/elements allocated in the nursery. Update
     // any slots/elements pointers stored in this frame.
 
     IonJSFrameLayout *layout = (IonJSFrameLayout *)frame.fp();
 
     IonScript *ionScript = nullptr;
     if (frame.checkInvalidation(&ionScript)) {
@@ -936,34 +936,34 @@ UpdateIonJSFrameForMinorGC(JSTracer *trc
     while (safepoint.getSlotsOrElementsSlot(&slot)) {
         HeapSlot **slots = reinterpret_cast<HeapSlot **>(layout->slotRef(slot));
         trc->runtime()->gcNursery.forwardBufferPointer(slots);
     }
 }
 #endif
 
 static void
-MarkBaselineStubFrame(JSTracer *trc, const IonFrameIterator &frame)
+MarkBaselineStubFrame(JSTracer *trc, const JitFrameIterator &frame)
 {
     // Mark the ICStub pointer stored in the stub frame. This is necessary
     // so that we don't destroy the stub code after unlinking the stub.
 
     JS_ASSERT(frame.type() == JitFrame_BaselineStub);
     IonBaselineStubFrameLayout *layout = (IonBaselineStubFrameLayout *)frame.fp();
 
     if (ICStub *stub = layout->maybeStubPtr()) {
         JS_ASSERT(ICStub::CanMakeCalls(stub->kind()));
         stub->trace(trc);
     }
 }
 
 void
 JitActivationIterator::jitStackRange(uintptr_t *&min, uintptr_t *&end)
 {
-    IonFrameIterator frames(jitTop(), SequentialExecution);
+    JitFrameIterator frames(jitTop(), SequentialExecution);
 
     if (frames.isFakeExitFrame()) {
         min = reinterpret_cast<uintptr_t *>(frames.fp());
     } else {
         IonExitFrameLayout *exitFrame = frames.exitFrame();
         IonExitFooterFrame *footer = exitFrame->footer();
         const VMFunction *f = footer->function();
         if (exitFrame->isWrapperExit() && f->outParam == Type_Handle) {
@@ -989,17 +989,17 @@ JitActivationIterator::jitStackRange(uin
 
     while (!frames.done())
         ++frames;
 
     end = reinterpret_cast<uintptr_t *>(frames.prevFp());
 }
 
 static void
-MarkJitExitFrame(JSTracer *trc, const IonFrameIterator &frame)
+MarkJitExitFrame(JSTracer *trc, const JitFrameIterator &frame)
 {
     // Ignore fake exit frames created by EnsureExitFrame.
     if (frame.isFakeExitFrame())
         return;
 
     IonExitFooterFrame *footer = frame.exitFrame()->footer();
 
     // Mark the code of the code handling the exit path.  This is needed because
@@ -1129,17 +1129,17 @@ MarkJitExitFrame(JSTracer *trc, const Io
           case VMFunction::RootCell:
             gc::MarkGCThingRoot(trc, footer->outParam<void *>(), "ion-vm-out");
             break;
         }
     }
 }
 
 static void
-MarkRectifierFrame(JSTracer *trc, const IonFrameIterator &frame)
+MarkRectifierFrame(JSTracer *trc, const JitFrameIterator &frame)
 {
     // Mark thisv.
     //
     // Baseline JIT code generated as part of the ICCall_Fallback stub may use
     // it if we're calling a constructor that returns a primitive value.
     IonRectifierFrameLayout *layout = (IonRectifierFrameLayout *)frame.fp();
     gc::MarkValueRoot(trc, &layout->argv()[0], "ion-thisv");
 }
@@ -1152,17 +1152,17 @@ MarkJitActivation(JSTracer *trc, const J
         // GC can modify spilled registers, breaking our register checks.
         // To handle this, we disable these checks for the current VM call
         // when a GC happens.
         JitActivation *activation = activations.activation()->asJit();
         activation->setCheckRegs(false);
     }
 #endif
 
-    for (IonFrameIterator frames(activations); !frames.done(); ++frames) {
+    for (JitFrameIterator frames(activations); !frames.done(); ++frames) {
         switch (frames.type()) {
           case JitFrame_Exit:
             MarkJitExitFrame(trc, frames);
             break;
           case JitFrame_BaselineJS:
             frames.baselineFrame()->trace(trc, frames);
             break;
           case JitFrame_BaselineStub:
@@ -1192,17 +1192,17 @@ MarkJitActivations(JSRuntime *rt, JSTrac
 }
 
 #ifdef JSGC_GENERATIONAL
 void
 UpdateJitActivationsForMinorGC(JSRuntime *rt, JSTracer *trc)
 {
     JS_ASSERT(trc->runtime()->isHeapMinorCollecting());
     for (JitActivationIterator activations(rt); !activations.done(); ++activations) {
-        for (IonFrameIterator frames(activations); !frames.done(); ++frames) {
+        for (JitFrameIterator frames(activations); !frames.done(); ++frames) {
             if (frames.type() == JitFrame_IonJS)
                 UpdateIonJSFrameForMinorGC(trc, frames);
         }
     }
 }
 #endif
 
 void
@@ -1215,17 +1215,17 @@ AutoTempAllocatorRooter::trace(JSTracer 
 void
 GetPcScript(JSContext *cx, JSScript **scriptRes, jsbytecode **pcRes)
 {
     IonSpew(IonSpew_Snapshots, "Recover PC & Script from the last frame.");
 
     JSRuntime *rt = cx->runtime();
 
     // Recover the return address.
-    IonFrameIterator it(rt->mainThread.ionTop, SequentialExecution);
+    JitFrameIterator it(rt->mainThread.ionTop, SequentialExecution);
 
     // If the previous frame is a rectifier frame (maybe unwound),
     // skip past it.
     if (it.prevType() == JitFrame_Rectifier || it.prevType() == JitFrame_Unwound_Rectifier) {
         ++it;
         JS_ASSERT(it.prevType() == JitFrame_BaselineStub ||
                   it.prevType() == JitFrame_BaselineJS ||
                   it.prevType() == JitFrame_IonJS);
@@ -1301,17 +1301,17 @@ SnapshotIterator::SnapshotIterator(IonSc
              ionScript->recoversSize()),
     fp_(fp),
     machine_(machine),
     ionScript_(ionScript)
 {
     JS_ASSERT(snapshotOffset < ionScript->snapshotsListSize());
 }
 
-SnapshotIterator::SnapshotIterator(const IonFrameIterator &iter)
+SnapshotIterator::SnapshotIterator(const JitFrameIterator &iter)
   : snapshot_(iter.ionScript()->snapshots(),
               iter.osiIndex()->snapshotOffset(),
               iter.ionScript()->snapshotsRVATableSize(),
               iter.ionScript()->snapshotsListSize()),
     recover_(snapshot_,
              iter.ionScript()->recovers(),
              iter.ionScript()->recoversSize()),
     fp_(iter.jsFrame()),
@@ -1531,62 +1531,62 @@ void
 SnapshotIterator::nextFrame()
 {
     nextInstruction();
     while (!instruction()->isResumePoint())
         skipInstruction();
 }
 
 IonScript *
-IonFrameIterator::ionScript() const
+JitFrameIterator::ionScript() const
 {
     JS_ASSERT(type() == JitFrame_IonJS);
 
     IonScript *ionScript = nullptr;
     if (checkInvalidation(&ionScript))
         return ionScript;
     switch (GetCalleeTokenTag(calleeToken())) {
       case CalleeToken_Function:
       case CalleeToken_Script:
         return mode_ == ParallelExecution ? script()->parallelIonScript() : script()->ionScript();
       default:
         MOZ_ASSUME_UNREACHABLE("unknown callee token type");
     }
 }
 
 const SafepointIndex *
-IonFrameIterator::safepoint() const
+JitFrameIterator::safepoint() const
 {
     if (!cachedSafepointIndex_)
         cachedSafepointIndex_ = ionScript()->getSafepointIndex(returnAddressToFp());
     return cachedSafepointIndex_;
 }
 
 const OsiIndex *
-IonFrameIterator::osiIndex() const
+JitFrameIterator::osiIndex() const
 {
     SafepointReader reader(ionScript(), safepoint());
     return ionScript()->getOsiIndex(reader.osiReturnPointOffset());
 }
 
 template <AllowGC allowGC>
 void
-InlineFrameIteratorMaybeGC<allowGC>::resetOn(const IonFrameIterator *iter)
+InlineFrameIteratorMaybeGC<allowGC>::resetOn(const JitFrameIterator *iter)
 {
     frame_ = iter;
     framesRead_ = 0;
     frameCount_ = UINT32_MAX;
 
     if (iter) {
         start_ = SnapshotIterator(*iter);
         findNextFrame();
     }
 }
-template void InlineFrameIteratorMaybeGC<NoGC>::resetOn(const IonFrameIterator *iter);
-template void InlineFrameIteratorMaybeGC<CanGC>::resetOn(const IonFrameIterator *iter);
+template void InlineFrameIteratorMaybeGC<NoGC>::resetOn(const JitFrameIterator *iter);
+template void InlineFrameIteratorMaybeGC<CanGC>::resetOn(const JitFrameIterator *iter);
 
 template <AllowGC allowGC>
 void
 InlineFrameIteratorMaybeGC<allowGC>::findNextFrame()
 {
     JS_ASSERT(more());
 
     si_ = start_;
@@ -1714,19 +1714,19 @@ InlineFrameIteratorMaybeGC<allowGC>::isC
     }
 
     return frame_->isConstructing();
 }
 template bool InlineFrameIteratorMaybeGC<NoGC>::isConstructing() const;
 template bool InlineFrameIteratorMaybeGC<CanGC>::isConstructing() const;
 
 bool
-IonFrameIterator::isConstructing() const
+JitFrameIterator::isConstructing() const
 {
-    IonFrameIterator parent(*this);
+    JitFrameIterator parent(*this);
 
     // Skip the current frame and look at the caller's.
     do {
         ++parent;
     } while (!parent.done() && !parent.isScripted());
 
     if (parent.isIonJS()) {
         // In the case of a JS frame, look up the pc from the snapshot.
@@ -1755,17 +1755,17 @@ IonFrameIterator::isConstructing() const
         return JSOp(*pc) == JSOP_NEW;
     }
 
     JS_ASSERT(parent.done());
     return activation_->firstFrameIsConstructing();
 }
 
 unsigned
-IonFrameIterator::numActualArgs() const
+JitFrameIterator::numActualArgs() const
 {
     if (isScripted())
         return jsFrame()->numActualArgs();
 
     JS_ASSERT(isNative());
     return exitFrame()->nativeExit()->argc();
 }
 
@@ -1786,17 +1786,17 @@ struct DumpOp {
 #else
         fprintf(stderr, "?\n");
 #endif
         i_++;
     }
 };
 
 void
-IonFrameIterator::dumpBaseline() const
+JitFrameIterator::dumpBaseline() const
 {
     JS_ASSERT(isBaselineJS());
 
     fprintf(stderr, " JS Baseline frame\n");
     if (isFunctionFrame()) {
         fprintf(stderr, "  callee fun: ");
 #ifdef DEBUG
         js_DumpObject(callee());
@@ -1893,17 +1893,17 @@ InlineFrameIteratorMaybeGC<allowGC>::dum
     }
 
     fputc('\n', stderr);
 }
 template void InlineFrameIteratorMaybeGC<NoGC>::dump() const;
 template void InlineFrameIteratorMaybeGC<CanGC>::dump() const;
 
 void
-IonFrameIterator::dump() const
+JitFrameIterator::dump() const
 {
     switch (type_) {
       case JitFrame_Entry:
         fprintf(stderr, " Entry frame\n");
         fprintf(stderr, "  Frame size: %u\n", unsigned(current()->prevFrameLocalSize()));
         break;
       case JitFrame_BaselineJS:
         dumpBaseline();
--- a/js/src/jit/IonFrames.h
+++ b/js/src/jit/IonFrames.h
@@ -9,17 +9,17 @@
 
 #ifdef JS_ION
 
 #include <stdint.h>
 
 #include "jscntxt.h"
 #include "jsfun.h"
 
-#include "jit/IonFrameIterator.h"
+#include "jit/JitFrameIterator.h"
 
 namespace js {
 namespace jit {
 
 typedef void * CalleeToken;
 
 enum CalleeTokenTag
 {
@@ -277,17 +277,17 @@ MakeFrameDescriptor(uint32_t frameSize, 
 {
     return (frameSize << FRAMESIZE_SHIFT) | type;
 }
 
 // Returns the JSScript associated with the topmost Ion frame.
 inline JSScript *
 GetTopIonJSScript(uint8_t *ionTop, void **returnAddrOut, ExecutionMode mode)
 {
-    IonFrameIterator iter(ionTop, mode);
+    JitFrameIterator iter(ionTop, mode);
     JS_ASSERT(iter.type() == JitFrame_Exit);
     ++iter;
 
     JS_ASSERT(iter.returnAddressToFp() != nullptr);
     if (returnAddrOut)
         *returnAddrOut = (void *) iter.returnAddressToFp();
 
     if (iter.isBaselineStub()) {
rename from js/src/jit/IonFrameIterator-inl.h
rename to js/src/jit/JitFrameIterator-inl.h
--- a/js/src/jit/IonFrameIterator-inl.h
+++ b/js/src/jit/JitFrameIterator-inl.h
@@ -1,20 +1,20 @@
 /* -*- 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 jit_IonFrameIterator_inl_h
-#define jit_IonFrameIterator_inl_h
+#ifndef jit_JitFrameIterator_inl_h
+#define jit_JitFrameIterator_inl_h
 
 #ifdef JS_ION
 
-#include "jit/IonFrameIterator.h"
+#include "jit/JitFrameIterator.h"
 
 #include "jit/Bailouts.h"
 #include "jit/BaselineFrame.h"
 
 namespace js {
 namespace jit {
 
 template <AllowGC allowGC>
@@ -29,20 +29,20 @@ InlineFrameIteratorMaybeGC<allowGC>::Inl
 {
     if (iter) {
         start_ = SnapshotIterator(*iter);
         findNextFrame();
     }
 }
 
 inline BaselineFrame *
-IonFrameIterator::baselineFrame() const
+JitFrameIterator::baselineFrame() const
 {
     JS_ASSERT(isBaselineJS());
     return (BaselineFrame *)(fp() - BaselineFrame::FramePointerOffset - BaselineFrame::Size());
 }
 
 } // namespace jit
 } // namespace js
 
 #endif // JS_ION
 
-#endif /* jit_IonFrameIterator_inl_h */
+#endif /* jit_JitFrameIterator_inl_h */
rename from js/src/jit/IonFrameIterator.h
rename to js/src/jit/JitFrameIterator.h
--- a/js/src/jit/IonFrameIterator.h
+++ b/js/src/jit/JitFrameIterator.h
@@ -1,16 +1,16 @@
 /* -*- 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 jit_IonFrameIterator_h
-#define jit_IonFrameIterator_h
+#ifndef jit_JitFrameIterator_h
+#define jit_JitFrameIterator_h
 
 #ifdef JS_ION
 
 #include "jsfun.h"
 #include "jsscript.h"
 #include "jstypes.h"
 
 #include "jit/IonCode.h"
@@ -76,45 +76,45 @@ enum ReadFrameArgsBehavior {
 class IonCommonFrameLayout;
 class IonJSFrameLayout;
 class IonExitFrameLayout;
 
 class BaselineFrame;
 
 class JitActivation;
 
-class IonFrameIterator
+class JitFrameIterator
 {
   protected:
     uint8_t *current_;
     FrameType type_;
     uint8_t *returnAddressToFp_;
     size_t frameSize_;
 
   private:
     mutable const SafepointIndex *cachedSafepointIndex_;
     const JitActivation *activation_;
     ExecutionMode mode_;
 
     void dumpBaseline() const;
 
   public:
-    explicit IonFrameIterator(uint8_t *top, ExecutionMode mode)
+    explicit JitFrameIterator(uint8_t *top, ExecutionMode mode)
       : current_(top),
         type_(JitFrame_Exit),
         returnAddressToFp_(nullptr),
         frameSize_(0),
         cachedSafepointIndex_(nullptr),
         activation_(nullptr),
         mode_(mode)
     { }
 
-    explicit IonFrameIterator(JSContext *cx);
-    explicit IonFrameIterator(const ActivationIterator &activations);
-    explicit IonFrameIterator(IonJSFrameLayout *fp, ExecutionMode mode);
+    explicit JitFrameIterator(JSContext *cx);
+    explicit JitFrameIterator(const ActivationIterator &activations);
+    explicit JitFrameIterator(IonJSFrameLayout *fp, ExecutionMode mode);
 
     // Current frame information.
     FrameType type() const {
         return type_;
     }
     uint8_t *fp() const {
         return current_;
     }
@@ -190,17 +190,17 @@ class IonFrameIterator
         return frameSize_;
     }
 
     // Functions used to iterate on frames. When prevType is JitFrame_Entry,
     // the current frame is the last frame.
     inline bool done() const {
         return type_ == JitFrame_Entry;
     }
-    IonFrameIterator &operator++();
+    JitFrameIterator &operator++();
 
     // Returns the IonScript associated with this JS frame.
     IonScript *ionScript() const;
 
     // Returns the Safepoint associated with this JS frame. Incurs a lookup
     // overhead.
     const SafepointIndex *safepoint() const;
 
@@ -346,17 +346,17 @@ class SnapshotIterator
     }
 
   public:
     // Connect all informations about the current script in order to recover the
     // content of baseline frames.
 
     SnapshotIterator(IonScript *ionScript, SnapshotOffset snapshotOffset,
                      IonJSFrameLayout *fp, const MachineState &machine);
-    SnapshotIterator(const IonFrameIterator &iter);
+    SnapshotIterator(const JitFrameIterator &iter);
     SnapshotIterator(const IonBailoutIterator &iter);
     SnapshotIterator();
 
     Value read() {
         return allocationValue(readAllocation());
     }
     Value maybeRead(bool silentFailure = false) {
         RValueAllocation a = readAllocation();
@@ -418,17 +418,17 @@ class SnapshotIterator
     }
 };
 
 // Reads frame information in callstack order (that is, innermost frame to
 // outermost frame).
 template <AllowGC allowGC=CanGC>
 class InlineFrameIteratorMaybeGC
 {
-    const IonFrameIterator *frame_;
+    const JitFrameIterator *frame_;
     SnapshotIterator start_;
     SnapshotIterator si_;
     uint32_t framesRead_;
 
     // When the inline-frame-iterator is created, this variable is defined to
     // UINT32_MAX. Then the first iteration of findNextFrame, which settle on
     // the innermost frame, is used to update this counter to the number of
     // frames contained in the recover buffer.
@@ -442,24 +442,24 @@ class InlineFrameIteratorMaybeGC
     struct Nop {
         void operator()(const Value &v) { }
     };
 
   private:
     void findNextFrame();
 
   public:
-    InlineFrameIteratorMaybeGC(JSContext *cx, const IonFrameIterator *iter)
+    InlineFrameIteratorMaybeGC(JSContext *cx, const JitFrameIterator *iter)
       : callee_(cx),
         script_(cx)
     {
         resetOn(iter);
     }
 
-    InlineFrameIteratorMaybeGC(JSRuntime *rt, const IonFrameIterator *iter)
+    InlineFrameIteratorMaybeGC(JSRuntime *rt, const JitFrameIterator *iter)
       : callee_(rt),
         script_(rt)
     {
         resetOn(iter);
     }
 
     InlineFrameIteratorMaybeGC(JSContext *cx, const IonBailoutIterator *iter);
 
@@ -609,19 +609,19 @@ class InlineFrameIteratorMaybeGC
 
     InlineFrameIteratorMaybeGC &operator++() {
         findNextFrame();
         return *this;
     }
 
     void dump() const;
 
-    void resetOn(const IonFrameIterator *iter);
+    void resetOn(const JitFrameIterator *iter);
 
-    const IonFrameIterator &frame() const {
+    const JitFrameIterator &frame() const {
         return *frame_;
     }
 
     // Inline frame number, 0 for the outermost (non-inlined) frame.
     size_t frameNo() const {
         MOZ_ASSERT(frameCount_ != UINT32_MAX);
         return frameCount_ - framesRead_;
     }
@@ -633,9 +633,9 @@ class InlineFrameIteratorMaybeGC
 typedef InlineFrameIteratorMaybeGC<CanGC> InlineFrameIterator;
 typedef InlineFrameIteratorMaybeGC<NoGC> InlineFrameIteratorNoGC;
 
 } // namespace jit
 } // namespace js
 
 #endif // JS_ION
 
-#endif /* jit_IonFrameIterator_h */
+#endif /* jit_JitFrameIterator_h */
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -2326,25 +2326,26 @@ LIRGenerator::visitMonitorTypes(MMonitor
 }
 
 bool
 LIRGenerator::visitPostWriteBarrier(MPostWriteBarrier *ins)
 {
 #ifdef JSGC_GENERATIONAL
     switch (ins->value()->type()) {
       case MIRType_Object: {
+        LDefinition tmp = needTempForPostBarrier() ? temp() : LDefinition::BogusTemp();
         LPostWriteBarrierO *lir =
             new(alloc()) LPostWriteBarrierO(useRegisterOrConstant(ins->object()),
-                                            useRegister(ins->value()),
-                                            temp());
+                                            useRegister(ins->value()), tmp);
         return add(lir, ins) && assignSafepoint(lir, ins);
       }
       case MIRType_Value: {
+        LDefinition tmp = needTempForPostBarrier() ? temp() : LDefinition::BogusTemp();
         LPostWriteBarrierV *lir =
-            new(alloc()) LPostWriteBarrierV(useRegisterOrConstant(ins->object()), temp());
+            new(alloc()) LPostWriteBarrierV(useRegisterOrConstant(ins->object()), tmp);
         if (!useBox(lir, LPostWriteBarrierV::Input, ins->value()))
             return false;
         return add(lir, ins) && assignSafepoint(lir, ins);
       }
       default:
         // Currently, only objects can be in the nursery. Other instruction
         // types cannot hold nursery pointers.
         return true;
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -1013,17 +1013,17 @@ StringReplace(JSContext *cx, HandleStrin
     return rval.toString();
 }
 
 bool
 Recompile(JSContext *cx)
 {
     JS_ASSERT(cx->currentlyRunningInJit());
     JitActivationIterator activations(cx->runtime());
-    IonFrameIterator iter(activations);
+    JitFrameIterator iter(activations);
 
     JS_ASSERT(iter.type() == JitFrame_Exit);
     ++iter;
 
     bool isConstructing = iter.isConstructing();
     RootedScript script(cx, iter.script());
     JS_ASSERT(script->hasIonScript());
 
--- a/js/src/jit/arm/Bailouts-arm.cpp
+++ b/js/src/jit/arm/Bailouts-arm.cpp
@@ -65,17 +65,17 @@ class BailoutStack
 // Make sure the compiler doesn't add extra padding.
 static_assert((sizeof(BailoutStack) % 8) == 0, "BailoutStack should be 8-byte aligned.");
 
 } // namespace jit
 } // namespace js
 
 IonBailoutIterator::IonBailoutIterator(const JitActivationIterator &activations,
                                        BailoutStack *bailout)
-  : IonFrameIterator(activations),
+  : JitFrameIterator(activations),
     machine_(bailout->machine())
 {
     uint8_t *sp = bailout->parentStackPointer();
     uint8_t *fp = sp + bailout->frameSize();
 
     current_ = fp;
     type_ = JitFrame_IonJS;
     topFrameSize_ = current_ - sp;
@@ -100,17 +100,17 @@ IonBailoutIterator::IonBailoutIterator(c
     uint32_t bailoutId = ((tableOffset - tableStart) / BAILOUT_TABLE_ENTRY_SIZE) - 1;
     JS_ASSERT(bailoutId < BAILOUT_TABLE_SIZE);
 
     snapshotOffset_ = topIonScript_->bailoutToSnapshot(bailoutId);
 }
 
 IonBailoutIterator::IonBailoutIterator(const JitActivationIterator &activations,
                                        InvalidationBailoutStack *bailout)
-  : IonFrameIterator(activations),
+  : JitFrameIterator(activations),
     machine_(bailout->machine())
 {
     returnAddressToFp_ = bailout->osiPointReturnAddress();
     topIonScript_ = bailout->ionScript();
     const OsiIndex *osiIndex = topIonScript_->getOsiIndex(returnAddressToFp_);
 
     current_ = (uint8_t*) bailout->fp();
     type_ = JitFrame_IonJS;
--- a/js/src/jit/arm/Lowering-arm.h
+++ b/js/src/jit/arm/Lowering-arm.h
@@ -30,16 +30,18 @@ class LIRGeneratorARM : public LIRGenera
     // stores and loads; on ARM all registers are okay.
     LAllocation useByteOpRegister(MDefinition *mir);
     LAllocation useByteOpRegisterOrNonDoubleConstant(MDefinition *mir);
 
     inline LDefinition tempToUnbox() {
         return LDefinition::BogusTemp();
     }
 
+    bool needTempForPostBarrier() { return false; }
+
     // x64 has a scratch register, so no need for another temp for dispatch
     // ICs.
     LDefinition tempForDispatchCache(MIRType outputType = MIRType_None) {
         return LDefinition::BogusTemp();
     }
 
     void lowerUntypedPhiInput(MPhi *phi, uint32_t inputPosition, LBlock *block, size_t lirIndex);
     bool defineUntypedPhi(MPhi *phi, size_t lirIndex);
--- a/js/src/jit/arm/MacroAssembler-arm.cpp
+++ b/js/src/jit/arm/MacroAssembler-arm.cpp
@@ -4329,8 +4329,34 @@ MacroAssemblerARMCompat::jumpWithPatch(R
     ARMBuffer::PoolEntry pe;
     BufferOffset bo = as_BranchPool(0xdeadbeef, label, &pe, cond);
     // Fill in a new CodeOffset with both the load and the
     // pool entry that the instruction loads from.
     CodeOffsetJump ret(bo.getOffset(), pe.encode());
     return ret;
 }
 
+#ifdef JSGC_GENERATIONAL
+
+void
+MacroAssemblerARMCompat::branchPtrInNurseryRange(Register ptr, Register temp, Label *label)
+{
+    JS_ASSERT(ptr != temp);
+    JS_ASSERT(ptr != ScratchRegister);
+
+    const Nursery &nursery = GetIonContext()->runtime->gcNursery();
+    movePtr(ImmWord(-ptrdiff_t(nursery.start())), ScratchRegister);
+    addPtr(ptr, ScratchRegister);
+    branchPtr(Assembler::Below, ScratchRegister, Imm32(Nursery::NurserySize), label);
+}
+
+void
+MacroAssemblerARMCompat::branchValueIsNurseryObject(ValueOperand value, Register temp, Label *label)
+{
+    Label done;
+
+    branchTestObject(Assembler::NotEqual, value, &done);
+    branchPtrInNurseryRange(value.payloadReg(), temp, label);
+
+    bind(&done);
+}
+
+#endif
--- a/js/src/jit/arm/MacroAssembler-arm.h
+++ b/js/src/jit/arm/MacroAssembler-arm.h
@@ -1554,16 +1554,21 @@ class MacroAssemblerARMCompat : public M
     }
     BufferOffset ma_BoundsCheck(Register bounded) {
         return as_cmp(bounded, Imm8(0));
     }
 
     void moveFloat32(FloatRegister src, FloatRegister dest) {
         as_vmov(VFPRegister(src).singleOverlay(), VFPRegister(dest).singleOverlay());
     }
+
+#ifdef JSGC_GENERATIONAL
+    void branchPtrInNurseryRange(Register ptr, Register temp, Label *label);
+    void branchValueIsNurseryObject(ValueOperand value, Register temp, Label *label);
+#endif
 };
 
 typedef MacroAssemblerARMCompat MacroAssemblerSpecific;
 
 } // namespace jit
 } // namespace js
 
 #endif /* jit_arm_MacroAssembler_arm_h */
--- a/js/src/jit/mips/MacroAssembler-mips.cpp
+++ b/js/src/jit/mips/MacroAssembler-mips.cpp
@@ -3240,8 +3240,26 @@ MacroAssemblerMIPSCompat::toggledCall(Ji
         as_nop();
     } else {
         as_nop();
         as_nop();
     }
     MOZ_ASSERT(nextOffset().getOffset() - offset.offset() == ToggledCallSize());
     return offset;
 }
+
+void
+MacroAssemblerMIPSCompat::branchPtrInNurseryRange(Register ptr, Register temp, Label *label)
+{
+    JS_ASSERT(temp != InvalidReg);
+    const Nursery &nursery = GetIonContext()->runtime->gcNursery();
+
+    // ptr and temp may be the same register, in which case we mustn't trash it
+    // before we use its contents.
+    if (ptr == temp) {
+        addPtr(ImmWord(-ptrdiff_t(nursery.start())), ptr);
+        branchPtr(Assembler::Below, ptr, Imm32(Nursery::NurserySize), label);
+    } else {
+        movePtr(ImmWord(-ptrdiff_t(nursery.start())), temp);
+        addPtr(ptr, temp);
+        branchPtr(Assembler::Below, temp, Imm32(Nursery::NurserySize), label);
+    }
+}
--- a/js/src/jit/mips/MacroAssembler-mips.h
+++ b/js/src/jit/mips/MacroAssembler-mips.h
@@ -1137,16 +1137,18 @@ public:
         BufferOffset bo = m_buffer.nextOffset();
         ma_liPatchable(bounded, Imm32(0));
         return bo;
     }
 
     void moveFloat32(FloatRegister src, FloatRegister dest) {
         as_movs(dest, src);
     }
+
+    void branchPtrInNurseryRange(Register ptr, Register temp, Label *label);
 };
 
 typedef MacroAssemblerMIPSCompat MacroAssemblerSpecific;
 
 } // namespace jit
 } // namespace js
 
 #endif /* jit_mips_MacroAssembler_mips_h */
--- a/js/src/jit/shared/CodeGenerator-x86-shared.cpp
+++ b/js/src/jit/shared/CodeGenerator-x86-shared.cpp
@@ -1553,20 +1553,22 @@ CodeGeneratorX86Shared::visitMathF(LMath
 
 bool
 CodeGeneratorX86Shared::visitFloor(LFloor *lir)
 {
     FloatRegister input = ToFloatRegister(lir->input());
     FloatRegister scratch = ScratchFloatReg;
     Register output = ToRegister(lir->output());
 
+    Label bailout;
+
     if (AssemblerX86Shared::HasSSE41()) {
         // Bail on negative-zero.
-        Assembler::Condition bailCond = masm.testNegativeZero(input, output);
-        if (!bailoutIf(bailCond, lir->snapshot()))
+        masm.branchNegativeZero(input, output, &bailout);
+        if (!bailoutFrom(&bailout, lir->snapshot()))
             return false;
 
         // Round toward -Infinity.
         masm.roundsd(input, scratch, JSC::X86Assembler::RoundDown);
 
         masm.cvttsd2si(scratch, output);
         masm.cmp32(output, Imm32(INT_MIN));
         if (!bailoutIf(Assembler::Equal, lir->snapshot()))
@@ -1574,18 +1576,18 @@ CodeGeneratorX86Shared::visitFloor(LFloo
     } else {
         Label negative, end;
 
         // Branch to a slow path for negative inputs. Doesn't catch NaN or -0.
         masm.xorpd(scratch, scratch);
         masm.branchDouble(Assembler::DoubleLessThan, input, scratch, &negative);
 
         // Bail on negative-zero.
-        Assembler::Condition bailCond = masm.testNegativeZero(input, output);
-        if (!bailoutIf(bailCond, lir->snapshot()))
+        masm.branchNegativeZero(input, output, &bailout);
+        if (!bailoutFrom(&bailout, lir->snapshot()))
             return false;
 
         // Input is non-negative, so truncation correctly rounds.
         masm.cvttsd2si(input, output);
         masm.cmp32(output, Imm32(INT_MIN));
         if (!bailoutIf(Assembler::Equal, lir->snapshot()))
             return false;
 
@@ -1620,20 +1622,22 @@ CodeGeneratorX86Shared::visitFloor(LFloo
 
 bool
 CodeGeneratorX86Shared::visitFloorF(LFloorF *lir)
 {
     FloatRegister input = ToFloatRegister(lir->input());
     FloatRegister scratch = ScratchFloatReg;
     Register output = ToRegister(lir->output());
 
+    Label bailout;
+
     if (AssemblerX86Shared::HasSSE41()) {
         // Bail on negative-zero.
-        Assembler::Condition bailCond = masm.testNegativeZeroFloat32(input, output);
-        if (!bailoutIf(bailCond, lir->snapshot()))
+        masm.branchNegativeZeroFloat32(input, output, &bailout);
+        if (!bailoutFrom(&bailout, lir->snapshot()))
             return false;
 
         // Round toward -Infinity.
         masm.roundss(input, scratch, JSC::X86Assembler::RoundDown);
 
         masm.cvttss2si(scratch, output);
         masm.cmp32(output, Imm32(INT_MIN));
         if (!bailoutIf(Assembler::Equal, lir->snapshot()))
@@ -1641,18 +1645,18 @@ CodeGeneratorX86Shared::visitFloorF(LFlo
     } else {
         Label negative, end;
 
         // Branch to a slow path for negative inputs. Doesn't catch NaN or -0.
         masm.xorps(scratch, scratch);
         masm.branchFloat(Assembler::DoubleLessThan, input, scratch, &negative);
 
         // Bail on negative-zero.
-        Assembler::Condition bailCond = masm.testNegativeZeroFloat32(input, output);
-        if (!bailoutIf(bailCond, lir->snapshot()))
+        masm.branchNegativeZeroFloat32(input, output, &bailout);
+        if (!bailoutFrom(&bailout, lir->snapshot()))
             return false;
 
         // Input is non-negative, so truncation correctly rounds.
         masm.cvttss2si(input, output);
         masm.cmp32(output, Imm32(INT_MIN));
         if (!bailoutIf(Assembler::Equal, lir->snapshot()))
             return false;
 
@@ -1688,28 +1692,28 @@ CodeGeneratorX86Shared::visitFloorF(LFlo
 bool
 CodeGeneratorX86Shared::visitRound(LRound *lir)
 {
     FloatRegister input = ToFloatRegister(lir->input());
     FloatRegister temp = ToFloatRegister(lir->temp());
     FloatRegister scratch = ScratchFloatReg;
     Register output = ToRegister(lir->output());
 
-    Label negative, end;
+    Label negative, end, bailout;
 
     // Load 0.5 in the temp register.
     masm.loadConstantDouble(0.5, temp);
 
     // Branch to a slow path for negative inputs. Doesn't catch NaN or -0.
     masm.xorpd(scratch, scratch);
     masm.branchDouble(Assembler::DoubleLessThan, input, scratch, &negative);
 
     // Bail on negative-zero.
-    Assembler::Condition bailCond = masm.testNegativeZero(input, output);
-    if (!bailoutIf(bailCond, lir->snapshot()))
+    masm.branchNegativeZero(input, output, &bailout);
+    if (!bailoutFrom(&bailout, lir->snapshot()))
         return false;
 
     // Input is non-negative. Add 0.5 and truncate, rounding down. Note that we
     // have to add the input to the temp register (which contains 0.5) because
     // we're not allowed to modify the input register.
     masm.addsd(input, temp);
 
     masm.cvttsd2si(temp, output);
@@ -1776,28 +1780,28 @@ CodeGeneratorX86Shared::visitRound(LRoun
 bool
 CodeGeneratorX86Shared::visitRoundF(LRoundF *lir)
 {
     FloatRegister input = ToFloatRegister(lir->input());
     FloatRegister temp = ToFloatRegister(lir->temp());
     FloatRegister scratch = ScratchFloatReg;
     Register output = ToRegister(lir->output());
 
-    Label negative, end;
+    Label negative, end, bailout;
 
     // Load 0.5 in the temp register.
     masm.loadConstantFloat32(0.5f, temp);
 
     // Branch to a slow path for negative inputs. Doesn't catch NaN or -0.
     masm.xorps(scratch, scratch);
     masm.branchFloat(Assembler::DoubleLessThan, input, scratch, &negative);
 
     // Bail on negative-zero.
-    Assembler::Condition bailCond = masm.testNegativeZeroFloat32(input, output);
-    if (!bailoutIf(bailCond, lir->snapshot()))
+    masm.branchNegativeZeroFloat32(input, output, &bailout);
+    if (!bailoutFrom(&bailout, lir->snapshot()))
         return false;
 
     // Input is non-negative. Add 0.5 and truncate, rounding down. Note that we
     // have to add the input to the temp register (which contains 0.5) because
     // we're not allowed to modify the input register.
     masm.addss(input, temp);
 
     masm.cvttss2si(temp, output);
--- a/js/src/jit/shared/MacroAssembler-x86-shared.cpp
+++ b/js/src/jit/shared/MacroAssembler-x86-shared.cpp
@@ -149,8 +149,50 @@ MacroAssemblerX86Shared::callWithExitFra
 bool
 MacroAssemblerX86Shared::buildOOLFakeExitFrame(void *fakeReturnAddr)
 {
     uint32_t descriptor = MakeFrameDescriptor(framePushed(), JitFrame_IonJS);
     Push(Imm32(descriptor));
     Push(ImmPtr(fakeReturnAddr));
     return true;
 }
+
+void
+MacroAssemblerX86Shared::branchNegativeZero(const FloatRegister &reg,
+                                            const Register &scratch,
+                                            Label *label)
+{
+    // Determines whether the low double contained in the XMM register reg
+    // is equal to -0.0.
+
+#if defined(JS_CODEGEN_X86)
+    Label nonZero;
+
+    // Compare to zero. Lets through {0, -0}.
+    xorpd(ScratchFloatReg, ScratchFloatReg);
+
+    // If reg is non-zero, jump to nonZero.
+    branchDouble(DoubleNotEqual, reg, ScratchFloatReg, &nonZero);
+
+    // Input register is either zero or negative zero. Retrieve sign of input.
+    movmskpd(reg, scratch);
+
+    // If reg is 1 or 3, input is negative zero.
+    // If reg is 0 or 2, input is a normal zero.
+    branchTest32(NonZero, scratch, Imm32(1), label);
+
+    bind(&nonZero);
+#elif defined(JS_CODEGEN_X64)
+    movq(reg, scratch);
+    cmpq(scratch, Imm32(1));
+    j(Overflow, label);
+#endif
+}
+
+void
+MacroAssemblerX86Shared::branchNegativeZeroFloat32(const FloatRegister &reg,
+                                                   const Register &scratch,
+                                                   Label *label)
+{
+    movd(reg, scratch);
+    cmpl(scratch, Imm32(1));
+    j(Overflow, label);
+}
--- a/js/src/jit/shared/MacroAssembler-x86-shared.h
+++ b/js/src/jit/shared/MacroAssembler-x86-shared.h
@@ -85,16 +85,19 @@ class MacroAssemblerX86Shared : public A
             j(Parity, label);
             return;
         }
 
         JS_ASSERT(!(cond & DoubleConditionBitSpecial));
         j(ConditionFromDoubleCondition(cond), label);
     }
 
+    void branchNegativeZero(const FloatRegister &reg, const Register &scratch, Label *label);
+    void branchNegativeZeroFloat32(const FloatRegister &reg, const Register &scratch, Label *label);
+
     void move32(const Imm32 &imm, const Register &dest) {
         // Use the ImmWord version of mov to register, which has special
         // optimizations. Casting to uint32_t here ensures that the value
         // is zero-extended.
         mov(ImmWord(uint32_t(imm.value)), dest);
     }
     void move32(const Imm32 &imm, const Operand &dest) {
         movl(imm, dest);
@@ -514,73 +517,43 @@ class MacroAssemblerX86Shared : public A
     }
 
     // Checks whether a double is representable as a 32-bit integer. If so, the
     // integer is written to the output register. Otherwise, a bailout is taken to
     // the given snapshot. This function overwrites the scratch float register.
     void convertDoubleToInt32(FloatRegister src, Register dest, Label *fail,
                               bool negativeZeroCheck = true)
     {
+        // Check for -0.0
+        if (negativeZeroCheck)
+            branchNegativeZero(src, dest, fail);
+
         cvttsd2si(src, dest);
         cvtsi2sd(dest, ScratchFloatReg);
         ucomisd(src, ScratchFloatReg);
         j(Assembler::Parity, fail);
         j(Assembler::NotEqual, fail);
 
-        // Check for -0
-        if (negativeZeroCheck) {
-            Label notZero;
-            testl(dest, dest);
-            j(Assembler::NonZero, &notZero);
-
-            if (Assembler::HasSSE41()) {
-                ptest(src, src);
-                j(Assembler::NonZero, fail);
-            } else {
-                // bit 0 = sign of low double
-                // bit 1 = sign of high double
-                movmskpd(src, dest);
-                andl(Imm32(1), dest);
-                j(Assembler::NonZero, fail);
-            }
-
-            bind(&notZero);
-        }
     }
 
     // Checks whether a float32 is representable as a 32-bit integer. If so, the
     // integer is written to the output register. Otherwise, a bailout is taken to
     // the given snapshot. This function overwrites the scratch float register.
     void convertFloat32ToInt32(FloatRegister src, Register dest, Label *fail,
                                bool negativeZeroCheck = true)
     {
+        // Check for -0.0
+        if (negativeZeroCheck)
+            branchNegativeZeroFloat32(src, dest, fail);
+
         cvttss2si(src, dest);
         convertInt32ToFloat32(dest, ScratchFloatReg);
         ucomiss(src, ScratchFloatReg);
         j(Assembler::Parity, fail);
         j(Assembler::NotEqual, fail);
-
-        // Check for -0
-        if (negativeZeroCheck) {
-            Label notZero;
-            branchTest32(Assembler::NonZero, dest, dest, &notZero);
-
-            if (Assembler::HasSSE41()) {
-                ptest(src, src);
-                j(Assembler::NonZero, fail);
-            } else {
-                // bit 0 = sign of low float
-                // bits 1 to 3 = signs of higher floats
-                movmskps(src, dest);
-                andl(Imm32(1), dest);
-                j(Assembler::NonZero, fail);
-            }
-
-            bind(&notZero);
-        }
     }
 
     void clampIntToUint8(Register reg) {
         Label inRange;
         branchTest32(Assembler::Zero, reg, Imm32(0xffffff00), &inRange);
         {
             sarl(Imm32(31), reg);
             notl(reg);
--- a/js/src/jit/x64/Bailouts-x64.cpp
+++ b/js/src/jit/x64/Bailouts-x64.cpp
@@ -42,32 +42,32 @@ class BailoutStack
 } // namespace js
 
 #if defined(_WIN32)
 # pragma pack(pop)
 #endif
 
 IonBailoutIterator::IonBailoutIterator(const JitActivationIterator &activations,
                                        BailoutStack *bailout)
-  : IonFrameIterator(activations),
+  : JitFrameIterator(activations),
     machine_(bailout->machineState())
 {
     uint8_t *sp = bailout->parentStackPointer();
     uint8_t *fp = sp + bailout->frameSize();
 
     current_ = fp;
     type_ = JitFrame_IonJS;
     topFrameSize_ = current_ - sp;
     topIonScript_ = script()->ionScript();
     snapshotOffset_ = bailout->snapshotOffset();
 }
 
 IonBailoutIterator::IonBailoutIterator(const JitActivationIterator &activations,
                                        InvalidationBailoutStack *bailout)
-  : IonFrameIterator(activations),
+  : JitFrameIterator(activations),
     machine_(bailout->machine())
 {
     returnAddressToFp_ = bailout->osiPointReturnAddress();
     topIonScript_ = bailout->ionScript();
     const OsiIndex *osiIndex = topIonScript_->getOsiIndex(returnAddressToFp_);
 
     current_ = (uint8_t*) bailout->fp();
     type_ = JitFrame_IonJS;
--- a/js/src/jit/x64/Lowering-x64.h
+++ b/js/src/jit/x64/Lowering-x64.h
@@ -30,16 +30,18 @@ class LIRGeneratorX64 : public LIRGenera
 
     // x86 has constraints on what registers can be formatted for 1-byte
     // stores and loads; on x64 all registers are okay.
     LAllocation useByteOpRegister(MDefinition *mir);
     LAllocation useByteOpRegisterOrNonDoubleConstant(MDefinition *mir);
 
     LDefinition tempToUnbox();
 
+    bool needTempForPostBarrier() { return false; }
+
     // x64 has a scratch register, so no need for another temp for dispatch
     // ICs.
     LDefinition tempForDispatchCache(MIRType outputType = MIRType_None) {
         return LDefinition::BogusTemp();
     }
 
   public:
     bool visitBox(MBox *box);
--- a/js/src/jit/x64/MacroAssembler-x64.cpp
+++ b/js/src/jit/x64/MacroAssembler-x64.cpp
@@ -362,23 +362,35 @@ MacroAssemblerX64::handleFailureWithHand
     // If we are bailing out to baseline to handle an exception, jump to
     // the bailout tail stub.
     bind(&bailout);
     loadPtr(Address(esp, offsetof(ResumeFromException, bailoutInfo)), r9);
     mov(ImmWord(BAILOUT_RETURN_OK), rax);
     jmp(Operand(rsp, offsetof(ResumeFromException, target)));
 }
 
-Assembler::Condition
-MacroAssemblerX64::testNegativeZero(const FloatRegister &reg, const Register &scratch)
+#ifdef JSGC_GENERATIONAL
+
+void
+MacroAssemblerX64::branchPtrInNurseryRange(Register ptr, Register temp, Label *label)
 {
-    movq(reg, scratch);
-    cmpq(scratch, Imm32(1));
-    return Overflow;
+    JS_ASSERT(ptr != temp);
+    JS_ASSERT(ptr != ScratchReg);
+
+    const Nursery &nursery = GetIonContext()->runtime->gcNursery();
+    movePtr(ImmWord(-ptrdiff_t(nursery.start())), ScratchReg);
+    addPtr(ptr, ScratchReg);
+    branchPtr(Assembler::Below, ScratchReg, Imm32(Nursery::NurserySize), label);
 }
 
-Assembler::Condition
-MacroAssemblerX64::testNegativeZeroFloat32(const FloatRegister &reg, const Register &scratch)
+void
+MacroAssemblerX64::branchValueIsNurseryObject(ValueOperand value, Register temp, Label *label)
 {
-    movd(reg, scratch);
-    cmpl(scratch, Imm32(1));
-    return Overflow;
+    // 'Value' representing the start of the nursery tagged as a JSObject
+    const Nursery &nursery = GetIonContext()->runtime->gcNursery();
+    Value start = ObjectValue(*reinterpret_cast<JSObject *>(nursery.start()));
+
+    movePtr(ImmWord(-ptrdiff_t(start.asRawBits())), ScratchReg);
+    addPtr(value.valueReg(), ScratchReg);
+    branchPtr(Assembler::Below, ScratchReg, Imm32(Nursery::NurserySize), label);
 }
+
+#endif
--- a/js/src/jit/x64/MacroAssembler-x64.h
+++ b/js/src/jit/x64/MacroAssembler-x64.h
@@ -502,19 +502,16 @@ class MacroAssemblerX64 : public MacroAs
 
     template <typename T1, typename T2>
     void cmpPtrSet(Assembler::Condition cond, T1 lhs, T2 rhs, const Register &dest)
     {
         cmpPtr(lhs, rhs);
         emitSet(cond, dest);
     }
 
-    Condition testNegativeZero(const FloatRegister &reg, const Register &scratch);
-    Condition testNegativeZeroFloat32(const FloatRegister &reg, const Register &scratch);
-
     /////////////////////////////////////////////////////////////////
     // Common interface.
     /////////////////////////////////////////////////////////////////
     void reserveStack(uint32_t amount) {
         if (amount)
             subq(Imm32(amount), StackPointer);
         framePushed_ += amount;
     }
@@ -1320,16 +1317,20 @@ class MacroAssemblerX64 : public MacroAs
         uint8_t *target = globalData + globalDataOffset;
         ((int32_t *)nextInsn)[-1] = target - nextInsn;
     }
     void memIntToValue(Address Source, Address Dest) {
         load32(Source, ScratchReg);
         storeValue(JSVAL_TYPE_INT32, ScratchReg, Dest);
     }
 
+#ifdef JSGC_GENERATIONAL
+    void branchPtrInNurseryRange(Register ptr, Register temp, Label *label);
+    void branchValueIsNurseryObject(ValueOperand value, Register temp, Label *label);
+#endif
 };
 
 typedef MacroAssemblerX64 MacroAssemblerSpecific;
 
 } // namespace jit
 } // namespace js
 
 #endif /* jit_x64_MacroAssembler_x64_h */
--- a/js/src/jit/x86/Bailouts-x86.cpp
+++ b/js/src/jit/x86/Bailouts-x86.cpp
@@ -62,17 +62,17 @@ class BailoutStack
 } // namespace js
 
 #if defined(_WIN32)
 # pragma pack(pop)
 #endif
 
 IonBailoutIterator::IonBailoutIterator(const JitActivationIterator &activations,
                                        BailoutStack *bailout)
-  : IonFrameIterator(activations),
+  : JitFrameIterator(activations),
     machine_(bailout->machine())
 {
     uint8_t *sp = bailout->parentStackPointer();
     uint8_t *fp = sp + bailout->frameSize();
 
     current_ = fp;
     type_ = JitFrame_IonJS;
     topFrameSize_ = current_ - sp;
@@ -97,17 +97,17 @@ IonBailoutIterator::IonBailoutIterator(c
     uint32_t bailoutId = ((tableOffset - tableStart) / BAILOUT_TABLE_ENTRY_SIZE) - 1;
     JS_ASSERT(bailoutId < BAILOUT_TABLE_SIZE);
 
     snapshotOffset_ = topIonScript_->bailoutToSnapshot(bailoutId);
 }
 
 IonBailoutIterator::IonBailoutIterator(const JitActivationIterator &activations,
                                        InvalidationBailoutStack *bailout)
-  : IonFrameIterator(activations),
+  : JitFrameIterator(activations),
     machine_(bailout->machine())
 {
     returnAddressToFp_ = bailout->osiPointReturnAddress();
     topIonScript_ = bailout->ionScript();
     const OsiIndex *osiIndex = topIonScript_->getOsiIndex(returnAddressToFp_);
 
     current_ = (uint8_t*) bailout->fp();
     type_ = JitFrame_IonJS;
--- a/js/src/jit/x86/Lowering-x86.h
+++ b/js/src/jit/x86/Lowering-x86.h
@@ -33,16 +33,18 @@ class LIRGeneratorX86 : public LIRGenera
     // give us one of {al,bl,cl,dl}. For now, just useFixed(al).
     LAllocation useByteOpRegister(MDefinition *mir);
     LAllocation useByteOpRegisterOrNonDoubleConstant(MDefinition *mir);
 
     inline LDefinition tempToUnbox() {
         return LDefinition::BogusTemp();
     }
 
+    bool needTempForPostBarrier() { return true; }
+
     LDefinition tempForDispatchCache(MIRType outputType = MIRType_None);
 
     void lowerUntypedPhiInput(MPhi *phi, uint32_t inputPosition, LBlock *block, size_t lirIndex);
     bool defineUntypedPhi(MPhi *phi, size_t lirIndex);
 
   public:
     bool visitBox(MBox *box);
     bool visitUnbox(MUnbox *unbox);
--- a/js/src/jit/x86/MacroAssembler-x86.cpp
+++ b/js/src/jit/x86/MacroAssembler-x86.cpp
@@ -382,48 +382,34 @@ MacroAssemblerX86::branchTestValue(Condi
         JS_ASSERT(cond == NotEqual);
         j(NotEqual, label);
 
         cmpl(value.typeReg(), Imm32(jv.s.tag));
         j(NotEqual, label);
     }
 }
 
-Assembler::Condition
-MacroAssemblerX86::testNegativeZero(const FloatRegister &reg, const Register &scratch)
-{
-    // Determines whether the single double contained in the XMM register reg
-    // is equal to double-precision -0.
-
-    Label nonZero;
-
-    // Compare to zero. Lets through {0, -0}.
-    xorpd(ScratchFloatReg, ScratchFloatReg);
-
-    // If reg is non-zero, jump to nonZero.
-    // Sets ZF=0 and PF=0.
-    branchDouble(DoubleNotEqual, reg, ScratchFloatReg, &nonZero);
+#ifdef JSGC_GENERATIONAL
 
-    // Input register is either zero or negative zero. Retrieve sign of input.
-    movmskpd(reg, scratch);
-
-    // If reg is 1 or 3, input is negative zero.
-    // If reg is 0 or 2, input is a normal zero.
-    // So the following test will set PF=1 for negative zero.
-    orl(Imm32(2), scratch);
+void
+MacroAssemblerX86::branchPtrInNurseryRange(Register ptr, Register temp, Label *label)
+{
+    JS_ASSERT(ptr != temp);
+    JS_ASSERT(temp != InvalidReg);  // A temp register is required for x86.
 
-    bind(&nonZero);
-
-    // Here we need to be able to test if the input is a negative zero.
-    // - branchDouble joins here for non-zero values in which case it sets
-    //   ZF=0 and PF=0. In that case the test should fail.
-    // - orl sets PF=1 on negative zero and PF=0 otherwise
-    // => So testing PF=1 will return if input is negative zero or not.
-    return Parity;
+    const Nursery &nursery = GetIonContext()->runtime->gcNursery();
+    movePtr(ImmWord(-ptrdiff_t(nursery.start())), temp);
+    addPtr(ptr, temp);
+    branchPtr(Assembler::Below, temp, Imm32(Nursery::NurserySize), label);
 }
 
-Assembler::Condition
-MacroAssemblerX86::testNegativeZeroFloat32(const FloatRegister &reg, const Register &scratch)
+void
+MacroAssemblerX86::branchValueIsNurseryObject(ValueOperand value, Register temp, Label *label)
 {
-    movd(reg, scratch);
-    cmpl(scratch, Imm32(1));
-    return Overflow;
+    Label done;
+
+    branchTestObject(Assembler::NotEqual, value, &done);
+    branchPtrInNurseryRange(value.payloadReg(), temp, label);
+
+    bind(&done);
 }
+
+#endif
--- a/js/src/jit/x86/MacroAssembler-x86.h
+++ b/js/src/jit/x86/MacroAssembler-x86.h
@@ -524,19 +524,16 @@ class MacroAssemblerX86 : public MacroAs
 
     template <typename T1, typename T2>
     void cmpPtrSet(Assembler::Condition cond, T1 lhs, T2 rhs, const Register &dest)
     {
         cmpPtr(lhs, rhs);
         emitSet(cond, dest);
     }
 
-    Condition testNegativeZero(const FloatRegister &reg, const Register &scratch);
-    Condition testNegativeZeroFloat32(const FloatRegister &reg, const Register &scratch);
-
     /////////////////////////////////////////////////////////////////
     // Common interface.
     /////////////////////////////////////////////////////////////////
     void reserveStack(uint32_t amount) {
         if (amount)
             subl(Imm32(amount), StackPointer);
         framePushed_ += amount;
     }
@@ -1109,16 +1106,21 @@ class MacroAssemblerX86 : public MacroAs
         call(target);
     }
 
     // Save an exit frame to the thread data of the current thread, given a
     // register that holds a PerThreadData *.
     void linkParallelExitFrame(const Register &pt) {
         movl(StackPointer, Operand(pt, offsetof(PerThreadData, ionTop)));
     }
+
+#ifdef JSGC_GENERATIONAL
+    void branchPtrInNurseryRange(Register ptr, Register temp, Label *label);
+    void branchValueIsNurseryObject(ValueOperand value, Register temp, Label *label);
+#endif
 };
 
 typedef MacroAssemblerX86 MacroAssemblerSpecific;
 
 } // namespace jit
 } // namespace js
 
 #endif /* jit_x86_MacroAssembler_x86_h */
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -28,17 +28,17 @@
 
 #include "builtin/Eval.h"
 #include "builtin/Object.h"
 #include "frontend/BytecodeCompiler.h"
 #include "frontend/TokenStream.h"
 #include "gc/Marking.h"
 #ifdef JS_ION
 #include "jit/Ion.h"
-#include "jit/IonFrameIterator.h"
+#include "jit/JitFrameIterator.h"
 #endif
 #include "vm/Interpreter.h"
 #include "vm/Shape.h"
 #include "vm/StringBuffer.h"
 #include "vm/WrapperObject.h"
 #include "vm/Xdr.h"
 
 #include "jsscriptinlines.h"
--- a/js/src/jsfun.h
+++ b/js/src/jsfun.h
@@ -539,18 +539,25 @@ bool IsConstructor(const Value &v);
  * Most functions do not have these extensions, but enough do that efficient
  * storage is required (no malloc'ed reserved slots).
  */
 class FunctionExtended : public JSFunction
 {
   public:
     static const unsigned NUM_EXTENDED_SLOTS = 2;
 
+    /* Arrow functions store their lexical |this| in the first extended slot. */
+    static const unsigned ARROW_THIS_SLOT = 0;
+
+    static inline size_t offsetOfExtendedSlot(unsigned which) {
+        MOZ_ASSERT(which < NUM_EXTENDED_SLOTS);
+        return offsetof(FunctionExtended, extendedSlots) + which * sizeof(HeapValue);
+    }
     static inline size_t offsetOfArrowThisSlot() {
-        return offsetof(FunctionExtended, extendedSlots) + 0 * sizeof(HeapValue);
+        return offsetOfExtendedSlot(ARROW_THIS_SLOT);
     }
 
   private:
     friend class JSFunction;
 
     /* Reserved slots available for storage by particular native functions. */
     HeapValue extendedSlots[NUM_EXTENDED_SLOTS];
 };
--- a/js/src/vm/Stack-inl.h
+++ b/js/src/vm/Stack-inl.h
@@ -333,21 +333,21 @@ FrameIter::unaliasedForEachActual(JSCont
       case DONE:
       case ASMJS:
         break;
       case INTERP:
         interpFrame()->unaliasedForEachActual(op);
         return;
       case JIT:
 #ifdef JS_ION
-        if (data_.ionFrames_.isIonJS()) {
+        if (data_.jitFrames_.isIonJS()) {
             ionInlineFrames_.unaliasedForEachActual(cx, op, jit::ReadFrame_Actuals);
         } else {
-            JS_ASSERT(data_.ionFrames_.isBaselineJS());
-            data_.ionFrames_.unaliasedForEachActual(op, jit::ReadFrame_Actuals);
+            JS_ASSERT(data_.jitFrames_.isBaselineJS());
+            data_.jitFrames_.unaliasedForEachActual(op, jit::ReadFrame_Actuals);
         }
         return;
 #else
         break;
 #endif
     }
     MOZ_ASSUME_UNREACHABLE("Unexpected state");
 }
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -13,17 +13,17 @@
 #include "gc/Marking.h"
 #ifdef JS_ION
 #include "jit/AsmJSModule.h"
 #include "jit/BaselineFrame.h"
 #include "jit/JitCompartment.h"
 #endif
 #include "vm/Opcodes.h"
 
-#include "jit/IonFrameIterator-inl.h"
+#include "jit/JitFrameIterator-inl.h"
 #include "vm/Interpreter-inl.h"
 #include "vm/Probes-inl.h"
 #include "vm/ScopeObject-inl.h"
 
 using namespace js;
 
 using mozilla::PodCopy;
 
@@ -563,25 +563,25 @@ FrameIter::settleOnActivation()
                     ++data_.activations_;
                     continue;
                 }
             }
         }
 
 #ifdef JS_ION
         if (activation->isJit()) {
-            data_.ionFrames_ = jit::IonFrameIterator(data_.activations_);
+            data_.jitFrames_ = jit::JitFrameIterator(data_.activations_);
 
             // Stop at the first scripted frame.
-            while (!data_.ionFrames_.isScripted() && !data_.ionFrames_.done())
-                ++data_.ionFrames_;
+            while (!data_.jitFrames_.isScripted() && !data_.jitFrames_.done())
+                ++data_.jitFrames_;
 
             // It's possible to have an JitActivation with no scripted frames,
             // for instance if we hit an over-recursion during bailout.
-            if (data_.ionFrames_.done()) {
+            if (data_.jitFrames_.done()) {
                 ++data_.activations_;
                 continue;
             }
 
             nextJitFrame();
             data_.state_ = JIT;
             return;
         }
@@ -627,102 +627,102 @@ FrameIter::Data::Data(JSContext *cx, Sav
   : cx_(cx),
     savedOption_(savedOption),
     contextOption_(contextOption),
     principals_(principals),
     pc_(nullptr),
     interpFrames_(nullptr),
     activations_(cx->runtime())
 #ifdef JS_ION
-  , ionFrames_((uint8_t *)nullptr, SequentialExecution)
+  , jitFrames_((uint8_t *)nullptr, SequentialExecution)
 #endif
 {
 }
 
 FrameIter::Data::Data(const FrameIter::Data &other)
   : cx_(other.cx_),
     savedOption_(other.savedOption_),
     contextOption_(other.contextOption_),
     principals_(other.principals_),
     state_(other.state_),
     pc_(other.pc_),
     interpFrames_(other.interpFrames_),
     activations_(other.activations_)
 #ifdef JS_ION
-  , ionFrames_(other.ionFrames_)
+  , jitFrames_(other.jitFrames_)
 #endif
 {
 }
 
 FrameIter::FrameIter(JSContext *cx, SavedOption savedOption)
   : data_(cx, savedOption, CURRENT_CONTEXT, nullptr)
 #ifdef JS_ION
-  , ionInlineFrames_(cx, (js::jit::IonFrameIterator*) nullptr)
+  , ionInlineFrames_(cx, (js::jit::JitFrameIterator*) nullptr)
 #endif
 {
     settleOnActivation();
 }
 
 FrameIter::FrameIter(JSContext *cx, ContextOption contextOption,
                      SavedOption savedOption, JSPrincipals *principals)
   : data_(cx, savedOption, contextOption, principals)
 #ifdef JS_ION
-  , ionInlineFrames_(cx, (js::jit::IonFrameIterator*) nullptr)
+  , ionInlineFrames_(cx, (js::jit::JitFrameIterator*) nullptr)
 #endif
 {
     settleOnActivation();
 }
 
 FrameIter::FrameIter(const FrameIter &other)
   : data_(other.data_)
 #ifdef JS_ION
   , ionInlineFrames_(other.data_.cx_,
-                     data_.ionFrames_.isScripted() ? &other.ionInlineFrames_ : nullptr)
+                     data_.jitFrames_.isScripted() ? &other.ionInlineFrames_ : nullptr)
 #endif
 {
 }
 
 FrameIter::FrameIter(const Data &data)
   : data_(data)
 #ifdef JS_ION
-  , ionInlineFrames_(data.cx_, data_.ionFrames_.isIonJS() ? &data_.ionFrames_ : nullptr)
+  , ionInlineFrames_(data.cx_, data_.jitFrames_.isIonJS() ? &data_.jitFrames_ : nullptr)
 #endif
 {
     JS_ASSERT(data.cx_);
 }
 
 #ifdef JS_ION
 void
 FrameIter::nextJitFrame()
 {
-    if (data_.ionFrames_.isIonJS()) {
-        ionInlineFrames_.resetOn(&data_.ionFrames_);
+    if (data_.jitFrames_.isIonJS()) {
+        ionInlineFrames_.resetOn(&data_.jitFrames_);
         data_.pc_ = ionInlineFrames_.pc();
     } else {
-        JS_ASSERT(data_.ionFrames_.isBaselineJS());
-        data_.ionFrames_.baselineScriptAndPc(nullptr, &data_.pc_);
+        JS_ASSERT(data_.jitFrames_.isBaselineJS());
+        data_.jitFrames_.baselineScriptAndPc(nullptr, &data_.pc_);
     }
 }
 
 void
 FrameIter::popJitFrame()
 {
     JS_ASSERT(data_.state_ == JIT);
 
-    if (data_.ionFrames_.isIonJS() && ionInlineFrames_.more()) {
+    if (data_.jitFrames_.isIonJS() && ionInlineFrames_.more()) {
         ++ionInlineFrames_;
         data_.pc_ = ionInlineFrames_.pc();
         return;
     }
 
-    ++data_.ionFrames_;
-    while (!data_.ionFrames_.done() && !data_.ionFrames_.isScripted())
-        ++data_.ionFrames_;
+    ++data_.jitFrames_;
+    while (!data_.jitFrames_.done() && !data_.jitFrames_.isScripted())
+        ++data_.jitFrames_;
 
-    if (!data_.ionFrames_.done()) {
+    if (!data_.jitFrames_.done()) {
         nextJitFrame();
         return;
     }
 
     popActivation();
 }
 #endif
 
@@ -786,17 +786,17 @@ FrameIter::Data *
 FrameIter::copyData() const
 {
 #ifdef JS_ION
     /*
      * This doesn't work for optimized Ion frames since ionInlineFrames_ is
      * not copied.
      */
     JS_ASSERT(data_.state_ != ASMJS);
-    JS_ASSERT(data_.ionFrames_.type() != jit::JitFrame_IonJS);
+    JS_ASSERT(data_.jitFrames_.type() != jit::JitFrame_IonJS);
 #endif
     return data_.cx_->new_<Data>(data_);
 }
 
 AbstractFramePtr
 FrameIter::copyDataAsAbstractFramePtr() const
 {
     AbstractFramePtr frame;
@@ -824,19 +824,19 @@ FrameIter::isFunctionFrame() const
 {
     switch (data_.state_) {
       case DONE:
         break;
       case INTERP:
         return interpFrame()->isFunctionFrame();
       case JIT:
 #ifdef JS_ION
-        JS_ASSERT(data_.ionFrames_.isScripted());
-        if (data_.ionFrames_.isBaselineJS())
-            return data_.ionFrames_.isFunctionFrame();
+        JS_ASSERT(data_.jitFrames_.isScripted());
+        if (data_.jitFrames_.isBaselineJS())
+            return data_.jitFrames_.isFunctionFrame();
         return ionInlineFrames_.isFunctionFrame();
 #else
         break;
 #endif
       case ASMJS:
         return true;
     }
     MOZ_ASSUME_UNREACHABLE("Unexpected state");
@@ -847,18 +847,18 @@ FrameIter::isGlobalFrame() const
 {
     switch (data_.state_) {
       case DONE:
         break;
       case INTERP:
         return interpFrame()->isGlobalFrame();
       case JIT:
 #ifdef JS_ION
-        if (data_.ionFrames_.isBaselineJS())
-            return data_.ionFrames_.baselineFrame()->isGlobalFrame();
+        if (data_.jitFrames_.isBaselineJS())
+            return data_.jitFrames_.baselineFrame()->isGlobalFrame();
         JS_ASSERT(!script()->isForEval());
         return !script()->functionNonDelazifying();
 #else
         break;
 #endif
       case ASMJS:
         return false;
     }
@@ -870,18 +870,18 @@ FrameIter::isEvalFrame() const
 {
     switch (data_.state_) {
       case DONE:
         break;
       case INTERP:
         return interpFrame()->isEvalFrame();
       case JIT:
 #ifdef JS_ION
-        if (data_.ionFrames_.isBaselineJS())
-            return data_.ionFrames_.baselineFrame()->isEvalFrame();
+        if (data_.jitFrames_.isBaselineJS())
+            return data_.jitFrames_.baselineFrame()->isEvalFrame();
         JS_ASSERT(!script()->isForEval());
         return false;
 #else
         break;
 #endif
       case ASMJS:
         return false;
     }
@@ -1035,20 +1035,20 @@ bool
 FrameIter::isConstructing() const
 {
     switch (data_.state_) {
       case DONE:
       case ASMJS:
         break;
       case JIT:
 #ifdef JS_ION
-        if (data_.ionFrames_.isIonJS())
+        if (data_.jitFrames_.isIonJS())
             return ionInlineFrames_.isConstructing();
-        JS_ASSERT(data_.ionFrames_.isBaselineJS());
-        return data_.ionFrames_.isConstructing();
+        JS_ASSERT(data_.jitFrames_.isBaselineJS());
+        return data_.jitFrames_.isConstructing();
 #else
         break;
 #endif
       case INTERP:
         return interpFrame()->isConstructing();
     }
     MOZ_ASSUME_UNREACHABLE("Unexpected state");
 }
@@ -1057,18 +1057,18 @@ AbstractFramePtr
 FrameIter::abstractFramePtr() const
 {
     switch (data_.state_) {
       case DONE:
       case ASMJS:
         break;
       case JIT:
 #ifdef JS_ION
-        if (data_.ionFrames_.isBaselineJS())
-            return data_.ionFrames_.baselineFrame();
+        if (data_.jitFrames_.isBaselineJS())
+            return data_.jitFrames_.baselineFrame();
 #endif
         break;
       case INTERP:
         JS_ASSERT(interpFrame());
         return AbstractFramePtr(interpFrame());
     }
     MOZ_ASSUME_UNREACHABLE("Unexpected state");
 }
@@ -1091,34 +1091,34 @@ FrameIter::updatePcQuadratic()
 
         // Update the pc.
         JS_ASSERT(data_.interpFrames_.frame() == frame);
         data_.pc_ = data_.interpFrames_.pc();
         return;
       }
       case JIT:
 #ifdef JS_ION
-        if (data_.ionFrames_.isBaselineJS()) {
-            jit::BaselineFrame *frame = data_.ionFrames_.baselineFrame();
+        if (data_.jitFrames_.isBaselineJS()) {
+            jit::BaselineFrame *frame = data_.jitFrames_.baselineFrame();
             jit::JitActivation *activation = data_.activations_.activation()->asJit();
 
             // ActivationIterator::ionTop_ may be invalid, so create a new
             // activation iterator.
             data_.activations_ = ActivationIterator(data_.cx_->runtime());
             while (data_.activations_.activation() != activation)
                 ++data_.activations_;
 
             // Look for the current frame.
-            data_.ionFrames_ = jit::IonFrameIterator(data_.activations_);
-            while (!data_.ionFrames_.isBaselineJS() || data_.ionFrames_.baselineFrame() != frame)
-                ++data_.ionFrames_;
+            data_.jitFrames_ = jit::JitFrameIterator(data_.activations_);
+            while (!data_.jitFrames_.isBaselineJS() || data_.jitFrames_.baselineFrame() != frame)
+                ++data_.jitFrames_;
 
             // Update the pc.
-            JS_ASSERT(data_.ionFrames_.baselineFrame() == frame);
-            data_.ionFrames_.baselineScriptAndPc(nullptr, &data_.pc_);
+            JS_ASSERT(data_.jitFrames_.baselineFrame() == frame);
+            data_.jitFrames_.baselineScriptAndPc(nullptr, &data_.pc_);
             return;
         }
 #endif
         break;
     }
     MOZ_ASSUME_UNREACHABLE("Unexpected state");
 }
 
@@ -1129,19 +1129,19 @@ FrameIter::callee() const
       case DONE:
       case ASMJS:
         break;
       case INTERP:
         JS_ASSERT(isFunctionFrame());
         return &interpFrame()->callee();
       case JIT:
 #ifdef JS_ION
-        if (data_.ionFrames_.isBaselineJS())
-            return data_.ionFrames_.callee();
-        JS_ASSERT(data_.ionFrames_.isIonJS());
+        if (data_.jitFrames_.isBaselineJS())
+            return data_.jitFrames_.callee();
+        JS_ASSERT(data_.jitFrames_.isIonJS());
         return ionInlineFrames_.callee();
 #else
         break;
 #endif
     }
     MOZ_ASSUME_UNREACHABLE("Unexpected state");
 }
 
@@ -1172,21 +1172,21 @@ FrameIter::numActualArgs() const
       case DONE:
       case ASMJS:
         break;
       case INTERP:
         JS_ASSERT(isFunctionFrame());
         return interpFrame()->numActualArgs();
       case JIT:
 #ifdef JS_ION
-        if (data_.ionFrames_.isIonJS())
+        if (data_.jitFrames_.isIonJS())
             return ionInlineFrames_.numActualArgs();
 
-        JS_ASSERT(data_.ionFrames_.isBaselineJS());
-        return data_.ionFrames_.numActualArgs();
+        JS_ASSERT(data_.jitFrames_.isBaselineJS());
+        return data_.jitFrames_.numActualArgs();
 #else
         break;
 #endif
     }
     MOZ_ASSUME_UNREACHABLE("Unexpected state");
 }
 
 unsigned
@@ -1201,37 +1201,37 @@ FrameIter::unaliasedActual(unsigned i, M
     switch (data_.state_) {
       case DONE:
       case ASMJS:
         break;
       case INTERP:
         return interpFrame()->unaliasedActual(i, checkAliasing);
       case JIT:
 #ifdef JS_ION
-        JS_ASSERT(data_.ionFrames_.isBaselineJS());
-        return data_.ionFrames_.baselineFrame()->unaliasedActual(i, checkAliasing);
+        JS_ASSERT(data_.jitFrames_.isBaselineJS());
+        return data_.jitFrames_.baselineFrame()->unaliasedActual(i, checkAliasing);
 #else
         break;
 #endif
     }
     MOZ_ASSUME_UNREACHABLE("Unexpected state");
 }
 
 JSObject *
 FrameIter::scopeChain() const
 {
     switch (data_.state_) {
       case DONE:
       case ASMJS:
         break;
       case JIT:
 #ifdef JS_ION
-        if (data_.ionFrames_.isIonJS())
+        if (data_.jitFrames_.isIonJS())
             return ionInlineFrames_.scopeChain();
-        return data_.ionFrames_.baselineFrame()->scopeChain();
+        return data_.jitFrames_.baselineFrame()->scopeChain();
 #else
         break;
 #endif
       case INTERP:
         return interpFrame()->scopeChain();
     }
     MOZ_ASSUME_UNREACHABLE("Unexpected state");
 }
@@ -1253,18 +1253,18 @@ FrameIter::hasArgsObj() const
     switch (data_.state_) {
       case DONE:
       case ASMJS:
         break;
       case INTERP:
         return interpFrame()->hasArgsObj();
       case JIT:
 #ifdef JS_ION
-        JS_ASSERT(data_.ionFrames_.isBaselineJS());
-        return data_.ionFrames_.baselineFrame()->hasArgsObj();
+        JS_ASSERT(data_.jitFrames_.isBaselineJS());
+        return data_.jitFrames_.baselineFrame()->hasArgsObj();
 #else
         break;
 #endif
     }
     MOZ_ASSUME_UNREACHABLE("Unexpected state");
 }
 
 ArgumentsObject &
@@ -1273,18 +1273,18 @@ FrameIter::argsObj() const
     JS_ASSERT(hasArgsObj());
 
     switch (data_.state_) {
       case DONE:
       case ASMJS:
         break;
       case JIT:
 #ifdef JS_ION
-        JS_ASSERT(data_.ionFrames_.isBaselineJS());
-        return data_.ionFrames_.baselineFrame()->argsObj();
+        JS_ASSERT(data_.jitFrames_.isBaselineJS());
+        return data_.jitFrames_.baselineFrame()->argsObj();
 #else
         break;
 #endif
       case INTERP:
         return interpFrame()->argsObj();
     }
     MOZ_ASSUME_UNREACHABLE("Unexpected state");
 }
@@ -1304,19 +1304,19 @@ Value
 FrameIter::thisv() const
 {
     switch (data_.state_) {
       case DONE:
       case ASMJS:
         break;
       case JIT:
 #ifdef JS_ION
-        if (data_.ionFrames_.isIonJS())
+        if (data_.jitFrames_.isIonJS())
             return ObjectValue(*ionInlineFrames_.thisObject());
-        return data_.ionFrames_.baselineFrame()->thisValue();
+        return data_.jitFrames_.baselineFrame()->thisValue();
 #else
         break;
 #endif
       case INTERP:
         return interpFrame()->thisValue();
     }
     MOZ_ASSUME_UNREACHABLE("Unexpected state");
 }
@@ -1325,18 +1325,18 @@ Value
 FrameIter::returnValue() const
 {
     switch (data_.state_) {
       case DONE:
       case ASMJS:
         break;
       case JIT:
 #ifdef JS_ION
-        if (data_.ionFrames_.isBaselineJS())
-            return data_.ionFrames_.baselineFrame()->returnValue();
+        if (data_.jitFrames_.isBaselineJS())
+            return data_.jitFrames_.baselineFrame()->returnValue();
 #endif
         break;
       case INTERP:
         return interpFrame()->returnValue();
     }
     MOZ_ASSUME_UNREACHABLE("Unexpected state");
 }
 
@@ -1344,18 +1344,18 @@ void
 FrameIter::setReturnValue(const Value &v)
 {
     switch (data_.state_) {
       case DONE:
       case ASMJS:
         break;
       case JIT:
 #ifdef JS_ION
-        if (data_.ionFrames_.isBaselineJS()) {
-            data_.ionFrames_.baselineFrame()->setReturnValue(v);
+        if (data_.jitFrames_.isBaselineJS()) {
+            data_.jitFrames_.baselineFrame()->setReturnValue(v);
             return;
         }
 #endif
         break;
       case INTERP:
         interpFrame()->setReturnValue(v);
         return;
     }
@@ -1366,22 +1366,22 @@ size_t
 FrameIter::numFrameSlots() const
 {
     switch (data_.state_) {
       case DONE:
       case ASMJS:
         break;
       case JIT: {
 #ifdef JS_ION
-        if (data_.ionFrames_.isIonJS()) {
+        if (data_.jitFrames_.isIonJS()) {
             return ionInlineFrames_.snapshotIterator().numAllocations() -
                 ionInlineFrames_.script()->nfixed();
         }
-        jit::BaselineFrame *frame = data_.ionFrames_.baselineFrame();
-        return frame->numValueSlots() - data_.ionFrames_.script()->nfixed();
+        jit::BaselineFrame *frame = data_.jitFrames_.baselineFrame();
+        return frame->numValueSlots() - data_.jitFrames_.script()->nfixed();
 #else
         break;
 #endif
       }
       case INTERP:
         JS_ASSERT(data_.interpFrames_.sp() >= interpFrame()->base());
         return data_.interpFrames_.sp() - interpFrame()->base();
     }
@@ -1392,24 +1392,24 @@ Value
 FrameIter::frameSlotValue(size_t index) const
 {
     switch (data_.state_) {
       case DONE:
       case ASMJS:
         break;
       case JIT:
 #ifdef JS_ION
-        if (data_.ionFrames_.isIonJS()) {
+        if (data_.jitFrames_.isIonJS()) {
             jit::SnapshotIterator si(ionInlineFrames_.snapshotIterator());
             index += ionInlineFrames_.script()->nfixed();
             return si.maybeReadAllocByIndex(index);
         }
 
-        index += data_.ionFrames_.script()->nfixed();
-        return *data_.ionFrames_.baselineFrame()->valueSlot(index);
+        index += data_.jitFrames_.script()->nfixed();
+        return *data_.jitFrames_.baselineFrame()->valueSlot(index);
 #else
         break;
 #endif
       case INTERP:
           return interpFrame()->base()[index];
     }
     MOZ_ASSUME_UNREACHABLE("Unexpected state");
 }
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -7,17 +7,17 @@
 #ifndef vm_Stack_h
 #define vm_Stack_h
 
 #include "mozilla/MemoryReporting.h"
 
 #include "jsfun.h"
 #include "jsscript.h"
 
-#include "jit/IonFrameIterator.h"
+#include "jit/JitFrameIterator.h"
 #ifdef CHECK_OSIPOINT_REGISTERS
 #include "jit/Registers.h" // for RegisterDump
 #endif
 #include "js/OldDebugAPI.h"
 
 struct JSCompartment;
 struct JSGenerator;
 
@@ -1510,17 +1510,17 @@ class FrameIter
         State           state_;
 
         jsbytecode *    pc_;
 
         InterpreterFrameIterator interpFrames_;
         ActivationIterator activations_;
 
 #ifdef JS_ION
-        jit::IonFrameIterator ionFrames_;
+        jit::JitFrameIterator jitFrames_;
 #endif
 
         Data(JSContext *cx, SavedOption savedOption, ContextOption contextOption,
              JSPrincipals *principals);
         Data(const Data &other);
     };
 
     FrameIter(JSContext *cx, SavedOption = STOP_AT_SAVED);
@@ -1753,39 +1753,39 @@ class AllFramesIter : public ScriptFrame
 inline JSScript *
 FrameIter::script() const
 {
     JS_ASSERT(!done());
     if (data_.state_ == INTERP)
         return interpFrame()->script();
 #ifdef JS_ION
     JS_ASSERT(data_.state_ == JIT);
-    if (data_.ionFrames_.isIonJS())
+    if (data_.jitFrames_.isIonJS())
         return ionInlineFrames_.script();
-    return data_.ionFrames_.script();
+    return data_.jitFrames_.script();
 #else
     return nullptr;
 #endif
 }
 
 inline bool
 FrameIter::isIon() const
 {
 #ifdef JS_ION
-    return isJit() && data_.ionFrames_.isIonJS();
+    return isJit() && data_.jitFrames_.isIonJS();
 #else
     return false;
 #endif
 }
 
 inline bool
 FrameIter::isBaseline() const
 {
 #ifdef JS_ION
-    return isJit() && data_.ionFrames_.isBaselineJS();
+    return isJit() && data_.jitFrames_.isBaselineJS();
 #else
     return false;
 #endif
 }
 
 inline InterpreterFrame *
 FrameIter::interpFrame() const
 {
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -5093,16 +5093,46 @@ nsRect nsDisplayTransform::TransformRect
 
   float factor = aFrame->PresContext()->AppUnitsPerDevPixel();
   return nsLayoutUtils::MatrixTransformRectOut
     (aUntransformedBounds,
      GetResultingTransformMatrix(aFrame, aOrigin, factor, aBoundsOverride),
      factor);
 }
 
+bool nsDisplayTransform::UntransformRect(const nsRect &aTransformedBounds,
+                                         const nsRect &aChildBounds,
+                                         const nsIFrame* aFrame,
+                                         const nsPoint &aOrigin,
+                                         nsRect *aOutRect)
+{
+  NS_PRECONDITION(aFrame, "Can't take the transform based on a null frame!");
+
+  float factor = aFrame->PresContext()->AppUnitsPerDevPixel();
+
+  gfx3DMatrix transform = GetResultingTransformMatrix(aFrame, aOrigin, factor, nullptr);
+  if (transform.IsSingular()) {
+    return false;
+  }
+
+  gfxRect result(NSAppUnitsToFloatPixels(aTransformedBounds.x, factor),
+                 NSAppUnitsToFloatPixels(aTransformedBounds.y, factor),
+                 NSAppUnitsToFloatPixels(aTransformedBounds.width, factor),
+                 NSAppUnitsToFloatPixels(aTransformedBounds.height, factor));
+
+  gfxRect childGfxBounds(NSAppUnitsToFloatPixels(aChildBounds.x, factor),
+                         NSAppUnitsToFloatPixels(aChildBounds.y, factor),
+                         NSAppUnitsToFloatPixels(aChildBounds.width, factor),
+                         NSAppUnitsToFloatPixels(aChildBounds.height, factor));
+
+  result = transform.UntransformBounds(result, childGfxBounds);
+  *aOutRect = nsLayoutUtils::RoundGfxRectToAppRect(result, factor);
+  return true;
+}
+
 bool nsDisplayTransform::UntransformVisibleRect(nsDisplayListBuilder* aBuilder,
                                                 nsRect *aOutRect)
 {
   const gfx3DMatrix& matrix = GetTransform();
   if (matrix.IsSingular())
     return false;
 
   // GetTransform always operates in dev pixels.
--- a/layout/base/nsDisplayList.h
+++ b/layout/base/nsDisplayList.h
@@ -3253,16 +3253,22 @@ public:
   static nsRect TransformRectOut(const nsRect &aUntransformedBounds, 
                                  const nsIFrame* aFrame,
                                  const nsPoint &aOrigin,
                                  const nsRect* aBoundsOverride = nullptr);
 
   /* UntransformRect is like TransformRect, except that it inverts the
    * transform.
    */
+  static bool UntransformRect(const nsRect &aTransformedBounds,
+                              const nsRect &aChildBounds,
+                              const nsIFrame* aFrame,
+                              const nsPoint &aOrigin,
+                              nsRect *aOutRect);
+
   bool UntransformVisibleRect(nsDisplayListBuilder* aBuilder,
                               nsRect* aOutRect);
 
   static gfxPoint3D GetDeltaToTransformOrigin(const nsIFrame* aFrame,
                                               float aAppUnitsPerPixel,
                                               const nsRect* aBoundsOverride);
 
   static gfxPoint3D GetDeltaToPerspectiveOrigin(const nsIFrame* aFrame,
--- a/layout/generic/StickyScrollContainer.cpp
+++ b/layout/generic/StickyScrollContainer.cpp
@@ -176,21 +176,16 @@ StickyScrollContainer::ComputeStickyLimi
     aFrame->Properties().Get(nsIFrame::ComputedOffsetProperty()));
   if (!computedOffsets) {
     // We haven't reflowed the scroll frame yet, so offsets haven't been
     // computed. Bail.
     return;
   }
 
   nsIFrame* scrolledFrame = mScrollFrame->GetScrolledFrame();
-  // FIXME (Bug 920688):  cbFrame isn't quite right if we're dealing
-  // with a block-in-inline split whose first part is a block.  We
-  // probably want the first in flow of the containing block of the
-  // first inline part.  (Or maybe those block-in-inline split pieces
-  // are never a containing block, and we're ok?)
   nsIFrame* cbFrame = aFrame->GetContainingBlock();
   NS_ASSERTION(cbFrame == scrolledFrame ||
     nsLayoutUtils::IsProperAncestorFrame(scrolledFrame, cbFrame),
     "Scroll frame should be an ancestor of the containing block");
 
   nsRect rect =
     nsLayoutUtils::GetAllInFlowRectsUnion(aFrame, aFrame->GetParent());
 
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -1924,32 +1924,26 @@ nsIFrame::BuildDisplayListForStackingCon
     const nsRect overflow = GetVisualOverflowRectRelativeToSelf();
     if (aBuilder->IsForPainting() &&
         nsDisplayTransform::ShouldPrerenderTransformedContent(aBuilder, this)) {
       dirtyRect = overflow;
     } else {
       if (overflow.IsEmpty() && !Preserves3DChildren()) {
         return;
       }
-      // Trying to back-transform arbitrary rects gives us really weird results. I believe 
-      // this is from points that lie beyond the vanishing point. As a workaround we transform
-      // the overflow rect into screen space and compare in that coordinate system.
-
-      // Transform the overflow rect into screen space.
+
       nsPoint offset = aBuilder->ToReferenceFrame(this);
-      nsRect trans = nsDisplayTransform::TransformRect(overflow + offset, this, offset);
       dirtyRect += offset;
-      if (dirtyRect.Intersects(trans)) {
-        // If they intersect, we take our whole overflow rect. We could instead take the intersection
-        // and then reverse transform it but I doubt this extra work is worthwhile.
-        dirtyRect = overflow;
+
+      nsRect untransformedDirtyRect;
+      if (nsDisplayTransform::UntransformRect(dirtyRect, overflow, this, offset, &untransformedDirtyRect)) {
+        dirtyRect = untransformedDirtyRect;
       } else {
-        if (!Preserves3DChildren()) {
-          return;
-        }
+        NS_WARNING("Unable to untransform dirty rect!");
+        // This should only happen if the transform is singular, in which case nothing is visible anyway
         dirtyRect.SetEmpty();
       }
     }
     inTransform = true;
   }
 
   bool useOpacity = HasVisualOpacity() && !nsSVGUtils::CanOptimizeOpacity(this);
   bool useBlendMode = disp->mMixBlendMode != NS_STYLE_BLEND_NORMAL;
--- a/layout/mathml/mathfont.properties
+++ b/layout/mathml/mathfont.properties
@@ -6,19 +6,19 @@
 #  Do not translate anything in this file
 
 # List of fonts that have corresponding properties files containing special
 # glyph tables for stretching MathML characters.  See the documentation at the
 # end of this file for details on the setup of the property file associated to
 # each font.  Do not include the Unicode table in this list.
 
 %ifdef XP_WIN
-font.mathfont-glyph-tables = MathJax_Main, STIXNonUnicode, STIXSizeOneSym, STIXSize1, Asana Math, Standard Symbols L, Symbol
+font.mathfont-glyph-tables = MathJax_Main, STIXNonUnicode, STIXSizeOneSym, Standard Symbols L, Symbol
 %else
-font.mathfont-glyph-tables = MathJax_Main, STIXNonUnicode, STIXSizeOneSym, STIXSize1, Asana Math, Standard Symbols L
+font.mathfont-glyph-tables = MathJax_Main, STIXNonUnicode, STIXSizeOneSym, Standard Symbols L
 %endif
 
 # The ordered list of fonts with which to attempt to stretch MathML
 # characters is controlled by setting pref("font.mathfont-family",
 # "CMSY10, CMEX10, ...") for example, or by setting the font-family list in
 # :-moz-math-stretchy in mathml.css.
 #
 # Preferred fonts for particular stretchy characters may be specified in
deleted file mode 100644
--- a/layout/mathml/mathfontAsanaMath.properties
+++ /dev/null
@@ -1,145 +0,0 @@
-# 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/.
-
-#  LOCALIZATION NOTE: FILE
-#  Do not translate anything in this file
-
-# This file contains the list of some stretchy MathML chars that
-# can be rendered with Asana Math font.
-
-#        [ T/L |  M  | B/R |  G  | size0 ... size{N-1} ]
-#        (*) not in the MathML operator dictionary
-
-\u0028 = \u239B\uFFFD\u239D\u239C\u0028\uDBFF\uDFF4\uDBFF\uDFF5\uDBFF\uDFF6 # (
-\u0029 = \u239E\uFFFD\u23A0\u239F\u0029\uDBFF\uDFF7\uDBFF\uDFF8\uDBFF\uDFF9 # )
-\u005B = \u23A1\uFFFD\u23A3\u23A2\u005B\uDBFF\uDFEE\uDBFF\uDFEF\uDBFF\uDFF0 # [
-\u005D = \u23A4\uFFFD\u23A6\u23A5\u005D\uDBFF\uDFF1\uDBFF\uDFF2\uDBFF\uDFF3 # ]
-\u007B = \u23A7\u23A8\u23A9\u23AA\u007B\uDBFF\uDFFA\uDBFF\uDFFB\uDBFF\uDFFC # {
-\u007C = \uFFFD\uFFFD\uFFFD\u007C\u007C\uDBFF\uDFD6\uDBFF\uDFD7\uDBFF\uDFD8\uDBFF\uDFD9 # |
-\u007D = \u23AB\u23AC\u23AD\u23AA\u007D\uDBFF\uDFFD\uDBFF\uDFFE\uDBFF\uDFFF # }
-\u2016 = \uFFFD\uFFFD\uFFFD\uDBFF\uDFD1\u2016\uDBFF\uDFCE\uDBFF\uDFCF\uDBFF\uDFD0\uDBFF\uDFD1 # DOUBLE VERTICAL LINE
-
-\u2044 = \uFFFD\uFFFD\uFFFD\uFFFD\u2044\uDBFF\uDFD2\uDBFF\uDFD3\uDBFF\uDFD4\uDBFF\uDFD5 # FRACTION SLASH
-# \u2045 = \uDBFF\uDFB6\uDBFF\uDF53\uDBFF\uDFB7\uDBFF\uDFBA\u2045\uDBFF\uDFBB\uDBFF\uDFBC\uDBFF\uDFBD # LEFT SQUARE BRACKET WITH QUILL (*)
-# \u2046 = \uDBFF\uDFB8\uDBFF\uDF54\uDBFF\uDFB9\uDBFF\uDF52\u2046\uDBFF\uDFBE\uDBFF\uDFBF\uDBFF\uDFC0 # RIGHT SQUARE BRACKET WITH QUILL (*)
-
-\u2191 = \u2191\uFFFD\uFFFD\uDBFF\uDEC6\u2191 # UPWARDS ARROW
-\u2193 = \uFFFD\uFFFD\u2193\uDBFF\uDEC6\u2193 # DOWNWARDS ARROW
-\u21D1 = \u21D1\uFFFD\uFFFD\uDBFF\uDEC7\u21D1 # UPWARDS DOUBLE ARROW
-\u21D3 = \uFFFD\uFFFD\u21D3\uDBFF\uDEC7\u21D3 # DOWNWARDS DOUBLE ARROW
-
-\u220F = \uFFFD\uFFFD\uFFFD\uFFFD\u220F\uDBFF\uDF9F\uDBFF\uDFA0\uDBFF\uDFA1 # N-ARY PRODUCT
-\u2210 = \uFFFD\uFFFD\uFFFD\uFFFD\u2210\uDBFF\uDFA2\uDBFF\uDFA3\uDBFF\uDFA4 # N-ARY COPRODUCT
-\u2211 = \uFFFD\uFFFD\uFFFD\uFFFD\u2211\uDBFF\uDF9C\uDBFF\uDF9D\uDBFF\uDF9E # summation N-ARY SUMMATION
-\u221A = \uDBFF\uDF6D\uFFFD\u23B7\u20D3\u221A\uDBFF\uDF6E\uDBFF\uDF6F\uDBFF\uDF70\uDBFF\uDF71 # SQUARE ROOT
-\u2223 = \uFFFD\uFFFD\uFFFD\u2223\u2223 # DIVIDES
-\u2225 = \uFFFD\uFFFD\uFFFD\u2225\u2225 # PARALLEL TO
-\u222B = \u2320\uFFFD\u2321\u23AE\u222B\uDBFF\uDF99\uDBFF\uDF9A\uDBFF\uDF9B # INTEGRAL
-\u222C = \uFFFD\uFFFD\uFFFD\uFFFD\u222C\uDBFF\uDF6A\uDBFF\uDF6B\uDBFF\uDF6C # DOUBLE INTEGRAL
-\u222D = \uFFFD\uFFFD\uFFFD\uFFFD\u222D\uDBFF\uDF67\uDBFF\uDF68\uDBFF\uDF69 # TRIPLE INTEGRAL
-\u222E = \uFFFD\uFFFD\uFFFD\uFFFD\u222E\uDBFF\uDF64\uDBFF\uDF65\uDBFF\uDF66 # CONTOUR INTEGRAL
-\u222F = \uFFFD\uFFFD\uFFFD\uFFFD\u222F\uDBFF\uDF61\uDBFF\uDF62\uDBFF\uDF63 # SURFACE INTEGRAL
-\u2230 = \uFFFD\uFFFD\uFFFD\uFFFD\u2230\uDBFF\uDF5E\uDBFF\uDF5F\uDBFF\uDF60 # VOLUME INTEGRAL
-\u2231 = \uFFFD\uFFFD\uFFFD\uFFFD\u2231\uDBFF\uDF5B\uDBFF\uDF5C\uDBFF\uDF5D # CLOCKWISE INTEGRAL
-\u2232 = \uFFFD\uFFFD\uFFFD\uFFFD\u2232\uDBFF\uDF58\uDBFF\uDF59\uDBFF\uDF5A # CLOCKWISE CONTOUR INTEGRAL
-\u2233 = \uFFFD\uFFFD\uFFFD\uFFFD\u2233\uDBFF\uDF55\uDBFF\uDF56\uDBFF\uDF57 # ANTICLOCKWISE CONTOUR INTEGRAL
-
-\u22C0 = \uFFFD\uFFFD\uFFFD\uFFFD\u22C0\uDBFF\uDF92\uDBFF\uDF93 # N-ARY LOGICAL AND
-\u22C1 = \uFFFD\uFFFD\uFFFD\uFFFD\u22C1\uDBFF\uDF94\uDBFF\uDF95 # N-ARY LOGICAL OR
-\u22C2 = \uFFFD\uFFFD\uFFFD\uFFFD\u22C2\uDBFF\uDF8E\uDBFF\uDF8F # N-ARY INTERSECTION
-\u22C3 = \uFFFD\uFFFD\uFFFD\uFFFD\u22C3\uDBFF\uDF8C\uDBFF\uDF8D # N-ARY UNION
-\u2308 = \u23A1\uFFFD\uFFFD\u23A2\u2308\uDBFF\uDFE2\uDBFF\uDFE3\uDBFF\uDFE4 # LEFT CEILING
-\u2309 = \u23A4\uFFFD\uFFFD\u23A5\u2309\uDBFF\uDFE5\uDBF\uDFE6\uDBFF\uDFE7 # RIGHT CEILING
-\u230A = \uFFFD\uFFFD\u23A3\u23A2\u230A\uDBFF\uDFE8\uDBFF\uDFE9\uDBFF\uDFEA # LEFT FLOOR
-\u230B = \uFFFD\uFFFD\u23A6\u23A5\u230B\u230B\uDBFF\uDFEB\uDBFF\uDFEC\uDBFF\uDFED # RIGHT FLOOR
-
-# \u27C5 = \uFFFD\uFFFD\uFFFD\uFFFD\u27C5\uDBFF\uDDF3\uDBFF\uDDF5\uDBFF\uDDF7\uDBFF\uDDF9\uDBFF\uDDFB # LEFT S-SHAPED BAG DELIMITER (*)
-# \u27C6 = \uFFFD\uFFFD\uFFFD\uFFFD\uDBFF\uDDF4\uDBFF\uDDF6\uDBFF\uDDF8\uDBFF\uDDFA\uDBFF\uDDFC # RIGHT S-SHAPED BAG DELIMITER (*)
-\u27E6 = \uFFFD\uFFFD\uFFFD\uFFFD\u27E6\uDBFF\uDFDA\uDBFF\uDFDB\uDBFF\uDFDC\uDBFF\uDFDD # MATHEMATICAL LEFT WHITE SQUARE BRACKET
-\u27E7 = \uFFFD\uFFFD\uFFFD\uFFFD\u27E7\uDBFF\uDFDE\uDBFF\uDFDF\uDBFF\uDFE0\uDBFF\uDFE1 # MATHEMATICAL RIGHT WHITE SQUARE BRACKET
-\u27E8 = \uFFFD\uFFFD\uFFFD\uFFFD\u27E8\uDBFF\uDF89\uDBFF\uDF8A\uDBFF\uD8B # MATHEMATICAL LEFT ANGLE BRACKET
-\u27E9 = \uFFFD\uFFFD\uFFFD\uFFFD\u27E9\uDBFF\uDF7C\uDBFF\uDF7D\uDBFF\uDF7E # MATHEMATICAL RIGHT ANGLE BRACKET
-\u27EA = \uFFFD\uFFFD\uFFFD\uFFFD\u27EA\uDBFF\uDF76\uDBFF\uDF77\uDBFF\uDF78 # MATHEMATICAL LEFT DOUBLE ANGLE BRACKET
-\u27EB = \uFFFD\uFFFD\uFFFD\uFFFD\u27EB\uDBFF\uDF79\uDBFF\uDF7A\uDBFF\uDF7B # MATHEMATICAL RIGHT DOUBLE ANGLE BRACKET
-
-\u29FC = \uFFFD\uFFFD\uFFFD\uFFFD\u29FC\uDBFF\uDEC8\uDBFF\uDEC9\uDBFF\uDECA # LEFT-POINTING CURVED ANGLE BRACKET
-\u29FD = \uFFFD\uFFFD\uFFFD\uFFFD\u29FD\uDBFF\uDECB\uDBFF\uDECC\uDBFF\uDECD # RIGHT-POINTING CURVED ANGLE BRACKET
-
-\u2A00 = \uFFFD\uFFFD\uFFFD\uFFFD\u2A00\uDBFF\uDF96\uDBFF\uDF97 # N-ARY CIRCLED DOT OPERATOR
-\u2A01 = \uFFFD\uFFFD\uFFFD\uFFFD\u2A01\uDBFF\uDF98\uDBFF\uDFA5 # N-ARY CIRCLED PLUS OPERATOR
-\u2A02 = \uFFFD\uFFFD\uFFFD\uFFFD\u2A02\uDBFF\uDF7F\uDBFF\uDF80 # N-ARY CIRCLED TIMES OPERATOR
-\u2A03 = \uFFFD\uFFFD\uFFFD\uFFFD\u2A03\uDBFF\uDF81\uDBFF\uDF82 # N-ARY UNION OPERATOR WITH DOT
-\u2A04 = \uFFFD\uFFFD\uFFFD\uFFFD\u2A04\uDBFF\uDF90\uDBFF\uDF91 # N-ARY UNION OPERATOR WITH PLUS
-\u2A05 = \uFFFD\uFFFD\uFFFD\uFFFD\u2A05\uDBFF\uDF83\uDBFF\uDF84 # N-ARY SQUARE INTERSECTION OPERATOR
-\u2A06 = \uFFFD\uFFFD\uFFFD\uFFFD\u2A06\uDBFF\uDF85\uDBFF\uDF86 # N-ARY SQUARE UNION OPERATOR
-\u2A07 = \uFFFD\uFFFD\uFFFD\uFFFD\u2A07\uDBFF\uDF72\uDBFF\uDF73 # TWO LOGICAL AND OPERATOR
-\u2A08 = \uFFFD\uFFFD\uFFFD\uFFFD\u2A08\uDBFF\uDF74\uDBFF\uDF75 # TWO LOGICAL OR OPERATOR
-\u2A09 = \uFFFD\uFFFD\uFFFD\uFFFD\u2A09\uDBFF\uDF87\uDBFF\uDF88 # N-ARY TIMES OPERATOR
-\u2A0C = \uFFFD\uFFFD\uFFFD\uFFFD\u2A0C\uDBFF\uDF1F\uDBFF\uDF20\uDBFF\uDF21 # QUADRUPLE INTEGRAL OPERATOR
-\u2A0D = \uFFFD\uFFFD\uFFFD\uFFFD\u2A0D\uDBFF\uDF22\uDBFF\uDF23\uDBFF\uDF24 # FINITE PART INTEGRAL
-\u2A0E = \uFFFD\uFFFD\uFFFD\uFFFD\u2A0E\uDBFF\uDF25\uDBFF\uDF26\uDBFF\uDF27 # INTEGRAL WITH DOUBLE STROKE
-\u2A0F = \uFFFD\uFFFD\uFFFD\uFFFD\u2A0F\uDBFF\uDF28\uDBFF\uDF29\uDBFF\uDF2A # INTEGRAL AVERAGE WITH SLASH
-\u2A10 = \uFFFD\uFFFD\uFFFD\uFFFD\u2A10\uDBFF\uDF2B\uDBFF\uDF2C\uDBFF\uDF2D # CIRCULATION FUNCTION
-\u2A11 = \uFFFD\uFFFD\uFFFD\uFFFD\u2A11\uDBFF\uDF2E\uDBFF\uDF2F\uDBFF\uDF30 # ANTICLOCKWISE INTEGRATION
-\u2A12 = \uFFFD\uFFFD\uFFFD\uFFFD\u2A12\uDBFF\uDF31\uDBFF\uDF32\uDBFF\uDF33 # LINE INTEGRATION WITH RECTANGULAR PATH AROUND POLE
-\u2A13 = \uFFFD\uFFFD\uFFFD\uFFFD\u2A13\uDBFF\uDF34\uDBFF\uDF35\uDBFF\uDF36 # LINE INTEGRATION WITH SEMICIRCULAR PATH AROUND POLE
-\u2A14 = \uFFFD\uFFFD\uFFFD\uFFFD\u2A14\uDBFF\uDF37\uDBFF\uDF38\uDBFF\uDF39 # LINE INTEGRATION NOT INCLUDING THE POLE
-\u2A15 = \uFFFD\uFFFD\uFFFD\uFFFD\u2A15\uDBFF\uDF3A\uDBFF\uDF3B\uDBFF\uDF3C # INTEGRAL AROUND A POINT OPERATOR
-\u2A16 = \uFFFD\uFFFD\uFFFD\uFFFD\u2A16\uDBFF\uDF3D\uDBFF\uDF3E\uDBFF\uDF3F # QUATERNION INTEGRAL OPERATOR
-\u2A17 = \uFFFD\uFFFD\uFFFD\uFFFD\u2A17\uDBFF\uDF40\uDBFF\uDF41\uDBFF\uDF42 # INTEGRAL WITH LEFTWARDS ARROW WITH HOOK
-\u2A18 = \uFFFD\uFFFD\uFFFD\uFFFD\u2A18\uDBFF\uDF43\uDBFF\uDF44\uDBFF\uDF45 # INTEGRAL WITH TIMES SIGN
-\u2A19 = \uFFFD\uFFFD\uFFFD\uFFFD\u2A19\uDBFF\uDF46\uDBFF\uDF47\uDBFF\uDF48 # INTEGRAL WITH INTERSECTION
-\u2A1A = \uFFFD\uFFFD\uFFFD\uFFFD\u2A1A\uDBFF\uDF49\uDBFF\uDF4A\uDBFF\uDF4B # INTEGRAL WITH UNION
-\u2A1B = \uFFFD\uFFFD\uFFFD\uFFFD\u2A1B\uDBFF\uDF4C\uDBFF\uDF4D\uDBFF\uDF4E # INTEGRAL WITH OVERBAR
-\u2A1C = \uFFFD\uFFFD\uFFFD\uFFFD\u2A1C\uDBFF\uDF4F\uDBFF\uDF50\uDBFF\uDF51 # INTEGRAL WITH UNDERBAR
-
-\u005E = \uFFFD\uFFFD\uFFFD\uFFFD\u005E\uDBFF\uDFA6\uDBFF\uDFA7\uDBFF\uDFA8 # CIRCUMFLEX ACCENT
-\u0302 = \uFFFD\uFFFD\uFFFD\uFFFD\u005E\uDBFF\uDFA6\uDBFF\uDFA7\uDBFF\uDFA8 # COMBINING CIRCUMFLEX ACCENT
-\u007E = \uFFFD\uFFFD\uFFFD\uFFFD\u007E\uDBFF\uDFAA\uDBFF\uDFAB\uDBFF\uDFAC\uDBFF\uDFAD # TILDE
-\u02DC = \uFFFD\uFFFD\uFFFD\uFFFD\u007E\uDBFF\uDFAA\uDBFF\uDFAB\uDBFF\uDFAC\uDBFF\uDFAD # SMALL TILDE
-# \u0303 = \uFFFD\uFFFD\uFFFD\uFFFD\u007E\uDBFF\uDFAA\uDBFF\uDFAB\uDBFF\uDFAC\uDBFF\uDFAD # COMBINING TILDE (*)
-# \u0305 = \uFFFD\uFFFD\uFFFD\uDBFF\uDF1E\u0305 COMBINING OVERLINE (*)
-# \u0306 = \uFFFD\uFFFD\uFFFD\uFFFD\u02D8\uDBFF\uDFB2\uDBFF\uDFB3\uDBFF\uDFB4\uDBFF\uDFB5 # COMBINING BREVE (*)
-# \u02D8 = \uFFFD\uFFFD\uFFFD\uFFFD\u02D8\uDBFF\uDFB2\uDBFF\uDFB3\uDBFF\uDFB4\uDBFF\uDFB5 # BREVE (not stretchy)
-\u02C7 = \uFFFD\uFFFD\uFFFD\uFFFD\u02C7\uDBFF\uDFAE\uDBFF\uDFAF\uDBFF\uDFB0\uDBFF\uDFB1 # CARON
-# \u030C = \uFFFD\uFFFD\uFFFD\uFFFD\u02C7\uDBFF\uDFAE\uDBFF\uDFAF\uDBFF\uDFB0\uDBFF\uDFB1 # COMBINING CARON (*)
-# \u0332 = \uFFFD\uFFFD\uFFFD\uDBFF\uDF1D\u0332\uDBFF\uDF1D\uDBFF\uDF18\uDBFF\uDF14 # COMBINING LOW LINE (*)
-# \u0333 = \uFFFD\uFFFD\uFFFD\uDBFF\uDF1C\u0333\uDBFF\uDF1C\uDBFF\uDF17\uDBFF\uDF13 # COMBINING DOUBLE LOW LINE (*)
-# \u033F = \uFFFD\uFFFD\uFFFD\uDBFF\uDF1B\u033F\uDBFF\uDF1B\uDBFF\uDF16\uDBFF\uDF12 # COMBINING DOUBLE OVERLINE (*)
-# \u20D0 = \u20D0\uFFFD\uFFFD\uDBFF\uDF1A\u20D0 # COMBINING LEFT HARPOON ABOVE (*)
-# \u20D1 = \uFFFD\uFFFD\u20D1\uDBFF\uDF1A\u20D1 # COMBINING RIGHT HARPOON ABOVE (*)
-# \u20D6 = \u20D6\uFFFD\uFFFD\uDBFF\uDF1A\u20D6\uDBFF\uDE4A\uDBFF\uDE4B\uDBFF\uDE4C\uDBFF\uDE4D # COMBINING LEFT ARROW ABOVE (*)
-# \u20D7 = \uFFFD\uFFFD\u20D7\uDBFF\uDF1A\u20D7\uDBFF\uDE4E\uDBFF\uDE4F\uDBFF\uDE50\uDBFF\uDE51 # COMBINING RIGHT ARROW ABOVE (*)
-# \u20E1 = \u20D6\uFFFD\u20D7\uDBFF\uDF1A\u20E1 # COMBINING LEFT RIGHT ARROW ABOVE (*)
-# \u20E9 = \uDBFF\uDEEC\uFFFD\uDBFF\uDEED\uDBFF\uDEEB\u20E9 # COMBINING WIDE BRIDGE ABOVE (*)
-
-\u2190 = \uDBFF\uDF11\uFFFD\uDBFF\uDF10\u23AF\u2190 # LEFTWARDS ARROW 
-\u2192 = \uDBFF\uDF0E\uFFFD\uDBFF\uDF0F\u23AF\u2192 # RIGHTWARDS ARROW
-\u2194 = \uDBFF\uDF11\uFFFD\uDBFF\uDF0F\u23AF\u2194 # LEFT RIGHT ARROW
-\u21A4 = \uDBFF\uDF11\uFFFD\uDBFF\uDF08\u23AF\u21A4 # LEFTWARDS ARROW FROM BAR
-\u21A6 = \uDBFF\uDF07\uFFFD\uDBFF\uDF0F\u23AF\u21A6 # RIGHTWARDS ARROW FROM BAR
-\u21A9 = \uDBFF\uDF11\uFFFD\uDBFF\uDF06\u23AF\u21A9 # LEFTWARDS ARROW WITH HOOK
-\u21AA = \uDBFF\uDF05\uFFFD\uDBFF\uDF0F\u23AF\u21AA # RIGHTWARDS ARROW WITH HOOK
-
-\u21D0 = \uDBFF\uDF0D\uFFFD\uDBFF\uDF0C\uDBFF\uDF09\u21D0 # LEFTWARDS DOUBLE ARROW
-\u21D2 = \uDBFF\uDF0A\uFFFD\uDBFF\uDF0B\uDBFF\uDF09\u21D2 # RIGHTWARDS DOUBLE ARROW
-\u21D4 = \uDBFF\uDF0D\uFFFD\uDBFF\uDF0B\uDBFF\uDF09\u21D4 # LEFT RIGHT DOUBLE ARROW
-
-\u23B4 = \uDBFF\uDEEC\uFFFD\uDBFF\uDEED\uDBFF\uDEEB\u23B4\uDBFF\uDEFD\uDBFF\uDEFE\uDBFF\uDEFF # TOP SQUARE BRACKET
-\u23B5 = \uDBFF\uDEEE\uFFFD\uDBFF\uDEEF\uDBFF\uDEEA\u23B5\uDBFF\uDF00\uDBFF\uDF01\uDBFF\uDF02 # BOTTOM SQUARE BRACKET
-
-\u23DC = \uDBFF\uDFC7\uFFFD\uDBFF\uDFC9\uDBFF\uDFCA\u23DC\uDBFF\uDEF7\uDBFF\uDEF8\uDBFF\uDEF9 # TOP PARENTHESIS
-\uFE35 = \uDBFF\uDFC7\uFFFD\uDBFF\uDFC9\uDBFF\uDFCA\u23DC\uDBFF\uDEF7\uDBFF\uDEF8\uDBFF\uDEF9 # &OverParenthesis; (MathML 2.0)
-\u23DD = \uDBFF\uDFCB\uFFFD\uDBFF\uDFCD\uDBFF\uDEF0\u23DD\uDBFF\uDEFA\uDBFF\uDEFB\uDBFF\uDEFC # BOTTOM PARENTHESIS
-\uFE36 = \uDBFF\uDFCB\uFFFD\uDBFF\uDFCD\uDBFF\uDEF0\u23DD\uDBFF\uDEFA\uDBFF\uDEFB\uDBFF\uDEFC # &UnderParenthesis; (MathML 2.0)
-
-\u23DE = \uDBFF\uDFC7\uDBFF\uDFC8\uDBFF\uDFC9\uDBFF\uDFCA\u23DE\uDBFF\uDFC1\uDBFF\uDFC2\uDBFF\uDFC3 # TOP CURLY BRACKET
-\uFE37 = \uDBFF\uDFC7\uDBFF\uDFC8\uDBFF\uDFC9\uDBFF\uDFCA\u23DE\uDBFF\uDFC1\uDBFF\uDFC2\uDBFF\uDFC3 # &OverBrace; (MathML 2.0)
-\u23DF = \uDBFF\uDFCB\uDBFF\uDFCC\uDBFF\uDFCD\uDBFF\uDEF0\u23DF\uDBFF\uDFC4\uDBFF\uDFC5\uDBFF\uDFC6 # BOTTOM CURLY BRACKET
-\uFE38 = \uDBFF\uDFCB\uDBFF\uDFCC\uDBFF\uDFCD\uDBFF\uDEF0\u23DF\uDBFF\uDFC4\uDBFF\uDFC5\uDBFF\uDFC6 # &UnderBrace; (MathML 2.0)
-\u23E0 = \uFFFD\uFFFD\uFFFD\uFFFD\u23E0\uDBFF\uDEF1\uDBFF\uDEF2\uDBFF\uDEF3 # TOP TORTOISE SHELL BRACKET
-\u23E1 = \uFFFD\uFFFD\uFFFD\uFFFD\u23E1\uDBFF\uDEF4\uDBFF\uDEF5\uDBFF\uDEF6 # BOTTOM TORTOISE SHELL BRACKET
-
-\u2906 = \uDBFF\uDF0D\uFFFD\uDBFF\uDF04\uDBFF\uDF09\u2906 # LEFTWARDS DOUBLE ARROW FROM BAR
-\u2907 = \uDBFF\uDF03\uFFFD\uDBFF\uDF0B\uDBFF\uDF09\u2907 # RIGHTWARDS DOUBLE ARROW FROM BAR
deleted file mode 100644
--- a/layout/mathml/mathfontSTIXSize1.properties
+++ /dev/null
@@ -1,70 +0,0 @@
-# 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/.
-
-#  LOCALIZATION NOTE: FILE
-#  Do not translate anything in this file
-
-# This file contains the list of some stretchy MathML chars that
-# can be rendered with STIXSize* set of fonts,
-# with some help from STIXNonUnicode and STIXGeneral.
-
-external.1 = STIXNonUnicode
-external.2 = STIXSize2
-external.3 = STIXSize3
-external.4 = STIXSize4
-external.5 = STIXSize5
-external.6 = STIXGeneral
-
-#        [ T/L |  M  | B/R |  G  | size0 ... size{N-1} ]
-\u0028 = \u239B\uFFFD\u239D\u239C\uFFFD((@2(@3(@4 # (
-\u0029 = \u239E\uFFFD\u23A0\u239F\uFFFD))@2)@3)@4 # )
-\u005B = \u23A1\uFFFD\u23A3\u23A2\u005B[[@2[@3[@4 # [
-\u005D = \u23A4\uFFFD\u23A6\u23A5\u005D]]@2]@3]@4 # ]
-\u007B = \u23A7\u23A8\u23A9\u23AA\u007B{{@2{@3{@4 # {
-\u007D = \u23AB\u23AC\u23AD\u23AA\u007D}}@2}@3}@4 # }
-
-# N-ARY operators
-\u2140 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2140 # DOUBLE-STRUCK N-ARY SUMMATION
-\u220F = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u220F # N-ARY PRODUCT
-\u2210 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2210 # N-ARY COPRODUCT
-\u2211 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2211 # N-ARY SUMMATION
-\u22C0 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u22C0 # N-ARY LOGICAL AND
-\u22C1 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u22C1 # N-ARY LOGICAL OR
-\u22C2 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u22C2 # N-ARY INTERSECTION
-\u22C3 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u22C3 # N-ARY UNION
-\u2A00 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A00 # N-ARY CIRCLED DOT OPERATOR
-\u2A01 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A01 # N-ARY CIRCLED PLUS OPERATOR
-\u2A02 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A02 # N-ARY CIRCLED TIMES OPERATOR
-\u2A03 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A03 # N-ARY UNION OPERATOR WITH DOT
-\u2A04 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A04 # N-ARY UNION OPERATOR WITH PLUS
-\u2A05 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A05 # N-ARY SQUARE INTERSECTION OPERATOR
-\u2A06 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A06 # N-ARY SQUARE UNION OPERATOR
-\u2A09 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A09 # N-ARY TIMES OPERATOR
-\u2AFF = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2AFF # N-ARY WHITE VERTICAL BAR
-
-# E000 stix-radical symbol vertical extender
-# E001 stix-radical symbol top corner
-\u221A = \uE001@1\uFFFD\u221A@4\uE000@1\uFFFD\u221A\u221A@2\u221A@3 # Sqrt, radic
-
-\u222B = \u2320\uFFFD\u2321\u23AE\uFFFD\u222B@6 # Integral, int
-
-\u27E8 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u27E8\u27E8@2\u27E8@3\u27E8@4 # LeftAngleBracket
-\u27E9 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u27E9\u27E9@2\u27E9@3\u27E9@4 # RightAngleBracket
-
-\u23DE = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u23DE\u23DE@2\u23DE@3\u23DE@4\u23DE@5 # &OverBrace; (Unicode)
-\uFE37 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u23DE\u23DE@2\u23DE@3\u23DE@4\u23DE@5 # &OverBrace; (MathML 2.0)
-\u23B4 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u23B4\u23B4@2\u23B4@3\u23B4@4\u23B4@5 # &OverBracket;
-\u23DC = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u23DC\u23DC@2\u23DC@3\u23DC@4\u23DC@5 # &OverParenthesis; (Unicode)
-\uFE35 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u23DC\u23DC@2\u23DC@3\u23DC@4\u23DC@5 # &OverParenthesis; (MathML 2.0)
-\u23DF = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u23DF\u23DF@2\u23DF@3\u23DF@4\u23DF@5 # &UnderBrace; (Unicode)
-\uFE38 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u23DF\u23DF@2\u23DF@3\u23DF@4\u23DF@5 # &UnderBrace; (MathML 2.0)
-\u23B5 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u23B5\u23B5@2\u23B5@3\u23B5@4\u23B5@5 # &UnderBracket;
-\u23DD = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u23DD\u23DD@2\u23DD@3\u23DD@4\u23DD@5 # &UnderParenthesis; (Unicode)
-\uFE36 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u23DD\u23DD@2\u23DD@3\u23DD@4\u23DD@5 # &UnderParenthesis; (MathML 2.0)
-
-\u005E = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u0302\u0302@2\u0302@3\u0302@4\u0302@5 # circumflex accent, COMBINING CIRCUMFLEX ACCENT
-\u02C6 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u0302\u0302@2\u0302@3\u0302@4\u0302@5 # modifier letter circumflex accent, COMBINING CIRCUMFLEX ACCENT
-\u007E = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u0303\u0303@2\u0303@3\u0303@4\u0303@5 # ~ tilde, COMBINING TILDE
-\u02DC = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u0303\u0303@2\u0303@3\u0303@4\u0303@5 # small tilde, COMBINING TILDE
-\u02C7 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u030C\u030C@2\u030C@3\u030C@4\u030C@5 # caron, COMBINING CARON
--- a/layout/mathml/moz.build
+++ b/layout/mathml/moz.build
@@ -50,21 +50,19 @@ if CONFIG['ENABLE_TESTS']:
     PARALLEL_DIRS += [
         'tests',
     ]
 
 JAR_MANIFESTS += ['jar.mn']
 
 RESOURCE_FILES.fonts += [
     'mathfont.properties',
-    'mathfontAsanaMath.properties',
     'mathfontMathJax_Main.properties',
     'mathfontStandardSymbolsL.properties',
     'mathfontSTIXNonUnicode.properties',
-    'mathfontSTIXSize1.properties',
     'mathfontSTIXSizeOneSym.properties',
     'mathfontUnicode.properties',
 ]
 
 RESOURCE_FILES.fonts['mathfont.properties'].preprocess = True
 
 if CONFIG['TARGET_MD_ARCH'] == 'win32':
     RESOURCE_FILES.fonts += [
--- a/layout/mathml/nsMathMLChar.cpp
+++ b/layout/mathml/nsMathMLChar.cpp
@@ -23,58 +23,103 @@
 #include "nsCSSRendering.h"
 #include "prprf.h"         // For PR_snprintf()
 
 #include "nsDisplayList.h"
 
 #include "nsMathMLOperators.h"
 #include <algorithm>
 
+#include "gfxMathTable.h"
+
 using namespace mozilla;
 
 //#define NOISY_SEARCH 1
 
+static const float kLargeOpFactor = float(M_SQRT2);
+static const float kIntegralFactor = 2.0;
+
 // -----------------------------------------------------------------------------
 static const nsGlyphCode kNullGlyph = {{{0, 0}}, 0};
 typedef enum {eExtension_base, eExtension_variants, eExtension_parts}
   nsMathfontPrefExtension;
 
 // -----------------------------------------------------------------------------
 // nsGlyphTable is a class that provides an interface for accessing glyphs
 // of stretchy chars. It acts like a table that stores the variants of bigger
 // sizes (if any) and the partial glyphs needed to build extensible symbols.
-// An instance of nsGlyphTable is associated to one primary font. Extra glyphs
-// can be taken in other additional fonts when stretching certain characters.
-// These supplementary fonts are referred to as "external" fonts to the table.
 //
-// Bigger sizes (if any) of the char can then be retrieved with
-// BigOf(aSize). Partial glyphs can be retrieved with ElementAt()
+// Bigger sizes (if any) of the char can then be retrieved with BigOf(...).
+// Partial glyphs can be retrieved with ElementAt(...).
 //
 // A table consists of "nsGlyphCode"s which are viewed either as Unicode
-// points or as direct glyph indices, depending on the type of the table.
-// XXX The latter is not yet supported.
+// points (for nsPropertiesTable) or as direct glyph indices (for
+// nsOpenTypeTable)
+// -----------------------------------------------------------------------------
+
+class nsGlyphTable {
+public:
+  virtual ~nsGlyphTable() {}
+
+  virtual const nsAString&
+  FontNameFor(const nsGlyphCode& aGlyphCode) const = 0;
+
+  // Getters for the parts
+  virtual nsGlyphCode ElementAt(gfxContext*   aThebesContext,
+                                int32_t       aAppUnitsPerDevPixel,
+                                gfxFontGroup* aFontGroup,
+                                char16_t      aChar,
+                                bool          aVertical,
+                                uint32_t      aPosition) = 0;
+  virtual nsGlyphCode BigOf(gfxContext*   aThebesContext,
+                            int32_t       aAppUnitsPerDevPixel,
+                            gfxFontGroup* aFontGroup,
+                            char16_t      aChar,
+                            bool          aVertical,
+                            uint32_t      aSize) = 0;
+
+  // True if this table contains parts to render this char
+  virtual bool HasPartsOf(gfxContext*   aThebesContext,
+                          int32_t       aAppUnitsPerDevPixel,
+                          gfxFontGroup* aFontGroup,
+                          char16_t      aChar,
+                          bool          aVertical) = 0;
+
+  virtual gfxTextRun* MakeTextRun(gfxContext*        aThebesContext,
+                                  int32_t            aAppUnitsPerDevPixel,
+                                  gfxFontGroup*      aFontGroup,
+                                  const nsGlyphCode& aGlyph) = 0;
+protected:
+  nsGlyphTable() : mCharCache(0) {}
+  // For speedy re-use, we always cache the last data used in the table.
+  // mCharCache is the Unicode point of the last char that was queried in this
+  // table.
+  char16_t mCharCache;
+};
+
+// An instance of nsPropertiesTable is associated with one primary font. Extra
+// glyphs can be taken in other additional fonts when stretching certain
+// characters.
+// These supplementary fonts are referred to as "external" fonts to the table.
 
 // General format of MathFont Property Files from which glyph data are
 // retrieved:
 // -----------------------------------------------------------------------------
 // Each font should have its set of glyph data. For example, the glyph data for
 // the "Symbol" font and the "MT Extra" font are in "mathfontSymbol.properties"
 // and "mathfontMTExtra.properties", respectively. The mathfont property file
 // is a set of all the stretchy MathML characters that can be rendered with that
 // font using larger and/or partial glyphs. The entry of each stretchy character
 // in the mathfont property file gives, in that order, the 4 partial glyphs:
 // Top (or Left), Middle, Bottom (or Right), Glue; and the variants of bigger
 // sizes (if any).
 // A position that is not relevant to a particular character is indicated there
 // with the UNICODE REPLACEMENT CHARACTER 0xFFFD.
 // -----------------------------------------------------------------------------
 
-#define NS_TABLE_TYPE_UNICODE       0
-#define NS_TABLE_TYPE_GLYPH_INDEX   1
-
 #define NS_TABLE_STATE_ERROR       -1
 #define NS_TABLE_STATE_EMPTY        0
 #define NS_TABLE_STATE_READY        1
 
 // helper to trim off comments from data in a MathFont Property File
 static void
 Clean(nsString& aValue)
 {
@@ -93,101 +138,124 @@ LoadProperties(const nsString& aName,
   uriStr.AssignLiteral("resource://gre/res/fonts/mathfont");
   uriStr.Append(aName);
   uriStr.StripWhitespace(); // that may come from aName
   uriStr.AppendLiteral(".properties");
   return NS_LoadPersistentPropertiesFromURISpec(getter_AddRefs(aProperties), 
                                                 NS_ConvertUTF16toUTF8(uriStr));
 }
 
-// -----------------------------------------------------------------------------
-
-class nsGlyphTable {
+class nsPropertiesTable MOZ_FINAL : public nsGlyphTable {
 public:
-  explicit nsGlyphTable(const nsString& aPrimaryFontName)
-    : mFontName(1), // ensure space for primary font name.
-      mState(NS_TABLE_STATE_EMPTY),
-      mCharCache(0)
+  explicit nsPropertiesTable(const nsString& aPrimaryFontName)
+    : mFontName(1) // ensure space for primary font name.
+    , mState(NS_TABLE_STATE_EMPTY)
   {
-    MOZ_COUNT_CTOR(nsGlyphTable);
+    MOZ_COUNT_CTOR(nsPropertiesTable);
     mFontName.AppendElement(aPrimaryFontName);
   }
 
-  // not a virtual destructor: this class is not intended to be subclassed
-  ~nsGlyphTable()
+  ~nsPropertiesTable()
   {
-    MOZ_COUNT_DTOR(nsGlyphTable);
+    MOZ_COUNT_DTOR(nsPropertiesTable);
   }
 
   const nsAString& PrimaryFontName() const
   {
     return mFontName[0];
   }
 
-  const nsAString& FontNameFor(const nsGlyphCode& aGlyphCode) const
+  const nsAString&
+  FontNameFor(const nsGlyphCode& aGlyphCode) const MOZ_OVERRIDE
   {
     NS_ASSERTION(!aGlyphCode.IsGlyphID(),
-                 "nsGlyphTable can only access glyphs by Unicode code point");
+                 "nsPropertiesTable can only access glyphs by code point");
     return mFontName[aGlyphCode.font];
   }
 
-  // Getters for the parts
-  nsGlyphCode ElementAt(char16_t aChar, uint32_t aPosition);
-  nsGlyphCode BigOf(char16_t aChar, int32_t aSize) {
-    return ElementAt(aChar, 4 + aSize);
+  virtual nsGlyphCode ElementAt(gfxContext*   aThebesContext,
+                                int32_t       aAppUnitsPerDevPixel,
+                                gfxFontGroup* aFontGroup,
+                                char16_t      aChar,
+                                bool          aVertical,
+                                uint32_t      aPosition) MOZ_OVERRIDE;
+
+  virtual nsGlyphCode BigOf(gfxContext*   aThebesContext,
+                            int32_t       aAppUnitsPerDevPixel,
+                            gfxFontGroup* aFontGroup,
+                            char16_t      aChar,
+                            bool          aVertical,
+                            uint32_t      aSize) MOZ_OVERRIDE
+  {
+    return ElementAt(aThebesContext, aAppUnitsPerDevPixel, aFontGroup,
+                     aChar, aVertical, 4 + aSize);
   }
 
-  // True if this table contains parts to render this char
-  bool HasPartsOf(char16_t aChar) {
-    return (ElementAt(aChar, 0).Exists() || ElementAt(aChar, 1).Exists() ||
-            ElementAt(aChar, 2).Exists() || ElementAt(aChar, 3).Exists());
+  virtual bool HasPartsOf(gfxContext*   aThebesContext,
+                          int32_t       aAppUnitsPerDevPixel,
+                          gfxFontGroup* aFontGroup,
+                          char16_t      aChar,
+                          bool          aVertical) MOZ_OVERRIDE
+  {
+    return (ElementAt(aThebesContext, aAppUnitsPerDevPixel, aFontGroup,
+                      aChar, aVertical, 0).Exists() ||
+            ElementAt(aThebesContext, aAppUnitsPerDevPixel, aFontGroup,
+                      aChar, aVertical, 1).Exists() ||
+            ElementAt(aThebesContext, aAppUnitsPerDevPixel, aFontGroup,
+                      aChar, aVertical, 2).Exists() ||
+            ElementAt(aThebesContext, aAppUnitsPerDevPixel, aFontGroup,
+                      aChar, aVertical, 3).Exists());
   }
 
-  gfxTextRun* MakeTextRun(gfxContext*        aThebesContext,
-                          int32_t            aAppUnitsPerDevPixel,
-                          gfxFontGroup*      aFontGroup,
-                          const nsGlyphCode& aGlyph);
+  virtual gfxTextRun* MakeTextRun(gfxContext*        aThebesContext,
+                                  int32_t            aAppUnitsPerDevPixel,
+                                  gfxFontGroup*      aFontGroup,
+                                  const nsGlyphCode& aGlyph) MOZ_OVERRIDE;
 private:
 
   // mFontName[0] is the primary font associated to this table. The others 
   // are possible "external" fonts for glyphs not in the primary font
   // but which are needed to stretch certain characters in the table
   nsTArray<nsString> mFontName;
 
   // Tri-state variable for error/empty/ready
   int32_t mState;
 
   // The set of glyph data in this table, as provided by the MathFont Property
   // File
   nsCOMPtr<nsIPersistentProperties> mGlyphProperties;
 
-  // For speedy re-use, we always cache the last data used in the table.
-  // mCharCache is the Unicode point of the last char that was queried in this
-  // table. mGlyphCache is a buffer containing the glyph data associated to
-  // that char. For a property line 'key = value' in the MathFont Property File,
+  // mGlyphCache is a buffer containing the glyph data associated with
+  // mCharCache.
+  // For a property line 'key = value' in the MathFont Property File,
   // mCharCache will retain the 'key' -- which is a Unicode point, while
   // mGlyphCache will retain the 'value', which is a consecutive list of
   // nsGlyphCodes, i.e., the pairs of 'code@font' needed by the char -- in
   // which 'code@0' can be specified
   // without the optional '@0'. However, to ease subsequent processing,
   // mGlyphCache excludes the '@' symbol and explicitly inserts all optional '0'
   // that indicates the primary font identifier. Specifically therefore, the
   // k-th glyph is characterized by :
   // 1) mGlyphCache[3*k],mGlyphCache[3*k+1] : its Unicode point
   // 2) mGlyphCache[3*k+2] : the numeric identifier of the font where it comes
   // from.
   // A font identifier of '0' means the default primary font associated to this
   // table. Other digits map to the "external" fonts that may have been
   // specified in the MathFont Property File.
   nsString  mGlyphCache;
-  char16_t mCharCache;
 };
 
+/* virtual */
 nsGlyphCode
-nsGlyphTable::ElementAt(char16_t aChar, uint32_t aPosition)
+nsPropertiesTable::ElementAt(gfxContext*   /* aThebesContext */,
+                             int32_t       /* aAppUnitsPerDevPixel */,
+                             gfxFontGroup* /* aFontGroup */,
+                             char16_t      aChar,
+                             bool          /* aVertical */,
+                             uint32_t      aPosition)
 {
   if (mState == NS_TABLE_STATE_ERROR) return kNullGlyph;
   // Load glyph properties if this is the first time we have been here
   if (mState == NS_TABLE_STATE_EMPTY) {
     nsresult rv = LoadProperties(mFontName[0], mGlyphProperties);
 #ifdef DEBUG
     nsAutoCString uriStr;
     uriStr.AssignLiteral("resource://gre/res/fonts/mathfont");
@@ -281,42 +349,230 @@ nsGlyphTable::ElementAt(char16_t aChar, 
   if (index+2 >= mGlyphCache.Length()) return kNullGlyph;
   nsGlyphCode ch;
   ch.code[0] = mGlyphCache.CharAt(index);
   ch.code[1] = mGlyphCache.CharAt(index + 1);
   ch.font = mGlyphCache.CharAt(index + 2);
   return ch.code[0] == char16_t(0xFFFD) ? kNullGlyph : ch;
 }
 
+/* virtual */
 gfxTextRun*
-nsGlyphTable::MakeTextRun(gfxContext*        aThebesContext,
-                          int32_t            aAppUnitsPerDevPixel,
-                          gfxFontGroup*      aFontGroup,
-                          const nsGlyphCode& aGlyph)
+nsPropertiesTable::MakeTextRun(gfxContext*        aThebesContext,
+                               int32_t            aAppUnitsPerDevPixel,
+                               gfxFontGroup*      aFontGroup,
+                               const nsGlyphCode& aGlyph)
 {
-  NS_ASSERTION(!aGlyph.IsGlyphID(), "not yet implemented");
+  NS_ASSERTION(!aGlyph.IsGlyphID(),
+               "nsPropertiesTable can only access glyphs by code point");
   return aFontGroup->
     MakeTextRun(aGlyph.code, aGlyph.Length(), aThebesContext,
                 aAppUnitsPerDevPixel, 0);
 }
 
+// An instance of nsOpenTypeTable is associated with one gfxFontEntry that
+// corresponds to an Open Type font with a MATH table. All the glyphs come from
+// the same font and the calls to access size variants and parts are directly
+// forwarded to the gfx code.
+class nsOpenTypeTable MOZ_FINAL : public nsGlyphTable {
+public:
+  ~nsOpenTypeTable()
+  {
+    MOZ_COUNT_DTOR(nsOpenTypeTable);
+  }
+
+  virtual nsGlyphCode ElementAt(gfxContext*   aThebesContext,
+                                int32_t       aAppUnitsPerDevPixel,
+                                gfxFontGroup* aFontGroup,
+                                char16_t      aChar,
+                                bool          aVertical,
+                                uint32_t      aPosition) MOZ_OVERRIDE;
+  virtual nsGlyphCode BigOf(gfxContext*   aThebesContext,
+                            int32_t       aAppUnitsPerDevPixel,
+                            gfxFontGroup* aFontGroup,
+                            char16_t      aChar,
+                            bool          aVertical,
+                            uint32_t      aSize) MOZ_OVERRIDE;
+  virtual bool HasPartsOf(gfxContext*   aThebesContext,
+                          int32_t       aAppUnitsPerDevPixel,
+                          gfxFontGroup* aFontGroup,
+                          char16_t      aChar,
+                          bool          aVertical) MOZ_OVERRIDE;
+
+  const nsAString&
+  FontNameFor(const nsGlyphCode& aGlyphCode) const MOZ_OVERRIDE {
+    NS_ASSERTION(aGlyphCode.IsGlyphID(),
+                 "nsOpenTypeTable can only access glyphs by id");
+    return mFontEntry->FamilyName();
+  }
+
+  virtual gfxTextRun* MakeTextRun(gfxContext*        aThebesContext,
+                                  int32_t            aAppUnitsPerDevPixel,
+                                  gfxFontGroup*      aFontGroup,
+                                  const nsGlyphCode& aGlyph) MOZ_OVERRIDE;
+
+  // This returns a new OpenTypeTable instance to give access to OpenType MATH
+  // table or nullptr if the font does not have such table. Ownership is passed
+  // to the caller.
+  static nsOpenTypeTable* Create(gfxFont* aFont)
+  {
+    if (!aFont->GetFontEntry()->TryGetMathTable(aFont)) {
+      return nullptr;
+    }
+    return new nsOpenTypeTable(aFont->GetFontEntry());
+  }
+
+private:
+  nsRefPtr<gfxFontEntry> mFontEntry;
+  uint32_t mGlyphID;
+
+  explicit nsOpenTypeTable(gfxFontEntry* aFontEntry)
+    : mFontEntry(aFontEntry) {
+    MOZ_COUNT_CTOR(nsOpenTypeTable);
+  }
+
+  void UpdateCache(gfxContext*   aThebesContext,
+                   int32_t       aAppUnitsPerDevPixel,
+                   gfxFontGroup* aFontGroup,
+                   char16_t      aChar);
+};
+
+void
+nsOpenTypeTable::UpdateCache(gfxContext*   aThebesContext,
+                             int32_t       aAppUnitsPerDevPixel,
+                             gfxFontGroup* aFontGroup,
+                             char16_t      aChar)
+{
+  if (mCharCache != aChar) {
+    nsAutoPtr<gfxTextRun> textRun;
+    textRun = aFontGroup->
+      MakeTextRun(&aChar, 1, aThebesContext, aAppUnitsPerDevPixel, 0);
+    const gfxTextRun::CompressedGlyph& data = textRun->GetCharacterGlyphs()[0];
+    if (data.IsSimpleGlyph()) {
+      mGlyphID = data.GetSimpleGlyph();
+    } else if (data.GetGlyphCount() == 1) {
+      mGlyphID = textRun->GetDetailedGlyphs(0)->mGlyphID;
+    } else {
+      mGlyphID = 0;
+    }
+    mCharCache = aChar;
+  }
+}
+
+/* virtual */
+nsGlyphCode
+nsOpenTypeTable::ElementAt(gfxContext*   aThebesContext,
+                           int32_t       aAppUnitsPerDevPixel,
+                           gfxFontGroup* aFontGroup,
+                           char16_t      aChar,
+                           bool          aVertical,
+                           uint32_t      aPosition)
+{
+  UpdateCache(aThebesContext, aAppUnitsPerDevPixel, aFontGroup, aChar);
+
+  uint32_t parts[4];
+  if (!mFontEntry->GetMathVariantsParts(mGlyphID, aVertical, parts)) {
+    return kNullGlyph;
+  }
+
+  uint32_t glyphID = parts[aPosition];
+  if (!glyphID) {
+    return kNullGlyph;
+  }
+  nsGlyphCode glyph;
+  glyph.glyphID = glyphID;
+  glyph.font = -1;
+  return glyph;
+}
+
+/* virtual */
+nsGlyphCode
+nsOpenTypeTable::BigOf(gfxContext*   aThebesContext,
+                       int32_t       aAppUnitsPerDevPixel,
+                       gfxFontGroup* aFontGroup,
+                       char16_t      aChar,
+                       bool          aVertical,
+                       uint32_t      aSize)
+{
+  UpdateCache(aThebesContext, aAppUnitsPerDevPixel, aFontGroup, aChar);
+
+  uint32_t glyphID =
+    mFontEntry->GetMathVariantsSize(mGlyphID, aVertical, aSize);
+  if (!glyphID) {
+    return kNullGlyph;
+  }
+
+  nsGlyphCode glyph;
+  glyph.glyphID = glyphID;
+  glyph.font = -1;
+  return glyph;
+}
+
+/* virtual */
+bool
+nsOpenTypeTable::HasPartsOf(gfxContext*   aThebesContext,
+                            int32_t       aAppUnitsPerDevPixel,
+                            gfxFontGroup* aFontGroup,
+                            char16_t      aChar,
+                            bool          aVertical)
+{
+  UpdateCache(aThebesContext, aAppUnitsPerDevPixel, aFontGroup, aChar);
+
+  uint32_t parts[4];
+  if (!mFontEntry->GetMathVariantsParts(mGlyphID, aVertical, parts)) {
+    return false;
+  }
+
+  return parts[0] || parts[1] || parts[2] || parts[3];
+}
+
+/* virtual */
+gfxTextRun*
+nsOpenTypeTable::MakeTextRun(gfxContext*        aThebesContext,
+                             int32_t            aAppUnitsPerDevPixel,
+                             gfxFontGroup*      aFontGroup,
+                             const nsGlyphCode& aGlyph)
+{
+  NS_ASSERTION(aGlyph.IsGlyphID(),
+               "nsOpenTypeTable can only access glyphs by id");
+
+  gfxTextRunFactory::Parameters params = {
+    aThebesContext, nullptr, nullptr, nullptr, 0, aAppUnitsPerDevPixel
+  };
+  gfxTextRun* textRun = gfxTextRun::Create(&params, 1, aFontGroup, 0);
+  textRun->AddGlyphRun(aFontGroup->GetFontAt(0), gfxTextRange::kFontGroup, 0,
+                       false);
+  gfxTextRun::DetailedGlyph detailedGlyph;
+  detailedGlyph.mGlyphID = aGlyph.glyphID;
+  detailedGlyph.mAdvance =
+    NSToCoordRound(aAppUnitsPerDevPixel *
+                   aFontGroup->GetFontAt(0)->
+                   GetGlyphHAdvance(aThebesContext, aGlyph.glyphID));
+  detailedGlyph.mXOffset = detailedGlyph.mYOffset = 0;
+  gfxShapedText::CompressedGlyph g;
+  g.SetComplex(true, true, 1);
+  textRun->SetGlyphs(0, g, &detailedGlyph);
+
+  return textRun;
+}
+
 // -----------------------------------------------------------------------------
 // This is the list of all the applicable glyph tables.
 // We will maintain a single global instance that will only reveal those
 // glyph tables that are associated to fonts currently installed on the
 // user' system. The class is an XPCOM shutdown observer to allow us to
 // free its allocated data at shutdown
 
 class nsGlyphTableList : public nsIObserver
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
-  nsGlyphTable mUnicodeTable;
+  nsPropertiesTable mUnicodeTable;
 
   nsGlyphTableList()
     : mUnicodeTable(NS_LITERAL_STRING("Unicode"))
   {
     MOZ_COUNT_CTOR(nsGlyphTableList);
   }
 
   virtual ~nsGlyphTableList()
@@ -331,25 +587,24 @@ public:
   nsGlyphTable*
   AddGlyphTable(const nsString& aPrimaryFontName);
 
   // Find the glyph table in the list corresponding to the given font family.
   nsGlyphTable*
   GetGlyphTableFor(const nsAString& aFamily);
 
 private:
-  nsGlyphTable* TableAt(int32_t aIndex) {
-    return &mTableList.ElementAt(aIndex);
+  nsPropertiesTable* PropertiesTableAt(int32_t aIndex) {
+    return &mPropertiesTableList.ElementAt(aIndex);
   }
-  int32_t Count() {
-    return mTableList.Length();
+  int32_t PropertiesTableCount() {
+    return mPropertiesTableList.Length();
   }
-
   // List of glyph tables;
-  nsTArray<nsGlyphTable> mTableList;
+  nsTArray<nsPropertiesTable> mPropertiesTableList;
 };
 
 NS_IMPL_ISUPPORTS1(nsGlyphTableList, nsIObserver)
 
 // -----------------------------------------------------------------------------
 // Here is the global list of applicable glyph tables that we will be using
 static nsGlyphTableList* gGlyphTableList = nullptr;
 
@@ -400,25 +655,25 @@ nsGlyphTable*
 nsGlyphTableList::AddGlyphTable(const nsString& aPrimaryFontName)
 {
   // See if there is already a special table for this family.
   nsGlyphTable* glyphTable = GetGlyphTableFor(aPrimaryFontName);
   if (glyphTable != &mUnicodeTable)
     return glyphTable;
 
   // allocate a table
-  glyphTable = mTableList.AppendElement(aPrimaryFontName);
+  glyphTable = mPropertiesTableList.AppendElement(aPrimaryFontName);
   return glyphTable;
 }
 
 nsGlyphTable*
 nsGlyphTableList::GetGlyphTableFor(const nsAString& aFamily)
 {
-  for (int32_t i = 0; i < Count(); i++) {
-    nsGlyphTable* glyphTable = TableAt(i);
+  for (int32_t i = 0; i < PropertiesTableCount(); i++) {
+    nsPropertiesTable* glyphTable = PropertiesTableAt(i);
     const nsAString& fontName = glyphTable->PrimaryFontName();
     // TODO: would be nice to consider StripWhitespace and other aliasing
     if (fontName.Equals(aFamily, nsCaseInsensitiveStringComparator())) {
       return glyphTable;
     }
   }
   // Fall back to default Unicode table
   return &mUnicodeTable;
@@ -925,18 +1180,22 @@ public:
       mTryVariants(true),
       mTryParts(true),
       mGlyphFound(aGlyphFound) {}
 
   static bool
   EnumCallback(const nsString& aFamily, bool aGeneric, void *aData);
 
 private:
-  bool TryVariants(nsGlyphTable* aGlyphTable, const nsAString& aFamily);
-  bool TryParts(nsGlyphTable* aGlyphTable, const nsAString& aFamily);
+  bool TryVariants(nsGlyphTable* aGlyphTable,
+                   nsRefPtr<gfxFontGroup>* aFontGroup,
+                   const nsAString& aFamily);
+  bool TryParts(nsGlyphTable* aGlyphTable,
+                nsRefPtr<gfxFontGroup>* aFontGroup,
+                const nsAString& aFamily);
 
   nsMathMLChar* mChar;
   nsPresContext* mPresContext;
   gfxContext* mThebesContext;
   const nsStretchDirection mDirection;
   const nscoord mTargetSize;
   const uint32_t mStretchHint;
   nsBoundingMetrics& mBoundingMetrics;
@@ -952,60 +1211,111 @@ private:
   bool&       mGlyphFound;
 };
 
 
 // 2. See if there are any glyphs of the appropriate size.
 // Returns true if the size is OK, false to keep searching.
 // Always updates the char if a better match is found.
 bool
-nsMathMLChar::StretchEnumContext::TryVariants(nsGlyphTable*    aGlyphTable,
-                                              const nsAString& aFamily)
+nsMathMLChar::
+StretchEnumContext::TryVariants(nsGlyphTable* aGlyphTable,
+                                nsRefPtr<gfxFontGroup>* aFontGroup,
+                                const nsAString& aFamily)
 {
   // Use our stretchy style context now that stretching is in progress
   nsStyleContext *sc = mChar->mStyleContext;
   nsFont font = sc->StyleFont()->mFont;
-  // Ensure SetFontFamily will set the font
-  font.name.Truncate();
 
   bool isVertical = (mDirection == NS_STRETCH_DIRECTION_VERTICAL);
+  nscoord oneDevPixel = mPresContext->AppUnitsPerDevPixel();
+  char16_t uchar = mChar->mData[0];
   bool largeop = (NS_STRETCH_LARGEOP & mStretchHint) != 0;
   bool largeopOnly =
     largeop && (NS_STRETCH_VARIABLE_MASK & mStretchHint) == 0;
   bool maxWidth = (NS_STRETCH_MAXWIDTH & mStretchHint) != 0;
 
   nscoord bestSize =
     isVertical ? mBoundingMetrics.ascent + mBoundingMetrics.descent
                : mBoundingMetrics.rightBearing - mBoundingMetrics.leftBearing;
   bool haveBetter = false;
 
   // start at size = 1 (size = 0 is the char at its normal size)
   int32_t size = 1;
+  nsGlyphCode ch;
+  nscoord displayOperatorMinHeight = 0;
+  if (largeopOnly) {
+    NS_ASSERTION(isVertical, "Stretching should be in the vertical direction");
+    ch = aGlyphTable->BigOf(mThebesContext, oneDevPixel, *aFontGroup, uchar,
+                            isVertical, 0);
+    if (ch.IsGlyphID()) {
+      gfxFont* mathFont = aFontGroup->get()->GetFontAt(0);
+      // For OpenType MATH fonts, we will rely on the DisplayOperatorMinHeight
+      // to select the right size variant. Note that the value is sometimes too
+      // small so we use kLargeOpFactor/kIntegralFactor as a minimum value.
+      displayOperatorMinHeight =
+        NSToCoordRound(mathFont->GetFontEntry()->
+                       GetMathConstant(gfxFontEntry::DisplayOperatorMinHeight) *
+                       mathFont->GetAdjustedSize() * oneDevPixel);
+      nsAutoPtr<gfxTextRun> textRun;
+      textRun = aGlyphTable->MakeTextRun(mThebesContext, oneDevPixel,
+                                         *aFontGroup, ch);
+      nsBoundingMetrics bm = MeasureTextRun(mThebesContext, textRun);
+      float largeopFactor = kLargeOpFactor;
+      if (NS_STRETCH_INTEGRAL & mStretchHint) {
+        // integrals are drawn taller
+        largeopFactor = kIntegralFactor;
+      }
+      nscoord minHeight = largeopFactor * (bm.ascent + bm.descent);
+      if (displayOperatorMinHeight < minHeight) {
+        displayOperatorMinHeight = minHeight;
+      }
+    }
+  }
 #ifdef NOISY_SEARCH
   printf("  searching in %s ...\n",
            NS_LossyConvertUTF16toASCII(aFamily).get());
 #endif
-
-  nsGlyphCode ch;
-  nsRefPtr<gfxFontGroup> fontGroup;
-  while ((ch = aGlyphTable->BigOf(mChar->mData[0], size)).Exists()) {
+  while ((ch = aGlyphTable->BigOf(mThebesContext, oneDevPixel, *aFontGroup,
+                                  uchar, isVertical, size)).Exists()) {
 
     if (!mChar->SetFontFamily(mPresContext, aGlyphTable, ch, aFamily, font,
-                              &fontGroup)) {
+                              aFontGroup)) {
       // if largeopOnly is set, break now
       if (largeopOnly) break;
       ++size;
       continue;
     }
 
     nsAutoPtr<gfxTextRun> textRun;
-    textRun = aGlyphTable->MakeTextRun(mThebesContext,
-                                       mPresContext->AppUnitsPerDevPixel(),
-                                       fontGroup, ch);
+    textRun = aGlyphTable->MakeTextRun(mThebesContext, oneDevPixel,
+                                       *aFontGroup, ch);
     nsBoundingMetrics bm = MeasureTextRun(mThebesContext, textRun);
+    if (ch.IsGlyphID()) {
+      gfxFont* mathFont = aFontGroup->get()->GetFontAt(0);
+      if (mathFont->GetFontEntry()->TryGetMathTable(mathFont)) {
+        // MeasureTextRun should have set the advance width to the right
+        // bearing for OpenType MATH fonts. We now subtract the italic
+        // correction, so that nsMathMLmmultiscripts will place the scripts
+        // correctly.
+        // Note that STIX-Word does not provide italic corrections but its
+        // advance widths do not match right bearings.
+        // (http://sourceforge.net/p/stixfonts/tracking/50/)
+        gfxFloat italicCorrection;
+        if (mathFont->GetFontEntry()->
+            GetMathItalicsCorrection(ch.glyphID, &italicCorrection)) {
+          bm.width -=
+            NSToCoordRound(italicCorrection *
+                           mathFont->GetAdjustedSize() * oneDevPixel);
+          if (bm.width < 0) {
+            bm.width = 0;
+          }
+        }
+      }
+    }
 
     nscoord charSize =
       isVertical ? bm.ascent + bm.descent
       : bm.rightBearing - bm.leftBearing;
 
     if (largeopOnly ||
         IsSizeBetter(charSize, bestSize, mTargetSize, mStretchHint)) {
       mGlyphFound = true;
@@ -1035,64 +1345,64 @@ nsMathMLChar::StretchEnumContext::TryVar
     else {
 #ifdef NOISY_SEARCH
       printf("    size:%d Rejected!\n", size);
 #endif
       if (haveBetter)
         break; // Not making an futher progress, stop searching
     }
 
-    // if largeopOnly is set, break now
-    if (largeopOnly) break;
+    // If this a largeop only operator, we stop if the glyph is large enough.
+    if (largeopOnly && (bm.ascent + bm.descent) >= displayOperatorMinHeight) {
+      break;
+    }
     ++size;
   }
 
   return haveBetter &&
     (largeopOnly ||
      IsSizeOK(mPresContext, bestSize, mTargetSize, mStretchHint));
 }
 
 // 3. Build by parts.
 // Returns true if the size is OK, false to keep searching.
 // Always updates the char if a better match is found.
 bool
-nsMathMLChar::StretchEnumContext::TryParts(nsGlyphTable*    aGlyphTable,
+nsMathMLChar::StretchEnumContext::TryParts(nsGlyphTable* aGlyphTable,
+                                           nsRefPtr<gfxFontGroup>* aFontGroup,
                                            const nsAString& aFamily)
 {
-  if (!aGlyphTable->HasPartsOf(mChar->mData[0]))
-    return false; // to next table
-
-  // See if the parts of this table fit in the desired space //////////////////
-
   // Use our stretchy style context now that stretching is in progress
   nsFont font = mChar->mStyleContext->StyleFont()->mFont;
-  // Ensure SetFontFamily will set the font
-  font.name.Truncate();
 
   // Compute the bounding metrics of all partial glyphs
   nsAutoPtr<gfxTextRun> textRun[4];
   nsGlyphCode chdata[4];
   nsBoundingMetrics bmdata[4];
   nscoord sizedata[4];
 
   bool isVertical = (mDirection == NS_STRETCH_DIRECTION_VERTICAL);
+  nscoord oneDevPixel = mPresContext->AppUnitsPerDevPixel();
+  char16_t uchar = mChar->mData[0];
   bool maxWidth = (NS_STRETCH_MAXWIDTH & mStretchHint) != 0;
-  nsRefPtr<gfxFontGroup> fontGroup;
+  if (!aGlyphTable->HasPartsOf(mThebesContext, oneDevPixel, *aFontGroup,
+                               uchar, isVertical))
+    return false; // to next table
 
   for (int32_t i = 0; i < 4; i++) {
-    nsGlyphCode ch = aGlyphTable->ElementAt(mChar->mData[0], i);
+    nsGlyphCode ch = aGlyphTable->ElementAt(mThebesContext, oneDevPixel,
+                                            *aFontGroup, uchar, isVertical, i);
     chdata[i] = ch;
     if (ch.Exists()) {
       if (!mChar->SetFontFamily(mPresContext, aGlyphTable, ch, aFamily, font,
-                                &fontGroup))
+                                aFontGroup))
         return false;
 
-      textRun[i] = aGlyphTable->MakeTextRun(mThebesContext,
-                                            mPresContext->AppUnitsPerDevPixel(),
-                                            fontGroup, ch);
+      textRun[i] = aGlyphTable->MakeTextRun(mThebesContext, oneDevPixel,
+                                            *aFontGroup, ch);
       nsBoundingMetrics bm = MeasureTextRun(mThebesContext, textRun[i]);
 
       // TODO: For the generic Unicode table, ideally we should check that the
       // glyphs are actually found and that they each come from the same
       // font.
       bmdata[i] = bm;
       sizedata[i] = isVertical ? bm.ascent + bm.descent
                                : bm.rightBearing - bm.leftBearing;
@@ -1202,48 +1512,61 @@ nsMathMLChar::StretchEnumContext::TryPar
 
 // This is called for each family, whether it exists or not
 bool
 nsMathMLChar::StretchEnumContext::EnumCallback(const nsString& aFamily,
                                                bool aGeneric, void *aData)
 {
   StretchEnumContext* context = static_cast<StretchEnumContext*>(aData);
 
-  // See if there is a special table for the family, but always use the
-  // Unicode table for generic fonts.
-  nsGlyphTable* glyphTable = aGeneric ?
-    &gGlyphTableList->mUnicodeTable :
-    gGlyphTableList->GetGlyphTableFor(aFamily);
-
-  if (context->mTablesTried.Contains(glyphTable))
-    return true; // already tried this one
-
   // Check font family if it is not a generic one
   // We test with the kNullGlyph
   nsStyleContext *sc = context->mChar->mStyleContext;
   nsFont font = sc->StyleFont()->mFont;
   nsRefPtr<gfxFontGroup> fontGroup;
   if (!aGeneric && !context->mChar->SetFontFamily(context->mPresContext,
                                                   nullptr, kNullGlyph, aFamily,
                                                   font, &fontGroup))
      return true; // Could not set the family
 
-  // Now see if the table has a glyph that matches the container
+  // Determine the glyph table to use for this font.
+  nsAutoPtr<nsOpenTypeTable> openTypeTable;
+  nsGlyphTable* glyphTable;
+  if (aGeneric) {
+    // This is a generic font, use the Unicode table.
+    glyphTable = &gGlyphTableList->mUnicodeTable;
+  } else {
+    // If the font contains an Open Type MATH table, use it.
+    openTypeTable = nsOpenTypeTable::Create(fontGroup->GetFontAt(0));
+    if (openTypeTable) {
+      glyphTable = openTypeTable;
+    } else {
+      // Otherwise try to find a .properties file corresponding to that font
+      // family or fallback to the Unicode table.
+      glyphTable = gGlyphTableList->GetGlyphTableFor(aFamily);
+    }
+  }
 
-  // Only try this table once.
-  context->mTablesTried.AppendElement(glyphTable);
+  if (!openTypeTable) {
+    if (context->mTablesTried.Contains(glyphTable))
+      return true; // already tried this one
+    
+    // Only try this table once.
+    context->mTablesTried.AppendElement(glyphTable);
+  }
 
   // If the unicode table is being used, then search all font families.  If a
   // special table is being used then the font in this family should have the
   // specified glyphs.
   const nsAString& family = glyphTable == &gGlyphTableList->mUnicodeTable ?
     context->mFamilies : aFamily;
 
-  if((context->mTryVariants && context->TryVariants(glyphTable, family)) ||
-     (context->mTryParts && context->TryParts(glyphTable, family)))
+  if((context->mTryVariants &&
+      context->TryVariants(glyphTable, &fontGroup, family)) ||
+     (context->mTryParts && context->TryParts(glyphTable, &fontGroup, family)))
     return false; // no need to continue
 
   return true; // true means continue
 }
 
 nsresult
 nsMathMLChar::StretchInternal(nsPresContext*           aPresContext,
                               gfxContext*              aThebesContext,
@@ -1469,17 +1792,17 @@ nsMathMLChar::StretchInternal(nsPresCont
       }
     }
   }
 
   // We do not have a char variant for this largeop in display mode, so we
   // apply a scale transform to the base char.
   if (!glyphFound && largeop) {
     float scale;
-    float largeopFactor = float(M_SQRT2);
+    float largeopFactor = kLargeOpFactor;
 
     // increase the width if it is not largeopFactor times larger
     // than the initial one.
     if ((aDesiredStretchSize.rightBearing - aDesiredStretchSize.leftBearing) <
         largeopFactor * (initialSize.rightBearing - initialSize.leftBearing)) {
       scale = (largeopFactor *
                (initialSize.rightBearing - initialSize.leftBearing)) /
         (aDesiredStretchSize.rightBearing - aDesiredStretchSize.leftBearing);
@@ -1490,17 +1813,17 @@ nsMathMLChar::StretchInternal(nsPresCont
       aDesiredStretchSize.rightBearing *= scale;
       aDesiredStretchSize.width *= scale;
     }
 
     // increase the height if it is not largeopFactor times larger
     // than the initial one.
     if (NS_STRETCH_INTEGRAL & aStretchHint) {
       // integrals are drawn taller
-      largeopFactor = 2.0;
+      largeopFactor = kIntegralFactor;
     }
     if ((aDesiredStretchSize.ascent + aDesiredStretchSize.descent) <
         largeopFactor * (initialSize.ascent + initialSize.descent)) {
       scale = (largeopFactor *
                (initialSize.ascent + initialSize.descent)) /
         (aDesiredStretchSize.ascent + aDesiredStretchSize.descent);
       if (!maxWidth) {
         mScaleY *= scale;
--- a/layout/mathml/nsMathMLChar.h
+++ b/layout/mathml/nsMathMLChar.h
@@ -194,16 +194,18 @@ public:
   // the Get/Set AdditionalStyleContext() APIs. Owners of MathMLChars
   // should honor these APIs.
   nsStyleContext* GetStyleContext() const;
 
   void SetStyleContext(nsStyleContext* aStyleContext);
 
 protected:
   friend class nsGlyphTable;
+  friend class nsPropertiesTable;
+  friend class nsOpenTypeTable;
   nsString           mData;
 
 private:
   nsRect             mRect;
   nsStretchDirection mDirection;
   nsBoundingMetrics  mBoundingMetrics;
   nsStyleContext*    mStyleContext;
   // mGlyphs/mBmData are arrays describing the glyphs used to draw the operator.
--- a/layout/reftests/first-letter/reftest.list
+++ b/layout/reftests/first-letter/reftest.list
@@ -30,17 +30,17 @@ random-if(d2d) == dynamic-2.html dynamic
 == 23605-2.html 23605-2-ref.html
 == 23605-3.html 23605-3-ref.html
 == 23605-4.html 23605-4-ref.html
 == 23605-5.html 23605-5-ref.html
 == 23605-6.html 23605-6-ref.html
 != 229764-1.html 229764-ref.html
 == 229764-2.html 229764-ref.html
 == 329069-1.html 329069-1-ref.html
-== 329069-2.html 329069-2-ref.html
+fails-if(Android&&AndroidVersion==10) == 329069-2.html 329069-2-ref.html # Bug 999139
 == 329069-3.html 329069-3-ref.html
 == 329069-4.html 329069-4-ref.html
 fails-if(!cocoaWidget) == 329069-5.html 329069-5-ref.html # bug 603710
 == 342120-1.xhtml 342120-1-ref.xhtml
 == 379799-1.html 379799-1-ref.html
 == 399941-1.html 399941-1-ref.html
 == 399941-2.html 399941-2-ref.html
 == 399941-3.html 399941-3-ref.html
--- a/layout/reftests/font-face/reftest.list
+++ b/layout/reftests/font-face/reftest.list
@@ -143,18 +143,18 @@ HTTP(..) != font-familiy-whitespace-1.ht
 skip-if(B2G) HTTP(..) == ivs-1.html ivs-1-ref.html # bug 773482
 skip-if(B2G) HTTP(..) == cjkcisvs-1.html cjkcisvs-1-ref.html
 
 skip-if(B2G) HTTP(..) == missing-names.html missing-names-ref.html # bug 773482
 
 # Tests for bug 670900 - handling of 404 (not found) error in @font-face URL
 # (using Chunkfive font data returned from a .sjs file)
 HTTP(..) == font-error-404-1.html font-error-404-1-ref.html # HTTP status 404, don't load
-skip-if(B2G) fails-if(Android&&AndroidVersion==17) HTTP(..) == font-error-404-2.html font-error-404-2-ref.html # HTTP status 200, load # bug 773482
-fails-if(Android&&AndroidVersion==17) HTTP(..) != font-error-404-1.html font-error-404-2.html # sanity-check that the results differ
+skip-if(B2G) fails-if(Android&&(AndroidVersion==10||AndroidVersion==17)) HTTP(..) == font-error-404-2.html font-error-404-2-ref.html # HTTP status 200, load # bug 773482
+fails-if(Android&&(AndroidVersion==10||AndroidVersion==17)) HTTP(..) != font-error-404-1.html font-error-404-2.html # sanity-check that the results differ
 
 # Font load redirection
 HTTP(..) == font-redirect.html order-1-ref.html
 
 # Tests for potential regressions from bug 879963
 == dynamic-duplicate-rule-1a.html dynamic-duplicate-rule-1-ref.html
 == dynamic-duplicate-rule-1b.html dynamic-duplicate-rule-1-ref.html
 == dynamic-duplicate-rule-1c.html dynamic-duplicate-rule-1-ref.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/fonts/math/README
@@ -0,0 +1,6 @@
+The fonts in this directory are autogenerated with FontForge using the Python
+script generate.py. See the comments in that file for more information on how
+to run the script.
+
+These fonts are intended to test the The MATH table and OpenType Features used
+in MathML. See layout/reftests/mathml/
new file mode 100644
--- /dev/null
+++ b/layout/reftests/fonts/math/generate.py
@@ -0,0 +1,231 @@
+#!/usr/bin/python
+# vim: set shiftwidth=4 tabstop=8 autoindent expandtab:
+# 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/.
+
+# For general fontforge documentation, see:
+#   http://fontforge.sourceforge.net/
+# For fontforge scripting documentation, see:
+#   http://fontforge.sourceforge.net/scripting-tutorial.html
+#   http://fontforge.sourceforge.net/scripting.html
+# and most importantly:
+#   http://fontforge.sourceforge.net/python.html
+
+# To install what you need, on Ubuntu,
+#   sudo apt-get install python-fontforge
+
+from __future__ import print_function
+import fontforge
+
+em = 1000
+
+def newMathFont(aName):
+    print("Generating %s.otf..." % aName, end="")
+    mathFont = fontforge.font()
+    mathFont.fontname = aName
+    mathFont.familyname = aName
+    mathFont.fullname = aName
+    mathFont.copyright = "Copyright (c) 2014 Mozilla Corporation"
+    mathFont.encoding = "UnicodeFull"
+
+    # Create a space character. Also force the creation of some MATH subtables
+    # so that OTS will not reject the MATH table.
+    g = mathFont.createChar(ord(" "), "space")
+    g.width = em
+    g.italicCorrection = 0
+    g.topaccent = 0
+    g.mathKern.bottomLeft = tuple([(0,0)])
+    g.mathKern.bottomRight = tuple([(0,0)])
+    g.mathKern.topLeft = tuple([(0,0)])
+    g.mathKern.topRight = tuple([(0,0)])
+    mathFont[ord(" ")].horizontalVariants = "space"
+    mathFont[ord(" ")].verticalVariants = "space"
+    return mathFont
+
+def saveMathFont(aFont):
+    aFont.em = em
+    aFont.ascent = aFont.descent = em/2
+    aFont.hhea_ascent = aFont.os2_typoascent = aFont.os2_winascent = em/2
+    aFont.descent = aFont.hhea_descent = em/2
+    aFont.os2_typodescent = aFont.os2_windescent = em/2
+    aFont.hhea_ascent_add = aFont.hhea_descent_add = 0
+    aFont.os2_typoascent_add = aFont.os2_typodescent_add = 0
+    aFont.os2_winascent_add = aFont.os2_windescent_add = 0
+    aFont.os2_use_typo_metrics = True
+    aFont.generate(aFont.fontname + ".otf")
+    print(" done.")
+
+################################################################################
+# Glyph variants and constructions
+f = newMathFont("stretchy")
+nvariants = 3
+
+# Draw boxes for the size variants and glues
+for i in range(0, nvariants):
+    s = em * (i + 1)
+
+    g = f.createChar(-1, "h%d" % i)
+    p = g.glyphPen()
+    p.moveTo(0, -em)
+    p.lineTo(0, em)
+    p.lineTo(s, em)
+    p.lineTo(s, -em)
+    p.closePath()
+    g.width = s
+
+    g = f.createChar(-1, "v%d" % i)
+    p = g.glyphPen()
+    p.moveTo(0, 0)
+    p.lineTo(0, s)
+    p.lineTo(2 * em, s)
+    p.lineTo(2 * em, 0)
+    p.closePath();
+    g.width = 2 * em
+
+# Draw some pieces for stretchy operators
+s = em * nvariants
+
+g = f.createChar(-1, "left")
+p = g.glyphPen()
+p.moveTo(0, -2 * em)
+p.lineTo(0, 2 * em)
+p.lineTo(s, em)
+p.lineTo(s, -em)
+p.closePath();
+g.width = s
+
+g = f.createChar(-1, "right")
+p = g.glyphPen()
+p.moveTo(0, -em)
+p.lineTo(0, em)
+p.lineTo(s, 2 * em)
+p.lineTo(s, -2 * em)
+p.closePath();
+g.width = s
+
+g = f.createChar(-1, "hmid")
+p = g.glyphPen()
+p.moveTo(0, -em)
+p.lineTo(0, em)
+p.lineTo(s, 2 * em)
+p.lineTo(2 * s, em)
+p.lineTo(2 * s, -em)
+p.lineTo(s, -2 * em)
+p.closePath();
+g.width = 2 * s
+
+g = f.createChar(-1, "bottom")
+p = g.glyphPen()
+p.moveTo(0, 0)
+p.lineTo(0, s)
+p.lineTo(2 * em, s)
+p.lineTo(4 * em, 0)
+p.closePath();
+g.width = 4 * em
+
+g = f.createChar(-1, "top")
+p = g.glyphPen()
+p.moveTo(0, 0)
+p.lineTo(4 * em, 0)
+p.lineTo(2 * em, -s)
+p.lineTo(0, -s)
+p.closePath();
+g.width = 4 * em
+
+g = f.createChar(-1, "vmid")
+p = g.glyphPen()
+p.moveTo(0, s)
+p.lineTo(2 * em, s)
+p.lineTo(4 * em, 0)
+p.lineTo(2 * em, -s)
+p.lineTo(0, -s)
+p.closePath();
+g.width = 3 * em
+
+# Create small rectangle of various size for some exotic arrows that are
+# unlikely to be stretchable with standard fonts.
+hstretchy = [
+    0x219C, # leftwards wave arrow
+    0x219D, # rightwards wave arrow
+    0x219E, # leftwards two headed arrow
+    0x21A0, # rightwards two headed arrow
+    0x21A2  # leftwards arrow with tail
+]
+vstretchy = [
+    0x219F, # upwards two headed arrow
+    0x21A1, # downwards two headed arrow
+    0x21A5, # upwards arrow from bar
+    0x21A7, # downwards arrow from bar
+    0x21A8  # up down arrow with base
+]
+for i in range(0, 1 + nvariants + 1):
+    s = (i + 1) * em/10
+
+    g = f.createChar(hstretchy[i])
+    p = g.glyphPen()
+    p.moveTo(0, -em/10)
+    p.lineTo(0, em/10)
+    p.lineTo(s, em/10)
+    p.lineTo(s, -em/10)
+    p.closePath()
+    g.width = s
+
+    g = f.createChar(vstretchy[i])
+    p = g.glyphPen()
+    p.moveTo(0, 0)
+    p.lineTo(0, s)
+    p.lineTo(2 * em/10, s)
+    p.lineTo(2 * em/10, 0)
+    p.closePath();
+    g.width = 2 * em/10
+
+# hstretchy[0] and vstretchy[0] have all the variants and the components. The others only have one of them.
+s = em * nvariants
+
+f[hstretchy[0]].horizontalVariants = "uni219C h0 h1 h2"
+f[hstretchy[0]].horizontalComponents = (("left", False, 0, 0, s), \
+("h2", True, 0, 0, s), ("hmid", False, 0, 0, 2 * s), ("h2", True, 0, 0, s), \
+("right", False, 0, 0, s))
+
+f[hstretchy[1]].horizontalVariants = "uni219D h0"
+f[hstretchy[2]].horizontalVariants = "uni219E h1"
+f[hstretchy[3]].horizontalVariants = "uni21A0 h2"
+f[hstretchy[4]].horizontalVariants = "uni21A2 h2"
+f[hstretchy[4]].horizontalComponents = f[hstretchy[0]].horizontalComponents
+
+f[vstretchy[0]].verticalVariants = "uni219F v0 v1 v2"
+f[vstretchy[0]].verticalComponents = (("bottom", False, 0, 0, s), \
+("v2", True, 0, 0, s), ("vmid", False, 0, 0, 2 * s), ("v2", True, 0, 0, s), \
+("top", False, 0, 0, s))
+
+f[vstretchy[1]].verticalVariants = "uni21A1 v0"
+f[vstretchy[2]].verticalVariants = "uni21A5 v1"
+f[vstretchy[3]].verticalVariants = "uni21A7 v2"
+f[vstretchy[4]].verticalVariants = "uni21A8"
+f[vstretchy[4]].verticalComponents = f[vstretchy[0]].verticalComponents
+
+################################################################################
+# Testing DisplayOperatorMinHeight
+f.math.DisplayOperatorMinHeight =  8 * em
+largeop = [0x2A1B, 0x2A1C] # integral with overbar/underbar
+
+# Draw boxes of size 1, 2, 7, 8, 9em.
+for i in [1, 2, 7, 8, 9]:
+    s = em * i
+    if i == 1 or i == 2:
+        g = f.createChar(largeop[i-1])
+    else:
+        g = f.createChar(-1, "L%d" % i)
+    p = g.glyphPen()
+    p.moveTo(0, 0)
+    p.lineTo(0, s)
+    p.lineTo(s, s)
+    p.lineTo(s, 0)
+    p.closePath();
+    g.width = s
+
+f[largeop[0]].verticalVariants = "uni2A1B L7 L8 L9"
+f[largeop[1]].verticalVariants = "uni2A1C L8"
+
+saveMathFont(f)
new file mode 100644
index 0000000000000000000000000000000000000000..f192de346316ee35c7da99356a83361b28ed7b61
GIT binary patch
literal 3336
zc%0o=Yiv_x7=GXOw7Yb49k7e;jGf7V0dBMnGA<(-Oc#D^4ulXS4bp9ePPeso-MFL}
zq6Fp+%2m7}P7-uKh$e;zB+NAWLnJ1QA0!$HfoKGOn8i3~DX{r`=bUyIB4YUG+4;Wr
zeV^}n-}gJ;t=}5ftZ5({?IbffYieti^4mwuL>4EJw6wN%P5monr~8O9f<z?OFI%(X
zx5MpAVDBX|r#Gyg=g#@2Y9<jYXy4e><_*?#HC#d4NnnkBpSQWh-Bymab0`b^0A_40
zc>($mN~ymsyp;!_QPyEp;WqEqAQi2I$_1#b?cO$@<H(_3VDBU{%?$=R!XvxMjs7{%
zt`G|<QQUDb`cih)EwY(!if9)*ds;{3(2D70)5~C{lZo^PK0L%jv|ix%3Q#kRe~O4!
zYBfn3(}MgxCh-+}b;;Mr#fWDjt2yN*qHG)&CrZj3S<j`_G$xZ`e1V-lPoXpEc|&LP
zcaNbX2^}+ZGnLYyp-Ys`GY#EB4qj#GR^VYUS;~M_r+EpT$w8flZlWyOZ|G)PK_?7d
zLY%3FZXt!|8M+mCBh^rVg0zi7)IyudPhosR88y*tNH;mjMf0K6gS8!uR{VKk3$GCT
zMD2x73$%8knm}+{sAaQ1tdup)R@_e4e5F3Hy`{C)tJDNS!9d6xZV7<b0hbW@P&Xmv
zHUQy}FWlsZRu8xt@j9suhOfD$6S7tdAJ$|D-Slalt3dY@T8uI=o{qYxlFHHj^3;b=
zZJ<3|8whRoDeelVvRFyxh3cv-pI<Jz{Lt>L1snoF|6m1iMdS)iir&y#U#J7$s5qUj
z3a8VlJT#g%k9v`fnf8d;(QJvT#YWOrxkO6I@sblIeRJfI5wX%rj>7tEPT|PN$n~q6
zt~Ot7y8P$FHP2RhU>i(){;mCg@EE&!<PCAoJoqq45;1c|YFcm#Fjd5}DZYT?m8SSY
zb39*C<)Ibw^fJ5_nQO9TB8SY89GRYyCEH}0X43X|Hs#@rDW$o{wFvW9N2_T)=5zy2
zkw&cRP4qGrP6t*&1Sj<Cw2OArNjgR6I@?>^u0=JvcvctB>7rH>%bdD!>B6mxXLM2N
z4TS<*I)lyajU7G#Epsi`MUCIt<@CF{Tz+?#yVbWTtZh$!TT63eARG>~g#*DZ7<H9(
z3+om=+V)M3)7WLqyobB_P3~cp_wrl(Ht*y8e1H$~AwJCS@DYBO-{bfB1OAYY@<)7(
zKjvQkgo?zl&A@@P7(?ZyS1}}qad4fc^Yjz_LH9YE6?XG-UdQdc3jxmZCBA7gn~F@8
zrd4ucpV}YW(a-(;v1q@!KQCSyyMC|K+JE<p+}Ms-^zM!{`*4n3j*rZWc~~7Z&yogX
zo>^AAX2v|QW1g7juE%PZ?|i3&Q2|B)My+;rP-CfJ4QiysTtS*8A&&=n?5Yt*9gUON
zXHdF9=@yjeC?zT=y<I6FUOExz7Qk?hnust8utX#g8ID-(w^cc9fJFsR$<#UK;Rp!8
zbcS$}U;&Cd0Sre{J|MzrVvxiQoq|@`b_N<2Is8P2bdG?~AfhF1KnPkQqUy{<#G*wI
z;4r}KUVy^@Wrr<^j^`)a)0c=85<FA{)ibCXu5J-Hwp!Xinx0(bfRN;y`$s|D3m#Mc
zQsu&REGp66!gb>PwGR`K|FbF=SF@-@OL4V$uVGf@!f#b<V-}TY(KfYw<wvVM=9wy|
z`Eu2)%tRz}aB%R>%B-wJ#HMSLu+^uCgCa|G0WSu{mOMi6{IK-+MIp!CRYXfBUE>N|
zKB_ENimU(A$#ve4vmb$b1l`#12t2pW)nrm16Js3i9#h8nb1u>S%h0QE;^je>LMHDx
ztet~ntx}XMnoI>W?H_xkGpF)bd;#88+?pzAIc`qtwG;C->Y;=5DSb&_(}fhf55I~Q
z{H|u3f2W+ZOEkrHmc-2}jlS0~p=WTp)+Y2!KA>^>w2TsEf%9bHi&R*=jO6LFHO?e(
zg`LcC3Oo~X*uiz6ZL*QWj2w0&hlCssBZn0^1m_g=biQTq#a%962-_J{Qw)vi38Cc&
z$6*C2-sAaAP1%!EoV=9ZD3<%!e)@b{Fzy*x>o&~wB-mCIF;0?pcf~zfyU90`wyODQ
mL+&@^kS>!&aMK1xWwMBTNhylxQ+BUjgk89fNbE83U%=n(=YQ`2
new file mode 100644
--- /dev/null
+++ b/layout/reftests/invalidation/filter-userspace-offset.svg
@@ -0,0 +1,156 @@
+<svg
+  width="500px" height="500px" viewBox="0 0 500 500"
+  xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
+  >
+  <title>Filters and offsets, user space origins, invalidation</title>
+  <defs>
+    <filter id="flood-boundingBox"
+            filterUnits="objectBoundingBox"
+            x="0%" y="0%" width="100%" height="100%"
+            color-interpolation-filters="sRGB">
+      <feFlood flood-color="lime"/>
+    </filter>
+    <filter id="matrix-boundingBox"
+            filterUnits="objectBoundingBox"
+            x="0%" y="0%" width="100%" height="100%"
+            color-interpolation-filters="sRGB">
+      <feColorMatrix type="matrix" values="0 1 0 0 0
+                                           1 0 0 0 0
+                                           0 0 1 0 0
+                                           0 0 0 1 0"/>
+    </filter>
+    <filter id="matrix-fillPaint-boundingBox"
+            filterUnits="objectBoundingBox"
+            x="0%" y="0%" width="100%" height="100%"
+            color-interpolation-filters="sRGB">
+      <feColorMatrix type="matrix" values="0 1 0 0 0
+                                           1 0 0 0 0
+                                           0 0 1 0 0
+                                           0 0 0 1 0" in="FillPaint"/>
+    </filter>
+    <filter id="flood-userSpace-atZero"
+            filterUnits="userSpaceOnUse"
+            x="0" y="0" width="100" height="100"
+            color-interpolation-filters="sRGB">
+      <feFlood flood-color="lime"/>
+    </filter>
+    <filter id="matrix-userSpace-atZero"
+            filterUnits="userSpaceOnUse"
+            x="0" y="0" width="100" height="100"
+            color-interpolation-filters="sRGB">
+      <feColorMatrix type="matrix" values="0 1 0 0 0
+                                           1 0 0 0 0
+                                           0 0 1 0 0
+                                           0 0 0 1 0"/>
+    </filter>
+    <filter id="flood-userSpace-at100"
+            filterUnits="userSpaceOnUse"
+            x="100" y="100" width="100" height="100"
+            color-interpolation-filters="sRGB">
+      <feFlood flood-color="lime"/>
+    </filter>
+    <filter id="matrix-userSpace-at100"
+            filterUnits="userSpaceOnUse"
+            x="100" y="100" width="100" height="100"
+            color-interpolation-filters="sRGB">
+      <feColorMatrix type="matrix" values="0 1 0 0 0
+                                           1 0 0 0 0
+                                           0 0 1 0 0
+                                           0 0 0 1 0"/>
+    </filter>
+    <filter id="matrix-fillPaint-userSpace-at100"
+            filterUnits="userSpaceOnUse"
+            x="100" y="100" width="100" height="100"
+            color-interpolation-filters="sRGB">
+      <feColorMatrix type="matrix" values="0 1 0 0 0
+                                           1 0 0 0 0
+                                           0 0 1 0 0
+                                           0 0 0 1 0" in="FillPaint"/>
+    </filter>
+    <mask id="boundingBox" maskContentUnits="objectBoundingBox">
+      <rect x="0" y="0" width="1" height="1" fill="white"/>
+    </mask>
+    <mask id="userSpace-atZero" maskContentUnits="userSpaceOnUse">
+      <rect x="0" y="0" width="100" height="100" fill="white"/>
+    </mask>
+    <mask id="userSpace-at100" maskContentUnits="userSpaceOnUse">
+      <rect x="100" y="100" width="100" height="100" fill="white"/>
+    </mask>
+    <g id="usedRect">
+      <rect class="fillColor" width="100" height="100"/>
+    </g>
+  </defs>
+  <g transform="translate(40,40)">
+    <rect stroke-width="1" stroke="black" fill="none" x="99.5" y="99.5" width="101" height="101"/>
+
+    <rect x="0" y="100" width="100" height="100" class="fillColor offsetContainer" id="rect"/>
+    <use xlink:href="#usedRect" x="0" y="100" class="offsetContainer" id="use"/>
+    <svg x="0" y="100" width="100" height="100" class="offsetContainer" id="innerSVG">
+      <rect class="fillColor" width="100" height="100"/>
+    </svg>
+    <foreignObject x="0" y="100" width="100" height="100" class="offsetContainer" id="foreignObject">
+      <svg width="100" height="100">
+        <rect class="fillColor" width="100" height="100"/>
+      </svg>
+    </foreignObject>
+  </g>
+  <script><![CDATA[
+
+var options = {
+  offsetContainer: "rect",
+  filter: null,
+  mask: null,
+  updateOffsetOn: "reftestInvalidate" // | "initial" | "timeout"
+};
+
+location.search.substr(1).split("&").forEach(function (s) {
+  var pv = s.split("=");
+  options[pv[0]] = pv[1] || true;
+});
+
+var offsetContainer = document.getElementById(options.offsetContainer);
+
+function updateOffsetNow() {
+  offsetContainer.setAttribute("x", "100");
+}
+
+function updateOffsetOnReftestInvalidate() {
+  document.documentElement.setAttribute("class", "reftest-wait");
+  document.addEventListener("MozReftestInvalidate", function () {
+    updateOffsetNow();
+    document.documentElement.removeAttribute("class");
+  }, false);
+}
+
+function updateOffsetOnTimeout() {
+  setTimeout(updateOffsetNow, 500);
+}
+
+options.updateOffset = options.updateOffsetOn == "initial" ? updateOffsetNow :
+                         (options.updateOffsetOn == "timeout" ? updateOffsetOnTimeout :
+                          updateOffsetOnReftestInvalidate);
+
+var offsetContainers = Array.prototype.slice.call(document.getElementsByClassName("offsetContainer"), 0);
+for (var i = 0; i < offsetContainers.length; i++) {
+  if (offsetContainers[i] != offsetContainer) {
+    offsetContainers[i].parentNode.removeChild(offsetContainers[i]);
+  }
+}
+
+var fillColor = options.filter ? "red" : "lime";
+if (options.filter) {
+  offsetContainer.setAttribute("filter", "url(#" + options.filter + ")");
+}
+if (options.mask) {
+  offsetContainer.setAttribute("mask", "url(#" + options.mask + ")");
+}
+
+var fillColors = document.getElementsByClassName("fillColor");
+for (var j = 0; j < fillColors.length; j++) {
+  fillColors[j].setAttribute("fill", fillColor);
+}
+
+options.updateOffset();
+
+]]></script>
+</svg>
--- a/layout/reftests/invalidation/reftest.list
+++ b/layout/reftests/invalidation/reftest.list
@@ -3,8 +3,38 @@
 == table-repaint-c.html table-repaint-c-ref.html
 == table-repaint-d.html table-repaint-d-ref.html
 skip-if(B2G&&browserIsRemote) == 540247-1.xul 540247-1-ref.xul  # bug 974780
 skip-if(B2G&&browserIsRemote) == 543681-1.html 543681-1-ref.html
 == test-image-layers.html test-image-layers-ref.html
 pref(layout.animated-image-layers.enabled,true) == test-animated-image-layers.html test-animated-image-layers-ref.html
 pref(layout.animated-image-layers.enabled,true) == test-animated-image-layers-background.html test-animated-image-layers-ref.html
 == box-shadow-border-radius.html box-shadow-border-radius-ref.html
+== filter-userspace-offset.svg?offsetContainer=rect filter-userspace-offset.svg
+== filter-userspace-offset.svg?offsetContainer=use filter-userspace-offset.svg
+== filter-userspace-offset.svg?offsetContainer=innerSVG filter-userspace-offset.svg
+== filter-userspace-offset.svg?offsetContainer=foreignObject filter-userspace-offset.svg
+== filter-userspace-offset.svg?offsetContainer=rect&filter=flood-boundingBox filter-userspace-offset.svg
+== filter-userspace-offset.svg?offsetContainer=use&filter=flood-boundingBox filter-userspace-offset.svg
+== filter-userspace-offset.svg?offsetContainer=innerSVG&filter=flood-boundingBox filter-userspace-offset.svg
+== filter-userspace-offset.svg?offsetContainer=foreignObject&filter=flood-boundingBox filter-userspace-offset.svg
+== filter-userspace-offset.svg?offsetContainer=rect&filter=matrix-boundingBox filter-userspace-offset.svg
+== filter-userspace-offset.svg?offsetContainer=use&filter=matrix-boundingBox filter-userspace-offset.svg
+== filter-userspace-offset.svg?offsetContainer=innerSVG&filter=matrix-boundingBox filter-userspace-offset.svg
+== filter-userspace-offset.svg?offsetContainer=foreignObject&filter=matrix-boundingBox filter-userspace-offset.svg
+== filter-userspace-offset.svg?offsetContainer=rect&filter=flood-userSpace-at100 filter-userspace-offset.svg
+== filter-userspace-offset.svg?offsetContainer=use&filter=flood-userSpace-atZero filter-userspace-offset.svg
+== filter-userspace-offset.svg?offsetContainer=innerSVG&filter=flood-userSpace-atZero filter-userspace-offset.svg
+== filter-userspace-offset.svg?offsetContainer=foreignObject&filter=flood-userSpace-at100 filter-userspace-offset.svg
+== filter-userspace-offset.svg?offsetContainer=rect&filter=matrix-userSpace-at100 filter-userspace-offset.svg
+== filter-userspace-offset.svg?offsetContainer=use&filter=matrix-userSpace-atZero filter-userspace-offset.svg
+== filter-userspace-offset.svg?offsetContainer=innerSVG&filter=matrix-userSpace-atZero filter-userspace-offset.svg
+== filter-userspace-offset.svg?offsetContainer=foreignObject&filter=matrix-userSpace-at100 filter-userspace-offset.svg
+== filter-userspace-offset.svg?offsetContainer=rect&mask=boundingBox filter-userspace-offset.svg
+== filter-userspace-offset.svg?offsetContainer=use&mask=boundingBox filter-userspace-offset.svg
+== filter-userspace-offset.svg?offsetContainer=innerSVG&mask=boundingBox filter-userspace-offset.svg
+== filter-userspace-offset.svg?offsetContainer=foreignObject&mask=boundingBox filter-userspace-offset.svg
+== filter-userspace-offset.svg?offsetContainer=rect&mask=userSpace-at100 filter-userspace-offset.svg
+== filter-userspace-offset.svg?offsetContainer=use&mask=userSpace-atZero filter-userspace-offset.svg
+== filter-userspace-offset.svg?offsetContainer=innerSVG&mask=userSpace-atZero filter-userspace-offset.svg
+== filter-userspace-offset.svg?offsetContainer=foreignObject&mask=userSpace-at100 filter-userspace-offset.svg
+== filter-userspace-offset.svg?offsetContainer=rect&filter=matrix-fillPaint-boundingBox filter-userspace-offset.svg
+== filter-userspace-offset.svg?offsetContainer=rect&filter=matrix-fillPaint-userSpace-at100 filter-userspace-offset.svg
new file mode 100644
--- /dev/null
+++ b/layout/reftests/mathml/opentype-stretchy-ref.html
@@ -0,0 +1,73 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Open Type MATH - stretchy operator</title>
+    <meta charset="utf-8"/>
+    <style type="text/css">
+      @font-face {
+        font-family: stretchy;
+        src: url(../fonts/math/stretchy.otf);
+      }
+      math {
+        font-family: stretchy;
+        font-size: 10px;
+      }
+      ::-moz-math-stretchy {
+        font-family: stretchy;
+        font-size: 10px;
+      }
+    </style>
+  </head>
+  <body>
+
+<!--
+hstretchy = [
+    0x219C, # leftwards wave arrow
+    0x219D, # rightwards wave arrow
+    0x219E, # leftwards two headed arrow
+    0x21A0, # rightwards two headed arrow
+    0x21A2  # leftwards arrow with tail
+]
+vstretchy = [
+    0x219F, # upwards two headed arrow
+    0x21A1, # downwards two headed arrow
+    0x21A5, # upwards arrow from bar
+    0x21A7, # downwards arrow from bar
+    0x21A8  # up down arrow with base
+]
+
+hstretchy[0] and vstretchy[0] have all the variants and the components. The others only have one of them.
+-->
+
+    <p>
+      <math>
+        <mstyle scriptsizemultiplier="1">
+          <mover><mo stretchy="true">&#x219D;</mo><mspace width="1em" height="1px" mathbackground="red"/></mover>
+          <mover><mo stretchy="true">&#x219E;</mo><mspace width="2em" height="1px" mathbackground="red"/></mover>
+          <mover><mo stretchy="true">&#x21A0;</mo><mspace width="3em" height="1px" mathbackground="red"/></mover>
+          <mover><mo stretchy="true">&#x21A2;</mo><mspace width="15em" height="1px" mathbackground="red"/></mover>
+        </mstyle>
+      </math>
+    </p>
+
+    <p>
+      <math><mrow><mo symmetric="false" stretchy="true" minsize="1em">&#x21A1;</mo></mrow></math>
+      <math><mrow><mo symmetric="false" stretchy="true" minsize="2em">&#x21A5;</mo></mrow></math>
+      <math><mrow><mo symmetric="false" stretchy="true" minsize="3em">&#x21A7;</mo></mrow></math>
+      <math><mrow><mo symmetric="false" stretchy="true" minsize="15em">&#x21A8;</mo></mrow></math>
+    </p>
+
+<!--
+DisplayOperatorMinHeight = 8em
+largeop = [0x2A1B, 0x2A1C] # integral with overbar/underbar
+largeop[0] has variants of size 7, 8, 9em
+largeop[1] has one variant of size 8em.
+-->
+    <p>
+      <math displaystyle="true">
+        <mrow><mo>&#x2A1C;</mo></mrow>
+      </math>
+    </p>
+
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/mathml/opentype-stretchy.html
@@ -0,0 +1,73 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Open Type MATH - stretchy operator</title>
+    <meta charset="utf-8"/>
+    <style type="text/css">
+      @font-face {
+        font-family: stretchy;
+        src: url(../fonts/math/stretchy.otf);
+      }
+      math {
+        font-family: stretchy;
+        font-size: 10px;
+      }
+      ::-moz-math-stretchy {
+        font-family: stretchy;
+        font-size: 10px;
+      }
+    </style>
+  </head>
+  <body>
+
+<!--
+hstretchy = [
+    0x219C, # leftwards wave arrow
+    0x219D, # rightwards wave arrow
+    0x219E, # leftwards two headed arrow
+    0x21A0, # rightwards two headed arrow
+    0x21A2  # leftwards arrow with tail
+]
+vstretchy = [
+    0x219F, # upwards two headed arrow
+    0x21A1, # downwards two headed arrow
+    0x21A5, # upwards arrow from bar
+    0x21A7, # downwards arrow from bar
+    0x21A8  # up down arrow with base
+]
+
+hstretchy[0] and vstretchy[0] have all the variants and the components. The others only have one of them.
+-->
+
+    <p>
+      <math>
+        <mstyle scriptsizemultiplier="1">
+          <mover><mo stretchy="true">&#x219C;</mo><mspace width="1em" height="1px" mathbackground="red"/></mover>
+          <mover><mo stretchy="true">&#x219C;</mo><mspace width="2em" height="1px" mathbackground="red"/></mover>
+          <mover><mo stretchy="true">&#x219C;</mo><mspace width="3em" height="1px" mathbackground="red"/></mover>
+          <mover><mo stretchy="true">&#x219C;</mo><mspace width="15em" height="1px" mathbackground="red"/></mover>
+        </mstyle>
+      </math>
+    </p>
+
+    <p>
+      <math><mrow><mo symmetric="false" stretchy="true" minsize="1em">&#x219F;</mo></mrow></math>
+      <math><mrow><mo symmetric="false" stretchy="true" minsize="2em">&#x219F;</mo></mrow></math>
+      <math><mrow><mo symmetric="false" stretchy="true" minsize="3em">&#x219F;</mo></mrow></math>
+      <math><mrow><mo symmetric="false" stretchy="true" minsize="15em">&#x219F;</mo></mrow></math>
+    </p>
+
+<!--
+DisplayOperatorMinHeight = 8em
+largeop = [0x2A1B, 0x2A1C] # integral with overbar/underbar
+largeop[0] has variants of size 7, 8, 9em
+largeop[1] has one variant of size 8em.
+-->
+    <p>
+      <math displaystyle="true">
+        <mrow><mo>&#x2A1B;</mo></mrow>
+      </math>
+    </p>
+
+  </body>
+</html>
--- a/layout/reftests/mathml/reftest.list
+++ b/layout/reftests/mathml/reftest.list
@@ -147,16 +147,17 @@ skip-if(B2G) == maction-dynamic-1.html m
 == mo-invisibleoperators.html mo-invisibleoperators-ref.html
 == mo-invisibleoperators-2.html mo-invisibleoperators-2-ref.html
 skip-if(B2G) == maction-dynamic-3.html maction-dynamic-3-ref.html # bug 773482
 == whitespace-trim-1.html whitespace-trim-1-ref.html
 == whitespace-trim-2.html whitespace-trim-2-ref.html
 == whitespace-trim-3.html whitespace-trim-3-ref.html
 fails == whitespace-trim-4.html whitespace-trim-4-ref.html # Bug 787215
 == whitespace-trim-5.html whitespace-trim-5-ref.html
+fails-if(winWidget&&!d2d) == opentype-stretchy.html opentype-stretchy-ref.html
 == operator-1.xhtml operator-1-ref.xhtml
 == scriptshift-1.xhtml scriptshift-1-ref.xhtml
 == number-size-1.xhtml number-size-1-ref.xhtml
 fails-if(B2G) == multiscripts-1.html multiscripts-1-ref.html # B2G - slight height variation from font metrics
 == mathml-mmultiscript-base.html mathml-mmultiscript-base-ref.html
 == mathml-mmultiscript-mprescript.html mathml-mmultiscript-mprescript-ref.html
 != menclose-1a.html menclose-1-ref.html
 != menclose-1b.html menclose-1-ref.html
--- a/layout/style/nsCSSKeywordList.h
+++ b/layout/style/nsCSSKeywordList.h
@@ -367,16 +367,17 @@ CSS_KEY(looped, looped)
 CSS_KEY(lower-alpha, lower_alpha)
 CSS_KEY(lower-greek, lower_greek)
 CSS_KEY(lower-latin, lower_latin)
 CSS_KEY(lower-roman, lower_roman)
 CSS_KEY(lowercase, lowercase)
 CSS_KEY(ltr, ltr)
 CSS_KEY(luminance, luminance)
 CSS_KEY(luminosity, luminosity)
+CSS_KEY(manipulation, manipulation)
 CSS_KEY(manual, manual)
 CSS_KEY(margin-box, margin_box)
 CSS_KEY(markers, markers)
 CSS_KEY(matrix, matrix)
 CSS_KEY(matrix3d, matrix3d)
 CSS_KEY(max-content, max_content)
 CSS_KEY(medium, medium)
 CSS_KEY(menu, menu)
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -12400,18 +12400,20 @@ CSSParserImpl::ParseTextOverflow(nsCSSVa
     aValue = left;
   }
   return true;
 }
 
 bool
 CSSParserImpl::ParseTouchAction(nsCSSValue& aValue)
 {
-  if (!ParseVariant(aValue, VARIANT_HK | VARIANT_NONE | VARIANT_AUTO,
-                    nsCSSProps::kTouchActionKTable)) {
+  // Avaliable values of property touch-action:
+  // auto | none | [pan-x || pan-y] | manipulation
+
+  if (!ParseVariant(aValue, VARIANT_HK, nsCSSProps::kTouchActionKTable)) {
     return false;
   }