Merge m-c to b2g-inbound. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Thu, 03 Jul 2014 23:26:38 -0400
changeset 206162 215d323a555b73b0899b20ee162315a0f558b3c4
parent 206161 7ec4d8b9649d6ed4472be016c0a16ed847042561 (current diff)
parent 206115 e8df6826a571509fdd08d5538f8158671c4fd58b (diff)
child 206163 83e7aa9e2a850a687b2bdf72357bbe045a96feb6
push id6561
push userasasaki@mozilla.com
push dateMon, 21 Jul 2014 21:23:20 +0000
treeherdermozilla-aurora@428d4d3c8588 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone33.0a1
Merge m-c to b2g-inbound. a=merge
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,11 +17,10 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Bug 800200: Removing the old JavaScript debugging API, js/jsd. I'm advised
-that our build system doesn't cope well with deletions, and that a spoonful
-of clobber helps the medicine go down (in a most delightful way).
+Bustage pile-up on inbound that appears to need clobbering to go away for good.
+
--- a/browser/base/content/browser-social.js
+++ b/browser/base/content/browser-social.js
@@ -546,24 +546,25 @@ SocialShare = {
       return false;
 
     if (!aURI || !(aURI.schemeIs('http') || aURI.schemeIs('https')))
       return false;
     return true;
   },
 
   update: function() {
-    let shareButton = this.shareButton;
-    if (!shareButton)
+    let widget = CustomizableUI.getWidget("social-share-button");
+    if (!widget)
       return;
-    // if we got here, the button is in the window somewhere, update it's hidden
-    // state based on available providers.
-    shareButton.hidden = !SocialUI.enabled ||
-                         [p for (p of Social.providers) if (p.shareURL)].length == 0;
-    let disabled = shareButton.hidden || !this.canSharePage(gBrowser.currentURI);
+    let shareButton = widget.forWindow(window).node;
+    // hidden state is based on available share providers and location of
+    // button. It's always visible and disabled in the customization palette.
+    shareButton.hidden = !SocialUI.enabled || (widget.areaType &&
+                         [p for (p of Social.providers) if (p.shareURL)].length == 0);
+    let disabled = !widget.areaType || shareButton.hidden || !this.canSharePage(gBrowser.currentURI);
 
     // 1. update the relevent command's disabled state so the keyboard
     // shortcut only works when available.
     // 2. If the button has been relocated to a place that is not visible by
     // default (e.g. menu panel) then the disabled attribute will not update
     // correctly based on the command, so we update the attribute directly as.
     let cmd = document.getElementById("Social:SharePage");
     if (disabled) {
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -934,16 +934,17 @@
 
 
         <toolbarbutton id="social-share-button"
                        class="toolbarbutton-1 chromeclass-toolbar-additional"
                        label="&sharePageCmd.label;"
                        tooltiptext="&sharePageCmd.label;"
                        cui-areatype="toolbar"
                        removable="true"
+                       hidden="true"
                        command="Social:SharePage"/>
       </hbox>
 
       <toolbarbutton id="nav-bar-overflow-button"
                      class="toolbarbutton-1 chromeclass-toolbar-additional overflow-button"
                      cui-areatype="toolbar"
                      skipintoolbarset="true"
                      tooltiptext="&navbarOverflow.label;"/>
--- a/browser/base/content/newtab/newTab.js
+++ b/browser/base/content/newtab/newTab.js
@@ -8,17 +8,16 @@ let Cu = Components.utils;
 let Ci = Components.interfaces;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/PageThumbs.jsm");
 Cu.import("resource://gre/modules/BackgroundPageThumbs.jsm");
 Cu.import("resource://gre/modules/DirectoryLinksProvider.jsm");
 Cu.import("resource://gre/modules/NewTabUtils.jsm");
-Cu.import("resource://gre/modules/Promise.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Rect",
   "resource://gre/modules/Geometry.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
   "resource://gre/modules/UpdateChannel.jsm");
 
--- a/browser/base/content/newtab/transformations.js
+++ b/browser/base/content/newtab/transformations.js
@@ -174,29 +174,28 @@ let gTransformation = {
     let callback = aOptions && aOptions.callback;
     let unfreeze = aOptions && aOptions.unfreeze;
 
     aSites.forEach(function (aSite, aIndex) {
       // Do not re-arrange empty cells or the dragged site.
       if (!aSite || aSite == gDrag.draggedSite)
         return;
 
-      let deferred = Promise.defer();
-      batch.push(deferred.promise);
-      let cb = deferred.resolve;
-
-      if (!cells[aIndex])
-        // The site disappeared from the grid, hide it.
-        this.hideSite(aSite, cb);
-      else if (this._getNodeOpacity(aSite.node) != 1)
-        // The site disappeared before but is now back, show it.
-        this.showSite(aSite, cb);
-      else
-        // The site's position has changed, move it around.
-        this._moveSite(aSite, aIndex, {unfreeze: unfreeze, callback: cb});
+      batch.push(new Promise(resolve => {
+        if (!cells[aIndex]) {
+          // The site disappeared from the grid, hide it.
+          this.hideSite(aSite, resolve);
+        } else if (this._getNodeOpacity(aSite.node) != 1) {
+          // The site disappeared before but is now back, show it.
+          this.showSite(aSite, resolve);
+        } else {
+          // The site's position has changed, move it around.
+          this._moveSite(aSite, aIndex, {unfreeze: unfreeze, callback: resolve});
+        }
+      }));
     }, this);
 
     if (callback) {
       Promise.all(batch).then(callback);
     }
   },
 
   /**
--- a/browser/base/content/newtab/updater.js
+++ b/browser/base/content/newtab/updater.js
@@ -129,27 +129,26 @@ let gUpdater = {
     let batch = [];
 
     // Delete sites that were removed from the grid.
     gGrid.sites.forEach(function (aSite) {
       // The site must be valid and not in the current grid.
       if (!aSite || aSites.indexOf(aSite) != -1)
         return;
 
-      let deferred = Promise.defer();
-      batch.push(deferred.promise);
+      batch.push(new Promise(resolve => {
+        // Fade out the to-be-removed site.
+        gTransformation.hideSite(aSite, function () {
+          let node = aSite.node;
 
-      // Fade out the to-be-removed site.
-      gTransformation.hideSite(aSite, function () {
-        let node = aSite.node;
-
-        // Remove the site from the DOM.
-        node.parentNode.removeChild(node);
-        deferred.resolve();
-      });
+          // Remove the site from the DOM.
+          node.parentNode.removeChild(node);
+          resolve();
+        });
+      }));
     });
 
     Promise.all(batch).then(aCallback);
   },
 
   /**
    * Tries to fill empty cells with new links if available.
    * @param aLinks The array of links.
@@ -159,26 +158,25 @@ let gUpdater = {
     let {cells, sites} = gGrid;
     let batch = [];
 
     // Find empty cells and fill them.
     sites.forEach(function (aSite, aIndex) {
       if (aSite || !aLinks[aIndex])
         return;
 
-      let deferred = Promise.defer();
-      batch.push(deferred.promise);
-
-      // Create the new site and fade it in.
-      let site = gGrid.createSite(aLinks[aIndex], cells[aIndex]);
+      batch.push(new Promise(resolve => {
+        // Create the new site and fade it in.
+        let site = gGrid.createSite(aLinks[aIndex], cells[aIndex]);
 
-      // Set the site's initial opacity to zero.
-      site.node.style.opacity = 0;
+        // Set the site's initial opacity to zero.
+        site.node.style.opacity = 0;
 
-      // Flush all style changes for the dynamically inserted site to make
-      // the fade-in transition work.
-      window.getComputedStyle(site.node).opacity;
-      gTransformation.showSite(site, function () deferred.resolve());
+        // Flush all style changes for the dynamically inserted site to make
+        // the fade-in transition work.
+        window.getComputedStyle(site.node).opacity;
+        gTransformation.showSite(site, resolve);
+      }));
     });
 
     Promise.all(batch).then(aCallback);
   }
 };
--- a/browser/base/content/test/newtab/browser_newtab_reportLinkAction.js
+++ b/browser/base/content/test/newtab/browser_newtab_reportLinkAction.js
@@ -42,16 +42,17 @@ function runTests() {
   expected.tile = 1;
   expected.pinned = false;
   yield EventUtils.synthesizeMouseAtCenter(pinButton, {}, getContentWindow());
 
   // Unpin that link
   expected.action = "unpin";
   expected.pinned = true;
   yield EventUtils.synthesizeMouseAtCenter(pinButton, {}, getContentWindow());
+  yield whenPagesUpdated();
 
   // Block the site in the 0th tile spot
   let blockedSite = getCell(0).node.querySelector(".newtab-site");
   let blockButton = blockedSite.querySelector(".newtab-control-block");
   expected.type = "organic";
   expected.link = 0;
   expected.action = "block";
   expected.tile = 0;
--- a/browser/branding/aurora/pref/firefox-branding.js
+++ b/browser/branding/aurora/pref/firefox-branding.js
@@ -6,17 +6,18 @@
 
 // We don't have pages ready for this, so leave these prefs empty for now (bug 648330)
 pref("startup.homepage_override_url","");
 pref("startup.homepage_welcome_url","");
 // The time interval between checks for a new version (in seconds)
 pref("app.update.interval", 28800); // 8 hours
 // The time interval between the downloading of mar file chunks in the
 // background (in seconds)
-pref("app.update.download.backgroundInterval", 60);
+// 0 means "download everything at once"
+pref("app.update.download.backgroundInterval", 0);
 // Give the user x seconds to react before showing the big UI. default=168 hours
 pref("app.update.promptWaitTime", 604800);
 // URL user can browse to manually if for some reason all update installation
 // attempts fail.
 pref("app.update.url.manual", "https://www.mozilla.org/firefox/aurora/");
 // A default value for the "More information about this update" link
 // supplied in the "An update is available" page of the update wizard. 
 pref("app.update.url.details", "https://www.mozilla.org/firefox/aurora/");
--- a/browser/branding/nightly/pref/firefox-branding.js
+++ b/browser/branding/nightly/pref/firefox-branding.js
@@ -3,17 +3,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 pref("startup.homepage_override_url", "https://www.mozilla.org/projects/firefox/%VERSION%/whatsnew/?oldversion=%OLD_VERSION%");
 pref("startup.homepage_welcome_url", "https://www.mozilla.org/projects/firefox/%VERSION%/firstrun/");
 // The time interval between checks for a new version (in seconds)
 pref("app.update.interval", 7200); // 2 hours
 // The time interval between the downloading of mar file chunks in the
 // background (in seconds)
-pref("app.update.download.backgroundInterval", 60);
+// 0 means "download everything at once"
+pref("app.update.download.backgroundInterval", 0);
 // Give the user x seconds to react before showing the big UI. default=12 hours
 pref("app.update.promptWaitTime", 43200);
 // URL user can browse to manually if for some reason all update installation
 // attempts fail.
 pref("app.update.url.manual", "https://nightly.mozilla.org");
 // A default value for the "More information about this update" link
 // supplied in the "An update is available" page of the update wizard. 
 pref("app.update.url.details", "https://nightly.mozilla.org");
--- a/browser/components/preferences/in-content/privacy.xul
+++ b/browser/components/preferences/in-content/privacy.xul
@@ -73,17 +73,17 @@
       data-category="panePrivacy">
   <image class="header-icon"/>
   <label class="header-name">&panePrivacy.title;</label>
 </hbox>
 
 <!-- Tracking -->
 <groupbox id="trackingGroup" data-category="panePrivacy" hidden="true" align="start">
   <caption><label>&tracking.label;</label></caption>
-  <radiogroup id="doNotTrackSelection" orient="vertical"
+  <radiogroup id="doNotTrackSelection" orient="vertical" align="start"
               preference="privacy.donottrackheader.value"
               onsynctopreference="return gPrivacyPane.setTrackingPrefs()"
               onsyncfrompreference="return gPrivacyPane.getTrackingPrefs()">
     <radio id="dntnotrack" value="1" label="&dntTrackingNotOkay.label2;"
             accesskey="&dntTrackingNotOkay.accesskey;" />
     <radio id="dntdotrack" value="0" label="&dntTrackingOkay.label2;"
             accesskey="&dntTrackingOkay.accesskey;" />
     <radio id="dntnopref" value="-1" label="&dntTrackingNopref.label2;"
@@ -146,47 +146,41 @@
           >&dontrememberActions.clearHistory.label;</html:a>&dontrememberActions.post.label;</description>
         </vbox>
         <spacer flex="1" class="indent"/>
       </hbox>
     </vbox>
     <vbox id="historyCustomPane">
       <separator class="thin"/>
       <vbox class="indent">
-        <hbox>
+        <vbox align="start">
           <checkbox id="privateBrowsingAutoStart"
                     label="&privateBrowsingPermanent2.label;"
                     accesskey="&privateBrowsingPermanent2.accesskey;"
                     preference="browser.privatebrowsing.autostart"
                     oncommand="gPrivacyPane.updateAutostart()"/>
-          <spacer flex="1"/>
-        </hbox>
+        </vbox>
         <vbox class="indent">
-          <hbox>
+          <vbox align="start">
             <checkbox id="rememberHistory"
                       label="&rememberHistory2.label;"
                       accesskey="&rememberHistory2.accesskey;"
                       preference="places.history.enabled"/>
-            <spacer flex="1"/>
-          </hbox>
-          <hbox>
             <checkbox id="rememberForms"
                       label="&rememberSearchForm.label;"
                       accesskey="&rememberSearchForm.accesskey;"
                       preference="browser.formfill.enable"/>
-            <spacer flex="1"/>
-          </hbox>
-
+          </vbox>
           <hbox id="cookiesBox">
             <checkbox id="acceptCookies" label="&acceptCookies.label;"
                       preference="network.cookie.cookieBehavior"
                       accesskey="&acceptCookies.accesskey;"
                       onsyncfrompreference="return gPrivacyPane.readAcceptCookies();"
                       onsynctopreference="return gPrivacyPane.writeAcceptCookies();"/>
-            <spacer flex="1"/>
+            <spacer flex="1" />
             <button id="cookieExceptions" oncommand="gPrivacyPane.showCookieExceptions();"
                     label="&cookieExceptions.label;" accesskey="&cookieExceptions.accesskey;"
                     preference="pref.privacy.disable_button.cookie_exceptions"/>
           </hbox>
           <hbox id="acceptThirdPartyRow"
                 class="indent"
                 align="center">
             <label id="acceptThirdPartyLabel" control="acceptThirdPartyMenu"
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -3613,16 +3613,17 @@ toolbarbutton.chevron > .toolbarbutton-m
 notification[value="translation"] {
   color: #484848;
   background-color: #EFEFEF;
   background-image: none;
   border-top: none;
   border-bottom: 1px solid #c4c4c4;
   padding-top: 1px;
   padding-bottom: 1px;
+  min-height: 35px;
 }
 
 .translate-infobar-element {
   margin-top: 0 !important;
   margin-bottom: 0 !important;
 }
 
 button.translate-infobar-element {
--- a/client.mk
+++ b/client.mk
@@ -160,17 +160,17 @@ CONFIGURES += $(TOPSRCDIR)/js/src/config
 # Make targets that are going to be passed to the real build system
 OBJDIR_TARGETS = install export libs clean realclean distclean maybe_clobber_profiledbuild upload sdk installer package package-compare stage-package source-package l10n-check automation/build
 
 #######################################################################
 # Rules
 
 # The default rule is build
 build::
-	$(MAKE) -f $(TOPSRCDIR)/client.mk $(if $(MOZ_PGO),profiledbuild,realbuild)
+	$(MAKE) -f $(TOPSRCDIR)/client.mk $(if $(MOZ_PGO),profiledbuild,realbuild) CREATE_MOZCONFIG_JSON=
 
 # Define mkdir
 include $(TOPSRCDIR)/config/makefiles/makeutils.mk
 include $(TOPSRCDIR)/config/makefiles/autotargets.mk
 
 # Create a makefile containing the mk_add_options values from mozconfig,
 # but only do so when OBJDIR is defined (see further above).
 ifdef MOZ_BUILD_PROJECTS
@@ -213,22 +213,22 @@ build_and_deploy: build package install
 everything: clean build
 
 ####################################
 # Profile-Guided Optimization
 #  This is up here, outside of the MOZ_CURRENT_PROJECT logic so that this
 #  is usable in multi-pass builds, where you might not have a runnable
 #  application until all the build passes and postflight scripts have run.
 profiledbuild::
-	$(MAKE) -f $(TOPSRCDIR)/client.mk realbuild MOZ_PROFILE_GENERATE=1 MOZ_PGO_INSTRUMENTED=1
+	$(MAKE) -f $(TOPSRCDIR)/client.mk realbuild MOZ_PROFILE_GENERATE=1 MOZ_PGO_INSTRUMENTED=1 CREATE_MOZCONFIG_JSON=
 	$(MAKE) -C $(OBJDIR) package MOZ_PGO_INSTRUMENTED=1 MOZ_INTERNAL_SIGNING_FORMAT= MOZ_EXTERNAL_SIGNING_FORMAT=
 	rm -f $(OBJDIR)/jarlog/en-US.log
 	MOZ_PGO_INSTRUMENTED=1 JARLOG_FILE=jarlog/en-US.log EXTRA_TEST_ARGS=10 $(MAKE) -C $(OBJDIR) pgo-profile-run
-	$(MAKE) -f $(TOPSRCDIR)/client.mk maybe_clobber_profiledbuild
-	$(MAKE) -f $(TOPSRCDIR)/client.mk realbuild MOZ_PROFILE_USE=1
+	$(MAKE) -f $(TOPSRCDIR)/client.mk maybe_clobber_profiledbuild CREATE_MOZCONFIG_JSON=
+	$(MAKE) -f $(TOPSRCDIR)/client.mk realbuild MOZ_PROFILE_USE=1 CREATE_MOZCONFIG_JSON=
 
 #####################################################
 # Build date unification
 
 ifdef MOZ_UNIFY_BDATE
 ifndef MOZ_BUILD_DATE
 ifdef MOZ_BUILD_PROJECTS
 MOZ_BUILD_DATE = $(shell $(PYTHON) $(TOPSRCDIR)/toolkit/xre/make-platformini.py --print-buildid)
@@ -328,19 +328,24 @@ configure-preqs = \
   $(OBJDIR)/CLOBBER \
   configure-files \
   $(call mkdir_deps,$(OBJDIR)) \
   $(if $(MOZ_BUILD_PROJECTS),$(call mkdir_deps,$(MOZ_OBJDIR))) \
   save-mozconfig \
   $(OBJDIR)/.mozconfig.json \
   $(NULL)
 
-CREATE_MOZCONFIG_JSON := $(shell $(TOPSRCDIR)/mach environment --format=json -o $(OBJDIR)/.mozconfig.json)
-$(OBJDIR)/.mozconfig.json: $(call mkdir_deps,$(OBJDIR))
-	@$(TOPSRCDIR)/mach environment --format=json -o $@
+CREATE_MOZCONFIG_JSON = $(shell $(TOPSRCDIR)/mach environment --format=json -o $(OBJDIR)/.mozconfig.json)
+# Force CREATE_MOZCONFIG_JSON above to be resolved, without side effects in
+# case the result is non empty, and allowing an override on the make command
+# line not running the command (using := $(shell) still runs the shell command).
+ifneq (,$(CREATE_MOZCONFIG_JSON))
+endif
+
+$(OBJDIR)/.mozconfig.json: $(call mkdir_deps,$(OBJDIR)) ;
 
 save-mozconfig: $(FOUND_MOZCONFIG)
 	-cp $(FOUND_MOZCONFIG) $(OBJDIR)/.mozconfig
 
 configure:: $(configure-preqs)
 	@echo cd $(OBJDIR);
 	@echo $(CONFIGURE) $(CONFIGURE_ARGS)
 	@cd $(OBJDIR) && $(BUILD_PROJECT_ARG) $(CONFIGURE_ENV_ARGS) $(CONFIGURE) $(CONFIGURE_ARGS) \
--- a/content/base/src/nsScriptLoader.cpp
+++ b/content/base/src/nsScriptLoader.cpp
@@ -890,17 +890,17 @@ nsScriptLoader::AttemptAsyncScriptParse(
   AutoJSAPI jsapi;
   if (!jsapi.InitWithLegacyErrorReporting(globalObject)) {
     return NS_ERROR_FAILURE;
   }
 
   JSContext* cx = jsapi.cx();
   JS::Rooted<JSObject*> global(cx, globalObject->GetGlobalJSObject());
   JS::CompileOptions options(cx);
-  FillCompileOptionsForRequest(aRequest, global, &options);
+  FillCompileOptionsForRequest(jsapi, aRequest, global, &options);
 
   if (!JS::CanCompileOffThread(cx, options, aRequest->mScriptTextLength)) {
     return NS_ERROR_FAILURE;
   }
 
   nsRefPtr<NotifyOffThreadScriptLoadCompletedRunnable> runnable =
     new NotifyOffThreadScriptLoadCompletedRunnable(aRequest, this);
 
@@ -1061,17 +1061,18 @@ nsScriptLoader::GetScriptGlobalObject()
   if (NS_FAILED(rv)) {
     return nullptr;
   }
 
   return globalObject.forget();
 }
 
 void
-nsScriptLoader::FillCompileOptionsForRequest(nsScriptLoadRequest *aRequest,
+nsScriptLoader::FillCompileOptionsForRequest(const AutoJSAPI &jsapi,
+                                             nsScriptLoadRequest *aRequest,
                                              JS::Handle<JSObject *> aScopeChain,
                                              JS::CompileOptions *aOptions)
 {
   // It's very important to use aRequest->mURI, not the final URI of the channel
   // aRequest ended up getting script data from, as the script filename.
   nsContentUtils::GetWrapperSafeScriptFilename(mDocument, aRequest->mURI, aRequest->mURL);
 
   aOptions->setIntroductionType("scriptElement");
@@ -1080,25 +1081,19 @@ nsScriptLoader::FillCompileOptionsForReq
   aOptions->setCompileAndGo(JS_IsGlobalObject(aScopeChain));
   if (aRequest->mHasSourceMapURL) {
     aOptions->setSourceMapURL(aRequest->mSourceMapURL.get());
   }
   if (aRequest->mOriginPrincipal) {
     aOptions->setOriginPrincipals(nsJSPrincipals::get(aRequest->mOriginPrincipal));
   }
 
-  AutoJSContext cx;
+  JSContext* cx = jsapi.cx();
   JS::Rooted<JS::Value> elementVal(cx);
   MOZ_ASSERT(aRequest->mElement);
-  // XXXbz this is super-fragile, because the code that _uses_ the
-  // JS::CompileOptions is nowhere near us, but we have to coordinate
-  // compartments with it... and in particular, it will compile in the
-  // compartment of aScopeChain, so we want to wrap into that compartment as
-  // well.
-  JSAutoCompartment ac(cx, aScopeChain);
   if (NS_SUCCEEDED(nsContentUtils::WrapNative(cx, aRequest->mElement,
                                               &elementVal,
                                               /* aAllowWrapping = */ true))) {
     MOZ_ASSERT(elementVal.isObject());
     aOptions->setElement(&elementVal.toObject());
   }
 }
 
@@ -1160,17 +1155,17 @@ nsScriptLoader::EvaluateScript(nsScriptL
       // execution currentScript of the master should refer to this
       // script. So let's update the mCurrentScript of the ScriptLoader
       // of the master document too.
       masterScriptUpdater.construct(master->ScriptLoader(),
                                     aRequest->mElement);
     }
 
     JS::CompileOptions options(entryScript.cx());
-    FillCompileOptionsForRequest(aRequest, global, &options);
+    FillCompileOptionsForRequest(entryScript, aRequest, global, &options);
     rv = nsJSUtils::EvaluateString(entryScript.cx(), aSrcBuf, global, options,
                                    aOffThreadToken);
   }
 
   context->SetProcessingScriptTag(oldProcessingScriptTag);
   return rv;
 }
 
--- a/content/base/src/nsScriptLoader.h
+++ b/content/base/src/nsScriptLoader.h
@@ -20,16 +20,22 @@
 
 class nsScriptLoadRequest;
 class nsIURI;
 
 namespace JS {
   class SourceBufferHolder;
 }
 
+namespace mozilla {
+namespace dom {
+class AutoJSAPI;
+}
+}
+
 //////////////////////////////////////////////////////////////
 // Script loader implementation
 //////////////////////////////////////////////////////////////
 
 class nsScriptLoader : public nsIStreamLoaderObserver
 {
   class MOZ_STACK_CLASS AutoCurrentScriptUpdater
   {
@@ -307,17 +313,18 @@ private:
                            nsScriptLoadRequest* aRequest);
   void FireScriptEvaluated(nsresult aResult,
                            nsScriptLoadRequest* aRequest);
   nsresult EvaluateScript(nsScriptLoadRequest* aRequest,
                           JS::SourceBufferHolder& aSrcBuf,
                           void **aOffThreadToken);
 
   already_AddRefed<nsIScriptGlobalObject> GetScriptGlobalObject();
-  void FillCompileOptionsForRequest(nsScriptLoadRequest *aRequest,
+  void FillCompileOptionsForRequest(const mozilla::dom::AutoJSAPI &jsapi,
+                                    nsScriptLoadRequest *aRequest,
                                     JS::Handle<JSObject *> aScopeChain,
                                     JS::CompileOptions *aOptions);
 
   nsresult PrepareLoadedRequest(nsScriptLoadRequest* aRequest,
                                 nsIStreamLoader* aLoader,
                                 nsresult aStatus,
                                 uint32_t aStringLen,
                                 const uint8_t* aString);
--- a/content/canvas/src/WebGLContext.cpp
+++ b/content/canvas/src/WebGLContext.cpp
@@ -26,16 +26,17 @@
 #include "nsIWidget.h"
 
 #include "nsIVariant.h"
 
 #include "ImageEncoder.h"
 
 #include "gfxContext.h"
 #include "gfxPattern.h"
+#include "gfxPrefs.h"
 #include "gfxUtils.h"
 
 #include "CanvasUtils.h"
 #include "nsDisplayList.h"
 
 #include "GLContextProvider.h"
 #include "GLContext.h"
 #include "ScopedGLHelpers.h"
@@ -432,16 +433,21 @@ WebGLContext::SetContextOptions(JSContex
     newOpts.preserveDrawingBuffer = attributes.mPreserveDrawingBuffer;
     if (attributes.mAlpha.WasPassed()) {
       newOpts.alpha = attributes.mAlpha.Value();
     }
 
     // enforce that if stencil is specified, we also give back depth
     newOpts.depth |= newOpts.stencil;
 
+    // Don't do antialiasing if we've disabled MSAA.
+    if (!gfxPrefs::MSAALevel()) {
+      newOpts.antialias = false;
+    }
+
 #if 0
     GenerateWarning("aaHint: %d stencil: %d depth: %d alpha: %d premult: %d preserve: %d\n",
                newOpts.antialias ? 1 : 0,
                newOpts.stencil ? 1 : 0,
                newOpts.depth ? 1 : 0,
                newOpts.alpha ? 1 : 0,
                newOpts.premultipliedAlpha ? 1 : 0,
                newOpts.preserveDrawingBuffer ? 1 : 0);
--- a/content/canvas/src/WebGLContextExtensions.cpp
+++ b/content/canvas/src/WebGLContextExtensions.cpp
@@ -114,20 +114,16 @@ bool WebGLContext::IsExtensionSupported(
         case WebGLExtensionID::OES_texture_half_float:
             // If we have Feature::texture_half_float, we must not be on ES2
             // and need to translate HALF_FLOAT_OES -> HALF_FLOAT.  We do that
             // right before making the relevant calls.
             return gl->IsExtensionSupported(GLContext::OES_texture_half_float) ||
                    gl->IsSupported(GLFeature::texture_half_float);
         case WebGLExtensionID::OES_texture_half_float_linear:
             return gl->IsSupported(GLFeature::texture_half_float_linear);
-        case WebGLExtensionID::WEBGL_color_buffer_float:
-            return WebGLExtensionColorBufferFloat::IsSupported(this);
-        case WebGLExtensionID::EXT_color_buffer_half_float:
-            return WebGLExtensionColorBufferHalfFloat::IsSupported(this);
         case WebGLExtensionID::OES_vertex_array_object:
             return WebGLExtensionVertexArray::IsSupported(this);
         case WebGLExtensionID::EXT_texture_filter_anisotropic:
             return gl->IsExtensionSupported(GLContext::EXT_texture_filter_anisotropic);
         case WebGLExtensionID::WEBGL_compressed_texture_s3tc:
             if (gl->IsExtensionSupported(GLContext::EXT_texture_compression_s3tc)) {
                 return true;
             }
@@ -163,16 +159,20 @@ bool WebGLContext::IsExtensionSupported(
             // For warnings-as-errors.
             break;
     }
 
     if (Preferences::GetBool("webgl.enable-draft-extensions", false) || IsWebGL2()) {
         switch (ext) {
             case WebGLExtensionID::EXT_blend_minmax:
                 return WebGLExtensionBlendMinMax::IsSupported(this);
+            case WebGLExtensionID::EXT_color_buffer_half_float:
+                return WebGLExtensionColorBufferHalfFloat::IsSupported(this);
+            case WebGLExtensionID::WEBGL_color_buffer_float:
+                return WebGLExtensionColorBufferFloat::IsSupported(this);
             default:
                 // For warnings-as-errors.
                 break;
         }
     }
 
     return false;
 }
--- a/content/media/test/mochitest.ini
+++ b/content/media/test/mochitest.ini
@@ -411,17 +411,17 @@ skip-if = true # bug 493692
 [test_referer.html]
 [test_replay_metadata.html]
 [test_reset_events_async.html]
 [test_reset_src.html]
 [test_resume.html]
 skip-if = true # bug 1021673
 [test_seek_out_of_range.html]
 [test_seek.html]
-skip-if = true # Intermittent test failures in bug 1023564 and bug 981153
+skip-if = buildapp == 'b2g' || toolkit == 'android' # Intermittent test failures in bug 1023564 and bug 981153
 [test_seek2.html]
 [test_seekable1.html]
 [test_seekable2.html]
 [test_seekable3.html]
 [test_seekLies.html]
 [test_source.html]
 [test_source_media.html]
 [test_source_null.html]
--- a/content/svg/content/src/SVGPathElement.cpp
+++ b/content/svg/content/src/SVGPathElement.cpp
@@ -63,26 +63,20 @@ NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGPathE
 
 already_AddRefed<SVGAnimatedNumber>
 SVGPathElement::PathLength()
 {
   return mPathLength.ToDOMAnimatedNumber(this);
 }
 
 float
-SVGPathElement::GetTotalLength(ErrorResult& rv)
+SVGPathElement::GetTotalLength()
 {
   RefPtr<Path> flat = GetPathForLengthOrPositionMeasuring();
-
-  if (!flat) {
-    rv.Throw(NS_ERROR_FAILURE);
-    return 0.f;
-  }
-
-  return flat->ComputeLength();
+  return flat ? flat->ComputeLength() : 0.f;
 }
 
 already_AddRefed<nsISVGPoint>
 SVGPathElement::GetPointAtLength(float distance, ErrorResult& rv)
 {
   RefPtr<Path> path = GetPathForLengthOrPositionMeasuring();
   if (!path) {
     rv.Throw(NS_ERROR_FAILURE);
@@ -348,32 +342,33 @@ float
 SVGPathElement::GetPathLengthScale(PathLengthScaleForType aFor)
 {
   NS_ABORT_IF_FALSE(aFor == eForTextPath || aFor == eForStroking,
                     "Unknown enum");
   if (mPathLength.IsExplicitlySet()) {
     float authorsPathLengthEstimate = mPathLength.GetAnimValue();
     if (authorsPathLengthEstimate > 0) {
       RefPtr<Path> path = GetPathForLengthOrPositionMeasuring();
-
+      if (!path) {
+        // The path is empty or invalid so its length must be zero and
+        // we know that 0 / authorsPathLengthEstimate = 0.
+        return 0.0;
+      }
       if (aFor == eForTextPath) {
         // For textPath, a transform on the referenced path affects the
         // textPath layout, so when calculating the actual path length
         // we need to take that into account.
         gfxMatrix matrix = PrependLocalTransformsTo(gfxMatrix());
         if (!matrix.IsIdentity()) {
           RefPtr<PathBuilder> builder =
             path->TransformedCopyToBuilder(ToMatrix(matrix));
           path = builder->Finish();
         }
       }
-
-      if (path) {
-        return path->ComputeLength() / authorsPathLengthEstimate;
-      }
+      return path->ComputeLength() / authorsPathLengthEstimate;
     }
   }
   return 1.0;
 }
 
 TemporaryRef<Path>
 SVGPathElement::BuildPath()
 {
--- a/content/svg/content/src/SVGPathElement.h
+++ b/content/svg/content/src/SVGPathElement.h
@@ -83,17 +83,17 @@ public:
    * Gets the ratio of the actual path length to the content author's estimated
    * length (as provided by the <path> element's 'pathLength' attribute). This
    * is used to scale stroke dashing, and to scale offsets along a textPath.
    */
   float GetPathLengthScale(PathLengthScaleForType aFor);
 
   // WebIDL
   already_AddRefed<SVGAnimatedNumber> PathLength();
-  float GetTotalLength(ErrorResult& rv);
+  float GetTotalLength();
   already_AddRefed<nsISVGPoint> GetPointAtLength(float distance, ErrorResult& rv);
   uint32_t GetPathSegAtLength(float distance);
   already_AddRefed<DOMSVGPathSegClosePath> CreateSVGPathSegClosePath();
   already_AddRefed<DOMSVGPathSegMovetoAbs> CreateSVGPathSegMovetoAbs(float x, float y);
   already_AddRefed<DOMSVGPathSegMovetoRel> CreateSVGPathSegMovetoRel(float x, float y);
   already_AddRefed<DOMSVGPathSegLinetoAbs> CreateSVGPathSegLinetoAbs(float x, float y);
   already_AddRefed<DOMSVGPathSegLinetoRel> CreateSVGPathSegLinetoRel(float x, float y);
   already_AddRefed<DOMSVGPathSegCurvetoCubicAbs>
--- a/content/svg/content/test/test_pathLength.html
+++ b/content/svg/content/test/test_pathLength.html
@@ -27,14 +27,20 @@ https://bugzilla.mozilla.org/show_bug.cg
   is(path.getTotalLength(), 500, "Unexpected path length");
 
   for (var i = 0; i < 2; i++) {
       path.pathSegList.removeItem(path.pathSegList.numberOfItems - 1);
   }
 
   is(path.getTotalLength(), 150, "Unexpected path length");
 
+  while (path.pathSegList.numberOfItems > 0) {
+      path.pathSegList.removeItem(0);
+  }
+
+  is(path.getTotalLength(), 0, "Unexpected path length");
+
   SimpleTest.finish();
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/asmjscache/test/file_slow.js
+++ b/dom/asmjscache/test/file_slow.js
@@ -64,8 +64,10 @@ if (this.jsFuns)
 
 begin = Date.now();
 lastSum = 0;
 if (f3Main() !== lastSum)
     throw "f3.main()";
 
 if (!this.jsFuns)
     postMessage("ok");
+else
+    complete();
--- a/dom/asmjscache/test/test_cachingBasic.html
+++ b/dom/asmjscache/test/test_cachingBasic.html
@@ -12,20 +12,16 @@ https://bugzilla.mozilla.org/show_bug.cg
 <body>
   <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=929236">asm.js browser tests</a>
   <p id="display"></p>
   <div id="content" style="display: none"></div>
   <pre id="test"></pre>
 
   <script>
   var jsFuns = SpecialPowers.Cu.getJSTestingFunctions();
-  ok(jsFuns.isAsmJSCompilationAvailable(), "asm.js compilation is available");
-
-  // generate a big ol asm.js module and compile it async so that we can hit
-  // the asm.js cache.
 
   var code = "function f() { 'use asm';\n";
   for (var i = 0; i < 5000; i++)
     code += "function g" + i + "() { return " + i + "}\n";
   code += "return g42 }\n";
   code += "ok(jsFuns.isAsmJSModule(f), 'f is an asm.js module')\n";
   code += "var g42 = f();\n";
   code += "ok(jsFuns.isAsmJSFunction(g42), 'g42 is an asm.js function')\n";
@@ -51,14 +47,24 @@ https://bugzilla.mozilla.org/show_bug.cg
         ok(jsFuns.isAsmJSModuleLoadedFromCache(module), 'module loaded from cache');
         SimpleTest.finish();
         break;
       default:
         throw "huh?";
     }
   }
 
-  SimpleTest.waitForExplicitFinish();
-  evalAsync(code);
+  function runTest() {
+      // generate a big ol asm.js module and compile it async so that we can hit
+      // the asm.js cache.
+      SimpleTest.waitForExplicitFinish();
+      evalAsync(code);
+  }
+
+  if (!jsFuns.isAsmJSCompilationAvailable()) {
+      ok(true, "isAsmJSCompilationAvailable is false, skipping this test!");
+  } else {
+      runTest();
+  }
   </script>
 
 </body>
 </html>
--- a/dom/asmjscache/test/test_cachingMulti.html
+++ b/dom/asmjscache/test/test_cachingMulti.html
@@ -12,17 +12,18 @@ https://bugzilla.mozilla.org/show_bug.cg
 <body>
   <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=944821">asm.js browser tests</a>
   <p id="display"></p>
   <div id="content" style="display: none"></div>
   <pre id="test"></pre>
 
   <script>
   var jsFuns = SpecialPowers.Cu.getJSTestingFunctions();
-  ok(jsFuns.isAsmJSCompilationAvailable(), "compilation is available");
+
+  var assertCacheHit = false;
 
   // generate four slightly different big asm.js modules and compile them async
   // so that we can hit the asm.js cache.
 
   var code = "function f() { 'use asm';\n";
   for (var i = 0; i < 5000; i++)
     code += "function g" + i + "() { return " + i + "}\n";
   ok(code.length > 100000, "code is long enough to definitely hit the cache");
@@ -45,33 +46,40 @@ https://bugzilla.mozilla.org/show_bug.cg
 
   function evalAsync(code) {
     var blob = new Blob([code], {type:"application/javascript"});
     var script = document.createElement('script');
     script.src = URL.createObjectURL(blob);
     document.body.appendChild(script);
   }
 
-  for (var i = 0; i < N; i++)
-    evalAsync(codes[i]);
-
   var finishedCount = 0;
-  var assertCacheHit = false;
   function finishedEvalAsync() {
     finishedCount++;
 
     if (finishedCount < 1 || finishedCount > 2*N) {
       throw "Huh?!";
     } else if (finishedCount == N) {
       assertCacheHit = true;
       for (var i = 0; i < N; i++)
         evalAsync(codes[i]);
     } else if (finishedCount == 2*N) {
       SimpleTest.finish();
     }
   }
 
-  SimpleTest.waitForExplicitFinish();
+  function runTest() {
+      for (var i = 0; i < N; i++)
+        evalAsync(codes[i]);
+
+      SimpleTest.waitForExplicitFinish();
+  }
+
+  if (!jsFuns.isAsmJSCompilationAvailable()) {
+      ok(true, "isAsmJSCompilationAvailable is false, skipping this test!");
+  } else {
+      runTest();
+  }
   </script>
 
 </body>
 </html>
 
--- a/dom/asmjscache/test/test_slow.html
+++ b/dom/asmjscache/test/test_slow.html
@@ -12,25 +12,37 @@ https://bugzilla.mozilla.org/show_bug.cg
 <body>
   <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=854209">asm.js browser tests</a>
   <p id="display"></p>
   <div id="content" style="display: none"></div>
   <pre id="test"></pre>
 
   <script>
   var jsFuns = SpecialPowers.Cu.getJSTestingFunctions();
-  ok(jsFuns.isAsmJSCompilationAvailable(), "asm.js compilation is available");
+
+  var completed = 0;
+  function complete() {
+      if (++completed == 2)
+          SimpleTest.finish();
+  }
+
+  if (!jsFuns.isAsmJSCompilationAvailable()) {
+      ok(true, "isAsmJSCompilationAvailable is false, skipping this test!");
+  } else {
+      var script = document.createElement("script");
+      script.src = "http://mochi.test:8888/tests/dom/asmjscache/test/file_slow.js";
+      document.body.appendChild(script);
+
+      var w = new Worker('http://mochi.test:8888/tests/dom/asmjscache/test/file_slow.js');
+      w.onmessage = function(e) {
+          ok(e.data === "ok", "Worker asm.js tests");
+          complete();
+      }
+
+      SimpleTest.waitForExplicitFinish();
+  }
   </script>
 
-  <script src="http://mochi.test:8888/tests/dom/asmjscache/test/file_slow.js"></script>
-
   <script>
-  var w = new Worker('http://mochi.test:8888/tests/dom/asmjscache/test/file_slow.js');
-  w.onmessage = function(e) {
-      ok(e.data === "ok", "Worker asm.js tests");
-      SimpleTest.finish();
-  }
-
-  SimpleTest.waitForExplicitFinish();
   </script>
 
 </body>
 </html>
--- a/dom/asmjscache/test/test_workers.html
+++ b/dom/asmjscache/test/test_workers.html
@@ -12,56 +12,63 @@ https://bugzilla.mozilla.org/show_bug.cg
 <body>
   <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=941830">asm.js browser tests</a>
   <p id="display"></p>
   <div id="content" style="display: none"></div>
   <pre id="test"></pre>
 
   <script>
   var jsFuns = SpecialPowers.Cu.getJSTestingFunctions();
-  ok(jsFuns.isAsmJSCompilationAvailable());
 
-  var asmjsCode = "function f() { 'use asm';";
-  for (var i = 0; i < 5000; i++)
-    asmjsCode += "function g" + i + "() { return " + i + "}";
-  asmjsCode += "return g42 }";
-  ok(asmjsCode.length > 100000, "code is long enough to definitely hit the cache");
+  function runTest() {
+      var asmjsCode = "function f() { 'use asm';";
+      for (var i = 0; i < 5000; i++)
+        asmjsCode += "function g" + i + "() { return " + i + "}";
+      asmjsCode += "return g42 }";
+      ok(asmjsCode.length > 100000, "code is long enough to definitely hit the cache");
 
-  var workerCode = asmjsCode;
-  workerCode += "if (f()() !== 42) postMessage('fail'); else postMessage('ok');";
-  workerCode = 'var code = "' + workerCode + '"; eval(code); eval(code)';
-  var workerBlob = new Blob([workerCode], {type:"application/javascript"});
+      var workerCode = asmjsCode;
+      workerCode += "if (f()() !== 42) postMessage('fail'); else postMessage('ok');";
+      workerCode = 'var code = "' + workerCode + '"; eval(code); eval(code)';
+      var workerBlob = new Blob([workerCode], {type:"application/javascript"});
 
-  var mainCode = asmjsCode;
-  mainCode += "ok(jsFuns.isAsmJSModuleLoadedFromCache(f), 'f is a cache hit')\n";
-  mainCode += "var g42 = f();\n";
-  mainCode += "ok(jsFuns.isAsmJSFunction(g42), 'g42 is an asm.js function');\n";
-  mainCode += "ok(g42() === 42, 'g42 returns the correct result');\n";
-  mainCode += "SimpleTest.finish();\n";
-  var mainBlob = new Blob([mainCode], {type:"application/javascript"});
+      var mainCode = asmjsCode;
+      mainCode += "ok(jsFuns.isAsmJSModuleLoadedFromCache(f), 'f is a cache hit')\n";
+      mainCode += "var g42 = f();\n";
+      mainCode += "ok(jsFuns.isAsmJSFunction(g42), 'g42 is an asm.js function');\n";
+      mainCode += "ok(g42() === 42, 'g42 returns the correct result');\n";
+      mainCode += "SimpleTest.finish();\n";
+      var mainBlob = new Blob([mainCode], {type:"application/javascript"});
 
-  var w = new Worker(URL.createObjectURL(workerBlob));
+      var w = new Worker(URL.createObjectURL(workerBlob));
 
-  var received = 0;
-  w.onmessage = function(e) {
-      switch (received) {
-        case 0:
-          ok(e.data === "ok", "Received first message");
-          received = 1;
-          break;
-        case 1:
-          ok(e.data === "ok", "Received second message");
-          received = 2;
+      var received = 0;
+      w.onmessage = function(e) {
+          switch (received) {
+            case 0:
+              ok(e.data === "ok", "Received first message");
+              received = 1;
+              break;
+            case 1:
+              ok(e.data === "ok", "Received second message");
+              received = 2;
 
-          var script = document.createElement('script');
-          script.src = URL.createObjectURL(mainBlob);
-          document.body.appendChild(script);
-          break;
-        default:
-          throw "Huh?";
+              var script = document.createElement('script');
+              script.src = URL.createObjectURL(mainBlob);
+              document.body.appendChild(script);
+              break;
+            default:
+              throw "Huh?";
+          }
       }
+
+      SimpleTest.waitForExplicitFinish();
   }
 
-  SimpleTest.waitForExplicitFinish();
+  if (!jsFuns.isAsmJSCompilationAvailable()) {
+      ok(true, "isAsmJSCompilationAvailable is false, skipping this test!");
+  } else {
+      runTest();
+  }
   </script>
 
 </body>
 </html>
--- a/dom/events/CustomEvent.cpp
+++ b/dom/events/CustomEvent.cpp
@@ -96,16 +96,21 @@ CustomEvent::GetDetail(nsIVariant** aDet
   NS_IF_ADDREF(*aDetail = mDetail);
   return NS_OK;
 }
 
 void
 CustomEvent::GetDetail(JSContext* aCx,
                        JS::MutableHandle<JS::Value> aRetval)
 {
+  if (!mDetail) {
+    aRetval.setNull();
+    return;
+  }
+
   VariantToJsval(aCx, mDetail, aRetval);
 }
 
 nsresult
 NS_NewDOMCustomEvent(nsIDOMEvent** aInstancePtrResult,
                      mozilla::dom::EventTarget* aOwner,
                      nsPresContext* aPresContext,
                      mozilla::WidgetEvent* aEvent)
new file mode 100644
--- /dev/null
+++ b/dom/events/crashtests/1033343.html
@@ -0,0 +1,5 @@
+<script>
+
+document.createEvent('CustomEvent').detail;
+
+</script>
--- a/dom/events/crashtests/crashtests.list
+++ b/dom/events/crashtests/crashtests.list
@@ -1,11 +1,12 @@
 load 104310-1.html
 load 116206-1.html
 load 135345-1.html
 load 422009-1.xhtml
 load 457776-1.html
 load 496308-1.html
 load 682637-1.html
+load 1033343.html
 load eventctor-nulldictionary.html
 load eventctor-nullstorage.html
 load recursive-onload.html
 load recursive-DOMNodeInserted.html
--- a/dom/smil/test/db_smilCSSFromBy.js
+++ b/dom/smil/test/db_smilCSSFromBy.js
@@ -37,16 +37,24 @@ var _fromByTestLists =
                                          toComp:   "40"}),
   ],
   lengthPx: [
     new AnimTestcaseFromBy("0px", "8px", { fromComp: "0px",
                                            midComp: "4px",
                                            toComp: "8px"}),
     new AnimTestcaseFromBy("1px", "10px", { midComp: "6px", toComp: "11px"}),
   ],
+  lengthPxSVG: [
+    new AnimTestcaseFromBy("0px", "8px", { fromComp: "0",
+                                           midComp: "4",
+                                           toComp: "8"}),
+    new AnimTestcaseFromBy("1px", "10px", { fromComp: "1",
+                                            midComp: "6",
+                                            toComp: "11"}),
+  ],
   opacity: [
     new AnimTestcaseFromBy("1", "-1", { midComp: "0.5", toComp: "0"}),
     new AnimTestcaseFromBy("0.4", "-0.6", { midComp: "0.1", toComp: "0"}),
     new AnimTestcaseFromBy("0.8", "-1.4", { midComp: "0.1", toComp: "0"},
                            "opacities with abs val >1 get clamped too early"),
     new AnimTestcaseFromBy("1.2", "-0.6", { midComp: "0.9", toComp: "0.6"},
                            "opacities with abs val >1 get clamped too early"),
   ],
@@ -132,10 +140,10 @@ var gFromByBundles =
     // These testcases implicitly have no effect, because stroke-dasharray is
     // non-additive (and is declared as such in db_smilCSSPropertyList.js)
     new AnimTestcaseFromBy("none", "5"),
     new AnimTestcaseFromBy("10", "5"),
     new AnimTestcaseFromBy("1", "2, 3"),
   ]),
   new TestcaseBundle(gPropList.stroke_width,
                      [].concat(_fromByTestLists.lengthNoUnitsSVG,
-                               _fromByTestLists.lengthPx))
+                               _fromByTestLists.lengthPxSVG))
 ];
--- a/dom/smil/test/db_smilCSSFromTo.js
+++ b/dom/smil/test/db_smilCSSFromTo.js
@@ -98,24 +98,46 @@ var _fromToTestLists = {
   lengthPx: [
     new AnimTestcaseFromTo("0px", "12px", { fromComp: "0px",
                                             midComp:  "6px"}),
     new AnimTestcaseFromTo("16px", "0px", { midComp: "8px",
                                             toComp:  "0px"}),
     new AnimTestcaseFromTo("10px", "20px", { midComp: "15px"}),
     new AnimTestcaseFromTo("41px", "1px", { midComp: "21px"}),
   ],
+  lengthPxSVG: [
+    new AnimTestcaseFromTo("0px", "12px", { fromComp: "0",
+                                            midComp:  "6",
+                                            toComp:  "12"}),
+    new AnimTestcaseFromTo("16px", "0px", { fromComp: "16",
+                                            midComp:   "8",
+                                            toComp:    "0"}),
+    new AnimTestcaseFromTo("10px", "20px", { fromComp: "10",
+                                             midComp:  "15",
+                                             toComp:   "20"}),
+    new AnimTestcaseFromTo("41px", "1px", { fromComp: "41",
+                                            midComp:  "21",
+                                            toComp:    "1"}),
+  ],
   lengthPctSVG: [
     new AnimTestcaseFromTo("20.5%", "0.5%", { midComp: "10.5%" }),
   ],
   lengthPxPctSVG: [
     new AnimTestcaseFromTo("10px", "10%", { midComp: "15px"},
                            "need support for interpolating between " +
                            "px and percent values"),
   ],
+  lengthPxNoUnitsSVG: [
+    new AnimTestcaseFromTo("10", "20px", { fromComp: "10",
+                                           midComp:  "15",
+                                           toComp:   "20"}),
+    new AnimTestcaseFromTo("10px", "20", { fromComp: "10",
+                                           midComp:  "15",
+                                           toComp:   "20"}),
+  ],
   opacity: [
     new AnimTestcaseFromTo("1", "0", { midComp: "0.5" }),
     new AnimTestcaseFromTo("0.2", "0.12", { midComp: "0.16" }),
     new AnimTestcaseFromTo("0.5", "0.7", { midComp: "0.6" }),
     new AnimTestcaseFromTo("0.5", "inherit",
                            { midComp: "0.75", toComp: "1" }),
     // Make sure we don't clamp out-of-range values before interpolation
     new AnimTestcaseFromTo("0.2", "1.2",
@@ -380,39 +402,41 @@ var gFromToBundles = [
                                           midComp: "1.5, 2"}),
     new AnimTestcaseFromTo("2, 8", "6", { midComp: "4, 7"}),
     new AnimTestcaseFromTo("1, 3", "1, 3, 5, 7, 9",
                            { fromComp: "1, 3, 1, 3, 1, 3, 1, 3, 1, 3",
                              midComp:  "1, 3, 3, 5, 5, 2, 2, 4, 4, 6"}),
   ])),
   new TestcaseBundle(gPropList.stroke_dashoffset,
                      [].concat(_fromToTestLists.lengthNoUnitsSVG,
-                               _fromToTestLists.lengthPx,
+                               _fromToTestLists.lengthPxSVG,
                                _fromToTestLists.lengthPxPctSVG,
-                               _fromToTestLists.lengthPctSVG)),
+                               _fromToTestLists.lengthPctSVG,
+                               _fromToTestLists.lengthPxNoUnitsSVG)),
   new TestcaseBundle(gPropList.stroke_linecap, [
     new AnimTestcaseFromTo("butt", "round"),
     new AnimTestcaseFromTo("round", "square"),
   ]),
   new TestcaseBundle(gPropList.stroke_linejoin, [
     new AnimTestcaseFromTo("miter", "round"),
     new AnimTestcaseFromTo("round", "bevel"),
   ]),
   new TestcaseBundle(gPropList.stroke_miterlimit, [
     new AnimTestcaseFromTo("1", "2", { midComp: "1.5" }),
     new AnimTestcaseFromTo("20.1", "10.1", { midComp: "15.1" }),
   ]),
   new TestcaseBundle(gPropList.stroke_opacity, _fromToTestLists.opacity),
   new TestcaseBundle(gPropList.stroke_width,
                      [].concat(_fromToTestLists.lengthNoUnitsSVG,
-                               _fromToTestLists.lengthPx,
+                               _fromToTestLists.lengthPxSVG,
                                _fromToTestLists.lengthPxPctSVG,
-                               _fromToTestLists.lengthPctSVG, [
+                               _fromToTestLists.lengthPctSVG,
+                               _fromToTestLists.lengthPxNoUnitsSVG, [
     new AnimTestcaseFromTo("inherit", "7px",
-                           { fromComp: "1px", midComp: "4px"}),
+                           { fromComp: "1", midComp: "4", toComp: "7" }),
   ])),
   new TestcaseBundle(gPropList.text_anchor, [
     new AnimTestcaseFromTo("start", "middle"),
     new AnimTestcaseFromTo("middle", "end"),
   ]),
   new TestcaseBundle(gPropList.text_decoration, [
     new AnimTestcaseFromTo("none", "underline"),
     new AnimTestcaseFromTo("overline", "line-through"),
--- a/dom/smil/test/db_smilCSSPaced.js
+++ b/dom/smil/test/db_smilCSSPaced.js
@@ -95,33 +95,40 @@ var _pacedTestLists =
   lengthPx : [
     new AnimTestcasePaced("0px; 2px; 6px",
                           { comp0:   "0px",
                             comp1_6: "1px",
                             comp1_3: "2px",
                             comp2_3: "4px",
                             comp1:   "6px"
                           }),
-  ],
-  lengthPx : [
-    new AnimTestcasePaced("0px; 2px; 6px",
-                          { comp0:   "0px",
-                            comp1_6: "1px",
-                            comp1_3: "2px",
-                            comp2_3: "4px",
-                            comp1:   "6px"
-                          }),
     new AnimTestcasePaced("10px; 12px; 8px",
                           { comp0:   "10px",
                             comp1_6: "11px",
                             comp1_3: "12px",
                             comp2_3: "10px",
                             comp1:   "8px"
                           }),
   ],
+  lengthPxSVG : [
+    new AnimTestcasePaced("0px; 2px; 6px",
+                          { comp0:   "0",
+                            comp1_6: "1",
+                            comp1_3: "2",
+                            comp2_3: "4",
+                            comp1:   "6"
+                          }),
+    new AnimTestcasePaced("10px; 12px; 8px",
+                          { comp0:   "10",
+                            comp1_6: "11",
+                            comp1_3: "12",
+                            comp2_3: "10",
+                            comp1:   "8"
+                          }),
+  ],
   lengthPctSVG : [
     new AnimTestcasePaced("5%; 6%; 4%",
                           { comp0:   "5%",
                             comp1_6: "5.5%",
                             comp1_3: "6%",
                             comp2_3: "5%",
                             comp1:   "4%"
                           }),
@@ -286,18 +293,18 @@ var gPacedBundles =
                             comp1_6: "7, 8.5, 5",
                             comp1_3: "7, 10, 3",
                             comp2_3: "4, 6, 3",
                             comp1:   "1, 2, 3"
                           }),
   ])),
   new TestcaseBundle(gPropList.stroke_dashoffset,
                      [].concat(_pacedTestLists.lengthNoUnitsSVG,
-                               _pacedTestLists.lengthPx,
+                               _pacedTestLists.lengthPxSVG,
                                _pacedTestLists.lengthPctSVG,
                                _pacedTestLists.lengthPxPctSVG)),
   new TestcaseBundle(gPropList.stroke_width,
                      [].concat(_pacedTestLists.lengthNoUnitsSVG,
-                               _pacedTestLists.lengthPx,
+                               _pacedTestLists.lengthPxSVG,
                                _pacedTestLists.lengthPctSVG,
                                _pacedTestLists.lengthPxPctSVG)),
   // XXXdholbert TODO: test 'stroke-dasharray' once we support animating it
 ];
--- a/dom/smil/test/test_smilTextZoom.xhtml
+++ b/dom/smil/test/test_smilTextZoom.xhtml
@@ -53,26 +53,26 @@ function main()
     // property, which should _not_ be affected by textZoom.
     var text = document.getElementsByTagName("text")[0];
     var rect = document.getElementsByTagName("rect")[0];
 
     verifyStyle(text, "font-size",    "5px");
     verifyStyle(rect, "stroke-width", "5px");
     svg.setCurrentTime(1);
     verifyStyle(text, "font-size",    "20px");
-    verifyStyle(rect, "stroke-width", "20px");
+    verifyStyle(rect, "stroke-width", "20");
     svg.setCurrentTime(1.5);
     verifyStyle(text, "font-size",    "30px");
-    verifyStyle(rect, "stroke-width", "30px");
+    verifyStyle(rect, "stroke-width", "30");
     svg.setCurrentTime(2);
     verifyStyle(text, "font-size",    "40px");
-    verifyStyle(rect, "stroke-width", "40px");
+    verifyStyle(rect, "stroke-width", "40");
     svg.setCurrentTime(3);
     verifyStyle(text, "font-size",    "40px");
-    verifyStyle(rect, "stroke-width", "40px");
+    verifyStyle(rect, "stroke-width", "40");
   } catch (e) {
     // If anything goes wrong, make sure we restore textZoom before bubbling
     // the exception upwards, so that we don't mess up subsequent tests.
     SpecialPowers.setTextZoom(window, origTextZoom);
 
     throw e;
   }
 
--- a/dom/webidl/SVGPathElement.webidl
+++ b/dom/webidl/SVGPathElement.webidl
@@ -8,17 +8,16 @@
  *
  * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
  * liability, trademark and document use rules apply.
  */
 interface SVGPathElement : SVGGraphicsElement {
 
   readonly attribute SVGAnimatedNumber pathLength;
 
-  [Throws]
   float getTotalLength();
   [NewObject, Throws]
   SVGPoint getPointAtLength(float distance);
   unsigned long getPathSegAtLength(float distance);
   [NewObject]
   SVGPathSegClosePath createSVGPathSegClosePath();
   [NewObject]
   SVGPathSegMovetoAbs createSVGPathSegMovetoAbs(float x, float y);
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -520,21 +520,27 @@ public:
     // vector classes.
     ScreenPoint velocity(mApzc.mX.GetVelocity(), mApzc.mY.GetVelocity());
 
     ScreenPoint offset = velocity * aDelta.ToMilliseconds();
 
     // Inversely scale the offset by the resolution (when you're zoomed further in,
     // the same swipe should move you a shorter distance).
     CSSPoint cssOffset = offset / aFrameMetrics.GetZoom();
+
+    // Ordinarily we might need to do a ScheduleComposite if either of
+    // the following AdjustDisplacement calls returns true, but this
+    // is already running as part of a FlingAnimation, so we'll be compositing
+    // per frame of animation anyway.
     CSSPoint overscroll;
-    aFrameMetrics.ScrollBy(CSSPoint(
-      mApzc.mX.AdjustDisplacement(cssOffset.x, overscroll.x),
-      mApzc.mY.AdjustDisplacement(cssOffset.y, overscroll.y)
-    ));
+    CSSPoint adjustedOffset;
+    mApzc.mX.AdjustDisplacement(cssOffset.x, adjustedOffset.x, overscroll.x);
+    mApzc.mY.AdjustDisplacement(cssOffset.y, adjustedOffset.y, overscroll.y);
+
+    aFrameMetrics.ScrollBy(adjustedOffset);
 
     // The fling may have caused us to reach the end of our scroll range.
     if (!IsZero(overscroll)) {
       if (mAllowOverscroll) {
         // If this is a fling that allows overscroll, then go into overscroll.
 
         mApzc.OverscrollBy(overscroll);
 
@@ -1608,24 +1614,27 @@ bool AsyncPanZoomController::AttemptScro
     ReentrantMonitorAutoEnter lock(mMonitor);
 
     CSSToScreenScale zoom = mFrameMetrics.GetZoom();
 
     // Inversely scale the offset by the resolution (when you're zoomed further in,
     // the same swipe should move you a shorter distance).
     CSSPoint cssDisplacement = displacement / zoom;
 
-    CSSPoint allowedDisplacement(mX.AdjustDisplacement(cssDisplacement.x,
-                                                       cssOverscroll.x),
-                                 mY.AdjustDisplacement(cssDisplacement.y,
-                                                       cssOverscroll.y));
+    CSSPoint adjustedDisplacement;
+    bool xChanged = mX.AdjustDisplacement(cssDisplacement.x, adjustedDisplacement.x, cssOverscroll.x);
+    bool yChanged = mY.AdjustDisplacement(cssDisplacement.y, adjustedDisplacement.y, cssOverscroll.y);
+    if (xChanged || yChanged) {
+      ScheduleComposite();
+    }
+
     overscroll = cssOverscroll * zoom;
 
-    if (!IsZero(allowedDisplacement)) {
-      ScrollBy(allowedDisplacement);
+    if (!IsZero(adjustedDisplacement)) {
+      ScrollBy(adjustedDisplacement);
       ScheduleCompositeAndMaybeRepaint();
       UpdateSharedCompositorFrameMetrics();
     }
   }
 
   // If we consumed the entire displacement as a normal scroll, great.
   if (IsZero(overscroll)) {
     return true;
@@ -1797,18 +1806,27 @@ CalculateDisplayPortSize(const CSSSize& 
                          const CSSPoint& aVelocity)
 {
   float xMultiplier = fabsf(aVelocity.x) < gfxPrefs::APZMinSkateSpeed()
                         ? gfxPrefs::APZXStationarySizeMultiplier()
                         : gfxPrefs::APZXSkateSizeMultiplier();
   float yMultiplier = fabsf(aVelocity.y) < gfxPrefs::APZMinSkateSpeed()
                         ? gfxPrefs::APZYStationarySizeMultiplier()
                         : gfxPrefs::APZYSkateSizeMultiplier();
-  return CSSSize(aCompositionSize.width * xMultiplier,
-                 aCompositionSize.height * yMultiplier);
+
+  // Ensure that it is at least as large as the visible area inflated by the
+  // danger zone. If this is not the case then the "AboutToCheckerboard"
+  // function in TiledContentClient.cpp will return true even in the stable
+  // state.
+  float xSize = std::max(aCompositionSize.width * xMultiplier,
+                         aCompositionSize.width + (2 * gfxPrefs::APZDangerZoneX()));
+  float ySize = std::max(aCompositionSize.height * yMultiplier,
+                         aCompositionSize.height + (2 * gfxPrefs::APZDangerZoneY()));
+
+  return CSSSize(xSize, ySize);
 }
 
 /**
  * Attempts to redistribute any area in the displayport that would get clipped
  * by the scrollable rect, or be inaccessible due to disabled scrolling, to the
  * other axis, while maintaining total displayport area.
  */
 static void
--- a/gfx/layers/apz/src/Axis.cpp
+++ b/gfx/layers/apz/src/Axis.cpp
@@ -57,45 +57,49 @@ void Axis::UpdateWithTouchAtDevicePoint(
 
 void Axis::StartTouch(int32_t aPos, uint32_t aTimestampMs) {
   mStartPos = aPos;
   mPos = aPos;
   mPosTimeMs = aTimestampMs;
   mAxisLocked = false;
 }
 
-float Axis::AdjustDisplacement(float aDisplacement, float& aOverscrollAmountOut) {
+bool Axis::AdjustDisplacement(float aDisplacement,
+                              float& aDisplacementOut,
+                              float& aOverscrollAmountOut)
+{
   if (mAxisLocked) {
     aOverscrollAmountOut = 0;
-    return 0;
+    aDisplacementOut = 0;
+    return false;
   }
 
   float displacement = aDisplacement;
 
   // First consume any overscroll in the opposite direction along this axis.
+  float consumedOverscroll = 0;
   if (mOverscroll > 0 && aDisplacement < 0) {
-    float consumedOverscroll = std::min(mOverscroll, -aDisplacement);
-    mOverscroll -= consumedOverscroll;
-    displacement += consumedOverscroll;
+    consumedOverscroll = std::min(mOverscroll, -aDisplacement);
   } else if (mOverscroll < 0 && aDisplacement > 0) {
-    float consumedOverscroll = std::min(-mOverscroll, aDisplacement);
-    mOverscroll += consumedOverscroll;
-    displacement -= consumedOverscroll;
+    consumedOverscroll = 0 - std::min(-mOverscroll, aDisplacement);
   }
+  mOverscroll -= consumedOverscroll;
+  displacement += consumedOverscroll;
 
   // Split the requested displacement into an allowed displacement that does
   // not overscroll, and an overscroll amount.
   if (DisplacementWillOverscroll(displacement) != OVERSCROLL_NONE) {
     // No need to have a velocity along this axis anymore; it won't take us
     // anywhere, so we're just spinning needlessly.
     mVelocity = 0.0f;
     aOverscrollAmountOut = DisplacementWillOverscrollAmount(displacement);
     displacement -= aOverscrollAmountOut;
   }
-  return displacement;
+  aDisplacementOut = displacement;
+  return fabsf(consumedOverscroll) > EPSILON;
 }
 
 float Axis::ApplyResistance(float aRequestedOverscroll) const {
   // 'resistanceFactor' is a value between 0 and 1, which:
   //   - tends to 1 as the existing overscroll tends to 0
   //   - tends to 0 as the existing overscroll tends to the composition length
   // The actual overscroll is the requested overscroll multiplied by this
   // factor; this should prevent overscrolling by more than the composition
--- a/gfx/layers/apz/src/Axis.h
+++ b/gfx/layers/apz/src/Axis.h
@@ -78,19 +78,23 @@ public:
   void CancelTouch();
 
   /**
    * Takes a requested displacement to the position of this axis, and adjusts it
    * to account for overscroll (which might decrease the displacement; this is
    * to prevent the viewport from overscrolling the page rect), and axis locking
    * (which might prevent any displacement from happening). If overscroll
    * ocurred, its amount is written to |aOverscrollAmountOut|.
-   * The adjusted displacement is returned.
+   * The |aDisplacementOut| parameter is set to the adjusted
+   * displacement, and the function returns true iff internal overscroll amounts
+   * were changed.
    */
-  float AdjustDisplacement(float aDisplacement, float& aOverscrollAmountOut);
+  bool AdjustDisplacement(float aDisplacement,
+                          float& aDisplacementOut,
+                          float& aOverscrollAmountOut);
 
   /**
    * Overscrolls this axis by the requested amount in the requested direction.
    * The axis must be at the end of its scroll range in this direction.
    */
   void OverscrollBy(float aOverscroll);
 
   /**
--- a/gfx/layers/client/TiledContentClient.cpp
+++ b/gfx/layers/client/TiledContentClient.cpp
@@ -187,25 +187,28 @@ SharedFrameMetricsHelper::UpdateFromComp
 
   aViewTransform = ComputeViewTransform(contentMetrics, compositorMetrics);
 
   // Reset the checkerboard risk flag when switching to low precision
   // rendering.
   if (aLowPrecision && !mLastProgressiveUpdateWasLowPrecision) {
     // Skip low precision rendering until we're at risk of checkerboarding.
     if (!mProgressiveUpdateWasInDanger) {
+      TILING_PRLOG(("TILING: Aborting low-precision rendering because not at risk of checkerboarding\n"));
       return true;
     }
     mProgressiveUpdateWasInDanger = false;
   }
   mLastProgressiveUpdateWasLowPrecision = aLowPrecision;
 
   // Always abort updates if the resolution has changed. There's no use
   // in drawing at the incorrect resolution.
   if (!FuzzyEquals(compositorMetrics.GetZoom().scale, contentMetrics.GetZoom().scale)) {
+    TILING_PRLOG(("TILING: Aborting because resolution changed from %f to %f\n",
+        contentMetrics.GetZoom().scale, compositorMetrics.GetZoom().scale));
     return true;
   }
 
   // Never abort drawing if we can't be sure we've sent a more recent
   // display-port. If we abort updating when we shouldn't, we can end up
   // with blank regions on the screen and we open up the risk of entering
   // an endless updating cycle.
   if (fabsf(contentMetrics.GetScrollOffset().x - compositorMetrics.GetScrollOffset().x) <= 2 &&
@@ -234,16 +237,17 @@ SharedFrameMetricsHelper::UpdateFromComp
       mProgressiveUpdateWasInDanger = true;
       return true;
     }
   }
 
   // Abort drawing stale low-precision content if there's a more recent
   // display-port in the pipeline.
   if (aLowPrecision && !aHasPendingNewThebesContent) {
+    TILING_PRLOG(("TILING: Aborting low-precision because of new pending content\n"));
     return true;
   }
 
   return false;
 }
 
 bool
 SharedFrameMetricsHelper::AboutToCheckerboard(const FrameMetrics& aContentMetrics,
@@ -267,17 +271,24 @@ SharedFrameMetricsHelper::AboutToChecker
                   / aCompositorMetrics.LayersPixelsPerCSSPixel());
 
   // Clamp both rects to the scrollable rect, because having either of those
   // exceed the scrollable rect doesn't make sense, and could lead to false
   // positives.
   painted = painted.Intersect(aContentMetrics.mScrollableRect);
   showing = showing.Intersect(aContentMetrics.mScrollableRect);
 
-  return !painted.Contains(showing);
+  if (!painted.Contains(showing)) {
+    TILING_PRLOG_OBJ(("TILING: About to checkerboard; content %s\n", tmpstr.get()), aContentMetrics);
+    TILING_PRLOG_OBJ(("TILING: About to checkerboard; painted %s\n", tmpstr.get()), painted);
+    TILING_PRLOG_OBJ(("TILING: About to checkerboard; compositor %s\n", tmpstr.get()), aCompositorMetrics);
+    TILING_PRLOG_OBJ(("TILING: About to checkerboard; showing %s\n", tmpstr.get()), showing);
+    return true;
+  }
+  return false;
 }
 
 ClientTiledLayerBuffer::ClientTiledLayerBuffer(ClientTiledThebesLayer* aThebesLayer,
                                              CompositableClient* aCompositableClient,
                                              ClientLayerManager* aManager,
                                              SharedFrameMetricsHelper* aHelper)
   : mThebesLayer(aThebesLayer)
   , mCompositableClient(aCompositableClient)
--- a/image/decoders/nsBMPDecoder.cpp
+++ b/image/decoders/nsBMPDecoder.cpp
@@ -192,44 +192,133 @@ void
 nsBMPDecoder::WriteInternal(const char* aBuffer, uint32_t aCount, DecodeStrategy)
 {
     NS_ABORT_IF_FALSE(!HasError(), "Shouldn't call WriteInternal after error!");
 
     // aCount=0 means EOF, mCurLine=0 means we're past end of image
     if (!aCount || !mCurLine)
         return;
 
+    // This code assumes that mRawBuf == WIN_V3_INTERNAL_BIH_LENGTH
+    // and that sizeof(mRawBuf) >= BFH_INTERNAL_LENGTH
+    MOZ_ASSERT(sizeof(mRawBuf) == WIN_V3_INTERNAL_BIH_LENGTH);
+    MOZ_ASSERT(sizeof(mRawBuf) >= BFH_INTERNAL_LENGTH);
+    MOZ_ASSERT(OS2_INTERNAL_BIH_LENGTH < WIN_V3_INTERNAL_BIH_LENGTH);
+    // This code also assumes it's working with a byte array
+    MOZ_ASSERT(sizeof(mRawBuf[0]) == 1);
+
     if (mPos < BFH_INTERNAL_LENGTH) { /* In BITMAPFILEHEADER */
+        // BFH_INTERNAL_LENGTH < sizeof(mRawBuf)
+        // mPos < BFH_INTERNAL_LENGTH
+        // BFH_INTERNAL_LENGTH - mPos < sizeof(mRawBuf)
+        // so toCopy <= BFH_INTERNAL_LENGTH
+        // so toCopy < sizeof(mRawBuf)
+        // so toCopy > 0 && toCopy <= BFH_INTERNAL_LENGTH
         uint32_t toCopy = BFH_INTERNAL_LENGTH - mPos;
         if (toCopy > aCount)
             toCopy = aCount;
+
+        // mRawBuf is a byte array of size WIN_V3_INTERNAL_BIH_LENGTH (verified above)
+        // mPos is < BFH_INTERNAL_LENGTH
+        // BFH_INTERNAL_LENGTH < WIN_V3_INTERNAL_BIH_LENGTH
+        // so mPos < sizeof(mRawBuf)
+        //
+        // Therefore this assert should hold
+        MOZ_ASSERT(mPos < sizeof(mRawBuf));
+
+        // toCopy <= BFH_INTERNAL_LENGTH
+        // mPos >= 0 && mPos < BFH_INTERNAL_LENGTH
+        // sizeof(mRawBuf) >= BFH_INTERNAL_LENGTH (verified above)
+        //
+        // Therefore this assert should hold
+        MOZ_ASSERT(mPos + toCopy <= sizeof(mRawBuf));
+
         memcpy(mRawBuf + mPos, aBuffer, toCopy);
         mPos += toCopy;
         aCount -= toCopy;
         aBuffer += toCopy;
     }
     if (mPos == BFH_INTERNAL_LENGTH) {
         ProcessFileHeader();
         if (mBFH.signature[0] != 'B' || mBFH.signature[1] != 'M') {
             PostDataError();
             return;
         }
         if (mBFH.bihsize == OS2_BIH_LENGTH)
             mLOH = OS2_HEADER_LENGTH;
     }
     if (mPos >= BFH_INTERNAL_LENGTH && mPos < mLOH) { /* In BITMAPINFOHEADER */
+        // mLOH == WIN_V3_HEADER_LENGTH || mLOH == OS2_HEADER_LENGTH
+        // OS2_HEADER_LENGTH < WIN_V3_HEADER_LENGTH
+        // BFH_INTERNAL_LENGTH < OS2_HEADER_LENGTH
+        // BFH_INTERNAL_LENGTH < WIN_V3_HEADER_LENGTH
+        //
+        // So toCopy is in the range
+        //      1 to (WIN_V3_HEADER_LENGTH - BFH_INTERNAL_LENGTH)
+        // or   1 to (OS2_HEADER_LENGTH - BFH_INTERNAL_LENGTH)
+        //
+        // But WIN_V3_HEADER_LENGTH = BFH_INTERNAL_LENGTH + WIN_V3_INTERNAL_BIH_LENGTH
+        // and OS2_HEADER_LENGTH = BFH_INTERNAL_LENGTH + OS2_INTERNAL_BIH_LENGTH
+        //
+        // So toCopy is in the range
+        //
+        //      1 to WIN_V3_INTERNAL_BIH_LENGTH
+        // or   1 to OS2_INTERNAL_BIH_LENGTH
+        // and  OS2_INTERNAL_BIH_LENGTH < WIN_V3_INTERNAL_BIH_LENGTH
+        //
+        // sizeof(mRawBuf) = WIN_V3_INTERNAL_BIH_LENGTH
+        // so toCopy <= sizeof(mRawBuf)
         uint32_t toCopy = mLOH - mPos;
         if (toCopy > aCount)
             toCopy = aCount;
-        memcpy(mRawBuf + (mPos - BFH_INTERNAL_LENGTH), aBuffer, toCopy);
+
+        // mPos is in the range
+        //      BFH_INTERNAL_LENGTH to (WIN_V3_HEADER_LENGTH - 1)
+        //
+        // offset is then in the range (see toCopy comments for more details)
+        //      0 to (WIN_V3_INTERNAL_BIH_LENGTH - 1)
+        //
+        // sizeof(mRawBuf) is WIN_V3_INTERNAL_BIH_LENGTH so this
+        // offset stays within bounds and this assert should hold
+        const uint32_t offset = mPos - BFH_INTERNAL_LENGTH;
+        MOZ_ASSERT(offset < sizeof(mRawBuf));
+
+        // Two cases:
+        //      mPos = BFH_INTERNAL_LENGTH
+        //      mLOH = WIN_V3_HEADER_LENGTH
+        //
+        // offset = 0
+        // toCopy = WIN_V3_INTERNAL_BIH_LENGTH
+        //
+        //      This will be in the bounds of sizeof(mRawBuf)
+        //
+        // Second Case:
+        //      mPos = WIN_V3_HEADER_LENGTH - 1
+        //      mLOH = WIN_V3_HEADER_LENGTH
+        //
+        // offset = WIN_V3_INTERNAL_BIH_LENGTH - 1
+        // toCopy = 1
+        //
+        //      This will be in the bounds of sizeof(mRawBuf)
+        //
+        // As sizeof(mRawBuf) == WIN_V3_INTERNAL_BIH_LENGTH (verified above)
+        // and WIN_V3_HEADER_LENGTH is the largest range of values. If mLOH
+        // was equal to OS2_HEADER_LENGTH then the ranges are smaller.
+        MOZ_ASSERT(offset + toCopy <= sizeof(mRawBuf));
+
+        memcpy(mRawBuf + offset, aBuffer, toCopy);
         mPos += toCopy;
         aCount -= toCopy;
         aBuffer += toCopy;
     }
 
+    // At this point mPos should be >= mLOH unless aBuffer did not have enough
+    // data. In the latter case aCount should be 0.
+    MOZ_ASSERT(mPos >= mLOH || aCount == 0);
+
     // HasSize is called to ensure that if at this point mPos == mLOH but
     // we have no data left to process, the next time WriteInternal is called
     // we won't enter this condition again.
     if (mPos == mLOH && !HasSize()) {
         ProcessInfoHeader();
         PR_LOG(GetBMPLog(), PR_LOG_DEBUG, ("BMP is %lix%lix%lu. compression=%lu\n",
                mBIH.width, mBIH.height, mBIH.bpp, mBIH.compression));
         // Verify we support this bit depth
@@ -371,22 +460,72 @@ nsBMPDecoder::WriteInternal(const char* 
                     break;
             }
             mPos++; aBuffer++; aCount--;
             at = (at + 1) % bytesPerColor;
         }
       }
     }
     else if (aCount && mBIH.compression == BI_BITFIELDS && mPos < (WIN_V3_HEADER_LENGTH + BITFIELD_LENGTH)) {
-        // If compression is used, this is a windows bitmap, hence we can
-        // use WIN_HEADER_LENGTH instead of mLOH
+        // If compression is used, this is a windows bitmap (compression can't be used with OS/2 bitmaps),
+        // hence we can use WIN_V3_HEADER_LENGTH instead of mLOH.
+        // (verified below)
+
+        // If aCount != 0 then mPos should be >= mLOH due to the if statements
+        // at the beginning of the function
+        MOZ_ASSERT(mPos >= mLOH);
+        MOZ_ASSERT(mLOH == WIN_V3_HEADER_LENGTH);
+
+        // mLOH == WIN_V3_HEADER_LENGTH (verified above)
+        // mPos >= mLOH (verified above)
+        // mPos < WIN_V3_HEADER_LENGTH + BITFIELD_LENGTH
+        //
+        // So toCopy is in the range
+        //      0 to (BITFIELD_LENGTH - 1)
         uint32_t toCopy = (WIN_V3_HEADER_LENGTH + BITFIELD_LENGTH) - mPos;
         if (toCopy > aCount)
             toCopy = aCount;
-        memcpy(mRawBuf + (mPos - WIN_V3_HEADER_LENGTH), aBuffer, toCopy);
+
+        // mPos >= WIN_V3_HEADER_LENGTH
+        // mPos < WIN_V3_HEADER_LENGTH + BITFIELD_LENGTH
+        //
+        // offset is in the range
+        //      0 to (BITFIELD_LENGTH - 1)
+        //
+        // BITFIELD_LENGTH < WIN_V3_INTERNAL_BIH_LENGTH
+        // and sizeof(mRawBuf) == WIN_V3_INTERNAL_BIH_LENGTH (verified at top of function)
+        //
+        // Therefore this assert should hold
+        const uint32_t offset = mPos - WIN_V3_HEADER_LENGTH;
+        MOZ_ASSERT(offset < sizeof(mRawBuf));
+
+        // Two cases:
+        //      mPos = WIN_V3_HEADER_LENGTH
+        //
+        // offset = 0
+        // toCopy = BITFIELD_LENGTH
+        //
+        //      This will be in the bounds of sizeof(mRawBuf)
+        //
+        // Second case:
+        //
+        //      mPos = WIN_V3_HEADER_LENGTH + BITFIELD_LENGTH - 1
+        //
+        // offset = BITFIELD_LENGTH - 1
+        // toCopy = 1
+        //
+        //      This will be in the bounds of sizeof(mRawBuf)
+        //
+        // As BITFIELD_LENGTH < WIN_V3_INTERNAL_BIH_LENGTH and
+        // sizeof(mRawBuf) == WIN_V3_INTERNAL_BIH_LENGTH
+        //
+        // Therefore this assert should hold
+        MOZ_ASSERT(offset + toCopy <= sizeof(mRawBuf));
+
+        memcpy(mRawBuf + offset, aBuffer, toCopy);
         mPos += toCopy;
         aBuffer += toCopy;
         aCount -= toCopy;
     }
     if (mPos == WIN_V3_HEADER_LENGTH + BITFIELD_LENGTH && 
         mBIH.compression == BI_BITFIELDS) {
         mBitFields.red = LittleEndian::readUint32(reinterpret_cast<uint32_t*>(mRawBuf));
         mBitFields.green = LittleEndian::readUint32(reinterpret_cast<uint32_t*>(mRawBuf + 4));
--- a/image/decoders/nsBMPDecoder.h
+++ b/image/decoders/nsBMPDecoder.h
@@ -49,21 +49,21 @@ public:
     virtual void FinishInternal();
 
 private:
 
     /** Calculates the red-, green- and blueshift in mBitFields using
      * the bitmasks from mBitFields */
     NS_METHOD CalcBitShift();
 
-    uint32_t mPos;
+    uint32_t mPos; ///< Number of bytes read from aBuffer in WriteInternal()
 
     BMPFILEHEADER mBFH;
     BITMAPV5HEADER mBIH;
-    char mRawBuf[WIN_V3_INTERNAL_BIH_LENGTH];
+    char mRawBuf[WIN_V3_INTERNAL_BIH_LENGTH]; ///< If this is changed, WriteInternal() MUST be updated
 
     uint32_t mLOH; ///< Length of the header
 
     uint32_t mNumColors; ///< The number of used colors, i.e. the number of entries in mColors
     colorTable *mColors;
 
     bitFields mBitFields;
 
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -875,18 +875,33 @@ GetSavedFrameCount(JSContext *cx, unsign
     args.rval().setNumber(cx->compartment()->savedStacks().count());
     return true;
 }
 
 static bool
 SaveStack(JSContext *cx, unsigned argc, jsval *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
+
+    unsigned maxFrameCount = 0;
+    if (args.length() >= 1) {
+        double d;
+        if (!ToNumber(cx, args[0], &d))
+            return false;
+        if (d < 0) {
+            js_ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE,
+                                     JSDVG_SEARCH_STACK, args[0], JS::NullPtr(),
+                                     "not a valid maximum frame count", NULL);
+            return false;
+        }
+        maxFrameCount = d;
+    }
+
     Rooted<JSObject*> stack(cx);
-    if (!JS::CaptureCurrentStack(cx, &stack))
+    if (!JS::CaptureCurrentStack(cx, &stack, maxFrameCount))
         return false;
     args.rval().setObjectOrNull(stack);
     return true;
 }
 
 static bool
 EnableTrackAllocations(JSContext *cx, unsigned argc, jsval *vp)
 {
--- a/js/src/jit-test/tests/asm.js/testFFI.js
+++ b/js/src/jit-test/tests/asm.js/testFFI.js
@@ -93,53 +93,55 @@ var recurse = function(i,j) { if (i == 0
 var f = asmLink(asmCompile('glob', 'imp', USE_ASM + 'var ffi=imp.ffi; function g(i,j,k) { i=i|0;j=+j;k=k|0; if (!(k|0)) ffi(i|0,j)|0; else g(i, j+1.0, (k-1)|0) } function f(i,j) { i=i|0;j=+j; g(i,j,4) } return f'), null, {ffi:recurse});
 assertThrowsValue(function() { f(0,2.4) }, 2.4+4);
 assertThrowsValue(function() { f(1,2.4) }, 2.4+8);
 assertThrowsValue(function() { f(8,2.4) }, 2.4+36);
 
 assertEq(asmLink(asmCompile('glob', 'imp', USE_ASM + 'var identity=imp.identity; function g(x) { x=+x; return +identity(x) } return g'), null, imp)(13.37), 13.37);
 
 // Test asm.js => ion paths
-setJitCompilerOption("ion.usecount.trigger", 20);
+setJitCompilerOption("ion.usecount.trigger", 10);
+setJitCompilerOption("baseline.usecount.trigger", 0);
+setJitCompilerOption("offthread-compilation.enable", 0);
 
 // In registers on x64 and ARM, on the stack for x86
 function ffiIntFew(a,b,c,d) { return d+1 }
 var f = asmLink(asmCompile('glob', 'imp', USE_ASM + 'var ffi=imp.ffi; function f(i) { i=i|0; return ffi(i|0,(i+1)|0,(i+2)|0,(i+3)|0)|0 } return f'), null, {ffi:ffiIntFew});
 for (var i = 0; i < 40; i++)
     assertEq(f(i), i+4);
 
 // Stack and registers for x64 and ARM, stack for x86
 function ffiIntMany(a,b,c,d,e,f,g,h,i,j) { return j+1 }
 var f = asmLink(asmCompile('glob', 'imp', USE_ASM + 'var ffi=imp.ffi; function f(i) { i=i|0; return ffi(i|0,(i+1)|0,(i+2)|0,(i+3)|0,(i+4)|0,(i+5)|0,(i+6)|0,(i+7)|0,(i+8)|0,(i+9)|0)|0 } return f'), null, {ffi:ffiIntMany});
-for (var i = 0; i < 40; i++)
+for (var i = 0; i < 15; i++)
     assertEq(f(i), i+10);
 
 // In registers on x64 and ARM, on the stack for x86
 function ffiDoubleFew(a,b,c,d) { return d+1 }
 var f = asmLink(asmCompile('glob', 'imp', USE_ASM + 'var ffi=imp.ffi; function f(i) { i=+i; return +ffi(i,i+1.0,i+2.0,i+3.0) } return f'), null, {ffi:ffiDoubleFew});
-for (var i = 0; i < 40; i++)
+for (var i = 0; i < 15; i++)
     assertEq(f(i), i+4);
 
 // Stack and registers for x64 and ARM, stack for x86
 function ffiDoubleMany(a,b,c,d,e,f,g,h,i,j) { return j+1 }
 var f = asmLink(asmCompile('glob', 'imp', USE_ASM + 'var ffi=imp.ffi; function f(i) { i=+i; return +ffi(i,i+1.0,i+2.0,i+3.0,i+4.0,i+5.0,i+6.0,i+7.0,i+8.0,i+9.0) } return f'), null, {ffi:ffiDoubleMany});
-for (var i = 0; i < 40; i++)
+for (var i = 0; i < 15; i++)
     assertEq(f(i), i+10);
 
 // Test the throw path
-function ffiThrow(n) { if (n == 38) throw 'yolo'; }
+function ffiThrow(n) { if (n == 14) throw 'yolo'; }
 var f = asmLink(asmCompile('glob', 'imp', USE_ASM + 'var ffi=imp.ffi; function f(i) { i=i|0; ffi(i >> 0); } return f'), null, {ffi:ffiThrow});
 var i = 0;
 try {
-    for (; i < 40; i++)
+    for (; i < 15; i++)
         f(i);
     throw 'assume unreachable';
 } catch (e) {
     assertEq(e, 'yolo');
-    assertEq(i, 38);
+    assertEq(i, 14);
 }
 
 // OOL conversion paths
 var INT32_MAX = Math.pow(2, 31) - 1;
 function ffiOOLConvertInt(n) { if (n == 40) return INT32_MAX + 1; return 42; }
 var f = asmLink(asmCompile('glob', 'imp', USE_ASM + 'var ffi=imp.ffi; function f(i) { i=i|0; return ffi(i >> 0) | 0; } return f'), null, {ffi:ffiOOLConvertInt});
 for (var i = 0; i < 40; i++)
     assertEq(f(i), 42);
--- a/js/src/jit-test/tests/asm.js/testStackWalking.js
+++ b/js/src/jit-test/tests/asm.js/testStackWalking.js
@@ -12,20 +12,24 @@ function matchStack(stackString, stackAr
 }
 
 var stack;
 function dumpStack()
 {
     stack = new Error().stack
 }
 
+setJitCompilerOption("ion.usecount.trigger", 10);
+setJitCompilerOption("baseline.usecount.trigger", 0);
+setJitCompilerOption("offthread-compilation.enable", 0);
+
 var callFFI = asmCompile('global', 'ffis', USE_ASM + "var ffi=ffis.ffi; function f() { return ffi()|0 } return f");
 
 var f = asmLink(callFFI, null, {ffi:dumpStack});
-for (var i = 0; i < 5000; i++) {
+for (var i = 0; i < 15; i++) {
     stack = null;
     f();
     matchStack(stack, ['dumpStack', 'f']);
 }
 
 if (isAsmJSCompilationAvailable() && isCachingEnabled()) {
     var callFFI = asmCompile('global', 'ffis', USE_ASM + "var ffi=ffis.ffi; function f() { return ffi()|0 } return f");
     assertEq(isAsmJSModuleLoadedFromCache(callFFI), true);
@@ -37,17 +41,17 @@ if (isAsmJSCompilationAvailable() && isC
 var f1 = asmLink(callFFI, null, {ffi:dumpStack});
 var f2 = asmLink(callFFI, null, {ffi:function middle() { f1() }});
 stack = null;
 (function outer() { f2() })();
 matchStack(stack, ["dumpStack", "f", "middle", "f"]);
 
 function returnStackDumper() { return { valueOf:function() { stack = new Error().stack } } }
 var f = asmLink(callFFI, null, {ffi:returnStackDumper});
-for (var i = 0; i < 5000; i++) {
+for (var i = 0; i < 15; i++) {
     stack = null;
     f();
     matchStack(stack, ['valueOf', 'f']);
 }
 
 var caught = false;
 try {
     stack = null;
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/max-frame-count.js
@@ -0,0 +1,42 @@
+// Test that we can capture only the N newest frames.
+// This is the maxFrameCount argument to JS::CaptureCurrentStack.
+
+load(libdir + 'asserts.js');
+
+function recur(n, limit) {
+  if (n > 0)
+    return recur(n - 1, limit);
+  return saveStack(limit);
+}
+
+// Negative values are rejected.
+assertThrowsInstanceOf(() => saveStack(-1), TypeError);
+
+// Zero means 'no limit'.
+assertEq(saveStack(0).parent, null);
+assertEq(recur(0, 0).parent !== null, true);
+assertEq(recur(0, 0).parent.parent, null);
+assertEq(recur(1, 0).parent.parent.parent, null);
+assertEq(recur(2, 0).parent.parent.parent.parent, null);
+assertEq(recur(3, 0).parent.parent.parent.parent.parent, null);
+
+// limit of 1
+assertEq(saveStack(1).parent, null);
+assertEq(recur(0, 1).parent, null);
+assertEq(recur(0, 1).parent, null);
+assertEq(recur(1, 1).parent, null);
+assertEq(recur(2, 1).parent, null);
+
+// limit of 2
+assertEq(saveStack(2).parent, null);
+assertEq(recur(0, 2).parent !== null, true);
+assertEq(recur(0, 2).parent.parent, null);
+assertEq(recur(1, 2).parent.parent, null);
+assertEq(recur(2, 2).parent.parent, null);
+
+// limit of 3
+assertEq(saveStack(3).parent, null);
+assertEq(recur(0, 3).parent !== null, true);
+assertEq(recur(0, 3).parent.parent, null);
+assertEq(recur(1, 3).parent.parent.parent, null);
+assertEq(recur(2, 3).parent.parent.parent, null);
--- a/js/src/jit/AsmJS.cpp
+++ b/js/src/jit/AsmJS.cpp
@@ -11,17 +11,16 @@
 #ifdef MOZ_VTUNE
 # include "vtune/VTuneWrapper.h"
 #endif
 
 #include "jsmath.h"
 #include "jsprf.h"
 #include "prmjtime.h"
 
-#include "assembler/assembler/MacroAssembler.h"
 #include "frontend/Parser.h"
 #include "jit/AsmJSLink.h"
 #include "jit/AsmJSModule.h"
 #include "jit/AsmJSSignalHandlers.h"
 #include "jit/CodeGenerator.h"
 #include "jit/CompileWrappers.h"
 #include "jit/MIR.h"
 #include "jit/MIRGraph.h"
@@ -1404,19 +1403,16 @@ class MOZ_STACK_CLASS ModuleCompiler
         if (p) {
             *exitIndex = p->value();
             return true;
         }
         if (!module_->addExit(ffiIndex, exitIndex))
             return false;
         return exits_.add(p, Move(exitDescriptor), *exitIndex);
     }
-    bool addFunctionName(PropertyName *name, uint32_t *index) {
-        return module_->addFunctionName(name, index);
-    }
 
     // Note a constraint on the minimum size of the heap.  The heap size is
     // constrained when linking to be at least the maximum of all such constraints.
     void requireHeapLengthToBeAtLeast(uint32_t len) {
         module_->requireHeapLengthToBeAtLeast(len);
     }
     uint32_t minHeapLength() const {
         return module_->minHeapLength();
@@ -1438,16 +1434,21 @@ class MOZ_STACK_CLASS ModuleCompiler
         masm_.resetForNewCodeGenerator(mir.alloc());
         masm_.align(CodeAlignment);
         masm_.bind(func.code());
     }
 
     bool finishGeneratingFunction(Func &func, MIRGenerator &mir, CodeGenerator &codegen) {
         JS_ASSERT(func.defined() && func.code()->bound());
 
+        uint32_t beginOffset = func.code()->offset();
+        uint32_t endOffset = masm_.currentOffset();
+        if (!module_->addFunctionCodeRange(func.name(), beginOffset, endOffset))
+            return false;
+
         jit::IonScriptCounts *counts = codegen.extractScriptCounts();
         if (counts && !module_->addFunctionCounts(counts)) {
             js_delete(counts);
             return false;
         }
 
 #if defined(MOZ_VTUNE) || defined(JS_ION_PERF)
         unsigned line = 0, column = 0;
@@ -1475,25 +1476,29 @@ class MOZ_STACK_CLASS ModuleCompiler
 
     void finishFunctionBodies() {
         JS_ASSERT(!finishedFunctionBodies_);
         masm_.align(AsmJSPageSize);
         finishedFunctionBodies_ = true;
         module_->finishFunctionBodies(masm_.currentOffset());
     }
 
+    void startGeneratingEntry(unsigned exportIndex) {
+        module_->exportedFunction(exportIndex).initCodeOffset(masm_.currentOffset());
+    }
+    bool finishGeneratingEntry(unsigned exportIndex) {
+        return module_->addEntryCodeRange(exportIndex, masm_.currentOffset());
+    }
+
     void setInterpExitOffset(unsigned exitIndex) {
         module_->exit(exitIndex).initInterpOffset(masm_.currentOffset());
     }
     void setIonExitOffset(unsigned exitIndex) {
         module_->exit(exitIndex).initIonOffset(masm_.currentOffset());
     }
-    void setEntryOffset(unsigned exportIndex) {
-        module_->exportedFunction(exportIndex).initCodeOffset(masm_.currentOffset());
-    }
 
     void buildCompilationTimeReport(bool storedInCache, ScopedJSFreePtr<char> *out) {
         ScopedJSFreePtr<char> slowFuns;
 #ifndef JS_MORE_DETERMINISTIC
         int64_t usecAfter = PRMJ_Now();
         int msTotal = (usecAfter - usecBefore_) / PRMJ_USEC_PER_MSEC;
         if (!slowFunctions_.empty()) {
             slowFuns.reset(JS_smprintf("; %d functions compiled slowly: ", slowFunctions_.length()));
@@ -1803,17 +1808,16 @@ class FunctionCompiler
     typedef Vector<TypedValue> VarInitializerVector;
     typedef HashMap<PropertyName*, BlockVector> LabeledBlockMap;
     typedef HashMap<ParseNode*, BlockVector> UnlabeledBlockMap;
     typedef Vector<ParseNode*, 4> NodeStack;
 
     ModuleCompiler &       m_;
     LifoAlloc &            lifo_;
     ParseNode *            fn_;
-    uint32_t               functionNameIndex_;
 
     LocalMap               locals_;
     VarInitializerVector   varInitializers_;
     Maybe<RetType>         alreadyReturned_;
 
     TempAllocator *        alloc_;
     MIRGraph *             graph_;
     CompileInfo *          info_;
@@ -1824,25 +1828,21 @@ class FunctionCompiler
 
     NodeStack              loopStack_;
     NodeStack              breakableStack_;
     UnlabeledBlockMap      unlabeledBreaks_;
     UnlabeledBlockMap      unlabeledContinues_;
     LabeledBlockMap        labeledBreaks_;
     LabeledBlockMap        labeledContinues_;
 
-    static const uint32_t NO_FUNCTION_NAME_INDEX = UINT32_MAX;
-    JS_STATIC_ASSERT(NO_FUNCTION_NAME_INDEX > CallSiteDesc::FUNCTION_NAME_INDEX_MAX);
-
   public:
     FunctionCompiler(ModuleCompiler &m, ParseNode *fn, LifoAlloc &lifo)
       : m_(m),
         lifo_(lifo),
         fn_(fn),
-        functionNameIndex_(NO_FUNCTION_NAME_INDEX),
         locals_(m.cx()),
         varInitializers_(m.cx()),
         alloc_(nullptr),
         graph_(nullptr),
         info_(nullptr),
         mirGen_(nullptr),
         curBlock_(nullptr),
         loopStack_(m.cx()),
@@ -2274,22 +2274,17 @@ class FunctionCompiler
         if (inDeadCode()) {
             *def = nullptr;
             return true;
         }
 
         uint32_t line, column;
         m_.tokenStream().srcCoords.lineNumAndColumnIndex(call.node_->pn_pos.begin, &line, &column);
 
-        if (functionNameIndex_ == NO_FUNCTION_NAME_INDEX) {
-            if (!m_.addFunctionName(FunctionName(fn_), &functionNameIndex_))
-                return false;
-        }
-
-        CallSiteDesc desc(line, column, functionNameIndex_);
+        CallSiteDesc desc(line, column);
         MAsmJSCall *ins = MAsmJSCall::New(alloc(), desc, callee, call.regArgs_, returnType,
                                           call.spIncrement_);
         if (!ins)
             return false;
 
         curBlock_->add(ins);
         *def = ins;
         return true;
@@ -5950,17 +5945,17 @@ GenerateEntry(ModuleCompiler &m, const A
     // In constrast to the system ABI, the Ion convention is that all registers
     // are clobbered by calls. Thus, we must save the caller's non-volatile
     // registers.
     //
     // NB: GenerateExits assumes that masm.framePushed() == 0 before
     // PushRegsInMask(NonVolatileRegs).
     masm.setFramePushed(0);
 
-    // See AsmJSFrameSize comment in Assembler-*.h.
+    // See AsmJSFrameSize comment in Assembler-shared.h.
 #if defined(JS_CODEGEN_ARM)
     masm.push(lr);
 #endif // JS_CODEGEN_ARM
 #if defined(JS_CODEGEN_MIPS)
     masm.push(ra);
 #endif
 
     masm.PushRegsInMask(NonVolatileRegs);
@@ -6025,17 +6020,17 @@ GenerateEntry(ModuleCompiler &m, const A
                 masm.storeDouble(ScratchDoubleReg, Address(StackPointer, iter->offsetFromArgBase()));
             }
             break;
         }
     }
 
     // Call into the real function.
     AssertStackAlignment(masm);
-    masm.call(CallSiteDesc::Entry(), func.code());
+    masm.call(func.code());
 
     // Pop the stack and recover the original 'argv' argument passed to the
     // trampoline (which was pushed on the stack).
     masm.freeStack(stackDec);
     masm.Pop(argv);
 
     // Store the return value in argv[0]
     switch (func.sig().retType().which()) {
@@ -6209,24 +6204,28 @@ static void
 GenerateFFIInterpreterExit(ModuleCompiler &m, const ModuleCompiler::ExitDescriptor &exit,
                            unsigned exitIndex, Label *throwLabel)
 {
     MacroAssembler &masm = m.masm();
     masm.align(CodeAlignment);
     m.setInterpExitOffset(exitIndex);
     masm.setFramePushed(0);
 
-    // See AsmJSFrameSize comment in Assembler-*.h.
+    // See AsmJSFrameSize comment in Assembler-shared.h.
 #if defined(JS_CODEGEN_ARM)
     masm.push(lr);
-#endif
-#if defined(JS_CODEGEN_MIPS)
+#elif defined(JS_CODEGEN_MIPS)
     masm.push(ra);
 #endif
 
+    // Store the frame pointer in AsmJSActivation::exitFP for stack unwinding.
+    Register activation = ABIArgGenerator::NonArgReturnVolatileReg0;
+    LoadAsmJSActivationIntoRegister(masm, activation);
+    masm.storePtr(StackPointer, Address(activation, AsmJSActivation::offsetOfExitFP()));
+
     MIRType typeArray[] = { MIRType_Pointer,   // cx
                             MIRType_Pointer,   // exitDatum
                             MIRType_Int32,     // argc
                             MIRType_Pointer }; // argv
     MIRTypeVector invokeArgTypes(m.cx());
     invokeArgTypes.infallibleAppend(typeArray, ArrayLength(typeArray));
 
     // At the point of the call, the stack layout shall be (sp grows to the left):
@@ -6235,26 +6234,21 @@ GenerateFFIInterpreterExit(ModuleCompile
     // sp is aligned.
     unsigned offsetToArgv = AlignBytes(StackArgBytes(invokeArgTypes) + MaybeRetAddr, StackAlignment);
     unsigned argvBytes = Max<size_t>(1, exit.sig().args().length()) * sizeof(Value);
     unsigned stackDec = StackDecrementForCall(masm, offsetToArgv + argvBytes);
     masm.reserveStack(stackDec);
 
     // Fill the argument array.
     unsigned offsetToCallerStackArgs = AsmJSFrameSize + masm.framePushed();
-    Register scratch = ABIArgGenerator::NonArgReturnVolatileReg0;
+    Register scratch = ABIArgGenerator::NonArgReturnVolatileReg1;
     FillArgumentArray(m, exit.sig().args(), offsetToArgv, offsetToCallerStackArgs, scratch);
 
     // Prepare the arguments for the call to InvokeFromAsmJS_*.
     ABIArgMIRTypeIter i(invokeArgTypes);
-    Register activation = ABIArgGenerator::NonArgReturnVolatileReg1;
-    LoadAsmJSActivationIntoRegister(masm, activation);
-
-    // Record sp in the AsmJSActivation for stack-walking.
-    masm.storePtr(StackPointer, Address(activation, AsmJSActivation::offsetOfExitSP()));
 
     // argument 0: cx
     if (i->kind() == ABIArg::GPR) {
         LoadJSContextFromActivation(masm, activation, i->gpr());
     } else {
         LoadJSContextFromActivation(masm, activation, scratch);
         masm.storePtr(scratch, Address(StackPointer, i->offsetFromArgBase()));
     }
@@ -6285,37 +6279,42 @@ GenerateFFIInterpreterExit(ModuleCompile
     }
     i++;
     JS_ASSERT(i.done());
 
     // Make the call, test whether it succeeded, and extract the return value.
     AssertStackAlignment(masm);
     switch (exit.sig().retType().which()) {
       case RetType::Void:
-        masm.callExit(AsmJSImmPtr(AsmJSImm_InvokeFromAsmJS_Ignore), i.stackBytesConsumedSoFar());
+        masm.call(AsmJSImmPtr(AsmJSImm_InvokeFromAsmJS_Ignore));
         masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
         break;
       case RetType::Signed:
-        masm.callExit(AsmJSImmPtr(AsmJSImm_InvokeFromAsmJS_ToInt32), i.stackBytesConsumedSoFar());
+        masm.call(AsmJSImmPtr(AsmJSImm_InvokeFromAsmJS_ToInt32));
         masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
         masm.unboxInt32(argv, ReturnReg);
         break;
       case RetType::Double:
-        masm.callExit(AsmJSImmPtr(AsmJSImm_InvokeFromAsmJS_ToNumber), i.stackBytesConsumedSoFar());
+        masm.call(AsmJSImmPtr(AsmJSImm_InvokeFromAsmJS_ToNumber));
         masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
         masm.loadDouble(argv, ReturnDoubleReg);
         break;
       case RetType::Float:
         MOZ_ASSUME_UNREACHABLE("Float32 shouldn't be returned from a FFI");
         break;
     }
 
     // Note: the caller is IonMonkey code which means there are no non-volatile
     // registers to restore.
     masm.freeStack(stackDec);
+
+    // Clear exitFP before the frame is destroyed.
+    LoadAsmJSActivationIntoRegister(masm, activation);
+    masm.storePtr(ImmWord(0), Address(activation, AsmJSActivation::offsetOfExitFP()));
+
     masm.ret();
 }
 
 static void
 GenerateOOLConvert(ModuleCompiler &m, RetType retType, Label *throwLabel)
 {
     MacroAssembler &masm = m.masm();
 
@@ -6330,19 +6329,16 @@ GenerateOOLConvert(ModuleCompiler &m, Re
     // Store value
     unsigned offsetToArgv = StackArgBytes(callArgTypes) + MaybeRetAddr;
     masm.storeValue(JSReturnOperand, Address(StackPointer, offsetToArgv));
 
     Register scratch = ABIArgGenerator::NonArgReturnVolatileReg0;
     Register activation = ABIArgGenerator::NonArgReturnVolatileReg1;
     LoadAsmJSActivationIntoRegister(masm, activation);
 
-    // Record sp in the AsmJSActivation for stack-walking.
-    masm.storePtr(StackPointer, Address(activation, AsmJSActivation::offsetOfExitSP()));
-
     // Store real arguments
     ABIArgMIRTypeIter i(callArgTypes);
 
     // argument 0: cx
     if (i->kind() == ABIArg::GPR) {
         LoadJSContextFromActivation(masm, activation, i->gpr());
     } else {
         LoadJSContextFromActivation(masm, activation, scratch);
@@ -6360,22 +6356,22 @@ GenerateOOLConvert(ModuleCompiler &m, Re
     }
     i++;
     JS_ASSERT(i.done());
 
     // Call
     AssertStackAlignment(masm);
     switch (retType.which()) {
       case RetType::Signed:
-        masm.callExit(AsmJSImmPtr(AsmJSImm_CoerceInPlace_ToInt32), i.stackBytesConsumedSoFar());
+        masm.call(AsmJSImmPtr(AsmJSImm_CoerceInPlace_ToInt32));
         masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
         masm.unboxInt32(Address(StackPointer, offsetToArgv), ReturnReg);
         break;
       case RetType::Double:
-        masm.callExit(AsmJSImmPtr(AsmJSImm_CoerceInPlace_ToNumber), i.stackBytesConsumedSoFar());
+        masm.call(AsmJSImmPtr(AsmJSImm_CoerceInPlace_ToNumber));
         masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
         masm.loadDouble(Address(StackPointer, offsetToArgv), ReturnDoubleReg);
         break;
       default:
         MOZ_ASSUME_UNREACHABLE("Unsupported convert type");
     }
 }
 
@@ -6383,29 +6379,32 @@ static void
 GenerateFFIIonExit(ModuleCompiler &m, const ModuleCompiler::ExitDescriptor &exit,
                          unsigned exitIndex, Label *throwLabel)
 {
     MacroAssembler &masm = m.masm();
     masm.align(CodeAlignment);
     m.setIonExitOffset(exitIndex);
     masm.setFramePushed(0);
 
-    // See AsmJSFrameSize comment in Assembler-*.h.
+    // See AsmJSFrameSize comment in Assembler-shared.h.
+#if defined(JS_CODEGEN_ARM)
+    masm.push(lr);
+#elif defined(JS_CODEGEN_MIPS)
+    masm.push(ra);
+#endif
+
+    // Store the frame pointer in AsmJSActivation::exitFP for stack unwinding.
+    Register activation = ABIArgGenerator::NonArgReturnVolatileReg0;
+    LoadAsmJSActivationIntoRegister(masm, activation);
+    masm.storePtr(StackPointer, Address(activation, AsmJSActivation::offsetOfExitFP()));
+
+    // Ion does not preserve nonvolatile registers, so we have to preserve them.
 #if defined(JS_CODEGEN_X64)
     masm.Push(HeapReg);
-#elif defined(JS_CODEGEN_ARM)
-    masm.push(lr);
-
-    // The GlobalReg (r10) and HeapReg (r11) also need to be restored before
-    // returning to asm.js code.
-    // The NANReg also needs to be restored, but is a constant and is reloaded before
-    // returning to asm.js code.
-    masm.PushRegsInMask(GeneralRegisterSet((1<<GlobalReg.code()) | (1<<HeapReg.code())));
-#elif defined(JS_CODEGEN_MIPS)
-    masm.push(ra);
+#elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
     masm.PushRegsInMask(GeneralRegisterSet((1<<GlobalReg.code()) | (1<<HeapReg.code())));
 #endif
 
     // The stack frame is used for the call into Ion and also for calls into C for OOL
     // conversion of the result.  A frame large enough for both is allocated.
     //
     // Arguments to the Ion function are in the following order on the stack:
     // | return address | descriptor | callee | argc | this | arg1 | arg2 | ...
@@ -6493,42 +6492,30 @@ GenerateFFIIonExit(ModuleCompiler &m, co
         // This sequence requires four registers, and needs to preserve the 'callee'
         // register, so there are five live registers.
         JS_ASSERT(callee == AsmJSIonExitRegCallee);
         Register reg0 = AsmJSIonExitRegE0;
         Register reg1 = AsmJSIonExitRegE1;
         Register reg2 = AsmJSIonExitRegE2;
         Register reg3 = AsmJSIonExitRegE3;
 
-        LoadAsmJSActivationIntoRegister(masm, reg0);
-
-        // Record sp in the AsmJSActivation for stack-walking.
-#if defined(JS_CODEGEN_MIPS)
-        // Add a flag to indicate to AsmJSFrameIterator that we are calling
-        // into Ion, since the offset from SP to the return address is
-        // different when calling Ion vs. the native ABI.
-        masm.ma_or(reg1, StackPointer, Imm32(0x1));
-        masm.storePtr(reg1, Address(reg0, AsmJSActivation::offsetOfExitSP()));
-#else
-        masm.storePtr(StackPointer, Address(reg0, AsmJSActivation::offsetOfExitSP()));
-#endif
-
         // The following is inlined:
         //   JSContext *cx = activation->cx();
         //   Activation *act = cx->mainThread().activation();
         //   act.active_ = true;
         //   act.prevJitTop_ = cx->mainThread().jitTop;
         //   act.prevJitJSContext_ = cx->mainThread().jitJSContext;
         //   cx->mainThread().jitJSContext = cx;
         // On the ARM store8() uses the secondScratchReg (lr) as a temp.
         size_t offsetOfActivation = offsetof(JSRuntime, mainThread) +
                                     PerThreadData::offsetOfActivation();
         size_t offsetOfJitTop = offsetof(JSRuntime, mainThread) + offsetof(PerThreadData, jitTop);
         size_t offsetOfJitJSContext = offsetof(JSRuntime, mainThread) +
                                       offsetof(PerThreadData, jitJSContext);
+        LoadAsmJSActivationIntoRegister(masm, reg0);
         masm.loadPtr(Address(reg0, AsmJSActivation::offsetOfContext()), reg3);
         masm.loadPtr(Address(reg3, JSContext::offsetOfRuntime()), reg0);
         masm.loadPtr(Address(reg0, offsetOfActivation), reg1);
         masm.store8(Imm32(1), Address(reg1, JitActivation::offsetOfActiveUint8()));
         masm.loadPtr(Address(reg0, offsetOfJitTop), reg2);
         masm.storePtr(reg2, Address(reg1, JitActivation::offsetOfPrevJitTop()));
         masm.loadPtr(Address(reg0, offsetOfJitJSContext), reg2);
         masm.storePtr(reg2, Address(reg1, JitActivation::offsetOfPrevJitJSContext()));
@@ -6590,23 +6577,29 @@ GenerateFFIIonExit(ModuleCompiler &m, co
         break;
       case RetType::Float:
         MOZ_ASSUME_UNREACHABLE("Float shouldn't be returned from a FFI");
         break;
     }
 
     masm.bind(&done);
     masm.freeStack(stackDec);
+
+    // Restore non-volatile registers saved in the prologue.
 #if defined(JS_CODEGEN_X64)
     masm.Pop(HeapReg);
-#endif
-#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
+#elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
     masm.loadConstantDouble(GenericNaN(), NANReg);
     masm.PopRegsInMask(GeneralRegisterSet((1<<GlobalReg.code()) | (1<<HeapReg.code())));
 #endif
+
+    // Clear exitFP before the frame is destroyed.
+    LoadAsmJSActivationIntoRegister(masm, activation);
+    masm.storePtr(ImmWord(0), Address(activation, AsmJSActivation::offsetOfExitFP()));
+
     masm.ret();
     JS_ASSERT(masm.framePushed() == 0);
 
     // oolConvert
     if (oolConvert.used()) {
         masm.bind(&oolConvert);
         masm.setFramePushed(oolConvertFramePushed);
         GenerateOOLConvert(m, exit.sig().retType(), throwLabel);
@@ -6637,43 +6630,49 @@ GenerateFFIExit(ModuleCompiler &m, const
 // all the frames.
 static bool
 GenerateStackOverflowExit(ModuleCompiler &m, Label *throwLabel)
 {
     MacroAssembler &masm = m.masm();
     masm.align(CodeAlignment);
     masm.bind(&m.stackOverflowLabel());
 
+    // The stack-overflow is checked before bumping the stack.
+    masm.setFramePushed(0);
+
+    // Store the frame pointer in AsmJSActivation::exitFP for stack unwinding.
+    Register activation = ABIArgGenerator::NonArgReturnVolatileReg0;
+    LoadAsmJSActivationIntoRegister(masm, activation);
+    masm.storePtr(StackPointer, Address(activation, AsmJSActivation::offsetOfExitFP()));
+
     MIRTypeVector argTypes(m.cx());
     argTypes.infallibleAppend(MIRType_Pointer); // cx
 
     unsigned stackDec = StackDecrementForCall(masm, argTypes, MaybeRetAddr);
     masm.reserveStack(stackDec);
 
-    Register activation = ABIArgGenerator::NonArgReturnVolatileReg0;
-    LoadAsmJSActivationIntoRegister(masm, activation);
-
-    // Record sp in the AsmJSActivation for stack-walking.
-    masm.storePtr(StackPointer, Address(activation, AsmJSActivation::offsetOfExitSP()));
-
     ABIArgMIRTypeIter i(argTypes);
 
     // argument 0: cx
     if (i->kind() == ABIArg::GPR) {
         LoadJSContextFromActivation(masm, activation, i->gpr());
     } else {
         LoadJSContextFromActivation(masm, activation, activation);
         masm.storePtr(activation, Address(StackPointer, i->offsetFromArgBase()));
     }
     i++;
 
     JS_ASSERT(i.done());
 
     AssertStackAlignment(masm);
-    masm.callExit(AsmJSImmPtr(AsmJSImm_ReportOverRecursed), i.stackBytesConsumedSoFar());
+    masm.call(AsmJSImmPtr(AsmJSImm_ReportOverRecursed));
+
+    // Clear exitFP before the frame is destroyed.
+    LoadAsmJSActivationIntoRegister(masm, activation);
+    masm.storePtr(ImmWord(0), Address(activation, AsmJSActivation::offsetOfExitFP()));
 
     // Don't worry about restoring the stack; throwLabel will pop everything.
     masm.jump(throwLabel);
     return !masm.oom();
 }
 
 static const RegisterSet AllRegsExceptSP =
     RegisterSet(GeneralRegisterSet(Registers::AllMask &
@@ -6862,20 +6861,20 @@ GenerateThrowExit(ModuleCompiler &m, Lab
 
     return !masm.oom();
 }
 
 static bool
 GenerateStubs(ModuleCompiler &m)
 {
     for (unsigned i = 0; i < m.module().numExportedFunctions(); i++) {
-        m.setEntryOffset(i);
+        m.startGeneratingEntry(i);
         if (!GenerateEntry(m, m.module().exportedFunction(i)))
             return false;
-        if (m.masm().oom())
+        if (m.masm().oom() || !m.finishGeneratingEntry(i))
             return false;
     }
 
     Label throwLabel;
 
     // The order of the iterations here is non-deterministic, since
     // m.allExits() is a hash keyed by pointer values!
     for (ModuleCompiler::ExitMap::Range r = m.allExits(); !r.empty(); r.popFront()) {
new file mode 100644
--- /dev/null
+++ b/js/src/jit/AsmJSFrameIterator.cpp
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "jit/AsmJSFrameIterator.h"
+
+#include "jit/AsmJS.h"
+#include "jit/AsmJSModule.h"
+
+using namespace js;
+using namespace js::jit;
+
+static void *
+ReturnAddressFromFP(uint8_t *fp)
+{
+    // In asm.js code, the "frame" consists of a single word: the saved
+    // return address of the caller.
+    static_assert(AsmJSFrameSize == sizeof(void*), "Frame size mismatch");
+    return *(uint8_t**)fp;
+}
+
+AsmJSFrameIterator::AsmJSFrameIterator(const AsmJSActivation &activation)
+  : module_(&activation.module()),
+    fp_(activation.exitFP())
+{
+    if (!fp_)
+        return;
+    settle(ReturnAddressFromFP(fp_));
+}
+
+void
+AsmJSFrameIterator::operator++()
+{
+    JS_ASSERT(!done());
+    fp_ += callsite_->stackDepth();
+    settle(ReturnAddressFromFP(fp_));
+}
+
+void
+AsmJSFrameIterator::settle(void *returnAddress)
+{
+    const AsmJSModule::CodeRange *codeRange = module_->lookupCodeRange(ReturnAddressFromFP(fp_));
+    JS_ASSERT(codeRange);
+    codeRange_ = codeRange;
+
+    switch (codeRange->kind()) {
+      case AsmJSModule::CodeRange::Entry:
+        fp_ = nullptr;
+        JS_ASSERT(done());
+        return;
+      case AsmJSModule::CodeRange::Function:
+        callsite_ = module_->lookupCallSite(returnAddress);
+        JS_ASSERT(callsite_);
+        break;
+    }
+}
+
+JSAtom *
+AsmJSFrameIterator::functionDisplayAtom() const
+{
+    JS_ASSERT(!done());
+    return reinterpret_cast<const AsmJSModule::CodeRange*>(codeRange_)->functionName(*module_);
+}
+
+unsigned
+AsmJSFrameIterator::computeLine(uint32_t *column) const
+{
+    JS_ASSERT(!done());
+    if (column)
+        *column = callsite_->column();
+    return callsite_->line();
+}
+
new file mode 100644
--- /dev/null
+++ b/js/src/jit/AsmJSFrameIterator.h
@@ -0,0 +1,46 @@
+/* -*- 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_AsmJSFrameIterator_h
+#define jit_AsmJSFrameIterator_h
+
+#include "mozilla/NullPtr.h"
+
+#include <stdint.h>
+
+class JSAtom;
+
+namespace js {
+
+class AsmJSActivation;
+class AsmJSModule;
+namespace jit { struct CallSite; }
+
+// Iterates over the frames of a single AsmJSActivation.
+class AsmJSFrameIterator
+{
+    const AsmJSModule *module_;
+    const jit::CallSite *callsite_;
+    uint8_t *fp_;
+
+    // Really, a const AsmJSModule::CodeRange*, but no forward declarations of
+    // nested classes, so use void* to avoid pulling in all of AsmJSModule.h.
+    const void *codeRange_;
+
+    void settle(void *returnAddress);
+
+  public:
+    explicit AsmJSFrameIterator() : module_(nullptr) {}
+    explicit AsmJSFrameIterator(const AsmJSActivation &activation);
+    void operator++();
+    bool done() const { return !fp_; }
+    JSAtom *functionDisplayAtom() const;
+    unsigned computeLine(uint32_t *column) const;
+};
+
+} // namespace js
+
+#endif // jit_AsmJSFrameIterator_h
--- a/js/src/jit/AsmJSLink.cpp
+++ b/js/src/jit/AsmJSLink.cpp
@@ -29,111 +29,16 @@
 #include "jsobjinlines.h"
 
 using namespace js;
 using namespace js::jit;
 
 using mozilla::IsNaN;
 using mozilla::PodZero;
 
-static uint8_t *
-ReturnAddressForExitCall(uint8_t **psp)
-{
-    uint8_t *sp = *psp;
-#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
-    // For calls to Ion/C++ on x86/x64, the exitSP is the SP right before the call
-    // to C++. Since the call instruction pushes the return address, we know
-    // that the return address is 1 word below exitSP.
-    return *(uint8_t**)(sp - sizeof(void*));
-#elif defined(JS_CODEGEN_ARM)
-    // For calls to Ion/C++ on ARM, the *caller* pushes the return address on
-    // the stack. For Ion, this is just part of the ABI. For C++, the return
-    // address is explicitly pushed before the call since we cannot expect the
-    // callee to immediately push lr. This means that exitSP points to the
-    // return address.
-    return *(uint8_t**)sp;
-#elif defined(JS_CODEGEN_MIPS)
-    // On MIPS we have two cases: an exit to C++ will store the return address
-    // at ShadowStackSpace above sp; an exit to Ion will store the return
-    // address at sp. To distinguish the two cases, the low bit of sp (which is
-    // aligned and therefore zero) is set for Ion exits.
-    if (uintptr_t(sp) & 0x1) {
-        sp = *psp -= 0x1;  // Clear the low bit
-        return *(uint8_t**)sp;
-    }
-    return *(uint8_t**)(sp + ShadowStackSpace);
-#else
-# error "Unknown architecture!"
-#endif
-}
-
-static uint8_t *
-ReturnAddressForJitCall(uint8_t *sp)
-{
-    // Once inside JIT code, sp always points to the word before the return
-    // address.
-    return *(uint8_t**)(sp - sizeof(void*));
-}
-
-AsmJSFrameIterator::AsmJSFrameIterator(const AsmJSActivation *activation)
-  : module_(nullptr)
-{
-    if (!activation || activation->isInterruptedSP())
-        return;
-
-    module_ = &activation->module();
-    sp_ = activation->exitSP();
-
-    settle(ReturnAddressForExitCall(&sp_));
-}
-
-void
-AsmJSFrameIterator::operator++()
-{
-    settle(ReturnAddressForJitCall(sp_));
-}
-
-void
-AsmJSFrameIterator::settle(uint8_t *returnAddress)
-{
-    callsite_ = module_->lookupCallSite(returnAddress);
-    if (!callsite_ || callsite_->isEntry()) {
-        module_ = nullptr;
-        return;
-    }
-
-    if (callsite_->isEntry()) {
-        module_ = nullptr;
-        return;
-    }
-
-    sp_ += callsite_->stackDepth();
-
-    if (callsite_->isExit())
-        return settle(ReturnAddressForJitCall(sp_));
-
-    JS_ASSERT(callsite_->isNormal());
-}
-
-JSAtom *
-AsmJSFrameIterator::functionDisplayAtom() const
-{
-    JS_ASSERT(!done());
-    return module_->functionName(callsite_->functionNameIndex());
-}
-
-unsigned
-AsmJSFrameIterator::computeLine(uint32_t *column) const
-{
-    JS_ASSERT(!done());
-    if (column)
-        *column = callsite_->column();
-    return callsite_->line();
-}
-
 static bool
 CloneModule(JSContext *cx, MutableHandle<AsmJSModuleObject*> moduleObj)
 {
     ScopedJSDeletePtr<AsmJSModule> module;
     if (!moduleObj->module().clone(cx, &module))
         return false;
 
     module->staticallyLink(cx);
--- a/js/src/jit/AsmJSLink.h
+++ b/js/src/jit/AsmJSLink.h
@@ -4,41 +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_AsmJSLink_h
 #define jit_AsmJSLink_h
 
 #include "NamespaceImports.h"
 
-class JSAtom;
-
 namespace js {
 
-class AsmJSActivation;
-class AsmJSModule;
-namespace jit { struct CallSite; }
-
-// Iterates over the frames of a single AsmJSActivation.
-class AsmJSFrameIterator
-{
-    const AsmJSModule *module_;
-    const jit::CallSite *callsite_;
-    uint8_t *sp_;
-
-    void settle(uint8_t *returnAddress);
-
-  public:
-    explicit AsmJSFrameIterator(const AsmJSActivation *activation);
-    void operator++();
-    bool done() const { return !module_; }
-    JSAtom *functionDisplayAtom() const;
-    unsigned computeLine(uint32_t *column) const;
-};
-
 #ifdef JS_ION
 
 // Create a new JSFunction to replace originalFun as the representation of the
 // function defining the succesfully-validated module 'moduleObj'.
 extern JSFunction *
 NewAsmJSModuleFunction(ExclusiveContext *cx, JSFunction *originalFun, HandleObject moduleObj);
 
 // Return whether this is the js::Native returned by NewAsmJSModuleFunction.
--- a/js/src/jit/AsmJSModule.cpp
+++ b/js/src/jit/AsmJSModule.cpp
@@ -162,16 +162,17 @@ AsmJSModule::addSizeOfMisc(mozilla::Mall
                            size_t *asmJSModuleData)
 {
     *asmJSModuleCode += pod.totalBytes_;
     *asmJSModuleData += mallocSizeOf(this) +
                         globals_.sizeOfExcludingThis(mallocSizeOf) +
                         exits_.sizeOfExcludingThis(mallocSizeOf) +
                         exports_.sizeOfExcludingThis(mallocSizeOf) +
                         callSites_.sizeOfExcludingThis(mallocSizeOf) +
+                        codeRanges_.sizeOfExcludingThis(mallocSizeOf) +
                         functionNames_.sizeOfExcludingThis(mallocSizeOf) +
                         heapAccesses_.sizeOfExcludingThis(mallocSizeOf) +
                         functionCounts_.sizeOfExcludingThis(mallocSizeOf) +
 #if defined(MOZ_VTUNE) || defined(JS_ION_PERF)
                         profiledFunctions_.sizeOfExcludingThis(mallocSizeOf) +
 #endif
 #if defined(JS_ION_PERF)
                         perfProfiledBlocksFunctions_.sizeOfExcludingThis(mallocSizeOf) +
@@ -184,47 +185,86 @@ struct CallSiteRetAddrOffset
     const CallSiteVector &callSites;
     explicit CallSiteRetAddrOffset(const CallSiteVector &callSites) : callSites(callSites) {}
     uint32_t operator[](size_t index) const {
         return callSites[index].returnAddressOffset();
     }
 };
 
 const CallSite *
-AsmJSModule::lookupCallSite(uint8_t *returnAddress) const
+AsmJSModule::lookupCallSite(void *returnAddress) const
 {
     JS_ASSERT(isFinished());
 
-    uint32_t target = returnAddress - code_;
+    uint32_t target = ((uint8_t*)returnAddress) - code_;
     size_t lowerBound = 0;
     size_t upperBound = callSites_.length();
 
     size_t match;
     if (!BinarySearch(CallSiteRetAddrOffset(callSites_), lowerBound, upperBound, target, &match))
         return nullptr;
 
     return &callSites_[match];
 }
 
+namespace js {
+
+// Create an ordering on CodeRange and pc offsets suitable for BinarySearch.
+// Stick these in the same namespace as AsmJSModule so that argument-dependent
+// lookup will find it.
+bool
+operator==(size_t pcOffset, const AsmJSModule::CodeRange &rhs)
+{
+    return pcOffset >= rhs.begin() && pcOffset < rhs.end();
+}
+bool
+operator<=(const AsmJSModule::CodeRange &lhs, const AsmJSModule::CodeRange &rhs)
+{
+    return lhs.begin() <= rhs.begin();
+}
+bool
+operator<(size_t pcOffset, const AsmJSModule::CodeRange &rhs)
+{
+    return pcOffset < rhs.begin();
+}
+
+} // namespace js
+
+const AsmJSModule::CodeRange *
+AsmJSModule::lookupCodeRange(void *pc) const
+{
+    JS_ASSERT(isFinished());
+
+    uint32_t target = ((uint8_t*)pc) - code_;
+    size_t lowerBound = 0;
+    size_t upperBound = codeRanges_.length();
+
+    size_t match;
+    if (!BinarySearch(codeRanges_, lowerBound, upperBound, target, &match))
+        return nullptr;
+
+    return &codeRanges_[match];
+}
+
 struct HeapAccessOffset
 {
     const AsmJSHeapAccessVector &accesses;
     explicit HeapAccessOffset(const AsmJSHeapAccessVector &accesses) : accesses(accesses) {}
     uintptr_t operator[](size_t index) const {
         return accesses[index].offset();
     }
 };
 
 const AsmJSHeapAccess *
-AsmJSModule::lookupHeapAccess(uint8_t *pc) const
+AsmJSModule::lookupHeapAccess(void *pc) const
 {
     JS_ASSERT(isFinished());
     JS_ASSERT(containsPC(pc));
 
-    uint32_t target = pc - code_;
+    uint32_t target = ((uint8_t*)pc) - code_;
     size_t lowerBound = 0;
     size_t upperBound = heapAccesses_.length();
 
     size_t match;
     if (!BinarySearch(HeapAccessOffset(heapAccesses_), lowerBound, upperBound, target, &match))
         return nullptr;
 
     return &heapAccesses_[match];
@@ -288,16 +328,21 @@ AsmJSModule::finish(ExclusiveContext *cx
     for (unsigned i = 0; i < numExportedFunctions(); i++)
         exportedFunction(i).updateCodeOffset(masm);
     for (unsigned i = 0; i < numExits(); i++)
         exit(i).updateOffsets(masm);
     for (size_t i = 0; i < callSites_.length(); i++) {
         CallSite &c = callSites_[i];
         c.setReturnAddressOffset(masm.actualOffset(c.returnAddressOffset()));
     }
+    for (size_t i = 0; i < codeRanges_.length(); i++) {
+        CodeRange &c = codeRanges_[i];
+        c.begin_ = masm.actualOffset(c.begin_);
+        c.end_ = masm.actualOffset(c.end_);
+    }
 #endif
     JS_ASSERT(pod.functionBytes_ % AsmJSPageSize == 0);
 
     // Absolute link metadata: absolute addresses that refer to some fixed
     // address in the address space.
     for (size_t i = 0; i < masm.numAsmJSAbsoluteLinks(); i++) {
         AsmJSAbsoluteLink src = masm.asmJSAbsoluteLink(i);
         AbsoluteLink link;
@@ -1079,16 +1124,17 @@ AsmJSModule::serializedSize() const
            pod.codeBytes_ +
            SerializedNameSize(globalArgumentName_) +
            SerializedNameSize(importArgumentName_) +
            SerializedNameSize(bufferArgumentName_) +
            SerializedVectorSize(globals_) +
            SerializedVectorSize(exits_) +
            SerializedVectorSize(exports_) +
            SerializedPodVectorSize(callSites_) +
+           SerializedPodVectorSize(codeRanges_) +
            SerializedVectorSize(functionNames_) +
            SerializedPodVectorSize(heapAccesses_) +
 #if defined(MOZ_VTUNE) || defined(JS_ION_PERF)
            SerializedVectorSize(profiledFunctions_) +
 #endif
            staticLinkData_.serializedSize();
 }
 
@@ -1099,16 +1145,17 @@ AsmJSModule::serialize(uint8_t *cursor) 
     cursor = WriteBytes(cursor, code_, pod.codeBytes_);
     cursor = SerializeName(cursor, globalArgumentName_);
     cursor = SerializeName(cursor, importArgumentName_);
     cursor = SerializeName(cursor, bufferArgumentName_);
     cursor = SerializeVector(cursor, globals_);
     cursor = SerializeVector(cursor, exits_);
     cursor = SerializeVector(cursor, exports_);
     cursor = SerializePodVector(cursor, callSites_);
+    cursor = SerializePodVector(cursor, codeRanges_);
     cursor = SerializeVector(cursor, functionNames_);
     cursor = SerializePodVector(cursor, heapAccesses_);
 #if defined(MOZ_VTUNE) || defined(JS_ION_PERF)
     cursor = SerializeVector(cursor, profiledFunctions_);
 #endif
     cursor = staticLinkData_.serialize(cursor);
     return cursor;
 }
@@ -1125,16 +1172,17 @@ AsmJSModule::deserialize(ExclusiveContex
     (cursor = ReadBytes(cursor, code_, pod.codeBytes_)) &&
     (cursor = DeserializeName(cx, cursor, &globalArgumentName_)) &&
     (cursor = DeserializeName(cx, cursor, &importArgumentName_)) &&
     (cursor = DeserializeName(cx, cursor, &bufferArgumentName_)) &&
     (cursor = DeserializeVector(cx, cursor, &globals_)) &&
     (cursor = DeserializeVector(cx, cursor, &exits_)) &&
     (cursor = DeserializeVector(cx, cursor, &exports_)) &&
     (cursor = DeserializePodVector(cx, cursor, &callSites_)) &&
+    (cursor = DeserializePodVector(cx, cursor, &codeRanges_)) &&
     (cursor = DeserializeVector(cx, cursor, &functionNames_)) &&
     (cursor = DeserializePodVector(cx, cursor, &heapAccesses_)) &&
 #if defined(MOZ_VTUNE) || defined(JS_ION_PERF)
     (cursor = DeserializeVector(cx, cursor, &profiledFunctions_)) &&
 #endif
     (cursor = staticLinkData_.deserialize(cx, cursor));
 
     loadedFromCache_ = true;
@@ -1195,16 +1243,17 @@ AsmJSModule::clone(JSContext *cx, Scoped
     out.globalArgumentName_ = globalArgumentName_;
     out.importArgumentName_ = importArgumentName_;
     out.bufferArgumentName_ = bufferArgumentName_;
 
     if (!CloneVector(cx, globals_, &out.globals_) ||
         !CloneVector(cx, exits_, &out.exits_) ||
         !CloneVector(cx, exports_, &out.exports_) ||
         !ClonePodVector(cx, callSites_, &out.callSites_) ||
+        !ClonePodVector(cx, codeRanges_, &out.codeRanges_) ||
         !CloneVector(cx, functionNames_, &out.functionNames_) ||
         !ClonePodVector(cx, heapAccesses_, &out.heapAccesses_) ||
         !staticLinkData_.clone(cx, &out.staticLinkData_))
     {
         return false;
     }
 
     out.loadedFromCache_ = loadedFromCache_;
--- a/js/src/jit/AsmJSModule.h
+++ b/js/src/jit/AsmJSModule.h
@@ -308,16 +308,43 @@ class AsmJSModule
         }
 
         size_t serializedSize() const;
         uint8_t *serialize(uint8_t *cursor) const;
         const uint8_t *deserialize(ExclusiveContext *cx, const uint8_t *cursor);
         bool clone(ExclusiveContext *cx, ExportedFunction *out) const;
     };
 
+    class CodeRange
+    {
+      public:
+        enum Kind { Entry, Function };
+
+      private:
+        Kind kind_;
+        uint32_t begin_;
+        uint32_t end_;
+        uint32_t functionNameIndex_;
+
+        friend class AsmJSModule;
+        CodeRange(Kind k, uint32_t begin, uint32_t end, uint32_t functionNameIndex)
+          : kind_(k), begin_(begin), end_(end), functionNameIndex_(functionNameIndex)
+        {}
+
+      public:
+        CodeRange() {}
+        Kind kind() const { return kind_; }
+        uint32_t begin() const { return begin_; }
+        uint32_t end() const { return end_; }
+        PropertyName *functionName(const AsmJSModule &module) const {
+            JS_ASSERT(kind_ == Function);
+            return module.functionNames_[functionNameIndex_].name();
+        }
+    };
+
     class Name
     {
         PropertyName *name_;
       public:
         Name() : name_(nullptr) {}
         MOZ_IMPLICIT Name(PropertyName *name) : name_(name) {}
         PropertyName *name() const { return name_; }
         PropertyName *&name() { return name_; }
@@ -474,16 +501,17 @@ class AsmJSModule
     // respect to caching.
     const uint32_t                        funcStart_;
     const uint32_t                        offsetToEndOfUseAsm_;
 
     Vector<Global,                 0, SystemAllocPolicy> globals_;
     Vector<Exit,                   0, SystemAllocPolicy> exits_;
     Vector<ExportedFunction,       0, SystemAllocPolicy> exports_;
     Vector<jit::CallSite,          0, SystemAllocPolicy> callSites_;
+    Vector<CodeRange,              0, SystemAllocPolicy> codeRanges_;
     Vector<Name,                   0, SystemAllocPolicy> functionNames_;
     Vector<jit::AsmJSHeapAccess,   0, SystemAllocPolicy> heapAccesses_;
     Vector<jit::IonScriptCounts*,  0, SystemAllocPolicy> functionCounts_;
 #if defined(MOZ_VTUNE) || defined(JS_ION_PERF)
     Vector<ProfiledFunction,       0, SystemAllocPolicy> profiledFunctions_;
 #endif
 #if defined(JS_ION_PERF)
     Vector<ProfiledBlocksFunction, 0, SystemAllocPolicy> perfProfiledBlocksFunctions_;
@@ -661,27 +689,30 @@ class AsmJSModule
     /*************************************************************************/
     // These functions are called while parsing/compiling function bodies:
 
     void requireHeapLengthToBeAtLeast(uint32_t len) {
         JS_ASSERT(isFinishedWithModulePrologue() && !isFinishedWithFunctionBodies());
         if (len > pod.minHeapLength_)
             pod.minHeapLength_ = len;
     }
-    bool addFunctionName(PropertyName *name, uint32_t *nameIndex) {
+    bool addFunctionCodeRange(PropertyName *name, uint32_t begin, uint32_t end) {
         JS_ASSERT(isFinishedWithModulePrologue() && !isFinishedWithFunctionBodies());
         JS_ASSERT(name->isTenured());
-        if (functionNames_.length() > jit::CallSiteDesc::FUNCTION_NAME_INDEX_MAX)
+        JS_ASSERT(begin <= end);
+        JS_ASSERT_IF(!codeRanges_.empty(), codeRanges_.back().end() <= begin);
+        if (functionNames_.length() >= UINT32_MAX)
             return false;
-        *nameIndex = functionNames_.length();
-        return functionNames_.append(name);
+        CodeRange codeRange(CodeRange::Function, begin, end, functionNames_.length());
+        return functionNames_.append(name) && codeRanges_.append(codeRange);
     }
-    PropertyName *functionName(uint32_t i) const {
-        JS_ASSERT(isFinished());
-        return functionNames_[i].name();
+    bool addEntryCodeRange(unsigned exportIndex, uint32_t end) {
+        uint32_t begin = exports_[exportIndex].pod.codeOffset_;
+        CodeRange codeRange(CodeRange::Entry, begin, end, UINT32_MAX);
+        return codeRanges_.append(codeRange);
     }
     bool addExit(unsigned ffiIndex, unsigned *exitIndex) {
         JS_ASSERT(isFinishedWithModulePrologue() && !isFinishedWithFunctionBodies());
         if (SIZE_MAX - pod.funcPtrTableAndExitBytes_ < sizeof(ExitDatum))
             return false;
         uint32_t globalDataOffset = globalDataBytes();
         JS_STATIC_ASSERT(sizeof(ExitDatum) % sizeof(void*) == 0);
         pod.funcPtrTableAndExitBytes_ += sizeof(ExitDatum);
@@ -847,21 +878,25 @@ class AsmJSModule
     uint8_t *ionExitTrampoline(const Exit &exit) const {
         JS_ASSERT(isFinished());
         JS_ASSERT(exit.ionCodeOffset_);
         return code_ + exit.ionCodeOffset_;
     }
 
     // Lookup a callsite by the return pc (from the callee to the caller).
     // Return null if no callsite was found.
-    const jit::CallSite *lookupCallSite(uint8_t *returnAddress) const;
+    const jit::CallSite *lookupCallSite(void *returnAddress) const;
+
+    // Lookup the name the code range containing the given pc. Return null if no
+    // code range was found.
+    const CodeRange *lookupCodeRange(void *pc) const;
 
     // Lookup a heap access site by the pc which performs the access. Return
     // null if no heap access was found.
-    const jit::AsmJSHeapAccess *lookupHeapAccess(uint8_t *pc) const;
+    const jit::AsmJSHeapAccess *lookupHeapAccess(void *pc) const;
 
     // The global data section is placed after the executable code (i.e., at
     // offset codeBytes_) in the module's linear allocation. The global data
     // are laid out in this order:
     //   0. a pointer (padded up to 8 bytes to ensure double-alignment of
     //      globals) for the heap that was linked to the module.
     //   1. global variable state (elements are sizeof(uint64_t))
     //   2. interleaved function-pointer tables and exits. These are allocated
--- a/js/src/jit/AsmJSSignalHandlers.cpp
+++ b/js/src/jit/AsmJSSignalHandlers.cpp
@@ -351,17 +351,17 @@ HandleSimulatorInterrupt(JSRuntime *rt, 
     // simulator could be in the middle of an instruction. On ARM, the signal
     // handlers are currently only used for Odin code, see bug 964258.
 
 #if defined(JS_ARM_SIMULATOR) || defined(JS_MIPS_SIMULATOR)
     const AsmJSModule &module = activation->module();
     if (module.containsPC((void *)rt->mainThread.simulator()->get_pc()) &&
         module.containsPC(faultingAddress))
     {
-        activation->setInterrupted(nullptr);
+        activation->setResumePC(nullptr);
         int32_t nextpc = int32_t(module.interruptExit());
         rt->mainThread.simulator()->set_resume_pc(nextpc);
         return true;
     }
 #endif
     return false;
 }
 
@@ -460,17 +460,17 @@ HandleException(PEXCEPTION_POINTERS exce
         return false;
 
     // If we faulted trying to execute code in 'module', this must be an
     // interrupt callback (see RequestInterruptForAsmJSCode). Redirect
     // execution to a trampoline which will call js::HandleExecutionInterrupt.
     // The trampoline will jump to activation->resumePC if execution isn't
     // interrupted.
     if (module.containsPC(faultingAddress)) {
-        activation->setInterrupted(pc);
+        activation->setResumePC(pc);
         *ppc = module.interruptExit();
 
         JSRuntime::AutoLockForInterrupt lock(rt);
         module.unprotectCode(rt);
         return true;
     }
 
 # if defined(JS_CODEGEN_X64)
@@ -663,17 +663,17 @@ HandleMachException(JSRuntime *rt, const
         return false;
 
     // If we faulted trying to execute code in 'module', this must be an
     // interrupt callback (see RequestInterruptForAsmJSCode). Redirect
     // execution to a trampoline which will call js::HandleExecutionInterrupt.
     // The trampoline will jump to activation->resumePC if execution isn't
     // interrupted.
     if (module.containsPC(faultingAddress)) {
-        activation->setInterrupted(pc);
+        activation->setResumePC(pc);
         *ppc = module.interruptExit();
 
         JSRuntime::AutoLockForInterrupt lock(rt);
         module.unprotectCode(rt);
 
         // Update the thread state with the new pc.
         kret = thread_set_state(rtThread, x86_THREAD_STATE, (thread_state_t)&state, x86_THREAD_STATE_COUNT);
         return kret == KERN_SUCCESS;
@@ -913,17 +913,17 @@ HandleSignal(int signum, siginfo_t *info
         return false;
 
     // If we faulted trying to execute code in 'module', this must be an
     // interrupt callback (see RequestInterruptForAsmJSCode). Redirect
     // execution to a trampoline which will call js::HandleExecutionInterrupt.
     // The trampoline will jump to activation->resumePC if execution isn't
     // interrupted.
     if (module.containsPC(faultingAddress)) {
-        activation->setInterrupted(pc);
+        activation->setResumePC(pc);
         *ppc = module.interruptExit();
 
         JSRuntime::AutoLockForInterrupt lock(rt);
         module.unprotectCode(rt);
         return true;
     }
 
 # if defined(JS_CODEGEN_X64)
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -8578,17 +8578,17 @@ CodeGenerator::visitAsmJSCall(LAsmJSCall
     switch (callee.which()) {
       case MAsmJSCall::Callee::Internal:
         masm.call(mir->desc(), callee.internal());
         break;
       case MAsmJSCall::Callee::Dynamic:
         masm.call(mir->desc(), ToRegister(ins->getOperand(mir->dynamicCalleeOperandIndex())));
         break;
       case MAsmJSCall::Callee::Builtin:
-        masm.call(mir->desc(), AsmJSImmPtr(callee.builtin()));
+        masm.call(AsmJSImmPtr(callee.builtin()));
         break;
     }
 
     if (mir->spIncrement())
         masm.reserveStack(mir->spIncrement());
 
     postAsmJSCall(ins);
     return true;
--- a/js/src/jit/arm/Assembler-arm.h
+++ b/js/src/jit/arm/Assembler-arm.h
@@ -135,22 +135,16 @@ static MOZ_CONSTEXPR_VAR FloatRegister d
 // ldrd/strd (dual-register load/store) operate in a single cycle
 // when the address they are dealing with is 8 byte aligned.
 // Also, the ARM abi wants the stack to be 8 byte aligned at
 // function boundaries.  I'm trying to make sure this is always true.
 static const uint32_t StackAlignment = 8;
 static const uint32_t CodeAlignment = 8;
 static const bool StackKeptAligned = true;
 
-// As an invariant across architectures, within asm.js code:
-//    $sp % StackAlignment = (AsmJSFrameSize + masm.framePushed) % StackAlignment
-// To achieve this on ARM, the first instruction of the asm.js prologue pushes
-// lr without incrementing masm.framePushed.
-static const uint32_t AsmJSFrameSize = sizeof(void*);
-
 static const Scale ScalePointer = TimesFour;
 
 class Instruction;
 class InstBranchImm;
 uint32_t RM(Register r);
 uint32_t RS(Register r);
 uint32_t RD(Register r);
 uint32_t RT(Register r);
--- a/js/src/jit/arm/CodeGenerator-arm.cpp
+++ b/js/src/jit/arm/CodeGenerator-arm.cpp
@@ -48,17 +48,17 @@ CodeGeneratorARM::generatePrologue()
     return true;
 }
 
 bool
 CodeGeneratorARM::generateAsmJSPrologue(Label *stackOverflowLabel)
 {
     JS_ASSERT(gen->compilingAsmJS());
 
-    // See comment in Assembler-arm.h about AsmJSFrameSize.
+    // See comment in Assembler-shared.h about AsmJSFrameSize.
     masm.push(lr);
 
     // The asm.js over-recursed handler wants to be able to assume that SP
     // points to the return address, so perform the check after pushing lr but
     // before pushing frameDepth.
     if (!omitOverRecursedCheck()) {
         masm.branchPtr(Assembler::AboveOrEqual,
                        AsmJSAbsoluteAddress(AsmJSImm_StackLimit),
--- a/js/src/jit/arm/MacroAssembler-arm.cpp
+++ b/js/src/jit/arm/MacroAssembler-arm.cpp
@@ -1787,16 +1787,27 @@ MacroAssemblerARMCompat::callIon(Registe
         ma_callIonHalfPush(callee);
     } else {
         adjustFrame(sizeof(void*));
         ma_callIon(callee);
     }
 }
 
 void
+MacroAssemblerARMCompat::callIonFromAsmJS(Register callee)
+{
+    ma_callIonNoPush(callee);
+
+    // The Ion ABI has the callee pop the return address off the stack.
+    // The asm.js caller assumes that the call leaves sp unchanged, so bump
+    // the stack.
+    subPtr(Imm32(sizeof(void*)), sp);
+}
+
+void
 MacroAssemblerARMCompat::reserveStack(uint32_t amount)
 {
     if (amount)
         ma_sub(Imm32(amount), sp);
     adjustFrame(amount);
 }
 void
 MacroAssemblerARMCompat::freeStack(uint32_t amount)
@@ -3622,29 +3633,16 @@ MacroAssemblerARM::ma_call(ImmPtr dest)
     else
         rs = L_LDR;
 
     ma_movPatchable(dest, CallReg, Always, rs);
     as_blx(CallReg);
 }
 
 void
-MacroAssemblerARM::ma_callAndStoreRet(const Register r, uint32_t stackArgBytes)
-{
-    // Note: this function stores the return address to sp[0]. The caller must
-    // anticipate this by pushing additional space on the stack. The ABI does
-    // not provide space for a return address so this function may only be
-    // called if no argument are passed.
-    JS_ASSERT(stackArgBytes == 0);
-    AutoForbidPools afp(this);
-    as_dtr(IsStore, 32, Offset, pc, DTRAddr(sp, DtrOffImm(0)));
-    as_blx(r);
-}
-
-void
 MacroAssemblerARMCompat::breakpoint()
 {
     as_bkpt();
 }
 
 void
 MacroAssemblerARMCompat::ensureDouble(const ValueOperand &source, FloatRegister dest, Label *failure)
 {
--- a/js/src/jit/arm/MacroAssembler-arm.h
+++ b/js/src/jit/arm/MacroAssembler-arm.h
@@ -393,19 +393,16 @@ class MacroAssemblerARM : public Assembl
     void ma_callIon(const Register reg);
     // callso an Ion function, assuming that sp has already been decremented
     void ma_callIonNoPush(const Register reg);
     // calls an ion function, assuming that the stack is currently not 8 byte aligned
     void ma_callIonHalfPush(const Register reg);
 
     void ma_call(ImmPtr dest);
 
-    // calls reg, storing the return address into sp[0]
-    void ma_callAndStoreRet(const Register reg, uint32_t stackArgBytes);
-
     // Float registers can only be loaded/stored in continuous runs
     // when using vstm/vldm.
     // This function breaks set into continuous runs and loads/stores
     // them at [rm]. rm will be modified and left in a state logically
     // suitable for the next load/store.
     // Returns the offset from [dm] for the logical next load/store.
     int32_t transferMultipleByRuns(FloatRegisterSet set, LoadStore ls,
                                    Register rm, DTMMode mode)
@@ -562,48 +559,23 @@ class MacroAssemblerARMCompat : public M
         if (HasMOVWT())
             rs = L_MOVWT;
         else
             rs = L_LDR;
 
         ma_movPatchable(ImmPtr(c->raw()), ScratchRegister, Always, rs);
         ma_callIonHalfPush(ScratchRegister);
     }
-
-    void appendCallSite(const CallSiteDesc &desc) {
-        // Add an extra sizeof(void*) to include the return address that was
-        // pushed by the call instruction (see CallSite::stackDepth).
-        enoughMemory_ &= append(CallSite(desc, currentOffset(), framePushed_ + AsmJSFrameSize));
-    }
-
     void call(const CallSiteDesc &desc, const Register reg) {
         call(reg);
-        appendCallSite(desc);
+        enoughMemory_ &= append(desc, currentOffset(), framePushed_);
     }
     void call(const CallSiteDesc &desc, Label *label) {
         call(label);
-        appendCallSite(desc);
-    }
-    void call(const CallSiteDesc &desc, AsmJSImmPtr imm) {
-        call(imm);
-        appendCallSite(desc);
-    }
-    void callExit(AsmJSImmPtr imm, uint32_t stackArgBytes) {
-        movePtr(imm, CallReg);
-        ma_callAndStoreRet(CallReg, stackArgBytes);
-        appendCallSite(CallSiteDesc::Exit());
-    }
-    void callIonFromAsmJS(const Register reg) {
-        ma_callIonNoPush(reg);
-        appendCallSite(CallSiteDesc::Exit());
-
-        // The Ion ABI has the callee pop the return address off the stack.
-        // The asm.js caller assumes that the call leaves sp unchanged, so bump
-        // the stack.
-        subPtr(Imm32(sizeof(void*)), sp);
+        enoughMemory_ &= append(desc, currentOffset(), framePushed_);
     }
 
     void branch(JitCode *c) {
         BufferOffset bo = m_buffer.nextOffset();
         addPendingJump(bo, ImmPtr(c->raw()), Relocation::JITCODE);
         RelocStyle rs;
         if (HasMOVWT())
             rs = L_MOVWT;
@@ -1275,16 +1247,17 @@ class MacroAssemblerARMCompat : public M
     bool buildFakeExitFrame(Register scratch, uint32_t *offset);
 
     void callWithExitFrame(JitCode *target);
     void callWithExitFrame(JitCode *target, Register dynStack);
 
     // Makes an Ion call using the only two methods that it is sane for
     // indep code to make a call
     void callIon(Register callee);
+    void callIonFromAsmJS(Register callee);
 
     void reserveStack(uint32_t amount);
     void freeStack(uint32_t amount);
     void freeStack(Register amount);
 
     void add32(Register src, Register dest);
     void add32(Imm32 imm, Register dest);
     void add32(Imm32 imm, const Address &dest);
--- a/js/src/jit/arm/Simulator-arm.cpp
+++ b/js/src/jit/arm/Simulator-arm.cpp
@@ -4065,17 +4065,17 @@ Simulator::execute()
         } else {
             SimInstruction *instr = reinterpret_cast<SimInstruction *>(program_counter);
             instructionDecode(instr);
             icount_++;
 
             int32_t rpc = resume_pc_;
             if (MOZ_UNLIKELY(rpc != 0)) {
                 // AsmJS signal handler ran and we have to adjust the pc.
-                activation->setInterrupted((void *)get_pc());
+                activation->setResumePC((void *)get_pc());
                 set_pc(rpc);
                 resume_pc_ = 0;
             }
         }
         program_counter = get_pc();
     }
 }
 
--- a/js/src/jit/mips/Assembler-mips.h
+++ b/js/src/jit/mips/Assembler-mips.h
@@ -147,22 +147,16 @@ static MOZ_CONSTEXPR_VAR FloatRegister f
 static MOZ_CONSTEXPR_VAR FloatRegister f30 = {FloatRegisters::f30};
 
 // MIPS CPUs can only load multibyte data that is "naturally"
 // four-byte-aligned, sp register should be eight-byte-aligned.
 static const uint32_t StackAlignment = 8;
 static const uint32_t CodeAlignment = 4;
 static const bool StackKeptAligned = true;
 
-// As an invariant across architectures, within asm.js code:
-//    $sp % StackAlignment = (AsmJSFrameSize + masm.framePushed) % StackAlignment
-// To achieve this on MIPS, the first instruction of the asm.js prologue pushes
-// ra without incrementing masm.framePushed.
-static const uint32_t AsmJSFrameSize = sizeof(void*);
-
 static const Scale ScalePointer = TimesFour;
 
 // MIPS instruction types
 //                +---------------------------------------------------------------+
 //                |    6      |    5    |    5    |    5    |    5    |    6      |
 //                +---------------------------------------------------------------+
 // Register type  |  Opcode   |    Rs   |    Rt   |    Rd   |    Sa   | Function  |
 //                +---------------------------------------------------------------+
--- a/js/src/jit/mips/CodeGenerator-mips.cpp
+++ b/js/src/jit/mips/CodeGenerator-mips.cpp
@@ -47,17 +47,17 @@ CodeGeneratorMIPS::generatePrologue()
     return true;
 }
 
 bool
 CodeGeneratorMIPS::generateAsmJSPrologue(Label *stackOverflowLabel)
 {
     JS_ASSERT(gen->compilingAsmJS());
 
-    // See comment in Assembler-mips.h about AsmJSFrameSize.
+    // See comment in Assembler-shared.h about AsmJSFrameSize.
     masm.push(ra);
 
     // The asm.js over-recursed handler wants to be able to assume that SP
     // points to the return address, so perform the check after pushing ra but
     // before pushing frameDepth.
     if (!omitOverRecursedCheck()) {
         masm.branchPtr(Assembler::AboveOrEqual,
                        AsmJSAbsoluteAddress(AsmJSImm_StackLimit),
--- a/js/src/jit/mips/MacroAssembler-mips.cpp
+++ b/js/src/jit/mips/MacroAssembler-mips.cpp
@@ -1518,16 +1518,26 @@ MacroAssemblerMIPSCompat::callIon(Regist
     MOZ_ASSERT((framePushed() & 3) == 0);
     if ((framePushed() & 7) == 4) {
         ma_callIonHalfPush(callee);
     } else {
         adjustFrame(sizeof(uint32_t));
         ma_callIon(callee);
     }
 }
+void
+MacroAssemblerMIPSCompat::callIonFromAsmJS(Register callee)
+{
+    ma_callIonNoPush(reg);
+
+    // The Ion ABI has the callee pop the return address off the stack.
+    // The asm.js caller assumes that the call leaves sp unchanged, so bump
+    // the stack.
+    subPtr(Imm32(sizeof(void*)), StackPointer);
+}
 
 void
 MacroAssemblerMIPSCompat::reserveStack(uint32_t amount)
 {
     if (amount)
         ma_subu(StackPointer, StackPointer, Imm32(amount));
     adjustFrame(amount);
 }
@@ -3020,31 +3030,16 @@ MacroAssemblerMIPS::ma_callIonHalfPush(c
 {
     // This is a MIPS hack to push return address during jalr delay slot.
     as_addiu(StackPointer, StackPointer, -sizeof(intptr_t));
     as_jalr(r);
     as_sw(ra, StackPointer, 0);
 }
 
 void
-MacroAssemblerMIPS::ma_callAndStoreRet(const Register r, uint32_t stackArgBytes)
-{
-    // Note: this function stores the return address to sp[16]. The caller
-    // must anticipate this by reserving additional space on the stack.
-    // The ABI does not provide space for a return address so this function
-    // stores 'ra' before any ABI arguments.
-    // This function may only be called if there are 4 or less arguments.
-    JS_ASSERT(stackArgBytes == 4 * sizeof(uintptr_t));
-
-    // This is a MIPS hack to push return address during jalr delay slot.
-    as_jalr(r);
-    as_sw(ra, StackPointer, 4 * sizeof(uintptr_t));
-}
-
-void
 MacroAssemblerMIPS::ma_call(ImmPtr dest)
 {
     ma_liPatchable(CallReg, dest);
     as_jalr(CallReg);
     as_nop();
 }
 
 void
--- a/js/src/jit/mips/MacroAssembler-mips.h
+++ b/js/src/jit/mips/MacroAssembler-mips.h
@@ -296,19 +296,16 @@ class MacroAssemblerMIPS : public Assemb
   public:
     // calls an Ion function, assumes that the stack is untouched (8 byte alinged)
     void ma_callIon(const Register reg);
     // callso an Ion function, assuming that sp has already been decremented
     void ma_callIonNoPush(const Register reg);
     // calls an ion function, assuming that the stack is currently not 8 byte aligned
     void ma_callIonHalfPush(const Register reg);
 
-    // calls reg, storing the return address into sp[stackArgBytes]
-    void ma_callAndStoreRet(const Register reg, uint32_t stackArgBytes);
-
     void ma_call(ImmPtr dest);
 
     void ma_jump(ImmPtr dest);
 
     void ma_cmp_set(Register dst, Register lhs, Register rhs, Condition c);
     void ma_cmp_set(Register dst, Register lhs, Imm32 imm, Condition c);
     void ma_cmp_set(Register dst, Register lhs, ImmPtr imm, Condition c) {
         ma_cmp_set(dst, lhs, Imm32(uint32_t(imm.value)), c);
@@ -410,48 +407,23 @@ class MacroAssemblerMIPSCompat : public 
         call(CallReg);
     }
     void call(JitCode *c) {
         BufferOffset bo = m_buffer.nextOffset();
         addPendingJump(bo, ImmPtr(c->raw()), Relocation::JITCODE);
         ma_liPatchable(ScratchRegister, Imm32((uint32_t)c->raw()));
         ma_callIonHalfPush(ScratchRegister);
     }
-
-    void appendCallSite(const CallSiteDesc &desc) {
-        // Add an extra sizeof(void*) to include the return address that was
-        // pushed by the call instruction (see CallSite::stackDepth).
-        enoughMemory_ &= append(CallSite(desc, currentOffset(), framePushed_ + AsmJSFrameSize));
-    }
-
     void call(const CallSiteDesc &desc, const Register reg) {
         call(reg);
-        appendCallSite(desc);
+        enoughMemory_ &= append(desc, currentOffset(), framePushed_);
     }
     void call(const CallSiteDesc &desc, Label *label) {
         call(label);
-        appendCallSite(desc);
-    }
-    void call(const CallSiteDesc &desc, AsmJSImmPtr imm) {
-        call(imm);
-        appendCallSite(desc);
-    }
-    void callExit(AsmJSImmPtr imm, uint32_t stackArgBytes) {
-        movePtr(imm, CallReg);
-        ma_callAndStoreRet(CallReg, stackArgBytes);
-        appendCallSite(CallSiteDesc::Exit());
-    }
-    void callIonFromAsmJS(const Register reg) {
-        ma_callIonNoPush(reg);
-        appendCallSite(CallSiteDesc::Exit());
-
-        // The Ion ABI has the callee pop the return address off the stack.
-        // The asm.js caller assumes that the call leaves sp unchanged, so bump
-        // the stack.
-        subPtr(Imm32(sizeof(void*)), StackPointer);
+        enoughMemory_ &= append(desc, currentOffset(), framePushed_);
     }
 
     void branch(JitCode *c) {
         BufferOffset bo = m_buffer.nextOffset();
         addPendingJump(bo, ImmPtr(c->raw()), Relocation::JITCODE);
         ma_liPatchable(ScratchRegister, Imm32((uint32_t)c->raw()));
         as_jr(ScratchRegister);
         as_nop();
@@ -997,16 +969,17 @@ public:
     bool buildFakeExitFrame(Register scratch, uint32_t *offset);
 
     void callWithExitFrame(JitCode *target);
     void callWithExitFrame(JitCode *target, Register dynStack);
 
     // Makes an Ion call using the only two methods that it is sane for
     // indep code to make a call
     void callIon(Register callee);
+    void callIonFromAsmJS(Register callee);
 
     void reserveStack(uint32_t amount);
     void freeStack(uint32_t amount);
     void freeStack(Register amount);
 
     void add32(Register src, Register dest);
     void add32(Imm32 imm, Register dest);
     void add32(Imm32 imm, const Address &dest);
--- a/js/src/jit/shared/Assembler-shared.h
+++ b/js/src/jit/shared/Assembler-shared.h
@@ -572,58 +572,29 @@ class CodeLocationLabel
         return raw_;
     }
     uint8_t *offset() const {
         JS_ASSERT(state_ == Relative);
         return raw_;
     }
 };
 
-// Describes the user-visible properties of a callsite.
-//
-// A few general notes about the stack-walking supported by CallSite(Desc):
-//  - This information facilitates stack-walking performed by FrameIter which
-//    is used by Error.stack and other user-visible stack-walking functions.
-//  - Ion/asm.js calling conventions do not maintain a frame-pointer so
-//    stack-walking must lookup the stack depth based on the PC.
-//  - Stack-walking only occurs from C++ after a synchronous calls (JS-to-JS and
-//    JS-to-C++). Thus, we do not need to map arbitrary PCs to stack-depths,
-//    just the return address at callsites.
-//  - An exception to the above rule is the interrupt callback which can happen
-//    at arbitrary PCs. In such cases, we drop frames from the stack-walk. In
-//    the future when a full PC->stack-depth map is maintained, we handle this
-//    case.
+// While the frame-pointer chain allows the stack to be unwound without
+// metadata, Error.stack still needs to know the line/column of every call in
+// the chain. A CallSiteDesc describes the line/column of a single callsite.
+// A CallSiteDesc is created by callers of MacroAssembler.
 class CallSiteDesc
 {
     uint32_t line_;
     uint32_t column_;
-    uint32_t functionNameIndex_;
-
-    static const uint32_t sEntryTrampoline = UINT32_MAX;
-    static const uint32_t sExit = UINT32_MAX - 1;
-
   public:
-    static const uint32_t FUNCTION_NAME_INDEX_MAX = UINT32_MAX - 2;
-
     CallSiteDesc() {}
-
-    CallSiteDesc(uint32_t line, uint32_t column, uint32_t functionNameIndex)
-     : line_(line), column_(column), functionNameIndex_(functionNameIndex)
-    {}
-
-    static CallSiteDesc Entry() { return CallSiteDesc(0, 0, sEntryTrampoline); }
-    static CallSiteDesc Exit() { return CallSiteDesc(0, 0, sExit); }
-
-    bool isEntry() const { return functionNameIndex_ == sEntryTrampoline; }
-    bool isExit() const { return functionNameIndex_ == sExit; }
-    bool isNormal() const { return !(isEntry() || isExit()); }
-
-    uint32_t line() const { JS_ASSERT(isNormal()); return line_; }
-    uint32_t column() const { JS_ASSERT(isNormal()); return column_; }
-    uint32_t functionNameIndex() const { JS_ASSERT(isNormal()); return functionNameIndex_; }
+    CallSiteDesc(uint32_t line, uint32_t column) : line_(line), column_(column) {}
+    uint32_t line() const { return line_; }
+    uint32_t column() const { return column_; }
 };
 
 // Adds to CallSiteDesc the metadata necessary to walk the stack given an
 // initial stack-pointer.
 struct CallSite : public CallSiteDesc
 {
     uint32_t returnAddressOffset_;
     uint32_t stackDepth_;
@@ -636,23 +607,31 @@ struct CallSite : public CallSiteDesc
         returnAddressOffset_(returnAddressOffset),
         stackDepth_(stackDepth)
     { }
 
     void setReturnAddressOffset(uint32_t r) { returnAddressOffset_ = r; }
     uint32_t returnAddressOffset() const { return returnAddressOffset_; }
 
     // The stackDepth measures the amount of stack space pushed since the
-    // function was called. In particular, this includes the word pushed by the
-    // call instruction on x86/x64.
-    uint32_t stackDepth() const { JS_ASSERT(!isEntry()); return stackDepth_; }
+    // function was called. In particular, this includes the pushed return
+    // address on all archs (whether or not the call instruction pushes the
+    // return address (x86/x64) or the prologue does (ARM/MIPS).
+    uint32_t stackDepth() const { return stackDepth_; }
 };
 
 typedef Vector<CallSite, 0, SystemAllocPolicy> CallSiteVector;
 
+// As an invariant across architectures, within asm.js code:
+//    $sp % StackAlignment = (AsmJSFrameSize + masm.framePushed) % StackAlignment
+// AsmJSFrameSize is 1 word, for the return address pushed by the call (or, in
+// the case of ARM/MIPS, by the first instruction of the prologue). This means
+// masm.framePushed never includes the pushed return address.
+static const uint32_t AsmJSFrameSize = sizeof(void*);
+
 // Summarizes a heap access made by asm.js code that needs to be patched later
 // and/or looked up by the asm.js signal handlers. Different architectures need
 // to know different things (x64: offset and length, ARM: where to patch in
 // heap length, x86: where to patch in heap length and base) hence the massive
 // #ifdefery.
 class AsmJSHeapAccess
 {
     uint32_t offset_;
@@ -816,17 +795,21 @@ class AssemblerShared
     void propagateOOM(bool success) {
         enoughMemory_ &= success;
     }
 
     bool oom() const {
         return !enoughMemory_;
     }
 
-    bool append(CallSite callsite) { return callsites_.append(callsite); }
+    bool append(const CallSiteDesc &desc, size_t currentOffset, size_t framePushed) {
+        // framePushed does not include AsmJSFrameSize, so add it in here (see
+        // CallSite::stackDepth).
+        return callsites_.append(CallSite(desc, currentOffset, framePushed + AsmJSFrameSize));
+    }
     CallSiteVector &&extractCallSites() { return Move(callsites_); }
 
     bool append(AsmJSHeapAccess access) { return asmJSHeapAccesses_.append(access); }
     AsmJSHeapAccessVector &&extractAsmJSHeapAccesses() { return Move(asmJSHeapAccesses_); }
 
     bool append(AsmJSGlobalAccess access) { return asmJSGlobalAccesses_.append(access); }
     size_t numAsmJSGlobalAccesses() const { return asmJSGlobalAccesses_.length(); }
     AsmJSGlobalAccess asmJSGlobalAccess(size_t i) const { return asmJSGlobalAccesses_[i]; }
--- a/js/src/jit/shared/MacroAssembler-x86-shared.h
+++ b/js/src/jit/shared/MacroAssembler-x86-shared.h
@@ -662,36 +662,33 @@ class MacroAssemblerX86Shared : public A
         lea(Operand(address), dest);
     }
 
     // Builds an exit frame on the stack, with a return address to an internal
     // non-function. Returns offset to be passed to markSafepointAt().
     bool buildFakeExitFrame(Register scratch, uint32_t *offset);
     void callWithExitFrame(JitCode *target);
 
+    void call(const CallSiteDesc &desc, Label *label) {
+        call(label);
+        enoughMemory_ &= append(desc, currentOffset(), framePushed_);
+    }
+    void call(const CallSiteDesc &desc, Register reg) {
+        call(reg);
+        enoughMemory_ &= append(desc, currentOffset(), framePushed_);
+    }
     void callIon(Register callee) {
         call(callee);
     }
-
-    void appendCallSite(const CallSiteDesc &desc) {
-        // Add an extra sizeof(void*) to include the return address that was
-        // pushed by the call instruction (see CallSite::stackDepth).
-        enoughMemory_ &= append(CallSite(desc, currentOffset(), framePushed_ + AsmJSFrameSize));
+    void callIonFromAsmJS(Register callee) {
+        call(callee);
     }
-
-    void call(const CallSiteDesc &desc, Label *label) {
-        call(label);
-        appendCallSite(desc);
-    }
-    void call(const CallSiteDesc &desc, Register reg) {
-        call(reg);
-        appendCallSite(desc);
-    }
-    void callIonFromAsmJS(Register reg) {
-        call(CallSiteDesc::Exit(), reg);
+    void call(AsmJSImmPtr target) {
+        mov(target, eax);
+        call(eax);
     }
 
     void checkStackAlignment() {
         // Exists for ARM compatibility.
     }
 
     CodeOffsetLabel labelForPatch() {
         return CodeOffsetLabel(size());
--- a/js/src/jit/x64/Assembler-x64.h
+++ b/js/src/jit/x64/Assembler-x64.h
@@ -180,23 +180,16 @@ static MOZ_CONSTEXPR_VAR Register OsrFra
 static MOZ_CONSTEXPR_VAR Register PreBarrierReg = rdx;
 
 // GCC stack is aligned on 16 bytes, but we don't maintain the invariant in
 // jitted code.
 static const uint32_t StackAlignment = 16;
 static const bool StackKeptAligned = false;
 static const uint32_t CodeAlignment = 8;
 
-// As an invariant across architectures, within asm.js code:
-//   $sp % StackAlignment = (AsmJSFrameSize + masm.framePushed) % StackAlignment
-// On x64, this naturally falls out of the fact that the 'call' instruction
-// pushes the return address on the stack and masm.framePushed = 0 at the first
-// instruction of the prologue.
-static const uint32_t AsmJSFrameSize = sizeof(void*);
-
 static const Scale ScalePointer = TimesEight;
 
 } // namespace jit
 } // namespace js
 
 #include "jit/shared/Assembler-x86-shared.h"
 
 namespace js {
--- a/js/src/jit/x64/MacroAssembler-x64.h
+++ b/js/src/jit/x64/MacroAssembler-x64.h
@@ -95,28 +95,16 @@ class MacroAssemblerX64 : public MacroAs
     /////////////////////////////////////////////////////////////////
     void call(ImmWord target) {
         mov(target, rax);
         call(rax);
     }
     void call(ImmPtr target) {
         call(ImmWord(uintptr_t(target.value)));
     }
-    void call(AsmJSImmPtr target) {
-        mov(target, rax);
-        call(rax);
-    }
-
-    void call(const CallSiteDesc &desc, AsmJSImmPtr target) {
-        call(target);
-        appendCallSite(desc);
-    }
-    void callExit(AsmJSImmPtr target, uint32_t stackArgBytes) {
-        call(CallSiteDesc::Exit(), target);
-    }
 
     // Refers to the upper 32 bits of a 64-bit Value operand.
     // On x86_64, the upper 32 bits do not necessarily only contain the type.
     Operand ToUpper32(Operand base) {
         switch (base.kind()) {
           case Operand::MEM_REG_DISP:
             return Operand(Register::FromCode(base.base()), base.disp() + 4);
 
--- a/js/src/jit/x86/Assembler-x86.h
+++ b/js/src/jit/x86/Assembler-x86.h
@@ -108,23 +108,16 @@ static MOZ_CONSTEXPR_VAR Register AsmJSI
 #if defined(__GNUC__)
 static const uint32_t StackAlignment = 16;
 #else
 static const uint32_t StackAlignment = 4;
 #endif
 static const bool StackKeptAligned = false;
 static const uint32_t CodeAlignment = 8;
 
-// As an invariant across architectures, within asm.js code:
-//   $sp % StackAlignment = (AsmJSFrameSize + masm.framePushed) % StackAlignment
-// On x86, this naturally falls out of the fact that the 'call' instruction
-// pushes the return address on the stack and masm.framePushed = 0 at the first
-// instruction of the prologue.
-static const uint32_t AsmJSFrameSize = sizeof(void*);
-
 struct ImmTag : public Imm32
 {
     ImmTag(JSValueTag mask)
       : Imm32(int32_t(mask))
     { }
 };
 
 struct ImmType : public ImmTag
@@ -377,23 +370,16 @@ class Assembler : public AssemblerX86Sha
     }
     void call(ImmWord target) {
         call(ImmPtr((void*)target.value));
     }
     void call(ImmPtr target) {
         JmpSrc src = masm.call();
         addPendingJump(src, target, Relocation::HARDCODED);
     }
-    void call(AsmJSImmPtr target) {
-        // Moving to a register is suboptimal. To fix (use a single
-        // call-immediate instruction) we'll need to distinguish a new type of
-        // relative patch to an absolute address in AsmJSAbsoluteLink.
-        mov(target, eax);
-        call(eax);
-    }
 
     // Emit a CALL or CMP (nop) instruction. ToggleCall can be used to patch
     // this instruction.
     CodeOffsetLabel toggledCall(JitCode *target, bool enabled) {
         CodeOffsetLabel offset(size());
         JmpSrc src = enabled ? masm.call() : masm.cmp_eax();
         addPendingJump(src, ImmPtr(target->raw()), Relocation::JITCODE);
         JS_ASSERT(size() - offset.offset() == ToggledCallSize(nullptr));
--- a/js/src/jit/x86/MacroAssembler-x86.h
+++ b/js/src/jit/x86/MacroAssembler-x86.h
@@ -1115,23 +1115,16 @@ class MacroAssemblerX86 : public MacroAs
     }
 
     void callWithExitFrame(JitCode *target, Register dynStack) {
         addPtr(Imm32(framePushed()), dynStack);
         makeFrameDescriptor(dynStack, JitFrame_IonJS);
         Push(dynStack);
         call(target);
     }
-    void call(const CallSiteDesc &desc, AsmJSImmPtr target) {
-        call(target);
-        appendCallSite(desc);
-    }
-    void callExit(AsmJSImmPtr target, uint32_t stackArgBytes) {
-        call(CallSiteDesc::Exit(), target);
-    }
 
 #ifdef JSGC_GENERATIONAL
     void branchPtrInNurseryRange(Condition cond, Register ptr, Register temp, Label *label);
     void branchValueIsNurseryObject(Condition cond, ValueOperand value, Register temp, Label *label);
 #endif
 };
 
 typedef MacroAssemblerX86 MacroAssemblerSpecific;
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -6584,18 +6584,18 @@ JS::SetLargeAllocationFailureCallback(JS
 JS_PUBLIC_API(void)
 JS::SetOutOfMemoryCallback(JSRuntime *rt, OutOfMemoryCallback cb, void *data)
 {
     rt->oomCallback = cb;
     rt->oomCallbackData = data;
 }
 
 JS_PUBLIC_API(bool)
-JS::CaptureCurrentStack(JSContext *cx, JS::MutableHandleObject stackp)
+JS::CaptureCurrentStack(JSContext *cx, JS::MutableHandleObject stackp, unsigned maxFrameCount)
 {
     JSCompartment *compartment = cx->compartment();
     JS_ASSERT(compartment);
     Rooted<SavedFrame *> frame(cx);
-    if (!compartment->savedStacks().saveCurrentStack(cx, &frame))
+    if (!compartment->savedStacks().saveCurrentStack(cx, &frame, maxFrameCount))
         return false;
     stackp.set(frame.get());
     return true;
 }
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -5189,14 +5189,20 @@ SetLargeAllocationFailureCallback(JSRunt
  */
 
 typedef void
 (* OutOfMemoryCallback)(JSContext *cx, void *data);
 
 extern JS_PUBLIC_API(void)
 SetOutOfMemoryCallback(JSRuntime *rt, OutOfMemoryCallback cb, void *data);
 
-extern JS_PUBLIC_API(bool)
-CaptureCurrentStack(JSContext *cx, MutableHandleObject stackp);
+
+/*
+ * Capture the current call stack as a chain of SavedFrame objects, and set
+ * |stackp| to the SavedFrame for the newest stack frame. If |maxFrameCount| is
+ * non-zero, capture at most the youngest |maxFrameCount| frames.
+ */
+extern JS_PUBLIC_API(bool)
+CaptureCurrentStack(JSContext *cx, MutableHandleObject stackp, unsigned maxFrameCount = 0);
 
 } /* namespace JS */
 
 #endif /* jsapi_h */
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -795,17 +795,17 @@ js::FindBody(JSContext *cx, HandleFuncti
         tt = ts.getToken();
     if (tt == TOK_ERROR)
         return false;
     bool braced = tt == TOK_LC;
     JS_ASSERT_IF(fun->isExprClosure(), !braced);
     *bodyStart = ts.currentToken().pos.begin;
     if (braced)
         *bodyStart += 1;
-    RangedPtr<const jschar> end = srcChars.end();
+    mozilla::RangedPtr<const jschar> end = srcChars.end();
     if (end[-1] == '}') {
         end--;
     } else {
         JS_ASSERT(!braced);
         for (; unicode::IsSpaceOrBOM2(end[-1]); end--)
             ;
     }
     *bodyEnd = end - srcChars.start();
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -22,17 +22,16 @@
 #include "mozilla/CheckedInt.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/Range.h"
 #include "mozilla/TypeTraits.h"
 
 #include <ctype.h>
 #include <string.h>
-#include <wchar.h>
 
 #include "jsapi.h"
 #include "jsarray.h"
 #include "jsatom.h"
 #include "jsbool.h"
 #include "jscntxt.h"
 #include "jsgc.h"
 #include "jsnum.h"
@@ -1163,32 +1162,22 @@ FirstCharMatcher8bit(const char *text, u
 #if  defined(__clang__)
     return FirstCharMatcherUnrolled<char, char>(text, n, pat);
 #else
     return reinterpret_cast<const char *>(memchr(text, pat, n));
 #endif
 }
 
 static const jschar *
-FirstCharMatcher16bit (const jschar *text, uint32_t n, const jschar pat)
+FirstCharMatcher16bit(const jschar *text, uint32_t n, const jschar pat)
 {
-    /* Some platforms define wchar_t as signed and others not. */
-#if (WCHAR_MIN == 0 && WCHAR_MAX == UINT16_MAX) || (WCHAR_MIN == INT16_MIN && WCHAR_MAX == INT16_MAX)
+#if defined(XP_DARWIN) || defined(XP_WIN)
     /*
-     * Wmemchr works the best.
-     * But only possible to use this when,
-     * size of jschar = size of wchar_t.
-     */
-    const wchar_t *wtext = (const wchar_t *) text;
-    const wchar_t wpat = (const wchar_t) pat;
-    return (jschar *) (wmemchr(wtext, wpat, n));
-#elif defined(__clang__)
-    /*
-     * Performance under memchr is horrible in clang.
-     * Hence it is best to use UnrolledMatcher in this case
+     * Performance of memchr is horrible in OSX. Windows is better,
+     * but it is still better to use UnrolledMatcher.
      */
     return FirstCharMatcherUnrolled<jschar, jschar>(text, n, pat);
 #else
     /*
      * For linux the best performance is obtained by slightly hacking memchr.
      * memchr works only on 8bit char but jschar is 16bit. So we treat jschar
      * in blocks of 8bit and use memchr.
      */
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -243,16 +243,17 @@ if CONFIG['ENABLE_TRACE_LOGGING']:
         'vm/TraceLogging.cpp',
     ]
 
 if CONFIG['ENABLE_ION']:
     UNIFIED_SOURCES += [
         'irregexp/NativeRegExpMacroAssembler.cpp',
         'jit/AliasAnalysis.cpp',
         'jit/AsmJS.cpp',
+        'jit/AsmJSFrameIterator.cpp',
         'jit/AsmJSLink.cpp',
         'jit/AsmJSModule.cpp',
         'jit/AsmJSSignalHandlers.cpp',
         'jit/BacktrackingAllocator.cpp',
         'jit/Bailouts.cpp',
         'jit/BaselineBailouts.cpp',
         'jit/BaselineCompiler.cpp',
         'jit/BaselineDebugModeOSR.cpp',
--- a/js/src/vm/SavedStacks.cpp
+++ b/js/src/vm/SavedStacks.cpp
@@ -391,23 +391,23 @@ SavedStacks::init()
 {
     if (!pcLocationMap.init())
         return false;
 
     return frames.init();
 }
 
 bool
-SavedStacks::saveCurrentStack(JSContext *cx, MutableHandleSavedFrame frame)
+SavedStacks::saveCurrentStack(JSContext *cx, MutableHandleSavedFrame frame, unsigned maxFrameCount)
 {
     JS_ASSERT(initialized());
     JS_ASSERT(&cx->compartment()->savedStacks() == this);
 
     ScriptFrameIter iter(cx);
-    return insertFrames(cx, iter, frame);
+    return insertFrames(cx, iter, frame, maxFrameCount);
 }
 
 void
 SavedStacks::sweep(JSRuntime *rt)
 {
     if (frames.initialized()) {
         for (SavedFrame::Set::Enum e(frames); !e.empty(); e.popFront()) {
             JSObject *obj = static_cast<JSObject *>(e.front());
@@ -471,17 +471,18 @@ SavedStacks::clear()
 
 size_t
 SavedStacks::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf)
 {
     return frames.sizeOfExcludingThis(mallocSizeOf);
 }
 
 bool
-SavedStacks::insertFrames(JSContext *cx, ScriptFrameIter &iter, MutableHandleSavedFrame frame)
+SavedStacks::insertFrames(JSContext *cx, ScriptFrameIter &iter, MutableHandleSavedFrame frame,
+                          unsigned maxFrameCount)
 {
     if (iter.done()) {
         frame.set(nullptr);
         return true;
     }
 
     // Don't report the over-recursion error because if we are blowing the stack
     // here, we already blew the stack in JS, reported it, and we are creating
@@ -492,18 +493,30 @@ SavedStacks::insertFrames(JSContext *cx,
     JS_CHECK_RECURSION_DONT_REPORT(cx, return false);
 
     RootedScript script(cx, iter.script());
     jsbytecode *pc = iter.pc();
     RootedFunction callee(cx, iter.maybeCallee());
     // script and callee should keep compartment alive.
     JSCompartment *compartment = iter.compartment();
     RootedSavedFrame parentFrame(cx);
-    if (!insertFrames(cx, ++iter, &parentFrame))
-        return false;
+
+    // If maxFrameCount is zero, then there's no limit on the number of frames.
+    if (maxFrameCount == 0) {
+        if (!insertFrames(cx, ++iter, &parentFrame, 0))
+            return false;
+    } else if (maxFrameCount == 1) {
+        // Since we were only asked to save one frame, the SavedFrame we're
+        // building here should have no parent, even if there are older frames
+        // on the stack.
+        parentFrame = nullptr;
+    } else {
+        if (!insertFrames(cx, ++iter, &parentFrame, maxFrameCount - 1))
+            return false;
+    }
 
     AutoLocationValueRooter location(cx);
     if (!getLocation(cx, script, pc, &location))
         return false;
 
     SavedFrame::AutoLookupRooter lookup(cx,
                                         location.get().source,
                                         location.get().line,
--- a/js/src/vm/SavedStacks.h
+++ b/js/src/vm/SavedStacks.h
@@ -99,29 +99,30 @@ struct SavedFrame::HashPolicy
 };
 
 class SavedStacks {
   public:
     SavedStacks() : frames(), savedFrameProto(nullptr) { }
 
     bool     init();
     bool     initialized() const { return frames.initialized(); }
-    bool     saveCurrentStack(JSContext *cx, MutableHandleSavedFrame frame);
+    bool     saveCurrentStack(JSContext *cx, MutableHandleSavedFrame frame, unsigned maxFrameCount = 0);
     void     sweep(JSRuntime *rt);
     void     trace(JSTracer *trc);
     uint32_t count();
     void     clear();
 
     size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf);
 
   private:
     SavedFrame::Set          frames;
     JSObject                 *savedFrameProto;
 
-    bool       insertFrames(JSContext *cx, ScriptFrameIter &iter, MutableHandleSavedFrame frame);
+    bool       insertFrames(JSContext *cx, ScriptFrameIter &iter, MutableHandleSavedFrame frame,
+                            unsigned maxFrameCount = 0);
     SavedFrame *getOrCreateSavedFrame(JSContext *cx, const SavedFrame::Lookup &lookup);
     // |SavedFrame.prototype| is created lazily and held weakly. It should only
     // be accessed through this method.
     JSObject   *getOrCreateSavedFramePrototype(JSContext *cx);
     SavedFrame *createFrameFromLookup(JSContext *cx, const SavedFrame::Lookup &lookup);
 
     // Cache for memoizing PCToLineNumber lookups.
 
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -582,17 +582,17 @@ FrameIter::settleOnActivation()
             }
 
             nextJitFrame();
             data_.state_ = JIT;
             return;
         }
 
         if (activation->isAsmJS()) {
-            data_.asmJSFrames_ = AsmJSFrameIterator(data_.activations_->asAsmJS());
+            data_.asmJSFrames_ = AsmJSFrameIterator(*data_.activations_->asAsmJS());
 
             if (data_.asmJSFrames_.done()) {
                 ++data_.activations_;
                 continue;
             }
 
             data_.state_ = ASMJS;
             return;
@@ -634,17 +634,17 @@ FrameIter::Data::Data(ThreadSafeContext 
     contextOption_(contextOption),
     principals_(principals),
     pc_(nullptr),
     interpFrames_(nullptr),
     activations_(cx->perThreadData)
 #ifdef JS_ION
   , jitFrames_((uint8_t *)nullptr, SequentialExecution)
   , ionInlineFrameNo_(0)
-  , asmJSFrames_(nullptr)
+  , asmJSFrames_()
 #endif
 {
 }
 
 FrameIter::Data::Data(const FrameIter::Data &other)
   : cx_(other.cx_),
     savedOption_(other.savedOption_),
     contextOption_(other.contextOption_),
@@ -1683,17 +1683,17 @@ jit::JitActivation::markRematerializedFr
 #endif // JS_ION
 
 AsmJSActivation::AsmJSActivation(JSContext *cx, AsmJSModule &module)
   : Activation(cx, AsmJS),
     module_(module),
     errorRejoinSP_(nullptr),
     profiler_(nullptr),
     resumePC_(nullptr),
-    exitSP_(nullptr)
+    exitFP_(nullptr)
 {
     if (cx->runtime()->spsProfiler.enabled()) {
         // Use a profiler string that matches jsMatch regex in
         // browser/devtools/profiler/cleopatra/js/parserWorker.js.
         // (For now use a single static string to avoid further slowing down
         // calls into asm.js.)
         profiler_ = &cx->runtime()->spsProfiler;
         profiler_->enterNative("asm.js code :0", this);
--- 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/AsmJSLink.h"
+#include "jit/AsmJSFrameIterator.h"
 #include "jit/JitFrameIterator.h"
 #ifdef CHECK_OSIPOINT_REGISTERS
 #include "jit/Registers.h" // for RegisterDump
 #endif
 #include "js/OldDebugAPI.h"
 
 struct JSCompartment;
 struct JSGenerator;
@@ -1506,44 +1506,44 @@ class InterpreterFrameIterator
 // all kinds of jit code.
 class AsmJSActivation : public Activation
 {
     AsmJSModule &module_;
     AsmJSActivation *prevAsmJS_;
     void *errorRejoinSP_;
     SPSProfiler *profiler_;
     void *resumePC_;
-    uint8_t *exitSP_;
-
-    static const intptr_t InterruptedSP = -1;
+    uint8_t *exitFP_;
 
   public:
     AsmJSActivation(JSContext *cx, AsmJSModule &module);
     ~AsmJSActivation();
 
     inline JSContext *cx();
     AsmJSModule &module() const { return module_; }
     AsmJSActivation *prevAsmJS() const { return prevAsmJS_; }
 
     // Read by JIT code:
     static unsigned offsetOfContext() { return offsetof(AsmJSActivation, cx_); }
     static unsigned offsetOfResumePC() { return offsetof(AsmJSActivation, resumePC_); }
 
     // Initialized by JIT code:
     static unsigned offsetOfErrorRejoinSP() { return offsetof(AsmJSActivation, errorRejoinSP_); }
-    static unsigned offsetOfExitSP() { return offsetof(AsmJSActivation, exitSP_); }
+    static unsigned offsetOfExitFP() { return offsetof(AsmJSActivation, exitFP_); }
 
     // Set from SIGSEGV handler:
-    void setInterrupted(void *pc) { resumePC_ = pc; exitSP_ = (uint8_t*)InterruptedSP; }
-    bool isInterruptedSP() const { return exitSP_ == (uint8_t*)InterruptedSP; }
+    void setResumePC(void *pc) { resumePC_ = pc; }
 
-    // Note: exitSP is the sp right before the call instruction. On x86, this
-    // means before the return address is pushed on the stack, on ARM, this
-    // means after.
-    uint8_t *exitSP() const { JS_ASSERT(!isInterruptedSP()); return exitSP_; }
+    // If pc is in C++/Ion code, exitFP points to the innermost asm.js frame
+    // (the one that called into C++). While in asm.js code, exitFP is either
+    // null or points to the innermost asm.js frame. Thus, it is always valid to
+    // unwind a non-null exitFP. The only way C++ can observe a null exitFP is
+    // asychronous interruption of asm.js execution (viz., via the profiler,
+    // a signal handler, or the interrupt exit).
+    uint8_t *exitFP() const { return exitFP_; }
 };
 
 // A FrameIter walks over the runtime's stack of JS script activations,
 // abstracting over whether the JS scripts were running in the interpreter or
 // different modes of compiled code.
 //
 // FrameIter is parameterized by what it includes in the stack iteration:
 //  - The SavedOption controls whether FrameIter stops when it finds an
--- a/js/xpconnect/src/ExportHelpers.cpp
+++ b/js/xpconnect/src/ExportHelpers.cpp
@@ -74,17 +74,18 @@ StackScopedCloneRead(JSContext *cx, JSSt
       MOZ_ASSERT(data < cloneData->mFunctions.length());
 
       RootedValue functionValue(cx);
       RootedObject obj(cx, cloneData->mFunctions[data]);
 
       if (!JS_WrapObject(cx, &obj))
           return nullptr;
 
-      if (!xpc::NewFunctionForwarder(cx, obj, true, &functionValue))
+      FunctionForwarderOptions forwarderOptions(cx);
+      if (!xpc::NewFunctionForwarder(cx, JSID_VOIDHANDLE, obj, forwarderOptions, &functionValue))
           return nullptr;
 
       return &functionValue.toObject();
     }
 
     MOZ_ASSERT_UNREACHABLE("Encountered garbage in the clone stream!");
     return nullptr;
 }
@@ -134,19 +135,24 @@ StackScopedCloneWrite(JSContext *cx, JSS
         size_t idx = cloneData->mReflectors.length() - 1;
         if (!JS_WriteUint32Pair(writer, SCTAG_REFLECTOR, 0))
             return false;
         if (!JS_WriteBytes(writer, &idx, sizeof(size_t)))
             return false;
         return true;
     }
 
-    if (cloneData->mOptions->cloneFunctions && JS_ObjectIsCallable(cx, obj)) {
-        cloneData->mFunctions.append(obj);
-        return JS_WriteUint32Pair(writer, SCTAG_FUNCTION, cloneData->mFunctions.length() - 1);
+    if (JS_ObjectIsCallable(cx, obj)) {
+        if (cloneData->mOptions->cloneFunctions) {
+            cloneData->mFunctions.append(obj);
+            return JS_WriteUint32Pair(writer, SCTAG_FUNCTION, cloneData->mFunctions.length() - 1);
+        } else {
+            JS_ReportError(cx, "Permission denied to pass a Function via structured clone");
+            return false;
+        }
     }
 
     JS_ReportError(cx, "Encountered unsupported value type writing stack-scoped structured clone");
     return false;
 }
 
 static const JSStructuredCloneCallbacks gStackScopedCloneCallbacks = {
     StackScopedCloneRead,
@@ -196,27 +202,40 @@ StackScopedClone(JSContext *cx, StackSco
  * Forwards the call to the exported function. Clones all the non reflectors, ignores
  * the |this| argument.
  */
 static bool
 CloningFunctionForwarder(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
-    RootedValue v(cx, js::GetFunctionNativeReserved(&args.callee(), 0));
-    NS_ASSERTION(v.isObject(), "weird function");
-    RootedObject origFunObj(cx, UncheckedUnwrap(&v.toObject()));
+    // Grab the options from the reserved slot.
+    RootedObject optionsObj(cx, &js::GetFunctionNativeReserved(&args.callee(), 1).toObject());
+    FunctionForwarderOptions options(cx, optionsObj);
+    if (!options.Parse())
+        return false;
+
+    // Grab and unwrap the underlying callable.
+    RootedObject forwarderObj(cx, &js::GetFunctionNativeReserved(&args.callee(), 0).toObject());
+    RootedObject origFunObj(cx, UncheckedUnwrap(forwarderObj));
     {
         JSAutoCompartment ac(cx, origFunObj);
         // Note: only the arguments are cloned not the |this| or the |callee|.
         // Function forwarder does not use those.
-        StackScopedCloneOptions options;
-        options.wrapReflectors = true;
+        StackScopedCloneOptions cloneOptions;
+        cloneOptions.wrapReflectors = true;
         for (unsigned i = 0; i < args.length(); i++) {
-            if (!StackScopedClone(cx, options, args[i])) {
+            RootedObject argObj(cx, args[i].isObject() ? &args[i].toObject() : nullptr);
+            if (options.allowCallbacks && argObj && JS_ObjectIsCallable(cx, argObj)) {
+                FunctionForwarderOptions innerOptions(cx);
+                if (!JS_WrapObject(cx, &argObj))
+                    return false;
+                if (!xpc::NewFunctionForwarder(cx, JSID_VOIDHANDLE, argObj, innerOptions, args[i]))
+                    return nullptr;
+            } else if (!StackScopedClone(cx, cloneOptions, args[i])) {
                 return false;
             }
         }
 
         // JS API does not support any JSObject to JSFunction conversion,
         // so let's use JS_CallFunctionValue instead.
         RootedValue functionVal(cx, ObjectValue(*origFunObj));
 
@@ -238,57 +257,71 @@ NonCloningFunctionForwarder(JSContext *c
 
     RootedObject obj(cx, JS_THIS_OBJECT(cx, vp));
     if (!obj) {
         return false;
     }
     return JS_CallFunctionValue(cx, obj, v, args, args.rval());
 }
 bool
-NewFunctionForwarder(JSContext *cx, HandleId id, HandleObject callable, bool doclone,
-                          MutableHandleValue vp)
+NewFunctionForwarder(JSContext *cx, HandleId idArg, HandleObject callable,
+                     FunctionForwarderOptions &options, MutableHandleValue vp)
 {
-    JSFunction *fun = js::NewFunctionByIdWithReserved(cx, doclone ? CloningFunctionForwarder :
-                                                                    NonCloningFunctionForwarder,
-                                                                    0,0, JS::CurrentGlobalOrNull(cx), id);
+    RootedId id(cx, idArg);
+    if (id == JSID_VOIDHANDLE)
+        id = GetRTIdByIndex(cx, XPCJSRuntime::IDX_EMPTYSTRING);
+
+    JSFunction *fun = js::NewFunctionByIdWithReserved(cx, CloningFunctionForwarder, 0,0,
+                                                      JS::CurrentGlobalOrNull(cx), id);
+    if (!fun)
+        return false;
+
+    // Stash the callable in slot 0.
+    AssertSameCompartment(cx, callable);
+    RootedObject funObj(cx, JS_GetFunctionObject(fun));
+    js::SetFunctionNativeReserved(funObj, 0, ObjectValue(*callable));
 
+    // Stash the options in slot 1.
+    RootedObject optionsObj(cx, options.ToJSObject(cx));
+    if (!optionsObj)
+        return false;
+    js::SetFunctionNativeReserved(funObj, 1, ObjectValue(*optionsObj));
+
+    vp.setObject(*funObj);
+    return true;
+}
+
+bool
+NewNonCloningFunctionForwarder(JSContext *cx, HandleId id, HandleObject callable,
+                               MutableHandleValue vp)
+{
+    JSFunction *fun = js::NewFunctionByIdWithReserved(cx, NonCloningFunctionForwarder,
+                                                      0,0, JS::CurrentGlobalOrNull(cx), id);
     if (!fun)
         return false;
 
     JSObject *funobj = JS_GetFunctionObject(fun);
     js::SetFunctionNativeReserved(funobj, 0, ObjectValue(*callable));
     vp.setObject(*funobj);
     return true;
 }
 
 bool
-NewFunctionForwarder(JSContext *cx, HandleObject callable, bool doclone,
-                          MutableHandleValue vp)
-{
-    RootedId emptyId(cx);
-    RootedValue emptyStringValue(cx, JS_GetEmptyStringValue(cx));
-    if (!JS_ValueToId(cx, emptyStringValue, &emptyId))
-        return false;
-
-    return NewFunctionForwarder(cx, emptyId, callable, doclone, vp);
-}
-
-bool
 ExportFunction(JSContext *cx, HandleValue vfunction, HandleValue vscope, HandleValue voptions,
                MutableHandleValue rval)
 {
     bool hasOptions = !voptions.isUndefined();
     if (!vscope.isObject() || !vfunction.isObject() || (hasOptions && !voptions.isObject())) {
         JS_ReportError(cx, "Invalid argument");
         return false;
     }
 
     RootedObject funObj(cx, &vfunction.toObject());
     RootedObject targetScope(cx, &vscope.toObject());
-    ExportOptions options(cx, hasOptions ? &voptions.toObject() : nullptr);
+    ExportFunctionOptions options(cx, hasOptions ? &voptions.toObject() : nullptr);
     if (hasOptions && !options.Parse())
         return false;
 
     // We can only export functions to scopes those are transparent for us,
     // so if there is a security wrapper around targetScope we must throw.
     targetScope = CheckedUnwrap(targetScope);
     if (!targetScope) {
         JS_ReportError(cx, "Permission denied to export function into scope");
@@ -329,17 +362,19 @@ ExportFunction(JSContext *cx, HandleValu
         // The function forwarder will live in the target compartment. Since
         // this function will be referenced from its private slot, to avoid a
         // GC hazard, we must wrap it to the same compartment.
         if (!JS_WrapObject(cx, &funObj))
             return false;
 
         // And now, let's create the forwarder function in the target compartment
         // for the function the be exported.
-        if (!NewFunctionForwarder(cx, id, funObj, /* doclone = */ true, rval)) {
+        FunctionForwarderOptions forwarderOptions(cx);
+        forwarderOptions.allowCallbacks = options.allowCallbacks;
+        if (!NewFunctionForwarder(cx, id, funObj, forwarderOptions, rval)) {
             JS_ReportError(cx, "Exporting function failed");
             return false;
         }
 
         // We have the forwarder function in the target compartment. If
         // defineAs was set, we also need to define it as a property on
         // the target.
         if (!JSID_IS_VOID(options.defineAs)) {
--- a/js/xpconnect/src/XPCComponents.cpp
+++ b/js/xpconnect/src/XPCComponents.cpp
@@ -3079,17 +3079,17 @@ nsXPCComponents_Utils::MakeObjectPropsNo
         if (v.isPrimitive())
             continue;
 
         RootedObject propobj(cx, &v.toObject());
         // TODO Deal with non-functions.
         if (!js::IsWrapper(propobj) || !JS_ObjectIsCallable(cx, propobj))
             continue;
 
-        if (!NewFunctionForwarder(cx, id, propobj, /* doclone = */ false, &v) ||
+        if (!NewNonCloningFunctionForwarder(cx, id, propobj, &v) ||
             !JS_SetPropertyById(cx, obj, id, v))
             return NS_ERROR_FAILURE;
     }
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -80,16 +80,17 @@ const char* const XPCJSRuntime::mStrings
     "__iterator__",         // IDX_ITERATOR
     "__exposedProps__",     // IDX_EXPOSEDPROPS
     "eval",                 // IDX_EVAL
     "controllers",          // IDX_CONTROLLERS
     "realFrameElement",     // IDX_REALFRAMEELEMENT
     "length",               // IDX_LENGTH
     "name",                 // IDX_NAME
     "undefined",            // IDX_UNDEFINED
+    "",                     // IDX_EMPTYSTRING
 };
 
 /***************************************************************************/
 
 static mozilla::Atomic<bool> sDiscardSystemSource(false);
 
 bool
 xpc::ShouldDiscardSystemSource() { return sDiscardSystemSource; }
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -476,16 +476,17 @@ public:
         IDX_ITERATOR                ,
         IDX_EXPOSEDPROPS            ,
         IDX_EVAL                    ,
         IDX_CONTROLLERS             ,
         IDX_REALFRAMEELEMENT        ,
         IDX_LENGTH                  ,
         IDX_NAME                    ,
         IDX_UNDEFINED               ,
+        IDX_EMPTYSTRING             ,
         IDX_TOTAL_COUNT // just a count of the above
     };
 
     JS::HandleId GetStringID(unsigned index) const
     {
         MOZ_ASSERT(index < IDX_TOTAL_COUNT, "index out of range");
         // fromMarkedLocation() is safe because the string is interned.
         return JS::HandleId::fromMarkedLocation(&mStrIDs[index]);
@@ -3275,27 +3276,33 @@ namespace xpc {
 
 // JSNatives to expose atob and btoa in various non-DOM XPConnect scopes.
 bool
 Atob(JSContext *cx, unsigned argc, jsval *vp);
 
 bool
 Btoa(JSContext *cx, unsigned argc, jsval *vp);
 
+class FunctionForwarderOptions;
 
 // Helper function that creates a JSFunction that wraps a native function that
-// forwards the call to the original 'callable'. If the 'doclone' argument is
-// set, it also structure clones non-native arguments for extra security.
+// forwards the call to the original 'callable'. For improved security, any
+// object-valued arguments are cloned at call time, unless either:
+//
+// * The object is a function and FunctionForwarderOptions::allowCallbacks is set
+// * The object is a reflector, in which case it is wrapped.
 bool
 NewFunctionForwarder(JSContext *cx, JS::HandleId id, JS::HandleObject callable,
-                     bool doclone, JS::MutableHandleValue vp);
-
+                     FunctionForwarderOptions &options, JS::MutableHandleValue vp);
+
+// Old-style function forwarding without structured-cloning for arguments. This
+// is deprecated.
 bool
-NewFunctionForwarder(JSContext *cx, JS::HandleObject callable,
-                     bool doclone, JS::MutableHandleValue vp);
+NewNonCloningFunctionForwarder(JSContext *cx, JS::HandleId id,
+                               JS::HandleObject callable, JS::MutableHandleValue vp);
 
 // Old fashioned xpc error reporter. Try to use JS_ReportError instead.
 nsresult
 ThrowAndFail(nsresult errNum, JSContext *cx, bool *retval);
 
 struct GlobalProperties {
     GlobalProperties(bool aPromise) {
       mozilla::PodZero(this);
@@ -3390,47 +3397,93 @@ public:
         , defineAs(cx, JSID_VOID)
     { }
 
     virtual bool Parse() { return ParseId("defineAs", &defineAs); };
 
     JS::RootedId defineAs;
 };
 
-class MOZ_STACK_CLASS ExportOptions : public OptionsBase {
+class MOZ_STACK_CLASS ExportFunctionOptions : public OptionsBase {
 public:
-    ExportOptions(JSContext *cx = xpc_GetSafeJSContext(),
+    ExportFunctionOptions(JSContext *cx = xpc_GetSafeJSContext(),
                   JSObject* options = nullptr)
         : OptionsBase(cx, options)
         , defineAs(cx, JSID_VOID)
+        , allowCallbacks(false)
     { }
 
-    virtual bool Parse() { return ParseId("defineAs", &defineAs); };
+    virtual bool Parse() {
+        return ParseId("defineAs", &defineAs) &&
+               ParseBoolean("allowCallbacks", &allowCallbacks);
+    };
 
     JS::RootedId defineAs;
+    bool allowCallbacks;
 };
 
 class MOZ_STACK_CLASS StackScopedCloneOptions : public OptionsBase {
 public:
     StackScopedCloneOptions(JSContext *cx = xpc_GetSafeJSContext(),
                             JSObject* options = nullptr)
         : OptionsBase(cx, options)
         , wrapReflectors(false)
         , cloneFunctions(false)
     { }
 
     virtual bool Parse() {
         return ParseBoolean("wrapReflectors", &wrapReflectors) &&
                ParseBoolean("cloneFunctions", &cloneFunctions);
     };
 
+    // When a reflector is encountered, wrap it rather than aborting the clone.
     bool wrapReflectors;
+
+    // When a function is encountered, clone it (exportFunction-style) rather than
+    // aborting the clone.
     bool cloneFunctions;
 };
 
+class MOZ_STACK_CLASS FunctionForwarderOptions : public OptionsBase {
+public:
+    FunctionForwarderOptions(JSContext *cx = xpc_GetSafeJSContext(),
+                             JSObject* options = nullptr)
+        : OptionsBase(cx, options)
+        , allowCallbacks(false)
+    { }
+
+    JSObject *ToJSObject(JSContext *cx) {
+        JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
+        JS::RootedObject obj(cx, JS_NewObjectWithGivenProto(cx, nullptr, JS::NullPtr(), global));
+        if (!obj)
+            return nullptr;
+
+        JS::RootedValue val(cx);
+        unsigned attrs = JSPROP_READONLY | JSPROP_PERMANENT;
+        val = JS::BooleanValue(allowCallbacks);
+        if (!JS_DefineProperty(cx, obj, "allowCallbacks", val, attrs))
+            return nullptr;
+
+        return obj;
+    }
+
+    virtual bool Parse() {
+        return ParseBoolean("allowCallbacks", &allowCallbacks);
+    };
+
+    // Allow callback arguments. This is similar to setting cloneFunctions in
+    // StackScopedCloneOptions, except that cloneFunctions will clone any Function
+    // encountered in the object graph, whereas this option only allows the base
+    // object to be supported.
+    //
+    // So invoking: |forwardedFunction(callback)| will work, but
+    // |forwardedFunction({ cb: callback })| will not.
+    bool allowCallbacks;
+};
+
 JSObject *
 CreateGlobalObject(JSContext *cx, const JSClass *clasp, nsIPrincipal *principal,
                    JS::CompartmentOptions& aOptions);
 
 // InitGlobalObject enters the compartment of aGlobal, so it doesn't matter what
 // compartment aJSContext is in.
 bool
 InitGlobalObject(JSContext* aJSContext, JS::Handle<JSObject*> aGlobal,
--- a/js/xpconnect/tests/unit/test_exportFunction.js
+++ b/js/xpconnect/tests/unit/test_exportFunction.js
@@ -5,54 +5,70 @@ function run_test() {
   var subsb2 = new Cu.Sandbox("http://example.com", { wantGlobalProperties: ["XMLHttpRequest"] });
   var xorigsb = new Cu.Sandbox("http://test.com", { wantGlobalProperties: ["XMLHttpRequest"] });
 
   epsb.subsb = subsb;
   epsb.xorigsb = xorigsb;
   epsb.do_check_true = do_check_true;
   epsb.do_check_eq = do_check_eq;
   epsb.do_check_neq = do_check_neq;
+  subsb.do_check_true = do_check_true;
 
   // Exporting should work if prinicipal of the source sandbox
   // subsumes the principal of the target sandbox.
   Cu.evalInSandbox("(" + function() {
     Object.prototype.protoProp = "common";
     var wasCalled = false;
     var _this = this;
-    this.funToExport = function(a, obj, native, mixed) {
+    this.funToExport = function(a, obj, native, mixed, callback) {
       do_check_eq(a, 42);
       do_check_neq(obj, subsb.tobecloned);
       do_check_eq(obj.cloned, "cloned");
       do_check_eq(obj.protoProp, "common");
       do_check_eq(native, subsb.native);
       do_check_eq(_this, this);
       do_check_eq(mixed.xrayed, subsb.xrayed);
       do_check_eq(mixed.xrayed2, subsb.xrayed2);
+      if (typeof callback == 'function') {
+        do_check_eq(typeof subsb.callback, 'function');
+        do_check_neq(callback, subsb.callback);
+        callback();
+      }
       wasCalled = true;
     };
     this.checkIfCalled = function() {
       do_check_true(wasCalled);
       wasCalled = false;
     }
-    exportFunction(funToExport, subsb, { defineAs: "imported" });
+    exportFunction(funToExport, subsb, { defineAs: "imported", allowCallbacks: true });
   }.toSource() + ")()", epsb);
 
   subsb.xrayed = Cu.evalInSandbox("(" + function () {
       return new XMLHttpRequest();
   }.toSource() + ")()", subsb2);
 
   // Exported function should be able to be call from the
   // target sandbox. Native arguments should be just wrapped
   // every other argument should be cloned.
   Cu.evalInSandbox("(" + function () {
     native = new XMLHttpRequest();
     xrayed2 = XPCNativeWrapper(new XMLHttpRequest());
     mixed = { xrayed: xrayed, xrayed2: xrayed2 };
     tobecloned = { cloned: "cloned" };
-    imported(42,tobecloned, native, mixed);
+    invokedCallback = false;
+    callback = function() { invokedCallback = true; };
+    imported(42, tobecloned, native, mixed, callback);
+    do_check_true(invokedCallback);
+    try {
+      // Callbacks must be functions, not objects leading to functions.
+      imported(42, tobecloned, native, mixed, { cb: callback });
+      do_check_true(false);
+    } catch (e) {
+      do_check_true(/denied/.test(e) && /Function/.test(e));
+    }
   }.toSource() + ")()", subsb);
 
   // Invoking an exported function with cross-origin arguments should throw.
   subsb.xoNative = Cu.evalInSandbox('new XMLHttpRequest()', xorigsb);
   try {
     Cu.evalInSandbox('imported({val: xoNative})', subsb);
     do_check_true(false);
   } catch (e) {
@@ -109,14 +125,24 @@ function run_test() {
   Cu.evalInSandbox("(" + function() {
     subsb.imported2 = exportFunction(funToExport, subsb);
   }.toSource() + ")()", epsb);
 
   Cu.evalInSandbox("(" + function () {
     imported2(42, tobecloned, native, mixed);
   }.toSource() + ")()", subsb);
 
+  // Make sure that functions may not be passed when allowCallbacks is not set.
+  try {
+    Cu.evalInSandbox("(" + function () {
+      imported2(42, tobecloned, native, mixed, callback);
+    }.toSource() + ")()", subsb);
+    do_check_true(false);
+  } catch (e) {
+    do_check_true(/denied/.test(e) && /Function/.test(e));
+  }
+
   Cu.evalInSandbox("(" + function() {
     checkIfCalled();
   }.toSource() + ")()", epsb);
 
   do_check_true(wasCalled, true);
 }
--- a/layout/base/FrameLayerBuilder.cpp
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -2008,43 +2008,47 @@ ContainerState::PopThebesLayerData()
   }
   layer->SetContentFlags(flags);
 
   SetFixedPositionLayerData(layer, data->mFixedPosFrameForLayerData);
 
   ThebesLayerData* containingThebesLayerData =
      mLayerBuilder->GetContainingThebesLayerData();
   if (containingThebesLayerData) {
-    nsRect rect = nsLayoutUtils::TransformFrameRectToAncestor(
-      mContainerReferenceFrame,
-      data->mDispatchToContentHitRegion.GetBounds(),
-      containingThebesLayerData->mReferenceFrame);
-    containingThebesLayerData->mDispatchToContentHitRegion.Or(
-      containingThebesLayerData->mDispatchToContentHitRegion, rect);
-
-    rect = nsLayoutUtils::TransformFrameRectToAncestor(
-      mContainerReferenceFrame,
-      data->mMaybeHitRegion.GetBounds(),
-      containingThebesLayerData->mReferenceFrame);
-    containingThebesLayerData->mMaybeHitRegion.Or(
-      containingThebesLayerData->mMaybeHitRegion, rect);
-
-    // Our definitely-hit region must go to the maybe-hit-region since
-    // this function is an approximation.
-    gfx3DMatrix matrix = nsLayoutUtils::GetTransformToAncestor(
-      mContainerReferenceFrame, containingThebesLayerData->mReferenceFrame);
-    gfxMatrix matrix2D;
-    bool isPrecise = matrix.Is2D(&matrix2D) && !matrix2D.HasNonAxisAlignedTransform();
-    rect = nsLayoutUtils::TransformFrameRectToAncestor(
-      mContainerReferenceFrame,
-      data->mHitRegion.GetBounds(),
-      containingThebesLayerData->mReferenceFrame);
-    nsRegion* dest = isPrecise ? &containingThebesLayerData->mHitRegion
-                               : &containingThebesLayerData->mMaybeHitRegion;
-    dest->Or(*dest, rect);
+    if (!data->mDispatchToContentHitRegion.GetBounds().IsEmpty()) {
+      nsRect rect = nsLayoutUtils::TransformFrameRectToAncestor(
+        mContainerReferenceFrame,
+        data->mDispatchToContentHitRegion.GetBounds(),
+        containingThebesLayerData->mReferenceFrame);
+      containingThebesLayerData->mDispatchToContentHitRegion.Or(
+        containingThebesLayerData->mDispatchToContentHitRegion, rect);
+    }
+    if (!data->mMaybeHitRegion.GetBounds().IsEmpty()) {
+      nsRect rect = nsLayoutUtils::TransformFrameRectToAncestor(
+        mContainerReferenceFrame,
+        data->mMaybeHitRegion.GetBounds(),
+        containingThebesLayerData->mReferenceFrame);
+      containingThebesLayerData->mMaybeHitRegion.Or(
+        containingThebesLayerData->mMaybeHitRegion, rect);
+    }
+    if (!data->mHitRegion.GetBounds().IsEmpty()) {
+      // Our definitely-hit region must go to the maybe-hit-region since
+      // this function is an approximation.
+      gfx3DMatrix matrix = nsLayoutUtils::GetTransformToAncestor(
+        mContainerReferenceFrame, containingThebesLayerData->mReferenceFrame);
+      gfxMatrix matrix2D;
+      bool isPrecise = matrix.Is2D(&matrix2D) && !matrix2D.HasNonAxisAlignedTransform();
+      nsRect rect = nsLayoutUtils::TransformFrameRectToAncestor(
+        mContainerReferenceFrame,
+        data->mHitRegion.GetBounds(),
+        containingThebesLayerData->mReferenceFrame);
+      nsRegion* dest = isPrecise ? &containingThebesLayerData->mHitRegion
+                                 : &containingThebesLayerData->mMaybeHitRegion;
+      dest->Or(*dest, rect);
+    }
   } else {
     EventRegions regions;
     regions.mHitRegion = ScaleRegionToOutsidePixels(data->mHitRegion);
     // Points whose hit-region status we're not sure about need to be dispatched
     // to the content thread.
     nsIntRegion maybeHitRegion = ScaleRegionToOutsidePixels(data->mMaybeHitRegion);
     regions.mDispatchToContentHitRegion.Sub(maybeHitRegion, regions.mHitRegion);
     regions.mDispatchToContentHitRegion.Or(regions.mDispatchToContentHitRegion,
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -2614,17 +2614,23 @@ CalculateFrameMetricsForDisplayPort(nsIF
   // Calculate the metrics necessary for calculating the displayport.
   // This code has a lot in common with the code in RecordFrameMetrics();
   // we may want to refactor this at some point.
   FrameMetrics metrics;
   nsPresContext* presContext = aScrollFrame->PresContext();
   nsIPresShell* presShell = presContext->PresShell();
   CSSToLayoutDeviceScale deviceScale(float(nsPresContext::AppUnitsPerCSSPixel())
                                      / presContext->AppUnitsPerDevPixel());
-  ParentLayerToLayerScale resolution(presShell->GetResolution().width);
+  ParentLayerToLayerScale resolution;
+  if (aScrollFrame == presShell->GetRootScrollFrame()) {
+    // Only the root scrollable frame for a given presShell should pick up
+    // the presShell's resolution. All the other frames are 1.0.
+    resolution = ParentLayerToLayerScale(presShell->GetXResolution(),
+                                         presShell->GetYResolution());
+  }
   LayoutDeviceToLayerScale cumulativeResolution(presShell->GetCumulativeResolution().width);
 
   metrics.mDevPixelsPerCSSPixel = deviceScale;
   metrics.mResolution = resolution;
   metrics.mCumulativeResolution = cumulativeResolution;
   metrics.SetZoom(deviceScale * cumulativeResolution * LayerToScreenScale(1));
 
   // Only the size of the composition bounds is relevant to the
--- a/layout/style/StyleAnimationValue.cpp
+++ b/layout/style/StyleAnimationValue.cpp
@@ -3321,19 +3321,30 @@ StyleAnimationValue::ExtractComputedValu
           break;
         }
 
         default:
           NS_ABORT_IF_FALSE(false, "missing property implementation");
           return false;
       };
       return true;
-    case eStyleAnimType_Coord:
-      return StyleCoordToValue(*static_cast<const nsStyleCoord*>(
-        StyleDataAtOffset(styleStruct, ssOffset)), aComputedValue);
+    case eStyleAnimType_Coord: {
+      const nsStyleCoord& coord = *static_cast<const nsStyleCoord*>(
+        StyleDataAtOffset(styleStruct, ssOffset));
+      if (nsCSSProps::PropHasFlags(aProperty, CSS_PROPERTY_NUMBERS_ARE_PIXELS) &&
+          coord.GetUnit() == eStyleUnit_Coord) {
+        // For SVG properties where number means the same thing as length,
+        // we want to animate them the same way.  Normalize both to number
+        // since it has more accuracy (float vs nscoord).
+        aComputedValue.SetFloatValue(nsPresContext::
+          AppUnitsToFloatCSSPixels(coord.GetCoordValue()));
+        return true;
+      }
+      return StyleCoordToValue(coord, aComputedValue);
+    }
     case eStyleAnimType_Sides_Top:
     case eStyleAnimType_Sides_Right:
     case eStyleAnimType_Sides_Bottom:
     case eStyleAnimType_Sides_Left: {
       static_assert(
        NS_SIDE_TOP    == eStyleAnimType_Sides_Top   -eStyleAnimType_Sides_Top &&
        NS_SIDE_RIGHT  == eStyleAnimType_Sides_Right -eStyleAnimType_Sides_Top &&
        NS_SIDE_BOTTOM == eStyleAnimType_Sides_Bottom-eStyleAnimType_Sides_Top &&
--- a/layout/style/nsCSSPropList.h
+++ b/layout/style/nsCSSPropList.h
@@ -3812,28 +3812,30 @@ CSS_PROP_SVG(
     kContextPatternKTable,
     offsetof(nsStyleSVG, mStroke),
     eStyleAnimType_PaintServer)
 CSS_PROP_SVG(
     stroke-dasharray,
     stroke_dasharray,
     StrokeDasharray,
     CSS_PROPERTY_PARSE_FUNCTION |
-        CSS_PROPERTY_VALUE_LIST_USES_COMMAS,
+        CSS_PROPERTY_VALUE_LIST_USES_COMMAS |
+        CSS_PROPERTY_NUMBERS_ARE_PIXELS,
         // NOTE: Internal values have range restrictions.
     "",
     0,
     kStrokeContextValueKTable,
     CSS_PROP_NO_OFFSET, /* property stored in 2 separate members */
     eStyleAnimType_Custom)
 CSS_PROP_SVG(
     stroke-dashoffset,
     stroke_dashoffset,
     StrokeDashoffset,
-    CSS_PROPERTY_PARSE_VALUE,
+    CSS_PROPERTY_PARSE_VALUE |
+        CSS_PROPERTY_NUMBERS_ARE_PIXELS,
     "",
     VARIANT_HLPN | VARIANT_OPENTYPE_SVG_KEYWORD,
     kStrokeContextValueKTable,
     offsetof(nsStyleSVG, mStrokeDashoffset),
     eStyleAnimType_Coord)
 CSS_PROP_SVG(
     stroke-linecap,
     stroke_linecap,
@@ -3875,17 +3877,18 @@ CSS_PROP_SVG(
     kContextOpacityKTable,
     offsetof(nsStyleSVG, mStrokeOpacity),
     eStyleAnimType_float)
 CSS_PROP_SVG(
     stroke-width,
     stroke_width,
     StrokeWidth,
     CSS_PROPERTY_PARSE_VALUE |
-        CSS_PROPERTY_VALUE_NONNEGATIVE,
+        CSS_PROPERTY_VALUE_NONNEGATIVE |
+        CSS_PROPERTY_NUMBERS_ARE_PIXELS,
     "",
     VARIANT_HLPN | VARIANT_OPENTYPE_SVG_KEYWORD,
     kStrokeContextValueKTable,
     offsetof(nsStyleSVG, mStrokeWidth),
     eStyleAnimType_Coord)
 CSS_PROP_SVG(
     text-anchor,
     text_anchor,
--- a/layout/style/nsCSSProps.h
+++ b/layout/style/nsCSSProps.h
@@ -208,16 +208,19 @@ static_assert((CSS_PROPERTY_PARSE_PROPER
 // This property is always enabled in chrome and in certified apps. This is
 // meant to be used together with a pref that enables the property for
 // non-privileged content. Note that if such a property has an alias, then any
 // use of that alias in privileged content will still be ignored unless the
 // pref is enabled. In other words, this bit has no effect on the use of
 // aliases.
 #define CSS_PROPERTY_ALWAYS_ENABLED_IN_CHROME_OR_CERTIFIED_APP (1<<23)
 
+// This property's unitless values are pixels.
+#define CSS_PROPERTY_NUMBERS_ARE_PIXELS           (1<<24)
+
 /**
  * Types of animatable values.
  */
 enum nsStyleAnimType {
   // requires a custom implementation in
   // StyleAnimationValue::ExtractComputedValue
   eStyleAnimType_Custom,
 
--- a/layout/style/test/test_transitions_per_property.html
+++ b/layout/style/test/test_transitions_per_property.html
@@ -213,28 +213,28 @@ var supported_properties = {
     "stop-opacity" : [ test_float_zeroToOne_transition,
                        // opacity is clamped in computed style
                        // (not parsing/interpolation)
                        test_float_zeroToOne_clamped ],
     "stroke": [ test_color_transition ],
     "stroke-dasharray": [ test_dasharray_transition ],
     // NOTE: when calc() is supported on 'stroke-dashoffset', we should
     // add test_length_percent_calc_transition.
-    "stroke-dashoffset": [ test_length_transition, test_percent_transition,
-                           test_length_unclamped, test_percent_unclamped ],
+    "stroke-dashoffset": [ test_length_transition_svg, test_percent_transition,
+                           test_length_unclamped_svg, test_percent_unclamped ],
     "stroke-miterlimit": [ test_float_aboveOne_transition,
                            test_float_aboveOne_clamped ],
     "stroke-opacity" : [ test_float_zeroToOne_transition,
                          // opacity is clamped in computed style
                          // (not parsing/interpolation)
                          test_float_zeroToOne_clamped ],
     // NOTE: when calc() is supported on 'stroke-width', we should add
     // test_length_percent_calc_transition.
-    "stroke-width": [ test_length_transition, test_percent_transition,
-                      test_length_clamped, test_percent_clamped ],
+    "stroke-width": [ test_length_transition_svg, test_percent_transition,
+                      test_length_clamped_svg, test_percent_clamped ],
     "text-indent": [ test_length_transition, test_percent_transition,
                      test_length_percent_calc_transition,
                      test_length_unclamped, test_percent_unclamped ],
     "text-shadow": [ test_shadow_transition ],
     "top": [ test_length_transition, test_percent_transition,
              test_length_percent_calc_transition,
              test_length_unclamped, test_percent_unclamped ],
     "transform": [ test_transform_transition ],
@@ -867,45 +867,62 @@ function check_distance(prop, start, qua
   var sq = get_distance(prop, start, quarter);
   var se = get_distance(prop, start, end);
   var qe = get_distance(prop, quarter, end);
 
   ok(Math.abs((sq * 4 - se) / se) < 0.0001, "property '" + prop + "': distance " + sq + " from start '" + start + "' to quarter '" + quarter + "' should be quarter distance " + se + " from start '" + start + "' to end '" + end + "'");
   ok(Math.abs((qe * 4 - se * 3) / se) < 0.0001, "property '" + prop + "': distance " + qe + " from quarter '" + quarter + "' to end '" + end + "' should be three quarters distance " + se + " from start '" + start + "' to end '" + end + "'");
 }
 
-function test_length_transition(prop) {
+function test_length_transition_svg_or_units(prop, numbers_are_pixels) {
   div.style.setProperty("transition-property", "none", "");
   div.style.setProperty(prop, "4px", "");
   is(cs.getPropertyValue(prop), "4px",
      "length-valued property " + prop + ": computed value before transition");
   div.style.setProperty("transition-property", prop, "");
   div.style.setProperty(prop, "12px", "");
-  is(cs.getPropertyValue(prop), "6px",
+  is(cs.getPropertyValue(prop), numbers_are_pixels ? "6" : "6px",
      "length-valued property " + prop + ": interpolation of lengths");
   check_distance(prop, "4px", "6px", "12px");
 }
 
+function test_length_transition(prop) {
+  test_length_transition_svg_or_units(prop, false);
+}
+
+function test_length_transition_svg(prop) {
+  test_length_transition_svg_or_units(prop, true);
+}
+
 function test_length_clamped(prop) {
-  test_length_clamped_or_unclamped(prop, true);
+  test_length_clamped_or_unclamped(prop, true, false);
 }
 
 function test_length_unclamped(prop) {
-  test_length_clamped_or_unclamped(prop, false);
+  test_length_clamped_or_unclamped(prop, false, false);
 }
 
-function test_length_clamped_or_unclamped(prop, is_clamped) {
+function test_length_clamped_svg(prop) {
+  test_length_clamped_or_unclamped(prop, true, true);
+}
+
+function test_length_unclamped_svg(prop) {
+  test_length_clamped_or_unclamped(prop, false, true);
+}
+
+function test_length_clamped_or_unclamped(prop, is_clamped, numbers_are_pixels) {
   div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
   div.style.setProperty("transition-property", "none", "");
   div.style.setProperty(prop, "0px", "");
   is(cs.getPropertyValue(prop), "0px",
      "length-valued property " + prop + ": flush before clamping test");
   div.style.setProperty("transition-property", prop, "");
   div.style.setProperty(prop, "100px", "");
-  (is_clamped ? is : isnot)(cs.getPropertyValue(prop), "0px",
+  (is_clamped ? is : isnot)(cs.getPropertyValue(prop),
+     numbers_are_pixels ? "0" : "0px",
      "length-valued property " + prop + ": clamping of negatives");
   div.style.setProperty("transition-timing-function", "linear", "");
 }
 
 // Test using float values in the range [0, 1] (e.g. opacity)
 function test_float_zeroToOne_transition(prop) {
   div.style.setProperty("transition-property", "none", "");
   div.style.setProperty(prop, "0.3", "");
--- a/mfbt/BinarySearch.h
+++ b/mfbt/BinarySearch.h
@@ -36,17 +36,20 @@ BinarySearch(const Container& aContainer
              T aTarget, size_t* aMatchOrInsertionPoint)
 {
   MOZ_ASSERT(aBegin <= aEnd);
 
   size_t low = aBegin;
   size_t high = aEnd;
   while (low != high) {
     size_t middle = low + (high - low) / 2;
-    const T& middleValue = aContainer[middle];
+
+    // Allow any intermediate type so long as it provides a suitable ordering
+    // relation.
+    const auto& middleValue = aContainer[middle];
 
     MOZ_ASSERT(aContainer[low] <= aContainer[middle]);
     MOZ_ASSERT(aContainer[middle] <= aContainer[high - 1]);
     MOZ_ASSERT(aContainer[low] <= aContainer[high - 1]);
 
     if (aTarget == middleValue) {
       *aMatchOrInsertionPoint = middle;
       return true;
--- a/mobile/android/base/PrivateTab.java
+++ b/mobile/android/base/PrivateTab.java
@@ -5,16 +5,21 @@
 
 package org.mozilla.gecko;
 
 import android.content.Context;
 
 public class PrivateTab extends Tab {
     public PrivateTab(Context context, int id, String url, boolean external, int parentId, String title) {
         super(context, id, url, external, parentId, title);
+
+        // Init background to background_private to ensure flicker-free
+        // private tab creation. Page loads will reset it to white as expected.
+        final int bgColor = context.getResources().getColor(R.color.background_private);
+        setBackgroundColor(bgColor);
     }
 
     @Override
     protected void saveThumbnailToDB() {}
 
     @Override
     public boolean isPrivate() {
         return true;
--- a/mobile/android/base/gfx/LayerRenderer.java
+++ b/mobile/android/base/gfx/LayerRenderer.java
@@ -131,20 +131,18 @@ public class LayerRenderer implements Ta
         "varying vec2 vTexCoord;\n" +
         "uniform sampler2D sTexture;\n" +
         "void main() {\n" +
         "    gl_FragColor = texture2D(sTexture, vTexCoord);\n" +
         "}\n";
 
     public LayerRenderer(LayerView view) {
         mView = view;
-        try {
-            mOverscrollColor = view.getContext().getResources().getColor(R.color.background_normal);
-        } catch (Resources.NotFoundException nfe) { mOverscrollColor = Color.BLACK; }
-        
+        setOverscrollColor(R.color.background_normal);
+
         Bitmap scrollbarImage = view.getScrollbarImage();
         IntSize size = new IntSize(scrollbarImage.getWidth(), scrollbarImage.getHeight());
         scrollbarImage = expandCanvasToPowerOfTwo(scrollbarImage, size);
 
         mTasks = new CopyOnWriteArrayList<RenderTask>();
         mLastFrameTime = System.nanoTime();
 
         mVertScrollLayer = new ScrollbarLayer(this, scrollbarImage, size, true);
@@ -194,16 +192,22 @@ public class LayerRenderer implements Ta
     }
 
     void onSurfaceCreated(EGLConfig config) {
         checkMonitoringEnabled();
         createDefaultProgram();
         activateDefaultProgram();
     }
 
+    void setOverscrollColor(int colorId) {
+        try {
+            mOverscrollColor = mView.getContext().getResources().getColor(colorId);
+        } catch (Resources.NotFoundException nfe) { mOverscrollColor = Color.BLACK; }
+    }
+
     public void createDefaultProgram() {
         int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, DEFAULT_VERTEX_SHADER);
         int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, DEFAULT_FRAGMENT_SHADER);
 
         mProgram = GLES20.glCreateProgram();
         GLES20.glAttachShader(mProgram, vertexShader);   // add the vertex shader to program
         GLES20.glAttachShader(mProgram, fragmentShader); // add the fragment shader to program
         GLES20.glLinkProgram(mProgram);                  // creates OpenGL program executables
@@ -712,16 +716,20 @@ public class LayerRenderer implements Ta
     @Override
     public void onTabChanged(final Tab tab, Tabs.TabEvents msg, Object data) {
         // Sets the background of the newly selected tab. This background color
         // gets cleared in endDrawing(). This function runs on the UI thread,
         // but other code that touches the paint state is run on the compositor
         // thread, so this may need to be changed if any problems appear.
         if (msg == Tabs.TabEvents.SELECTED) {
             if (mView != null) {
+                final int overscrollColor =
+                        (tab.isPrivate() ? R.color.background_private : R.color.background_normal);
+                setOverscrollColor(overscrollColor);
+
                 if (mView.getChildAt(0) != null) {
                     mView.getChildAt(0).setBackgroundColor(tab.getBackgroundColor());
                 }
                 mView.setPaintState(LayerView.PAINT_START);
             }
         }
     }
 }
--- a/mobile/android/base/resources/layout/remote_tabs_setup_panel.xml
+++ b/mobile/android/base/resources/layout/remote_tabs_setup_panel.xml
@@ -1,18 +1,14 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
-<org.mozilla.gecko.tabspanel.RemoteTabsSetupPanel
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:visibility="gone">
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
 
     <LinearLayout android:id="@+id/remote_tabs_setup_containing_layout"
                   style="@style/TabsPanelFrame"
                   android:layout_width="match_parent"
                   android:layout_height="match_parent">
 
         <LinearLayout style="@style/TabsPanelSection"
                       android:layout_width="match_parent"
@@ -51,9 +47,9 @@
                       android:layout_height="wrap_content"
                       style="@style/TabsPanelItem.TextAppearance.Linkified"
                       android:text="@string/fxaccount_getting_started_old_firefox"/>
 
         </LinearLayout>
 
     </LinearLayout>
 
-</org.mozilla.gecko.tabspanel.RemoteTabsSetupPanel>
+</merge>
--- a/mobile/android/base/resources/layout/remote_tabs_verification_panel.xml
+++ b/mobile/android/base/resources/layout/remote_tabs_verification_panel.xml
@@ -1,18 +1,14 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
-<org.mozilla.gecko.tabspanel.RemoteTabsVerificationPanel
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:visibility="gone">
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
 
     <LinearLayout android:id="@+id/remote_tabs_verification_containing_layout"
                   style="@style/TabsPanelFrame"
                   android:layout_width="match_parent"
                   android:layout_height="match_parent">
 
         <LinearLayout style="@style/TabsPanelSection"
                       android:layout_width="match_parent"
@@ -46,9 +42,9 @@
                       style="@style/TabsPanelItem.TextAppearance.Linkified.Resend"
                       android:layout_width="match_parent"
                       android:text="@string/fxaccount_confirm_account_resend_email"/>
 
         </LinearLayout>
 
     </LinearLayout>
 
-</org.mozilla.gecko.tabspanel.RemoteTabsVerificationPanel>
+</merge>
--- a/mobile/android/base/tabspanel/RemoteTabsPanel.java
+++ b/mobile/android/base/tabspanel/RemoteTabsPanel.java
@@ -94,30 +94,30 @@ class RemoteTabsPanel extends FrameLayou
         if (accountState.getNeededAction() == State.Action.NeedsVerification) {
             return RemotePanelType.VERIFICATION;
         }
 
         return RemotePanelType.CONTAINER;
     }
 
     private PanelView inflatePanel(final RemotePanelType panelType) {
-        final LayoutInflater inflater = LayoutInflater.from(getContext());
-        final View inflatedView;
+        final PanelView view;
         switch (panelType) {
             case SETUP:
-                inflatedView = inflater.inflate(R.layout.remote_tabs_setup_panel, null);
+                view = new RemoteTabsSetupPanel(getContext());
                 break;
 
             case VERIFICATION:
-                inflatedView = inflater.inflate(R.layout.remote_tabs_verification_panel, null);
+                view = new RemoteTabsVerificationPanel(getContext());
                 break;
 
             case CONTAINER:
-                inflatedView = inflater.inflate(R.layout.remote_tabs_container_panel, null);
+                final LayoutInflater inflater = LayoutInflater.from(getContext());
+                view = (PanelView) inflater.inflate(R.layout.remote_tabs_container_panel, null);
                 break;
 
             default:
                 throw new IllegalArgumentException("Unknown panelType, " + panelType);
         }
 
-        return (PanelView) inflatedView;
+        return view;
     }
 }
--- a/mobile/android/base/tabspanel/RemoteTabsSetupPanel.java
+++ b/mobile/android/base/tabspanel/RemoteTabsSetupPanel.java
@@ -9,39 +9,35 @@ import java.util.Locale;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.fxa.FirefoxAccounts;
 import org.mozilla.gecko.fxa.activities.FxAccountCreateAccountActivity;
 import org.mozilla.gecko.tabspanel.TabsPanel.PanelView;
 
 import android.content.Context;
 import android.content.Intent;
-import android.util.AttributeSet;
+import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.LinearLayout;
 import android.widget.ScrollView;
 
 /**
  * A tabs panel which allows a user to get started setting up a Firefox
  * Accounts account. Currently used as one sub-panel in a sequence
  * contained by the {@link RemoteTabsPanel}.
  */
 class RemoteTabsSetupPanel extends ScrollView implements PanelView {
-    private LinearLayout containingLayout;
+    private final LinearLayout containingLayout;
 
     private TabsPanel tabsPanel;
 
-    public RemoteTabsSetupPanel(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
+    public RemoteTabsSetupPanel(Context context) {
+        super(context);
 
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-
+        LayoutInflater.from(context).inflate(R.layout.remote_tabs_setup_panel, this);
         containingLayout = (LinearLayout) findViewById(R.id.remote_tabs_setup_containing_layout);
 
         final View setupGetStartedButton =
                 containingLayout.findViewById(R.id.remote_tabs_setup_get_started);
         setupGetStartedButton.setOnClickListener(new OnClickListener() {
             @Override
             public void onClick(final View v) {
                 final Context context = getContext();
--- a/mobile/android/base/tabspanel/RemoteTabsVerificationPanel.java
+++ b/mobile/android/base/tabspanel/RemoteTabsVerificationPanel.java
@@ -5,43 +5,39 @@
 package org.mozilla.gecko.tabspanel;
 
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.fxa.FirefoxAccounts;
 import org.mozilla.gecko.fxa.login.State;
 import org.mozilla.gecko.tabspanel.TabsPanel.PanelView;
 
 import android.content.Context;
-import android.util.AttributeSet;
 import android.util.Log;
+import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.LinearLayout;
 import android.widget.ScrollView;
 import android.widget.TextView;
 
 /**
  * A tabs panel which allows a user to get resend the verification email
  * to confirm a Firefox Account. Currently used as one sub-panel in a sequence
  * contained by the {@link RemoteTabsPanel}.
  */
 class RemoteTabsVerificationPanel extends ScrollView implements PanelView {
     private static final String LOG_TAG = RemoteTabsVerificationPanel.class.getSimpleName();
 
-    private LinearLayout containingLayout;
+    private final LinearLayout containingLayout;
 
     private TabsPanel tabsPanel;
 
-    public RemoteTabsVerificationPanel(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
+    public RemoteTabsVerificationPanel(Context context) {
+        super(context);
 
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-
+        LayoutInflater.from(context).inflate(R.layout.remote_tabs_verification_panel, this);
         containingLayout = (LinearLayout) findViewById(R.id.remote_tabs_verification_containing_layout);
 
         final View resendLink = containingLayout.findViewById(R.id.remote_tabs_confirm_resend);
         resendLink.setOnClickListener(new OnClickListener() {
             @Override
             public void onClick(View v) {
                 final State accountState = FirefoxAccounts.getFirefoxAccountState(getContext());
                 final State.Action neededAction = accountState.getNeededAction();
--- a/mobile/android/base/tabspanel/TabsPanel.java
+++ b/mobile/android/base/tabspanel/TabsPanel.java
@@ -167,19 +167,25 @@ public class TabsPanel extends LinearLay
 
         mTabWidget.setTabSelectionListener(this);
 
         mMenuButton = (ImageButton) findViewById(R.id.menu);
         mMenuButton.setOnClickListener(new Button.OnClickListener() {
             @Override
             public void onClick(View view) {
                 final Menu menu = mPopupMenu.getMenu();
+
+                // Each panel has a "+" shortcut button, so don't show it for that panel.
+                menu.findItem(R.id.new_tab).setVisible(mCurrentPanel != Panel.NORMAL_TABS);
+                menu.findItem(R.id.new_private_tab).setVisible(mCurrentPanel != Panel.PRIVATE_TABS);
+
+                // Only show "Clear * tabs" for current panel.
                 menu.findItem(R.id.close_all_tabs).setVisible(mCurrentPanel == Panel.NORMAL_TABS);
                 menu.findItem(R.id.close_private_tabs).setVisible(mCurrentPanel == Panel.PRIVATE_TABS);
-
+ 
                 mPopupMenu.show();
             }
         });
         mPopupMenu.setAnchor(mMenuButton);
     }
 
     private void addTab() {
         if (mCurrentPanel == Panel.NORMAL_TABS) {
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -3697,17 +3697,22 @@ pref("image.multithreaded_decoding.limit
 // Limit for the canvas image cache. 0 means we don't limit the size of the
 // cache.
 pref("canvas.image.cache.limit", 0);
 
 // How many images to eagerly decode on a given page. 0 means "no limit".
 pref("image.onload.decode.limit", 0);
 
 // WebGL prefs
+#ifdef ANDROID
+// Disable MSAA on mobile.
+pref("gl.msaa-level", 0);
+#else
 pref("gl.msaa-level", 2);
+#endif
 pref("webgl.force-enabled", false);
 pref("webgl.disabled", false);
 pref("webgl.shader_validator", true);
 pref("webgl.prefer-native-gl", false);
 pref("webgl.min_capability_mode", false);
 pref("webgl.disable-extensions", false);
 pref("webgl.msaa-force", false);
 pref("webgl.prefer-16bpp", false);
--- a/netwerk/base/src/nsTemporaryFileInputStream.cpp
+++ b/netwerk/base/src/nsTemporaryFileInputStream.cpp
@@ -1,9 +1,9 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsTemporaryFileInputStream.h"
 #include "nsStreamUtils.h"
 #include <algorithm>
 
@@ -55,36 +55,49 @@ nsTemporaryFileInputStream::ReadSegments
 
   if (mClosed) {
     return NS_BASE_STREAM_CLOSED;
   }
 
   mozilla::MutexAutoLock lock(mFileDescOwner->FileMutex());
   PR_Seek64(mFileDescOwner->mFD, mStartPos, PR_SEEK_SET);
 
+  // Limit requested count to the amount remaining in our section of the file.
   count = std::min(count, uint32_t(mEndPos - mStartPos));
-  uint32_t remainBufCount = count;
 
   char buf[4096];
-  while (remainBufCount > 0) {
-    uint32_t bufCount = std::min(remainBufCount, (uint32_t)sizeof(buf));
-    int32_t read_result = PR_Read(mFileDescOwner->mFD, buf, bufCount);
-    if (read_result < 0) {
+  while (*result < count) {
+    uint32_t bufCount = std::min(count - *result, (uint32_t) sizeof(buf));
+    int32_t bytesRead = PR_Read(mFileDescOwner->mFD, buf, bufCount);
+    if (bytesRead < 0) {
       return NS_ErrorAccordingToNSPR();
     }
-    uint32_t write_result = 0;
-    nsresult rv = writer(this, closure, buf,
-                         count - remainBufCount, bufCount, &write_result);
-    remainBufCount -= bufCount;
-    NS_ENSURE_SUCCESS(rv, rv);
-    NS_ASSERTION(write_result <= bufCount,
-                 "writer should not write more than we asked it to write");
-    mStartPos += bufCount;
+
+    int32_t bytesWritten = 0;
+    while (bytesWritten < bytesRead) {
+      uint32_t writerCount = 0;
+      nsresult rv = writer(this, closure, buf + bytesWritten, *result,
+                           bytesRead - bytesWritten, &writerCount);
+      if (NS_FAILED(rv) || writerCount == 0) {
+        // nsIInputStream::ReadSegments' contract specifies that errors
+        // from writer are not propagated to ReadSegments' caller.
+        //
+        // If writer fails, leaving bytes still in buf, that's okay: we
+        // only update mStartPos to reflect successful writes, so the call
+        // to PR_Seek64 at the top will restart us at the right spot.
+        return NS_OK;
+      }
+      NS_ASSERTION(writerCount <= (uint32_t) (bytesRead - bytesWritten),
+                   "writer should not write more than we asked it to write");
+      bytesWritten += writerCount;
+      *result += writerCount;
+      mStartPos += writerCount;
+    }
   }
-  *result = count;
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsTemporaryFileInputStream::IsNonBlocking(bool * nonBlocking)
 {
   *nonBlocking = false;
   return NS_OK;
--- a/security/manager/boot/src/StaticHPKPins.h
+++ b/security/manager/boot/src/StaticHPKPins.h
@@ -118,20 +118,28 @@ static const char kEquifax_Secure_eBusin
 /* GOOGLE_PIN_AlphaSSL_G2 */
 static const char kGOOGLE_PIN_AlphaSSL_G2Fingerprint[] =
   "yxgiWGK++SFB9ySwt3M3qpn5HO0ZLFY5D+h+G/vcT/c=";
 
 /* GOOGLE_PIN_CryptoCat1 */
 static const char kGOOGLE_PIN_CryptoCat1Fingerprint[] =
   "vKaqtTLWmVuXPVJE+0OqN5sRc4VCcSQHI/W3XTDVR24=";
 
+/* GOOGLE_PIN_EntrustRootEC1 */
+static const char kGOOGLE_PIN_EntrustRootEC1Fingerprint[] =
+  "/qK31kX7pz11PB7Jp4cMQOH3sMVh6Se5hb9xGGbjbyI=";
+
 /* GOOGLE_PIN_Entrust_G2 */
 static const char kGOOGLE_PIN_Entrust_G2Fingerprint[] =
   "du6FkDdMcVQ3u8prumAo6t3i3G27uMP2EOhR8R0at/U=";
 
+/* GOOGLE_PIN_GoDaddySecure */
+static const char kGOOGLE_PIN_GoDaddySecureFingerprint[] =
+  "MrZLZnJ6IGPkBm87lYywqu5Xal7O/ZUzmbuIdHMdlYc=";
+
 /* GOOGLE_PIN_Libertylavabitcom */
 static const char kGOOGLE_PIN_LibertylavabitcomFingerprint[] =
   "WnKzsDXgqPtS1KvtImrhQPqcxfpmfssuI2cSJt4LMks=";
 
 /* GOOGLE_PIN_RapidSSL */
 static const char kGOOGLE_PIN_RapidSSLFingerprint[] =
   "lT09gPUeQfbYrlxRtpsHrjDblj9Rpz+u7ajfCrg4qDM=";
 
@@ -653,16 +661,43 @@ static const char* kPinset_lavabit_sha25
 };
 static const StaticFingerprints kPinset_lavabit_sha256 = { 1, kPinset_lavabit_sha256_Data };
 
 static const StaticPinset kPinset_lavabit = {
   nullptr,
   &kPinset_lavabit_sha256
 };
 
+static const char* kPinset_dropbox_sha256_Data[] = {
+  kGOOGLE_PIN_EntrustRootEC1Fingerprint,
+  kThawte_Premium_Server_CAFingerprint,
+  kthawte_Primary_Root_CA___G3Fingerprint,
+  kthawte_Primary_Root_CAFingerprint,
+  kEntrust_net_Premium_2048_Secure_Server_CAFingerprint,
+  kDigiCert_Assured_ID_Root_CAFingerprint,
+  kGo_Daddy_Root_Certificate_Authority___G2Fingerprint,
+  kGOOGLE_PIN_GoDaddySecureFingerprint,
+  kGeoTrust_Primary_Certification_AuthorityFingerprint,
+  kGo_Daddy_Class_2_CAFingerprint,
+  kDigiCert_High_Assurance_EV_Root_CAFingerprint,
+  kthawte_Primary_Root_CA___G2Fingerprint,
+  kEntrust_Root_Certification_AuthorityFingerprint,
+  kGOOGLE_PIN_Entrust_G2Fingerprint,
+  kGeoTrust_Global_CAFingerprint,
+  kGeoTrust_Primary_Certification_Authority___G3Fingerprint,
+  kDigiCert_Global_Root_CAFingerprint,
+  kGeoTrust_Primary_Certification_Authority___G2Fingerprint,
+};
+static const StaticFingerprints kPinset_dropbox_sha256 = { 18, kPinset_dropbox_sha256_Data };
+
+static const StaticPinset kPinset_dropbox = {
+  nullptr,
+  &kPinset_dropbox_sha256
+};
+
 /* Domainlist */
 struct TransportSecurityPreload {
   const char* mHost;
   const bool mIncludeSubdomains;
   const bool mTestMode;
   const bool mIsMoz;
   const int32_t mId;
   const StaticPinset *pinset;
@@ -671,16 +706,17 @@ struct TransportSecurityPreload {
 /* Sort hostnames for binary search. */
 static const TransportSecurityPreload kPublicKeyPinningPreloadList[] = {
   { "accounts.firefox.com", true, true, false, 4, &kPinset_mozilla_fxa },
   { "accounts.google.com", true, false, false, -1, &kPinset_google_root_pems },
   { "addons.mozilla.net", true, false, true, 2, &kPinset_mozilla },
   { "addons.mozilla.org", true, false, true, 1, &kPinset_mozilla },
   { "admin.google.com", true, false, false, -1, &kPinset_google_root_pems },
   { "android.com", true, false, false, -1, &kPinset_google_root_pems },
+  { "api.accounts.firefox.com", true, true, false, 5, &kPinset_mozilla_fxa },
   { "api.twitter.com", true, false, false, -1, &kPinset_twitterCDN },
   { "apis.google.com", true, false, false, -1, &kPinset_google_root_pems },
   { "appengine.google.com", true, false, false, -1, &kPinset_google_root_pems },
   { "appspot.com", true, false, false, -1, &kPinset_google_root_pems },
   { "aus4.mozilla.org", true, true, true, 3, &kPinset_mozilla },
   { "blog.torproject.org", true, true, false, -1, &kPinset_tor },
   { "business.twitter.com", true, false, false, -1, &kPinset_twitterCom },
   { "cdn.mozilla.net", true, false, true, -1, &kPinset_mozilla },
@@ -697,16 +733,17 @@ static const TransportSecurityPreload kP
   { "codereview.chromium.org", true, false, false, -1, &kPinset_google_root_pems },
   { "crypto.cat", false, true, false, -1, &kPinset_cryptoCat },
   { "dev.twitter.com", true, false, false, -1, &kPinset_twitterCom },
   { "dist.torproject.org", true, true, false, -1, &kPinset_tor },
   { "dl.google.com", true, false, false, -1, &kPinset_google_root_pems },
   { "docs.google.com", true, false, false, -1, &kPinset_google_root_pems },
   { "doubleclick.net", true, false, false, -1, &kPinset_google_root_pems },
   { "drive.google.com", true, false, false, -1, &kPinset_google_root_pems },
+  { "dropbox.com", false, true, false, -1, &kPinset_dropbox },
   { "encrypted.google.com", true, false, false, -1, &kPinset_google_root_pems },
   { "exclude-subdomains.pinning.example.com", false, false, false, 0, &kPinset_mozilla_test },
   { "g.co", true, false, false, -1, &kPinset_google_root_pems },
   { "glass.google.com", true, false, false, -1, &kPinset_google_root_pems },
   { "gmail.com", false, false, false, -1, &kPinset_google_root_pems },
   { "goo.gl", true, false, false, -1, &kPinset_google_root_pems },
   { "google-analytics.com", true, false, false, -1, &kPinset_google_root_pems },
   { "google.ac", true, false, false, -1, &kPinset_google_root_pems },
--- a/security/manager/tools/PreloadedHPKPins.json
+++ b/security/manager/tools/PreloadedHPKPins.json
@@ -185,16 +185,18 @@
     { "name": "addons.mozilla.org", "include_subdomains": true,
       "pins": "mozilla", "test_mode": false, "id": 1 },
     { "name": "addons.mozilla.net", "include_subdomains": true,
       "pins": "mozilla", "test_mode": false, "id": 2 },
     { "name": "aus4.mozilla.org", "include_subdomains": true,
       "pins": "mozilla", "test_mode": true, "id": 3 },
     { "name": "accounts.firefox.com", "include_subdomains": true,
       "pins": "mozilla_fxa", "test_mode": true, "id": 4 },
+    { "name": "api.accounts.firefox.com", "include_subdomains": true,
+      "pins": "mozilla_fxa", "test_mode": true, "id": 5 },
     { "name": "cdn.mozilla.net", "include_subdomains": true,
       "pins": "mozilla", "test_mode": false },
     { "name": "cdn.mozilla.org", "include_subdomains": true,
       "pins": "mozilla", "test_mode": false },
     { "name": "media.mozilla.com", "include_subdomains": true,
       "pins": "mozilla", "test_mode": false },
     { "name": "include-subdomains.pinning.example.com",
       "include_subdomains": true, "pins": "mozilla_test",
--- a/services/healthreport/docs/dataformat.rst
+++ b/services/healthreport/docs/dataformat.rst
@@ -1068,16 +1068,45 @@ Example
       ]
     }
 
 org.mozilla.crashes.crashes
 ---------------------------
 
 This measurement contains a historical record of application crashes.
 
+Version 4
+^^^^^^^^^
+
+This version follows up from version 3, adding submissions which are now
+tracked by the :ref:`crashes_crashmanager`.
+
+This measurement will be reported on each day there was a crash or crash
+submission. Records may contain the following fields, whose values indicate
+the number of crashes, hangs, or submissions that occurred on the given day:
+
+* main-crash
+* main-crash-submission-succeeded
+* main-crash-submission-failed
+* main-hang
+* main-hang-submission-succeeded
+* main-hang-submission-failed
+* content-crash
+* content-crash-submission-succeeded
+* content-crash-submission-failed
+* content-hang
+* content-hang-submission-succeeded
+* content-hang-submission-failed
+* plugin-crash
+* plugin-crash-submission-succeeded
+* plugin-crash-submission-failed
+* plugin-hang
+* plugin-hang-submission-succeeded
+* plugin-hang-submission-failed
+
 Version 3
 ^^^^^^^^^
 
 This version follows up from version 2, building on improvements to
 the :ref:`crashes_crashmanager`.
 
 This measurement will be reported on each day there was a
 crash. Records may contain the following fields, whose values indicate
@@ -1147,16 +1176,24 @@ Example
       "_v": 1,
       "pending": 1,
       "submitted": 2
     },
     "org.mozilla.crashes.crashes": {
       "_v": 2,
       "mainCrash": 2
     }
+    "org.mozilla.crashes.crashes": {
+      "_v": 4,
+      "main-crash": 2,
+      "main-crash-submission-succeeded": 1,
+      "main-crash-submission-failed": 1,
+      "main-hang": 1,
+      "plugin-crash": 2
+    }
 
 org.mozilla.healthreport.submissions
 ------------------------------------
 
 This measurement contains a history of FHR's own data submission activity.
 It was added in Firefox 23 in early May 2013.
 
 Version 2
--- a/services/healthreport/providers.jsm
+++ b/services/healthreport/providers.jsm
@@ -1042,46 +1042,79 @@ DailyCrashesMeasurement3.prototype = Obj
     "main-hang": DAILY_LAST_NUMERIC_FIELD,
     "content-crash": DAILY_LAST_NUMERIC_FIELD,
     "content-hang": DAILY_LAST_NUMERIC_FIELD,
     "plugin-crash": DAILY_LAST_NUMERIC_FIELD,
     "plugin-hang": DAILY_LAST_NUMERIC_FIELD,
   },
 });
 
+function DailyCrashesMeasurement4() {
+  Metrics.Measurement.call(this);
+}
+
+DailyCrashesMeasurement4.prototype = Object.freeze({
+  __proto__: Metrics.Measurement.prototype,
+
+  name: "crashes",
+  version: 4,
+
+  fields: {
+    "main-crash": DAILY_LAST_NUMERIC_FIELD,
+    "main-crash-submission-succeeded": DAILY_LAST_NUMERIC_FIELD,
+    "main-crash-submission-failed": DAILY_LAST_NUMERIC_FIELD,
+    "main-hang": DAILY_LAST_NUMERIC_FIELD,
+    "main-hang-submission-succeeded": DAILY_LAST_NUMERIC_FIELD,
+    "main-hang-submission-failed": DAILY_LAST_NUMERIC_FIELD,
+    "content-crash": DAILY_LAST_NUMERIC_FIELD,
+    "content-crash-submission-succeeded": DAILY_LAST_NUMERIC_FIELD,
+    "content-crash-submission-failed": DAILY_LAST_NUMERIC_FIELD,
+    "content-hang": DAILY_LAST_NUMERIC_FIELD,
+    "content-hang-submission-succeeded": DAILY_LAST_NUMERIC_FIELD,
+    "content-hang-submission-failed": DAILY_LAST_NUMERIC_FIELD,
+    "plugin-crash": DAILY_LAST_NUMERIC_FIELD,
+    "plugin-crash-submission-succeeded": DAILY_LAST_NUMERIC_FIELD,
+    "plugin-crash-submission-failed": DAILY_LAST_NUMERIC_FIELD,
+    "plugin-hang": DAILY_LAST_NUMERIC_FIELD,
+    "plugin-hang-submission-succeeded": DAILY_LAST_NUMERIC_FIELD,
+    "plugin-hang-submission-failed": DAILY_LAST_NUMERIC_FIELD,
+  },
+});
+
 this.CrashesProvider = function () {
   Metrics.Provider.call(this);
 
   // So we can unit test.
   this._manager = Services.crashmanager;
 };
 
 CrashesProvider.prototype = Object.freeze({
   __proto__: Metrics.Provider.prototype,
 
   name: "org.mozilla.crashes",
 
   measurementTypes: [
     DailyCrashesMeasurement1,
     DailyCrashesMeasurement2,
     DailyCrashesMeasurement3,
+    DailyCrashesMeasurement4,
   ],
 
   pullOnly: true,
 
   collectDailyData: function () {
     return this.storage.enqueueTransaction(this._populateCrashCounts.bind(this));
   },
 
   _populateCrashCounts: function () {
     this._log.info("Grabbing crash counts from crash manager.");
     let crashCounts = yield this._manager.getCrashCountsByDay();
 
-    let m = this.getMeasurement("crashes", 3);
-    let fields = DailyCrashesMeasurement3.prototype.fields;
+    let m = this.getMeasurement("crashes", 4);
+    let fields = DailyCrashesMeasurement4.prototype.fields;
 
     for (let [day, types] of crashCounts) {
       let date = Metrics.daysToDate(day);
       for (let [type, count] of types) {
         if (!(type in fields)) {
           this._log.warn("Unknown crash type encountered: " + type);
           continue;
         }
--- a/services/healthreport/tests/xpcshell/test_provider_crashes.js
+++ b/services/healthreport/tests/xpcshell/test_provider_crashes.js
@@ -45,57 +45,75 @@ add_task(function* test_collect() {
   provider._manager = manager;
 
   let day1 = new Date(2014, 0, 1, 0, 0, 0);
   let day2 = new Date(2014, 0, 3, 0, 0, 0);
 
   yield manager.addCrash(manager.PROCESS_TYPE_MAIN,
                          manager.CRASH_TYPE_CRASH,
                          "mc1", day1);
+  yield manager.addSubmission(manager.PROCESS_TYPE_MAIN,
+                              manager.CRASH_TYPE_CRASH,
+                              true,
+                              "mc1", day1)
   yield manager.addCrash(manager.PROCESS_TYPE_MAIN,
                          manager.CRASH_TYPE_CRASH,
                          "mc2", day1);
+  yield manager.addSubmission(manager.PROCESS_TYPE_MAIN,
+                              manager.CRASH_TYPE_CRASH,
+                              false,
+                              "mc2", day1)
   yield manager.addCrash(manager.PROCESS_TYPE_CONTENT,
                          manager.CRASH_TYPE_HANG,
                          "ch", day1);
   yield manager.addCrash(manager.PROCESS_TYPE_PLUGIN,
                          manager.CRASH_TYPE_CRASH,
                          "pc", day1);
 
   yield manager.addCrash(manager.PROCESS_TYPE_MAIN,
                          manager.CRASH_TYPE_HANG,
                          "mh", day2);
   yield manager.addCrash(manager.PROCESS_TYPE_CONTENT,
                          manager.CRASH_TYPE_CRASH,
                          "cc", day2);
+  yield manager.addSubmission(manager.PROCESS_TYPE_CONTENT,
+                              manager.CRASH_TYPE_CRASH,
+                              true,
+                              "cc", day2)
   yield manager.addCrash(manager.PROCESS_TYPE_PLUGIN,
                          manager.CRASH_TYPE_HANG,
                          "ph", day2);
 
   yield provider.collectDailyData();
 
-  let m = provider.getMeasurement("crashes", 3);
+  let m = provider.getMeasurement("crashes", 4);
   let values = yield m.getValues();
   do_check_eq(values.days.size, 2);
   do_check_true(values.days.hasDay(day1));
   do_check_true(values.days.hasDay(day2));
 
   let value = values.days.getDay(day1);
   do_check_true(value.has("main-crash"));
   do_check_eq(value.get("main-crash"), 2);
+  do_check_true(value.has("main-crash-submission-succeeded"));
+  do_check_eq(value.get("main-crash-submission-succeeded"), 1);
+  do_check_true(value.has("main-crash-submission-failed"));
+  do_check_eq(value.get("main-crash-submission-failed"), 1);
   do_check_true(value.has("content-hang"));
   do_check_eq(value.get("content-hang"), 1);
   do_check_true(value.has("plugin-crash"));
   do_check_eq(value.get("plugin-crash"), 1);
 
   value = values.days.getDay(day2);
   do_check_true(value.has("main-hang"));
   do_check_eq(value.get("main-hang"), 1);
   do_check_true(value.has("content-crash"));
   do_check_eq(value.get("content-crash"), 1);
+  do_check_true(value.has("content-crash-submission-succeeded"));
+  do_check_eq(value.get("content-crash-submission-succeeded"), 1);
   do_check_true(value.has("plugin-hang"));
   do_check_eq(value.get("plugin-hang"), 1);
 
   // Check that adding a new crash increments counter on next collect.
   yield manager.addCrash(manager.PROCESS_TYPE_MAIN,
                          manager.CRASH_TYPE_HANG,
                          "mc3", day2);
 
--- a/testing/marionette/client/setup.py
+++ b/testing/marionette/client/setup.py
@@ -1,13 +1,13 @@
 import os
 from setuptools import setup, find_packages
 import sys
 
-version = '0.7.10'
+version = '0.7.11'
 
 # dependencies
 with open('requirements.txt') as f:
     deps = f.read().splitlines()
 
 setup(name='marionette_client',
       version=version,
       description="Marionette test automation client",
--- a/toolkit/components/crashes/CrashManager.jsm
+++ b/toolkit/components/crashes/CrashManager.jsm
@@ -125,22 +125,31 @@ this.CrashManager.prototype = Object.fre
   PROCESS_TYPE_MAIN: "main",
 
   // A crash in a content process.
   PROCESS_TYPE_CONTENT: "content",
 
   // A crash in a plugin process.
   PROCESS_TYPE_PLUGIN: "plugin",
 
+  // A submission of a crash.
+  PROCESS_TYPE_SUBMISSION: "submission",
+
   // A real crash.
   CRASH_TYPE_CRASH: "crash",
 
   // A hang.
   CRASH_TYPE_HANG: "hang",
 
+  // A successful submission.
+  SUBMISSION_TYPE_SUCCEEDED: "succeeded",
+
+  // A failed submission.
+  SUBMISSION_TYPE_FAILED: "failed",
+
   DUMP_REGEX: /^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.dmp$/i,
   SUBMITTED_REGEX: /^bp-(?:hr-)?([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.txt$/i,
   ALL_REGEX: /^(.*)$/,
 
   // How long the store object should persist in memory before being
   // automatically garbage collected.
   STORE_EXPIRATION_MS: 60 * 1000,
 
@@ -356,16 +365,48 @@ this.CrashManager.prototype = Object.fre
       let store = yield this._getStore();
       if (store.addCrash(processType, crashType, id, date)) {
         yield store.save();
       }
     }.bind(this));
   },
 
   /**
+   * Record the occurrence of a crash submission.
+   *
+   * @param processType (string) One of the PROCESS_TYPE constants.
+   * @param crashType (string) One of the CRASH_TYPE constants.
+   * @param succeeded (boolean) Whether the submission succeeded.
+   * @param id (string) Crash ID. Likely a UUID.
+   * @param date (Date) When the crash occurred.
+   *
+   * @return boolean True if the crash submission was recorded and false if not.
+   */
+  addSubmission: function (processType, crashType, succeeded, id, date) {
+    return Task.spawn(function* () {
+      let store = yield this._getStore();
+      if (this._addSubmissionAsCrash(store, processType, crashType, succeeded,
+                                     id, date)) {
+        yield store.save();
+      }
+    }.bind(this));
+  },
+
+  _addSubmissionAsCrash: function (store, processType, crashType, succeeded,
+                                   id, date) {
+    let id = id + "-" + this.PROCESS_TYPE_SUBMISSION;
+    let process = processType + "-" + crashType + "-" +
+                  this.PROCESS_TYPE_SUBMISSION;
+    let submission_type = (
+      succeeded ? this.SUBMISSION_TYPE_SUCCEEDED : this.SUBMISSION_TYPE_FAILED);
+
+    return store.addCrash(process, submission_type, id, date);
+  },
+
+  /**
    * Obtain the paths of all unprocessed events files.
    *
    * The promise-resolved array is sorted by file mtime, oldest to newest.
    */
   _getUnprocessedEventsFiles: function () {
     return Task.spawn(function* () {
       let entries = [];
 
@@ -420,37 +461,45 @@ this.CrashManager.prototype = Object.fre
       return this._handleEventFilePayload(store, entry, type, date, payload);
     }.bind(this));
   },
 
   _handleEventFilePayload: function (store, entry, type, date, payload) {
       // The payload types and formats are documented in docs/crash-events.rst.
       // Do not change the format of an existing type. Instead, invent a new
       // type.
-
-      // type in event file => [processType, crashType]
-      let eventMap = {
-        "crash.main.1": ["main", "crash"],
-      };
+      // DO NOT ADD NEW TYPES WITHOUT DOCUMENTING!
+      let lines = payload.split("\n");
 
-      if (type in eventMap) {
-        let lines = payload.split("\n");
-        if (lines.length > 1) {
-          this._log.warn("Multiple lines unexpected in payload for " +
-                         entry.path);
-          return this.EVENT_FILE_ERROR_MALFORMED;
-        }
+      switch (type) {
+        case "crash.main.1":
+          if (lines.length > 1) {
+            this._log.warn("Multiple lines unexpected in payload for " +
+                           entry.path);
+            return this.EVENT_FILE_ERROR_MALFORMED;
+          }
+          store.addCrash(this.PROCESS_TYPE_MAIN, this.CRASH_TYPE_CRASH,
+                         payload, date);
+          break;
 
-        store.addCrash(...eventMap[type], payload, date);
-        return this.EVENT_FILE_SUCCESS;
+        case "crash.submission.1":
+          if (lines.length == 3) {
+            this._addSubmissionAsCrash(store, this.PROCESS_TYPE_MAIN,
+                                       this.CRASH_TYPE_CRASH,
+                                       lines[1] === "true", lines[0], date);
+          } else {
+            return this.EVENT_FILE_ERROR_MALFORMED;
+          }
+          break;
+
+        default:
+          return this.EVENT_FILE_ERROR_UNKNOWN_EVENT;
       }
 
-      // DO NOT ADD NEW TYPES WITHOUT DOCUMENTING!
-
-      return this.EVENT_FILE_ERROR_UNKNOWN_EVENT;
+      return this.EVENT_FILE_SUCCESS;
   },
 
   /**
    * The resolved promise is an array of objects with the properties:
    *
    *   path -- String filename
    *   id -- regexp.match()[1] (likely the crash ID)
    *   date -- Date mtime of the file
--- a/toolkit/components/crashes/docs/crash-events.rst
+++ b/toolkit/components/crashes/docs/crash-events.rst
@@ -69,16 +69,28 @@ crash.main.1
 ^^^^^^^^^^^^
 
 This event is produced when the main process crashes.
 
 The payload of this event is the string crash ID, very likely a UUID.
 There should be ``UUID.dmp`` and ``UUID.extra`` files on disk, saved by
 Breakpad.
 
+crash.submission.1
+^^^^^^^^^^^^
+
+This event is produced when a crash is submitted.
+
+The payload of this event is delimited by UNIX newlines (*\n*) and contains the
+following fields:
+
+* The crash ID string
+* "true" if the submission succeeded or "false" otherwise
+* The remote crash ID string if the submission succeeded
+
 Aggregated Event Log
 ====================
 
 Crash events are aggregated together into a unified event *log*. Currently,
 this *log* is really a JSON file. However, this is an implementation detail
 and it could change at any time. The interface to crash data provided by
 the JavaScript API is the only supported interface.
 
--- a/toolkit/components/crashes/tests/xpcshell/test_crash_manager.js
+++ b/toolkit/components/crashes/tests/xpcshell/test_crash_manager.js
@@ -299,8 +299,47 @@ add_task(function* test_addCrash() {
   Assert.ok(crash.isOfType(m.PROCESS_TYPE_PLUGIN, m.CRASH_TYPE_CRASH));
 
   crash = map.get("plugin-hang");
   Assert.ok(!!crash);
   Assert.equal(crash.crashDate, DUMMY_DATE);
   Assert.equal(crash.type, m.PROCESS_TYPE_PLUGIN + "-" + m.CRASH_TYPE_HANG);
   Assert.ok(crash.isOfType(m.PROCESS_TYPE_PLUGIN, m.CRASH_TYPE_HANG));
 });
+
+add_task(function* test_addSubmission() {
+  let m = yield getManager();
+
+  let crashes = yield m.getCrashes();
+  Assert.equal(crashes.length, 0);
+
+  yield m.addSubmission(m.PROCESS_TYPE_MAIN, m.CRASH_TYPE_CRASH, true,
+                        "success", DUMMY_DATE);
+  yield m.addSubmission(m.PROCESS_TYPE_MAIN, m.CRASH_TYPE_CRASH, false,
+                        "failure", DUMMY_DATE);
+
+  crashes = yield m.getCrashes();
+  Assert.equal(crashes.length, 2);
+
+  let map = new Map(crashes.map(crash => [crash.id, crash]));
+
+  let crash = map.get("success-submission");
+  Assert.ok(!!crash);
+  Assert.equal(crash.crashDate, DUMMY_DATE);
+  Assert.equal(crash.type,
+               m.PROCESS_TYPE_MAIN + "-" + m.CRASH_TYPE_CRASH + "-" +
+               m.PROCESS_TYPE_SUBMISSION + "-" + m.SUBMISSION_TYPE_SUCCEEDED);
+  Assert.ok(
+    crash.isOfType(m.PROCESS_TYPE_MAIN + "-" + m.CRASH_TYPE_CRASH + "-" +
+                   m.PROCESS_TYPE_SUBMISSION, m.SUBMISSION_TYPE_SUCCEEDED));
+
+  let crash = map.get("failure-submission");
+  Assert.ok(!!crash);
+  Assert.equal(crash.crashDate, DUMMY_DATE);
+  Assert.equal(crash.type,
+               m.PROCESS_TYPE_MAIN + "-" + m.CRASH_TYPE_CRASH + "-" +
+               m.PROCESS_TYPE_SUBMISSION + "-" + m.SUBMISSION_TYPE_FAILED);
+  Assert.ok(
+    crash.isOfType(m.PROCESS_TYPE_MAIN + "-" + m.CRASH_TYPE_CRASH + "-" +
+                   m.PROCESS_TYPE_SUBMISSION, m.SUBMISSION_TYPE_FAILED));
+
+});
+
--- a/toolkit/components/crashes/tests/xpcshell/test_crash_store.js
+++ b/toolkit/components/crashes/tests/xpcshell/test_crash_store.js
@@ -12,18 +12,21 @@ const {classes: Cc, interfaces: Ci, util
 let bsp = Cu.import("resource://gre/modules/CrashManager.jsm", this);
 Cu.import("resource://gre/modules/osfile.jsm", this);
 Cu.import("resource://gre/modules/Task.jsm", this);
 
 const {
   PROCESS_TYPE_MAIN,
   PROCESS_TYPE_CONTENT,
   PROCESS_TYPE_PLUGIN,
+  PROCESS_TYPE_SUBMISSION,
   CRASH_TYPE_CRASH,
   CRASH_TYPE_HANG,
+  SUBMISSION_TYPE_SUCCEEDED,
+  SUBMISSION_TYPE_FAILED,
 } = CrashManager.prototype;
 
 const CrashStore = bsp.CrashStore;
 
 let STORE_DIR_COUNT = 0;
 
 function getStore() {
   return Task.spawn(function* () {
@@ -268,16 +271,54 @@ add_task(function* test_add_plugin_hang(
     s.addCrash(PROCESS_TYPE_PLUGIN, CRASH_TYPE_HANG, "id1", new Date())
   );
   Assert.equal(s.crashesCount, 2);
 
   let crashes = s.getCrashesOfType(PROCESS_TYPE_PLUGIN, CRASH_TYPE_HANG);
   Assert.equal(crashes.length, 2);
 });
 
+add_task(function* test_add_submission() {
+  let s = yield getStore();
+
+  Assert.ok(
+    s.addCrash(PROCESS_TYPE_MAIN + "-" + CRASH_TYPE_CRASH + "-" +
+               PROCESS_TYPE_SUBMISSION, SUBMISSION_TYPE_SUCCEEDED,
+               "id1", new Date())
+  );
+  Assert.equal(s.crashesCount, 1);
+
+  let c = s.crashes[0];
+  Assert.ok(c.crashDate);
+  Assert.equal(c.type, PROCESS_TYPE_MAIN + "-" + CRASH_TYPE_CRASH + "-" +
+               PROCESS_TYPE_SUBMISSION + "-" + SUBMISSION_TYPE_SUCCEEDED);
+  Assert.ok(c.isOfType(PROCESS_TYPE_MAIN + "-" + CRASH_TYPE_CRASH + "-" +
+                       PROCESS_TYPE_SUBMISSION, SUBMISSION_TYPE_SUCCEEDED));
+
+  Assert.ok(
+    s.addCrash(PROCESS_TYPE_MAIN + "-" + CRASH_TYPE_CRASH + "-" +
+               PROCESS_TYPE_SUBMISSION, SUBMISSION_TYPE_FAILED,
+               "id2", new Date())
+  );
+  Assert.equal(s.crashesCount, 2);
+
+  // Duplicate.
+  Assert.ok(
+    s.addCrash(PROCESS_TYPE_MAIN + "-" + CRASH_TYPE_CRASH + "-" +
+               PROCESS_TYPE_SUBMISSION, SUBMISSION_TYPE_SUCCEEDED,
+               "id1", new Date())
+  );
+  Assert.equal(s.crashesCount, 2);
+
+  let crashes = s.getCrashesOfType(PROCESS_TYPE_MAIN + "-" + CRASH_TYPE_CRASH +
+                                   "-" + PROCESS_TYPE_SUBMISSION,
+                                   SUBMISSION_TYPE_SUCCEEDED);
+  Assert.equal(crashes.length, 1);
+});
+
 add_task(function* test_add_mixed_types() {
   let s = yield getStore();
 
   Assert.ok(
     s.addCrash(PROCESS_TYPE_MAIN, CRASH_TYPE_CRASH, "mcrash", new Date()) &&
     s.addCrash(PROCESS_TYPE_MAIN, CRASH_TYPE_HANG, "mhang", new Date()) &&
     s.addCrash(PROCESS_TYPE_CONTENT, CRASH_TYPE_CRASH, "ccrash", new Date()) &&
     s.addCrash(PROCESS_TYPE_CONTENT, CRASH_TYPE_HANG, "chang", new Date()) &&
--- a/toolkit/crashreporter/client/crashreporter.cpp
+++ b/toolkit/crashreporter/client/crashreporter.cpp
@@ -27,19 +27,22 @@ using std::ostream;
 using std::ofstream;
 using std::vector;
 using std::auto_ptr;
 
 namespace CrashReporter {
 
 StringTable  gStrings;
 string       gSettingsPath;
+string       gEventsPath;
 int          gArgc;
 char**       gArgv;
 
+enum SubmissionResult {Succeeded, Failed};
+
 static auto_ptr<ofstream> gLogStream(nullptr);
 static string             gReporterDumpFile;
 static string             gExtraFile;
 
 static string kExtraDataExtension = ".extra";
 
 void UIError(const string& message)
 {
@@ -167,16 +170,63 @@ bool WriteStringsToFile(const string& pa
     success = WriteStrings(*f, header, strings, escape);
     f->close();
   }
 
   delete f;
   return success;
 }
 
+static string Basename(const string& file)
+{
+  string::size_type slashIndex = file.rfind(UI_DIR_SEPARATOR);
+  if (slashIndex != string::npos)
+    return file.substr(slashIndex + 1);
+  else
+    return file;
+}
+
+static string GetDumpLocalID()
+{
+  string localId = Basename(gReporterDumpFile);
+  string::size_type dot = localId.rfind('.');
+
+  if (dot == string::npos)
+    return "";
+
+  return localId.substr(0, dot);
+}
+
+static void WriteSubmissionEvent(SubmissionResult result,
+                                 const string& remoteId)
+{
+  if (gEventsPath.empty()) {
+    // If there is no path for writing the submission event, skip it.
+    return;
+  }
+
+  string localId = GetDumpLocalID();
+  string fpath = gEventsPath + UI_DIR_SEPARATOR + localId + "-submission";
+  ofstream* f = UIOpenWrite(fpath.c_str());
+  time_t tm;
+  time(&tm);
+
+  if (f->is_open()) {
+    *f << "crash.submission.1\n";
+    *f << tm << "\n";
+    *f << localId << "\n";
+    *f << (result == Succeeded ? "true" : "false") << "\n";
+    *f << remoteId;
+
+    f->close();
+  }
+
+  delete f;
+}
+
 void LogMessage(const std::string& message)
 {
   if (gLogStream.get()) {
     char date[64];
     time_t tm;
     time(&tm);
     if (strftime(date, sizeof(date) - 1, "%c", localtime(&tm)) == 0)
         date[0] = '\0';
@@ -213,25 +263,16 @@ static string GetExtraDataFilename(const
   int dot = filename.rfind('.');
   if (dot < 0)
     return "";
 
   filename.replace(dot, filename.length() - dot, kExtraDataExtension);
   return filename;
 }
 
-static string Basename(const string& file)
-{
-  int slashIndex = file.rfind(UI_DIR_SEPARATOR);
-  if (slashIndex >= 0)
-    return file.substr(slashIndex + 1);
-  else
-    return file;
-}
-
 static bool MoveCrashData(const string& toDir,
                           string& dumpfile,
                           string& extrafile)
 {
   if (!UIEnsurePathExists(toDir)) {
     UIError(gStrings[ST_ERROR_CREATEDUMPDIR]);
     return false;
   }
@@ -311,16 +352,17 @@ static bool AddSubmittedReport(const str
                 gStrings["CrashDetailsURL"].c_str(),
                 responseItems["ViewURL"].c_str());
     *file << buf << "\n";
   }
 
   file->close();
   delete file;
 
+  WriteSubmissionEvent(Succeeded, responseItems["CrashID"]);
   return true;
 }
 
 void DeleteDump()
 {
   const char* noDelete = getenv("MOZ_CRASHREPORTER_NO_DELETE_DUMP");
   if (!noDelete || *noDelete == '\0') {
     if (!gReporterDumpFile.empty())
@@ -338,17 +380,20 @@ void SendCompleted(bool success, const s
     }
     else {
       string directory = gReporterDumpFile;
       int slashpos = directory.find_last_of("/\\");
       if (slashpos < 2)
         return;
       directory.resize(slashpos);
       UIPruneSavedDumps(directory);
+      WriteSubmissionEvent(Failed, "");
     }
+  } else {
+    WriteSubmissionEvent(Failed, "");
   }
 }
 
 bool ShouldEnableSending()
 {
   srand(time(0));
   return ((rand() % 100) < MOZ_CRASHREPORTER_ENABLE_PERCENT);
 }
@@ -509,16 +554,33 @@ int main(int argc, char** argv)
 
     if (gSettingsPath.empty() || !UIEnsurePathExists(gSettingsPath)) {
       UIError(gStrings[ST_ERROR_NOSETTINGSPATH]);
       return 0;
     }
 
     OpenLogFile();
 
+#ifdef XP_WIN32
+    static const wchar_t kEventsDirKey[] = L"MOZ_CRASHREPORTER_EVENTS_DIRECTORY";
+    const wchar_t *eventsPath = _wgetenv(kEventsDirKey);
+    if (eventsPath && *eventsPath) {
+      gEventsPath = WideToUTF8(eventsPath);
+    }
+#else
+    static const char kEventsDirKey[] = "MOZ_CRASHREPORTER_EVENTS_DIRECTORY";
+    const char *eventsPath = getenv(kEventsDirKey);
+    if (eventsPath && *eventsPath) {
+      gEventsPath = eventsPath;
+    }
+#endif
+    else {
+      gEventsPath.clear();
+    }
+
     if (!UIFileExists(gReporterDumpFile)) {
       UIError(gStrings[ST_ERROR_DUMPFILEEXISTS]);
       return 0;
     }
 
     string pendingDir = gSettingsPath + UI_DIR_SEPARATOR + "pending";
     if (!MoveCrashData(pendingDir, gReporterDumpFile, gExtraFile)) {
       return 0;
--- a/toolkit/crashreporter/client/crashreporter.h
+++ b/toolkit/crashreporter/client/crashreporter.h
@@ -77,16 +77,17 @@ typedef std::map<std::string, std::strin
 
 //=============================================================================
 // implemented in crashreporter.cpp
 //=============================================================================
 
 namespace CrashReporter {
   extern StringTable  gStrings;
   extern std::string  gSettingsPath;
+  extern std::string  gEventsPath;
   extern int          gArgc;
   extern char**       gArgv;
 
   void UIError(const std::string& message);
 
   // The UI finished sending the report
   void SendCompleted(bool success, const std::string& serverResponse);
 
--- a/toolkit/crashreporter/nsExceptionHandler.cpp
+++ b/toolkit/crashreporter/nsExceptionHandler.cpp
@@ -158,16 +158,17 @@ static const char kCrashMainID[] = "cras
 
 static google_breakpad::ExceptionHandler* gExceptionHandler = nullptr;
 
 static XP_CHAR* pendingDirectory;
 static XP_CHAR* crashReporterPath;
 
 // Where crash events should go.
 static XP_CHAR* eventsDirectory;
+static char* eventsEnv = nullptr;
 
 // If this is false, we don't launch the crash reporter
 static bool doReport = true;
 
 // If this is true, we don't have a crash reporter
 static bool headlessClient = false;
 
 // if this is true, we pass the exception on to the OS crash reporter
@@ -2102,20 +2103,39 @@ SetCrashEventsDir(nsIFile* aDir)
   if (eventsDirectory) {
     NS_Free(eventsDirectory);
   }
 
 #ifdef XP_WIN
   nsString path;
   eventsDir->GetPath(path);
   eventsDirectory = reinterpret_cast<wchar_t*>(ToNewUnicode(path));
+
+  // Save the path in the environment for the crash reporter application.
+  nsAutoString eventsDirEnv(NS_LITERAL_STRING("MOZ_CRASHREPORTER_EVENTS_DIRECTORY="));
+  eventsDirEnv.Append(path);
+  _wputenv(eventsDirEnv.get());
 #else
   nsCString path;
   eventsDir->GetNativePath(path);
   eventsDirectory = ToNewCString(path);
+
+  // Save the path in the environment for the crash reporter application.
+  nsAutoCString eventsDirEnv("MOZ_CRASHREPORTER_EVENTS_DIRECTORY=");
+  eventsDirEnv.Append(path);
+
+  // PR_SetEnv() wants the string to be available for the lifetime
+  // of the app, so dup it here.
+  char* oldEventsEnv = eventsEnv;
+  eventsEnv = ToNewCString(eventsDirEnv);
+  PR_SetEnv(eventsEnv);
+
+  if (oldEventsEnv) {
+    NS_Free(oldEventsEnv);
+  }
 #endif
 }
 
 void
 SetProfileDirectory(nsIFile* aDir)
 {
   nsCOMPtr<nsIFile> dir;
   aDir->Clone(getter_AddRefs(dir));
--- a/toolkit/mozapps/update/tests/chrome/test_0084_error_patchApplyFailure_verify_failed.xul
+++ b/toolkit/mozapps/update/tests/chrome/test_0084_error_patchApplyFailure_verify_failed.xul
@@ -11,17 +11,16 @@
 <window title="Update Wizard pages: error patching, download, and errors (partial failed and download complete verification failure)"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         onload="runTestDefault();">
 <script type="application/javascript"
         src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
 <script type="application/javascript"
         src="utils.js"/>
 
-<script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"></script>
 <script type="application/javascript">
 <![CDATA[
 
 const TESTS = [ {
   pageid: PAGEID_ERROR_PATCHING,
   buttonClick: "next"
 }, {
   pageid: PAGEID_DOWNLOADING,
--- a/toolkit/themes/osx/global/global.css
+++ b/toolkit/themes/osx/global/global.css
@@ -348,16 +348,17 @@ notification > button > .button-box > .b
 }
 
 .close-icon:hover:active {
   -moz-image-region: rect(0, 48px, 16px, 32px);
 }
 
 @media (min-resolution: 2dppx) {
   .close-icon > .button-icon,
+  .close-icon > .button-box > .button-icon,
   .close-icon > .toolbarbutton-icon {
     width: 16px;
   }
 
   .close-icon {
     list-style-image: url("chrome://global/skin/icons/close@2x.png");
     -moz-image-region: rect(0, 32px, 32px, 0);
   }
--- a/webapprt/content/mochitest-shared.js
+++ b/webapprt/content/mochitest-shared.js
@@ -1,16 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* Note: this script is loaded by both mochitest.js and head.js, so make sure
  * the code you put here can be evaluated by both! */
 
 Cu.import("resource://webapprt/modules/WebappRT.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
 
 // When WebappsHandler opens an install confirmation dialog for apps we install,
 // close it, which will be seen as the equivalent of cancelling the install.
 // This doesn't prevent us from installing those apps, as we listen for the same
 // notification as WebappsHandler and do the install ourselves.  It just
 // prevents the modal installation confirmation dialogs from hanging tests.
 Services.ww.registerNotification({
   observe: function(win, topic) {
@@ -37,53 +38,53 @@ Services.ww.registerNotification({
  * @param {Object} parameters
  *        The value to pass as the "parameters" argument to
  *        mozIDOMApplicationRegistry.install, e.g., { receipts: ... }.
  *        Use undefined to pass nothing.
  * @param {Function} onBecome
  *        The callback to call once the transmogrification is complete.
  */
 function becomeWebapp(manifestURL, parameters, onBecome) {
-  function observeInstall(subj, topic, data) {
+  let observeInstall = Task.async(function*(subj, topic, data) {
     Services.obs.removeObserver(observeInstall, "webapps-ask-install");
 
     // Step 2: Configure the runtime session to represent the app.
     // We load DOMApplicationRegistry into a local scope to avoid appearing
     // to leak it.
 
     let scope = {};
     Cu.import("resource://gre/modules/Webapps.jsm", scope);
     Cu.import("resource://webapprt/modules/Startup.jsm", scope);
-    scope.DOMApplicationRegistry.confirmInstall(JSON.parse(data));
+    yield scope.DOMApplicationRegistry.confirmInstall(JSON.parse(data));
 
     let installRecord = JSON.parse(data);
     installRecord.mm = subj;
     installRecord.registryDir = Services.dirsvc.get("ProfD", Ci.nsIFile).path;
     WebappRT.config = installRecord;
 
     let win = Services.wm.getMostRecentWindow("webapprt:webapp");
     if (!win) {
       win = Services.ww.openWindow(null,
                                    "chrome://webapprt/content/webapp.xul",
                                    "_blank",
                                    "chrome,dialog=no,resizable,scrollbars,centerscreen",
                                    null);
     }
 
-    let promise = scope.startup(win);
-
     // During chrome tests, we use the same window to load all the tests. We
     // need to change the buildID so that the permissions for the currently
     // tested application get installed.
     Services.prefs.setCharPref("webapprt.buildID", WebappRT.config.app.manifestURL);
 
-    // During tests, the webapps registry is already loaded.
-    // The Startup module needs to be notified when the webapps registry
-    // gets loaded, so we do that now.
+    // During tests, the webapps registry is already loaded,
+    // but SystemMessageInternal expects to be notified when the registry
+    // start and then when it's ready, so we do that now.
     Services.obs.notifyObservers(this, "webapps-registry-start", null);
+    Services.obs.notifyObservers(this, "webapps-registry-ready", null);
 
-    promise.then(onBecome);
-  }
+    yield scope.startup(win);
+    onBecome();
+  });
   Services.obs.addObserver(observeInstall, "webapps-ask-install", false);
 
   // Step 1: Install the app at the URL specified by the manifest.
   navigator.mozApps.install(manifestURL, parameters);
 }
--- a/webapprt/content/mochitest.xul
+++ b/webapprt/content/mochitest.xul
@@ -1,16 +1,17 @@
 <?xml version="1.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/.  -->
 
 <?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
 
-<window windowtype="webapprt:mochitest"
+<window id="browserTestHarness"
+        windowtype="webapprt:mochitest"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
 <script type="application/javascript" src="chrome://webapprt/content/mochitest.js"/>
 
 <description value="WebappRT Test Shim"/>
 
 </window>
--- a/xpcom/base/SystemMemoryReporter.cpp
+++ b/xpcom/base/SystemMemoryReporter.cpp
@@ -612,20 +612,20 @@ private:
       // Read the total size.  The file gives the size in decimal and
       // hex, in the form "13631488(0xd00000)"; we parse the former.
       nsPrintfCString sizePath("/sys/kernel/pmem_regions/%s/size", name);
       FILE* sizeFile = fopen(sizePath.get(), "r");
       if (NS_WARN_IF(!sizeFile)) {
         continue;
       }
       scanned = fscanf(sizeFile, "%" SCNu64, &size);
+      fclose(sizeFile);
       if (NS_WARN_IF(scanned != 1)) {
         continue;
       }
-      fclose(sizeFile);
 
       // Read mapped regions; format described below.
       uint64_t freeSize = size;
       nsPrintfCString regionsPath("/sys/kernel/pmem_regions/%s/mapped_regions",
                                   name);
       FILE* regionsFile = fopen(regionsPath.get(), "r");
       if (regionsFile) {
         static const size_t bufLen = 4096;