Merge latest green birch changeset and mozilla-central
authorEd Morley <emorley@mozilla.com>
Thu, 06 Jun 2013 10:44:11 +0100
changeset 134227 a47f4e36197f7fe3422a08c5d668f168cdf7e1f9
parent 134226 0dbc583676132a4d1f6174cb63a25ddc2aa8f6d2 (current diff)
parent 134194 10a6781c41e46473cfb52eca8510750f28adf93a (diff)
child 134228 8aa6c4c5dabce8cb7aee4474047d0f493159469b
child 134334 c50f98c031f324cfc5209c0f8f02dc305cd75e2a
child 155461 70f7083ce1d29d90ea2f90449f1617b979622ba4
push id29104
push useremorley@mozilla.com
push dateThu, 06 Jun 2013 09:46:57 +0000
treeherdermozilla-inbound@8aa6c4c5dabc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone24.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge latest green birch changeset and mozilla-central
gfx/tests/gfxTestCocoaHelper.h
gfx/tests/gfxTestCocoaHelper.mm
security/patches/revert-bug-808217.patch
--- a/browser/app/Makefile.in
+++ b/browser/app/Makefile.in
@@ -172,23 +172,20 @@ clean clobber repackage::
 ifdef LIBXUL_SDK
 APPFILES = Resources
 else
 APPFILES = MacOS
 endif
 
 MAC_BUNDLE_VERSION = $(shell $(PYTHON) $(srcdir)/macversion.py --version=$(MOZ_APP_VERSION) --buildid=$(DEPTH)/config/buildid)
 
-libs-preqs = \
-  $(call mkdir_deps,$(dist_dest)/Contents/MacOS) \
-  $(call mkdir_deps,$(dist_dest)/Contents/Resources/$(AB).lproj) \
-  $(NULL)
-
 .PHONY: repackage
-libs repackage:: $(PROGRAM) $(libs-preqs)
+libs repackage:: $(PROGRAM)
+	$(MKDIR) -p $(dist_dest)/Contents/MacOS
+	$(MKDIR) -p $(dist_dest)/Contents/Resources/$(AB).lproj
 	rsync -a --exclude "*.in" $(srcdir)/macbuild/Contents $(dist_dest) --exclude English.lproj
 	rsync -a --exclude "*.in" $(srcdir)/macbuild/Contents/Resources/English.lproj/ $(dist_dest)/Contents/Resources/$(AB).lproj
 	sed -e "s/%APP_VERSION%/$(MOZ_APP_VERSION)/" -e "s/%MAC_APP_NAME%/$(MAC_APP_NAME)/" -e "s/%MOZ_MACBUNDLE_ID%/$(MOZ_MACBUNDLE_ID)/" -e "s/%MAC_BUNDLE_VERSION%/$(MAC_BUNDLE_VERSION)/" $(srcdir)/macbuild/Contents/Info.plist.in > $(dist_dest)/Contents/Info.plist
 	sed -e "s/%MAC_APP_NAME%/$(MAC_APP_NAME)/" $(srcdir)/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in | iconv -f UTF-8 -t UTF-16 > $(dist_dest)/Contents/Resources/$(AB).lproj/InfoPlist.strings
 	rsync -a $(DIST)/bin/ $(dist_dest)/Contents/$(APPFILES)
 	$(RM) $(dist_dest)/Contents/MacOS/$(PROGRAM)
 	rsync -aL $(PROGRAM) $(dist_dest)/Contents/MacOS
 	cp -RL $(DIST)/branding/firefox.icns $(dist_dest)/Contents/Resources/firefox.icns
--- a/browser/base/content/browser-fullZoom.js
+++ b/browser/base/content/browser-fullZoom.js
@@ -18,16 +18,21 @@ var FullZoom = {
   name: "browser.content.full-zoom",
 
   // browser.zoom.siteSpecific preference cache
   _siteSpecificPref: undefined,
 
   // browser.zoom.updateBackgroundTabs preference cache
   updateBackgroundTabs: undefined,
 
+  // Incremented each time the zoom is changed so that operations that change
+  // the zoom asynchronously don't clobber more recent zoom changes.  See
+  // _getState below.
+  _zoomChangeToken: 0,
+
   get siteSpecific() {
     return this._siteSpecificPref;
   },
 
   //**************************************************************************//
   // nsISupports
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMEventListener,
@@ -115,27 +120,24 @@ var FullZoom = {
     } catch (e) {}
     if (!isZoomEvent)
       return;
 
     // XXX Lazily cache all the possible action prefs so we don't have to get
     // them anew from the pref service for every scroll event?  We'd have to
     // make sure to observe them so we can update the cache when they change.
 
-    // We have to call _applySettingToPref in a timeout because we handle
-    // the event before the event state manager has a chance to apply the zoom
+    // We have to call _applyZoomToPref in a timeout because we handle the
+    // event before the event state manager has a chance to apply the zoom
     // during nsEventStateManager::PostHandleEvent.
-    //
-    // Since the zoom will have already been updated by the time
-    // _applySettingToPref is called, pass true to suppress updating the zoom
-    // again.  Note that this is not an optimization: due to asynchronicity of
-    // the content preference service, the zoom may have been updated again by
-    // the time that onContentPrefSet is called as a result of this call to
-    // _applySettingToPref.
-    window.setTimeout(function (self) self._applySettingToPref(true), 0, this);
+    let state = this._getState();
+    window.setTimeout(function () {
+      if (this._isStateCurrent(state))
+        this._applyZoomToPref();
+    }.bind(this), 0);
   },
 
   // nsIObserver
 
   observe: function (aSubject, aTopic, aData) {
     switch (aTopic) {
       case "nsPref:changed":
         switch (aData) {
@@ -150,61 +152,64 @@ var FullZoom = {
         }
         break;
     }
   },
 
   // nsIContentPrefObserver
 
   onContentPrefSet: function FullZoom_onContentPrefSet(aGroup, aName, aValue) {
-    if (this._ignoreNextOnContentPrefSet) {
-      delete this._ignoreNextOnContentPrefSet;
-      return;
-    }
     this._onContentPrefChanged(aGroup, aValue);
   },
 
   onContentPrefRemoved: function FullZoom_onContentPrefRemoved(aGroup, aName) {
     this._onContentPrefChanged(aGroup, undefined);
   },
 
   /**
    * Appropriately updates the zoom level after a content preference has
    * changed.
    *
    * @param aGroup  The group of the changed preference.
    * @param aValue  The new value of the changed preference.  Pass undefined to
    *                indicate the preference's removal.
    */
   _onContentPrefChanged: function FullZoom__onContentPrefChanged(aGroup, aValue) {
+    if (this._isNextContentPrefChangeInternal) {
+      // Ignore changes that FullZoom itself makes.  This works because the
+      // content pref service calls callbacks before notifying observers, and it
+      // does both in the same turn of the event loop.
+      delete this._isNextContentPrefChangeInternal;
+      return;
+    }
+
     if (!gBrowser.currentURI)
       return;
 
     let domain = this._cps2.extractDomain(gBrowser.currentURI.spec);
     if (aGroup) {
       if (aGroup == domain)
-        this._applyPrefToSetting(aValue);
+        this._applyPrefToZoom(aValue);
       return;
     }
 
     this._globalValue = aValue === undefined ? aValue :
                           this._ensureValid(aValue);
 
     // If the current page doesn't have a site-specific preference, then its
     // zoom should be set to the new global preference now that the global
     // preference has changed.
+    let state = this._getState();
     let hasPref = false;
     let ctxt = this._loadContextFromWindow(gBrowser.contentWindow);
     this._cps2.getByDomainAndName(gBrowser.currentURI.spec, this.name, ctxt, {
       handleResult: function () hasPref = true,
       handleCompletion: function () {
-        if (!hasPref &&
-            gBrowser.currentURI &&
-            this._cps2.extractDomain(gBrowser.currentURI.spec) == domain)
-          this._applyPrefToSetting();
+        if (!hasPref && this._isStateCurrent(state))
+          this._applyPrefToZoom(undefined, { state: state });
       }.bind(this)
     });
   },
 
   // location change observer
 
   /**
    * Called when the location of a tab changes.
@@ -224,50 +229,56 @@ var FullZoom = {
 
     if (!aURI || (aIsTabSwitch && !this.siteSpecific)) {
       this._notifyOnLocationChange();
       return;
     }
 
     // Avoid the cps roundtrip and apply the default/global pref.
     if (aURI.spec == "about:blank") {
-      this._applyPrefToSetting(undefined, aBrowser, function () {
-        this._notifyOnLocationChange();
-      }.bind(this));
+      this._applyPrefToZoom(undefined, {
+        browser: aBrowser,
+        onDone: this._notifyOnLocationChange.bind(this),
+      });
       return;
     }
 
     let browser = aBrowser || gBrowser.selectedBrowser;
 
     // Media documents should always start at 1, and are not affected by prefs.
     if (!aIsTabSwitch && browser.contentDocument.mozSyntheticDocument) {
       ZoomManager.setZoomForBrowser(browser, 1);
+      this._zoomChangeToken++;
       this._notifyOnLocationChange();
       return;
     }
 
+    // See if the zoom pref is cached.
     let ctxt = this._loadContextFromWindow(browser.contentWindow);
     let pref = this._cps2.getCachedByDomainAndName(aURI.spec, this.name, ctxt);
     if (pref) {
-      this._applyPrefToSetting(pref.value, browser, function () {
-        this._notifyOnLocationChange();
-      }.bind(this));
+      this._applyPrefToZoom(pref.value, {
+        browser: browser,
+        onDone: this._notifyOnLocationChange.bind(this),
+      });
       return;
     }
 
+    // It's not cached, so have to asynchronously fetch it.
+    let state = this._getState(browser);
     let value = undefined;
     this._cps2.getByDomainAndName(aURI.spec, this.name, ctxt, {
       handleResult: function (resultPref) value = resultPref.value,
       handleCompletion: function () {
-        if (browser.currentURI &&
-            this._cps2.extractDomain(browser.currentURI.spec) ==
-              this._cps2.extractDomain(aURI.spec)) {
-          this._applyPrefToSetting(value, browser, function () {
-            this._notifyOnLocationChange();
-          }.bind(this));
+        if (this._isStateCurrent(state)) {
+          this._applyPrefToZoom(value, {
+            browser: browser,
+            state: state,
+            onDone: this._notifyOnLocationChange.bind(this),
+          });
         }
       }.bind(this)
     });
   },
 
   // update state of zoom type menu item
 
   updateMenu: function FullZoom_updateMenu() {
@@ -276,51 +287,48 @@ var FullZoom = {
     menuItem.setAttribute("checked", !ZoomManager.useFullZoom);
   },
 
   //**************************************************************************//
   // Setting & Pref Manipulation
 
   /**
    * Reduces the zoom level of the page in the current browser.
-   *
-   * @param callback  Optional.  If given, it's asynchronously called when the
-   *                  zoom update completes.
    */
-  reduce: function FullZoom_reduce(callback) {
+  reduce: function FullZoom_reduce() {
     ZoomManager.reduce();
-    this._applySettingToPref(false, callback);
+    this._zoomChangeToken++;
+    this._applyZoomToPref();
   },
 
   /**
    * Enlarges the zoom level of the page in the current browser.
-   *
-   * @param callback  Optional.  If given, it's asynchronously called when the
-   *                  zoom update completes.
    */
-  enlarge: function FullZoom_enlarge(callback) {
+  enlarge: function FullZoom_enlarge() {
     ZoomManager.enlarge();
-    this._applySettingToPref(false, callback);
+    this._zoomChangeToken++;
+    this._applyZoomToPref();
   },
 
   /**
    * Sets the zoom level of the page in the current browser to the global zoom
    * level.
-   *
-   * @param callback  Optional.  If given, it's asynchronously called when the
-   *                  zoom update completes.
    */
-  reset: function FullZoom_reset(callback) {
+  reset: function FullZoom_reset() {
+    let state = this._getState();
     this._getGlobalValue(gBrowser.contentWindow, function (value) {
-      if (value === undefined)
-        ZoomManager.reset();
-      else
-        ZoomManager.zoom = value;
-      this._removePref(callback);
+      if (this._isStateCurrent(state)) {
+        if (value === undefined)
+          ZoomManager.reset();
+        else
+          ZoomManager.zoom = value;
+        this._zoomChangeToken++;
+      }
     });
+    this._removePref();
   },
 
   /**
    * Set the zoom level for the current tab.
    *
    * Per nsPresContext::setFullZoom, we can set the zoom to its current value
    * without significant impact on performance, as the setting is only applied
    * if it differs from the current setting.  In fact getting the zoom and then
@@ -332,106 +340,118 @@ var FullZoom = {
    * those child text zooms should get updated when the parent zoom gets set,
    * and perhaps the same is true for full zoom
    * (although nsDocumentViewer::SetFullZoom doesn't mention it).
    *
    * So when we apply new zoom values to the browser, we simply set the zoom.
    * We don't check first to see if the new value is the same as the current
    * one.
    *
-   * @param aValue     The zoom level value.
-   * @param aBrowser   The browser containing the page whose zoom level is to be
-   *                   set.  If falsey, the currently selected browser is used.
-   * @param aCallback  Optional.  If given, it's asynchronously called when
-   *                   complete.
+   * @param aValue    The zoom level value.
+   * @param aOptions  An object with the following optional propeties:
+   * @prop browser    The browser containing the page whose zoom level is to be
+   *                  set.  If falsey, the currently selected browser is used.
+   * @prop state      This method may need to update the zoom asynchronously.
+   *                  If the caller is itself asynchronous, then it should have
+   *                  access to a FullZoom state (see _getState); pass that
+   *                  state here.  If not given, the state is automatically
+   *                  captured.
+   * @prop onDone     If given, it's asynchronously called when complete.
    */
-  _applyPrefToSetting: function FullZoom__applyPrefToSetting(aValue, aBrowser, aCallback) {
+  _applyPrefToZoom: function FullZoom__applyPrefToZoom(aValue, aOptions={}) {
     if (!this.siteSpecific || gInPrintPreviewMode) {
-      this._executeSoon(aCallback);
+      this._executeSoon(aOptions.onDone);
       return;
     }
 
-    var browser = aBrowser || (gBrowser && gBrowser.selectedBrowser);
+    var browser = aOptions.browser || (gBrowser && gBrowser.selectedBrowser);
     if (browser.contentDocument.mozSyntheticDocument) {
-      this._executeSoon(aCallback);
+      this._executeSoon(aOptions.onDone);
       return;
     }
 
     if (aValue !== undefined) {
       ZoomManager.setZoomForBrowser(browser, this._ensureValid(aValue));
-      this._executeSoon(aCallback);
+      this._zoomChangeToken++;
+      this._executeSoon(aOptions.onDone);
       return;
     }
 
+    let state = aOptions.state || this._getState(browser);
     this._getGlobalValue(browser.contentWindow, function (value) {
-      if (gBrowser.selectedBrowser == browser)
+      if (this._isStateCurrent(state)) {
         ZoomManager.setZoomForBrowser(browser, value === undefined ? 1 : value);
-      this._executeSoon(aCallback);
+        this._zoomChangeToken++;
+      }
+      this._executeSoon(aOptions.onDone);
     });
   },
 
   /**
    * Saves the zoom level of the page in the current browser to the content
    * prefs store.
-   *
-   * @param suppressZoomChange  Because this method sets a content preference,
-   *                            our onContentPrefSet method is called as a
-   *                            result, which updates the current zoom level.
-   *                            If suppressZoomChange is true, then the next
-   *                            call to onContentPrefSet will not update the
-   *                            zoom level.
-   * @param callback            Optional.  If given, it's asynchronously called
-   *                            when done.
    */
-  _applySettingToPref: function FullZoom__applySettingToPref(suppressZoomChange, callback) {
+  _applyZoomToPref: function FullZoom__applyZoomToPref() {
     if (!this.siteSpecific ||
         gInPrintPreviewMode ||
-        content.document.mozSyntheticDocument) {
-      this._executeSoon(callback);
+        content.document.mozSyntheticDocument)
       return;
-    }
 
     this._cps2.set(gBrowser.currentURI.spec, this.name, ZoomManager.zoom,
                    this._loadContextFromWindow(gBrowser.contentWindow), {
       handleCompletion: function () {
-        if (suppressZoomChange)
-          // The content preference service calls observers after callbacks, and
-          // it calls both in the same turn of the event loop.  onContentPrefSet
-          // will therefore be called next, in the same turn of the event loop,
-          // and it will check this flag.
-          this._ignoreNextOnContentPrefSet = true;
-        if (callback)
-          callback();
-      }.bind(this)
+        this._isNextContentPrefChangeInternal = true;
+      }.bind(this),
     });
   },
 
   /**
    * Removes from the content prefs store the zoom level of the current browser.
-   *
-   * @param callback  Optional.  If given, it's asynchronously called when done.
    */
-  _removePref: function FullZoom__removePref(callback) {
-    if (content.document.mozSyntheticDocument) {
-      this._executeSoon(callback);
+  _removePref: function FullZoom__removePref() {
+    if (content.document.mozSyntheticDocument)
       return;
-    }
     let ctxt = this._loadContextFromWindow(gBrowser.contentWindow);
     this._cps2.removeByDomainAndName(gBrowser.currentURI.spec, this.name, ctxt, {
       handleCompletion: function () {
-        if (callback)
-          callback();
-      }
+        this._isNextContentPrefChangeInternal = true;
+      }.bind(this),
     });
   },
 
-
   //**************************************************************************//
   // Utilities
 
+  /**
+   * Returns the current FullZoom "state".  Asynchronous operations that change
+   * the zoom should use this method to capture the state before starting and
+   * use _isStateCurrent to determine if it's still current when done.  If the
+   * captured state is no longer current, then the zoom should not be changed.
+   * Doing so would either change the zoom of the wrong tab or clobber an
+   * earlier zoom change that occurred after the operation started.
+   *
+   * @param browser  The browser associated with the state.  If not given, the
+   *                 currently selected browser is used.
+   */
+  _getState: function FullZoom__getState(browser) {
+    let browser = browser || gBrowser.selectedBrowser;
+    return { uri: browser.currentURI, token: this._zoomChangeToken };
+  },
+
+  /**
+   * Returns true if the given state is current.
+   */
+  _isStateCurrent: function FullZoom__isStateCurrent(state) {
+    let currState = this._getState();
+    return currState.token === state.token &&
+           currState.uri && state.uri &&
+           this._cps2.extractDomain(currState.uri.spec) ==
+             this._cps2.extractDomain(state.uri.spec);
+  },
+
   _ensureValid: function FullZoom__ensureValid(aValue) {
     // Note that undefined is a valid value for aValue that indicates a known-
     // not-to-exist value.
     if (isNaN(aValue))
       return 1;
 
     if (aValue < ZoomManager.MIN)
       return ZoomManager.MIN;
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -2336,20 +2336,23 @@ let BrowserOnClick = {
     else if (ownerDoc.documentURI.toLowerCase() == "about:home") {
       this.onAboutHome(originalTarget, ownerDoc);
     }
   },
 
   onAboutCertError: function BrowserOnClick_onAboutCertError(aTargetElm, aOwnerDoc) {
     let elmId = aTargetElm.getAttribute("id");
     let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI");
+    let isTopFrame = (aOwnerDoc.defaultView.parent === aOwnerDoc.defaultView);
 
     switch (elmId) {
       case "exceptionDialogButton":
-        secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_BAD_CERT_CLICK_ADD_EXCEPTION);
+        if (isTopFrame) {
+          secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_BAD_CERT_TOP_CLICK_ADD_EXCEPTION);
+        }
         let params = { exceptionAdded : false };
 
         try {
           switch (Services.prefs.getIntPref("browser.ssl_override_behavior")) {
             case 2 : // Pre-fetch & pre-populate
               params.prefetchCert = true;
             case 1 : // Pre-populate
               params.location = aOwnerDoc.location.href;
@@ -2363,26 +2366,32 @@ let BrowserOnClick = {
 
         // If the user added the exception cert, attempt to reload the page
         if (params.exceptionAdded) {
           aOwnerDoc.location.reload();
         }
         break;
 
       case "getMeOutOfHereButton":
-        secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_BAD_CERT_GET_ME_OUT_OF_HERE);
+        if (isTopFrame) {
+          secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_BAD_CERT_TOP_GET_ME_OUT_OF_HERE);
+        }
         getMeOutOfHere();
         break;
 
       case "technicalContent":
-        secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_BAD_CERT_TECHNICAL_DETAILS);
+        if (isTopFrame) {
+          secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_BAD_CERT_TOP_TECHNICAL_DETAILS);
+        }
         break;
 
       case "expertContent":
-        secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_BAD_CERT_UNDERSTAND_RISKS);
+        if (isTopFrame) {
+          secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_BAD_CERT_TOP_UNDERSTAND_RISKS);
+        }
         break;
 
     }
   },
 
   onAboutBlocked: function BrowserOnClick_onAboutBlocked(aTargetElm, aOwnerDoc) {
     let elmId = aTargetElm.getAttribute("id");
     let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI");
--- a/browser/base/content/test/browser_bug386835.js
+++ b/browser/base/content/test/browser_bug386835.js
@@ -22,17 +22,17 @@ function test() {
 function secondPageLoaded() {
   Task.spawn(function () {
     FullZoomHelper.zoomTest(gTab1, 1, "Initial zoom of tab 1 should be 1");
     FullZoomHelper.zoomTest(gTab2, 1, "Initial zoom of tab 2 should be 1");
     FullZoomHelper.zoomTest(gTab3, 1, "Initial zoom of tab 3 should be 1");
 
     // Now have three tabs, two with the test page, one blank. Tab 1 is selected
     // Zoom tab 1
-    yield FullZoomHelper.enlarge();
+    FullZoom.enlarge();
     gLevel = ZoomManager.getZoomForBrowser(gBrowser.getBrowserForTab(gTab1));
 
     ok(gLevel > 1, "New zoom for tab 1 should be greater than 1");
     FullZoomHelper.zoomTest(gTab2, 1, "Zooming tab 1 should not affect tab 2");
     FullZoomHelper.zoomTest(gTab3, 1, "Zooming tab 1 should not affect tab 3");
 
     yield FullZoomHelper.load(gTab3, gTestPage);
   }).then(thirdPageLoaded, FullZoomHelper.failAndContinue(finish));
@@ -74,16 +74,16 @@ function imageZoomSwitch() {
 }
 
 var finishTestStarted  = false;
 function finishTest() {
   Task.spawn(function () {
     ok(!finishTestStarted, "finishTest called more than once");
     finishTestStarted = true;
     yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1);
-    yield FullZoomHelper.reset();
+    FullZoom.reset();
     gBrowser.removeTab(gTab1);
-    yield FullZoomHelper.reset();
+    FullZoom.reset();
     gBrowser.removeTab(gTab2);
-    yield FullZoomHelper.reset();
+    FullZoom.reset();
     gBrowser.removeTab(gTab3);
   }).then(finish, FullZoomHelper.failAndContinue(finish));
 }
--- a/browser/base/content/test/browser_bug416661.js
+++ b/browser/base/content/test/browser_bug416661.js
@@ -1,39 +1,38 @@
 var tabElm, zoomLevel;
 function start_test_prefNotSet() {
   Task.spawn(function () {
     is(ZoomManager.zoom, 1, "initial zoom level should be 1");
-    yield FullZoomHelper.enlarge();
+    FullZoom.enlarge();
 
     //capture the zoom level to test later
     zoomLevel = ZoomManager.zoom;
     isnot(zoomLevel, 1, "zoom level should have changed");
 
     yield FullZoomHelper.load(gBrowser.selectedTab, "http://mochi.test:8888/browser/browser/base/content/test/moz.png");
   }).then(continue_test_prefNotSet, FullZoomHelper.failAndContinue(finish));
 }
 
 function continue_test_prefNotSet () {
   Task.spawn(function () {
     is(ZoomManager.zoom, 1, "zoom level pref should not apply to an image");
-    yield FullZoomHelper.reset();
+    FullZoom.reset();
 
     yield FullZoomHelper.load(gBrowser.selectedTab, "http://mochi.test:8888/browser/browser/base/content/test/zoom_test.html");
   }).then(end_test_prefNotSet, FullZoomHelper.failAndContinue(finish));
 }
 
 function end_test_prefNotSet() {
-  Task.spawn(function () {
-    is(ZoomManager.zoom, zoomLevel, "the zoom level should have persisted");
+  is(ZoomManager.zoom, zoomLevel, "the zoom level should have persisted");
 
-    // Reset the zoom so that other tests have a fresh zoom level
-    yield FullZoomHelper.reset();
-    gBrowser.removeCurrentTab();
-  }).then(finish, FullZoomHelper.failAndContinue(finish));
+  // Reset the zoom so that other tests have a fresh zoom level
+  FullZoom.reset();
+  gBrowser.removeCurrentTab();
+  finish();
 }
 
 function test() {
   waitForExplicitFinish();
 
   Task.spawn(function () {
     tabElm = gBrowser.addTab();
     yield FullZoomHelper.selectTabAndWaitForLocationChange(tabElm);
--- a/browser/base/content/test/browser_bug419612.js
+++ b/browser/base/content/test/browser_bug419612.js
@@ -5,25 +5,25 @@ function test() {
     let testPage = "http://example.org/browser/browser/base/content/test/dummy_page.html";
     let tab1 = gBrowser.addTab();
     yield FullZoomHelper.selectTabAndWaitForLocationChange(tab1);
     yield FullZoomHelper.load(tab1, testPage);
 
     let tab2 = gBrowser.addTab();
     yield FullZoomHelper.load(tab2, testPage);
 
-    yield FullZoomHelper.enlarge();
+    FullZoom.enlarge();
     let tab1Zoom = ZoomManager.getZoomForBrowser(tab1.linkedBrowser);
 
     yield FullZoomHelper.selectTabAndWaitForLocationChange(tab2);
     let tab2Zoom = ZoomManager.getZoomForBrowser(tab2.linkedBrowser);
     is(tab2Zoom, tab1Zoom, "Zoom should affect background tabs");
 
     gPrefService.setBoolPref("browser.zoom.updateBackgroundTabs", false);
-    yield FullZoomHelper.reset();
+    FullZoom.reset();
     gBrowser.selectedTab = tab1;
     tab1Zoom = ZoomManager.getZoomForBrowser(tab1.linkedBrowser);
     tab2Zoom = ZoomManager.getZoomForBrowser(tab2.linkedBrowser);
     isnot(tab1Zoom, tab2Zoom, "Zoom should not affect background tabs");
 
     if (gPrefService.prefHasUserValue("browser.zoom.updateBackgroundTabs"))
       gPrefService.clearUserPref("browser.zoom.updateBackgroundTabs");
     gBrowser.removeTab(tab1);
--- a/browser/base/content/test/browser_bug441778.js
+++ b/browser/base/content/test/browser_bug441778.js
@@ -19,17 +19,17 @@ function test() {
     yield FullZoomHelper.selectTabAndWaitForLocationChange(tab);
 
     let testBrowser = tab.linkedBrowser;
 
     yield FullZoomHelper.load(tab, TEST_PAGE_URL);
 
     // Change the zoom level and then save it so we can compare it to the level
     // after loading the sub-document.
-    yield FullZoomHelper.enlarge();
+    FullZoom.enlarge();
     var zoomLevel = ZoomManager.zoom;
 
     // Start the sub-document load.
     let deferred = Promise.defer();
     executeSoon(function () {
       testBrowser.addEventListener("load", function (e) {
         testBrowser.removeEventListener("load", arguments.callee, true);
 
--- a/browser/base/content/test/browser_bug555224.js
+++ b/browser/base/content/test/browser_bug555224.js
@@ -1,29 +1,28 @@
 /* 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/. */
 const TEST_PAGE = "/browser/browser/base/content/test/dummy_page.html";
 var gTestTab, gBgTab, gTestZoom;
 
 function testBackgroundLoad() {
-  Task.spawn(function () {
-    is(ZoomManager.zoom, gTestZoom, "opening a background tab should not change foreground zoom");
+  is(ZoomManager.zoom, gTestZoom, "opening a background tab should not change foreground zoom");
+
+  gBrowser.removeTab(gBgTab);
 
-    gBrowser.removeTab(gBgTab);
-
-    yield FullZoomHelper.reset();
-    gBrowser.removeTab(gTestTab);
-  }).then(finish, FullZoomHelper.failAndContinue(finish));
+  FullZoom.reset();
+  gBrowser.removeTab(gTestTab);
+  finish();
 }
 
 function testInitialZoom() {
   Task.spawn(function () {
     is(ZoomManager.zoom, 1, "initial zoom level should be 1");
-    yield FullZoomHelper.enlarge();
+    FullZoom.enlarge();
 
     gTestZoom = ZoomManager.zoom;
     isnot(gTestZoom, 1, "zoom level should have changed");
 
     gBgTab = gBrowser.addTab();
     yield FullZoomHelper.load(gBgTab, "http://mochi.test:8888" + TEST_PAGE);
   }).then(testBackgroundLoad, FullZoomHelper.failAndContinue(finish));
 }
--- a/browser/base/content/test/browser_bug575830.js
+++ b/browser/base/content/test/browser_bug575830.js
@@ -16,17 +16,17 @@ function test() {
   Task.spawn(function () {
     tab1 = gBrowser.addTab();
     tab2 = gBrowser.addTab();
     yield FullZoomHelper.selectTabAndWaitForLocationChange(tab1);
     yield FullZoomHelper.load(tab1, TEST_IMAGE);
 
     is(ZoomManager.zoom, 1, "initial zoom level for first should be 1");
 
-    yield FullZoomHelper.enlarge();
+    FullZoom.enlarge();
     let zoom = ZoomManager.zoom;
     isnot(zoom, 1, "zoom level should have changed");
 
     yield FullZoomHelper.selectTabAndWaitForLocationChange(tab2);
     is(ZoomManager.zoom, 1, "initial zoom level for second tab should be 1");
 
     yield FullZoomHelper.selectTabAndWaitForLocationChange(tab1);
     is(ZoomManager.zoom, zoom, "zoom level for first tab should not have changed");
--- a/browser/base/content/test/browser_bug719271.js
+++ b/browser/base/content/test/browser_bug719271.js
@@ -22,33 +22,33 @@ function test() {
 }
 
 function zoomTab1() {
   Task.spawn(function () {
     is(gBrowser.selectedTab, gTab1, "Tab 1 is selected");
     FullZoomHelper.zoomTest(gTab1, 1, "Initial zoom of tab 1 should be 1");
     FullZoomHelper.zoomTest(gTab2, 1, "Initial zoom of tab 2 should be 1");
 
-    yield FullZoomHelper.enlarge();
+    FullZoom.enlarge();
     gLevel1 = ZoomManager.getZoomForBrowser(gBrowser.getBrowserForTab(gTab1));
 
     ok(gLevel1 > 1, "New zoom for tab 1 should be greater than 1");
     FullZoomHelper.zoomTest(gTab2, 1, "Zooming tab 1 should not affect tab 2");
 
     yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab2);
     FullZoomHelper.zoomTest(gTab2, 1, "Tab 2 is still unzoomed after it is selected");
     FullZoomHelper.zoomTest(gTab1, gLevel1, "Tab 1 is still zoomed");
   }).then(zoomTab2, FullZoomHelper.failAndContinue(finish));
 }
 
 function zoomTab2() {
   Task.spawn(function () {
     is(gBrowser.selectedTab, gTab2, "Tab 2 is selected");
 
-    yield FullZoomHelper.reduce();
+    FullZoom.reduce();
     let gLevel2 = ZoomManager.getZoomForBrowser(gBrowser.getBrowserForTab(gTab2));
 
     ok(gLevel2 < 1, "New zoom for tab 2 should be less than 1");
     FullZoomHelper.zoomTest(gTab1, gLevel1, "Zooming tab 2 should not affect tab 1");
 
     yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1);
     FullZoomHelper.zoomTest(gTab1, gLevel1, "Tab 1 should have the same zoom after it's selected");
   }).then(testNavigation, FullZoomHelper.failAndContinue(finish));
@@ -75,15 +75,15 @@ function waitForNextTurn() {
 
 var finishTestStarted  = false;
 function finishTest() {
   Task.spawn(function () {
     ok(!finishTestStarted, "finishTest called more than once");
     finishTestStarted = true;
 
     yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1);
-    yield FullZoomHelper.reset();
+    FullZoom.reset();
     gBrowser.removeTab(gTab1);
     yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab2);
-    yield FullZoomHelper.reset();
+    FullZoom.reset();
     gBrowser.removeTab(gTab2);
   }).then(finish, FullZoomHelper.failAndContinue(finish));
 }
--- a/browser/base/content/test/test_contextmenu.html
+++ b/browser/base/content/test/test_contextmenu.html
@@ -16,36 +16,38 @@ Browser context menu tests.
 <pre id="test">
 <script> var perWindowPrivateBrowsing = false; </script>
 <script type="text/javascript" src="privateBrowsingMode.js"></script>
 <script class="testbody" type="text/javascript">
 
 /** Test for Login Manager: multiple login autocomplete. **/
 
 SpecialPowers.Cu.import("resource://gre/modules/InlineSpellChecker.jsm", window);
+SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm", window);
 
 const Ci = SpecialPowers.Ci;
 
-function openContextMenuFor(element, shiftkey, shouldWaitForFocus) {
+function openContextMenuFor(element, shiftkey, waitForSpellCheck) {
     // Context menu should be closed before we open it again.
     is(SpecialPowers.wrap(contextMenu).state, "closed", "checking if popup is closed");
 
     if (lastElement)
       lastElement.blur();
     element.focus();
 
     // Some elements need time to focus and spellcheck before any tests are
     // run on them.
     function actuallyOpenContextMenuFor() {
       lastElement = element;
       var eventDetails = { type : "contextmenu", button : 2, shiftKey : shiftkey };
       synthesizeMouse(element, 2, 2, eventDetails, element.ownerDocument.defaultView);
     }
-    if (shouldWaitForFocus)
-      SimpleTest.executeSoon(actuallyOpenContextMenuFor);
+
+    if (waitForSpellCheck)
+      onSpellCheck(element, actuallyOpenContextMenuFor);
     else
       actuallyOpenContextMenuFor();
 }
 
 function closeContextMenu() {
     contextMenu.hidePopup();
 }
 
@@ -603,17 +605,17 @@ function runTest(testNum) {
                           "spell-check-enabled", true,
                           "spell-dictionaries",  true,
                               ["spell-check-dictionary-en-US", true,
                                "---",                          null,
                                "spell-add-dictionaries",       true], null
                          ].concat(inspectItems));
         contextMenu.ownerDocument.getElementById("spell-undo-add-to-dictionary").doCommand(); // Undo add to dictionary
         closeContextMenu();
-        openContextMenuFor(contenteditable);
+        openContextMenuFor(contenteditable, false, true);
         break;
 
     case 18:
         // Context menu for contenteditable
         checkContextMenu(["spell-no-suggestions", false,
                           "spell-add-to-dictionary", true,
                           "---",                 null,
                           "context-undo",        false,
@@ -628,17 +630,17 @@ function runTest(testNum) {
                           "spell-check-enabled", true,
                           "spell-dictionaries",  true,
                               ["spell-check-dictionary-en-US", true,
                                "---",                          null,
                                "spell-add-dictionaries",       true], null
                          ].concat(inspectItems));
 
         closeContextMenu();
-        openContextMenuFor(inputspell); // Invoke context menu for next test.
+        openContextMenuFor(inputspell, false, true); // Invoke context menu for next test.
         break;
 
     case 19:
         // Context menu for spell-check input
         checkContextMenu(["*prodigality",        true, // spelling suggestion
                           "spell-add-to-dictionary", true,
                           "---",                 null,
                           "context-undo",        false,
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_zoom.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_zoom.js
@@ -20,39 +20,38 @@ function test() {
         aWindow.gBrowser.selectedTab = tabMozilla;
 
         let mozillaBrowser = aWindow.gBrowser.getBrowserForTab(tabMozilla);
         mozillaBrowser.addEventListener("load", function onMozillaBrowserLoad() {
           mozillaBrowser.removeEventListener("load", onMozillaBrowserLoad, true);
           let mozillaZoom = aWindow.ZoomManager.zoom;
 
           // change the zoom on the mozilla page
-          aWindow.FullZoom.enlarge(function () {
-            // make sure the zoom level has been changed
-            isnot(aWindow.ZoomManager.zoom, mozillaZoom, "Zoom level can be changed");
-            mozillaZoom = aWindow.ZoomManager.zoom;
+          aWindow.FullZoom.enlarge();
+          // make sure the zoom level has been changed
+          isnot(aWindow.ZoomManager.zoom, mozillaZoom, "Zoom level can be changed");
+          mozillaZoom = aWindow.ZoomManager.zoom;
 
-            // switch to about: tab
-            aWindow.gBrowser.selectedTab = tabAbout;
+          // switch to about: tab
+          aWindow.gBrowser.selectedTab = tabAbout;
 
-            // switch back to mozilla tab
-            aWindow.gBrowser.selectedTab = tabMozilla;
+          // switch back to mozilla tab
+          aWindow.gBrowser.selectedTab = tabMozilla;
 
-            // make sure the zoom level has not changed
-            is(aWindow.ZoomManager.zoom, mozillaZoom,
-              "Entering private browsing should not reset the zoom on a tab");
+          // make sure the zoom level has not changed
+          is(aWindow.ZoomManager.zoom, mozillaZoom,
+            "Entering private browsing should not reset the zoom on a tab");
 
-            // cleanup
-            aWindow.FullZoom.reset(function () {
-              aWindow.gBrowser.removeTab(tabMozilla);
-              aWindow.gBrowser.removeTab(tabAbout);
-              aWindow.close();
-              aCallback();
-            });
-          });
+          // cleanup
+          aWindow.FullZoom.reset();
+          aWindow.gBrowser.removeTab(tabMozilla);
+          aWindow.gBrowser.removeTab(tabAbout);
+          aWindow.close();
+          aCallback();
+
         }, true);
         mozillaBrowser.contentWindow.location = "about:mozilla";
       }, true);
       aboutBrowser.contentWindow.location = "about:";
     });
   }
 
   whenNewWindowLoaded({private: true}, function(privateWindow) {
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_zoomrestore.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_zoomrestore.js
@@ -36,41 +36,32 @@ function test() {
     browser.loadURI("about:blank");
   }
 
   function doTest(aIsZoomedWindow, aWindow, aCallback) {
     if (aIsZoomedWindow) {
       is(aWindow.ZoomManager.zoom, 1,
          "Zoom level for freshly loaded about:blank should be 1");
       // change the zoom on the blank page
-      aWindow.FullZoom.enlarge(function () {
-        isnot(aWindow.ZoomManager.zoom, 1, "Zoom level for about:blank should be changed");
-        aCallback();
-      });
+      aWindow.FullZoom.enlarge();
+      isnot(aWindow.ZoomManager.zoom, 1, "Zoom level for about:blank should be changed");
+      aCallback();
       return;
     }
     // make sure the zoom level is set to 1
     is(aWindow.ZoomManager.zoom, 1, "Zoom level for about:privatebrowsing should be reset");
     aCallback();
   }
 
   function finishTest() {
     // cleanup
-    let numWindows = windowsToReset.length;
-    if (!numWindows) {
-      finish();
-      return;
-    }
     windowsToReset.forEach(function(win) {
-      win.FullZoom.reset(function onReset() {
-        numWindows--;
-        if (!numWindows)
-          finish();
-      });
+      win.FullZoom.reset();
     });
+    finish();
   }
 
   function testOnWindow(options, callback) {
     let win = OpenBrowserWindow(options);
     win.addEventListener("load", function onLoad() {
       win.removeEventListener("load", onLoad, false);
       windowsToClose.push(win);
       windowsToReset.push(win);
--- a/build/mobile/robocop/Makefile.in
+++ b/build/mobile/robocop/Makefile.in
@@ -68,16 +68,17 @@ robocop_FILES   := \
 robocop-deps := $(notdir $(robocop_FILES))
 
 MOCHITEST_ROBOCOP_FILES := \
   $(wildcard $(TESTPATH)/*.html) \
   $(wildcard $(TESTPATH)/*.jpg) \
   $(wildcard $(TESTPATH)/*.sjs) \
   $(wildcard $(TESTPATH)/test*.js) \
   $(wildcard $(TESTPATH)/robocop*.js) \
+  $(wildcard $(TESTPATH)/*.xml) \
   $(NULL)
 
 GARBAGE += \
   AndroidManifest.xml \
   $(java-tests-dep) \
   $(_JAVA_HARNESS) \
   classes.dex \
   robocop.ap_ \
--- a/caps/src/nsScriptSecurityManager.cpp
+++ b/caps/src/nsScriptSecurityManager.cpp
@@ -2214,34 +2214,34 @@ nsScriptSecurityManager::CanCreateWrappe
 
     return rv;
 }
 
 NS_IMETHODIMP
 nsScriptSecurityManager::CanCreateInstance(JSContext *cx,
                                            const nsCID &aCID)
 {
-    nsresult rv = CheckXPCPermissions(nullptr, nullptr, nullptr, nullptr, nullptr);
+    nsresult rv = CheckXPCPermissions(cx, nullptr, nullptr, nullptr, nullptr);
     if (NS_FAILED(rv))
     {
         //-- Access denied, report an error
         nsAutoCString errorMsg("Permission denied to create instance of class. CID=");
         char cidStr[NSID_LENGTH];
         aCID.ToProvidedString(cidStr);
         errorMsg.Append(cidStr);
         SetPendingException(cx, errorMsg.get());
     }
     return rv;
 }
 
 NS_IMETHODIMP
 nsScriptSecurityManager::CanGetService(JSContext *cx,
                                        const nsCID &aCID)
 {
-    nsresult rv = CheckXPCPermissions(nullptr, nullptr, nullptr, nullptr, nullptr);
+    nsresult rv = CheckXPCPermissions(cx, nullptr, nullptr, nullptr, nullptr);
     if (NS_FAILED(rv))
     {
         //-- Access denied, report an error
         nsAutoCString errorMsg("Permission denied to get service. CID=");
         char cidStr[NSID_LENGTH];
         aCID.ToProvidedString(cidStr);
         errorMsg.Append(cidStr);
         SetPendingException(cx, errorMsg.get());
@@ -2267,16 +2267,17 @@ nsScriptSecurityManager::CanAccess(uint3
 }
 
 nsresult
 nsScriptSecurityManager::CheckXPCPermissions(JSContext* cx,
                                              nsISupports* aObj, JSObject* aJSObject,
                                              nsIPrincipal* aSubjectPrincipal,
                                              const char* aObjectSecurityLevel)
 {
+    MOZ_ASSERT(cx);
     JS::RootedObject jsObject(cx, aJSObject);
     // Check if the subject is privileged.
     if (SubjectIsPrivileged())
         return NS_OK;
 
     //-- If the object implements nsISecurityCheckedComponent, it has a non-default policy.
     if (aObjectSecurityLevel)
     {
--- a/content/canvas/test/Makefile.in
+++ b/content/canvas/test/Makefile.in
@@ -106,16 +106,17 @@ MOCHITEST_FILES = \
 	test_bug753758.html \
 	test_bug764125.html \
 	test_bug856472.html \
 	test_bug866575.html \
 	test_drawImage_edge_cases.html \
 	test_drawImage_document_domain.html \
   test_mozDashOffset.html \
 	file_drawImage_document_domain.html \
+	test_windingRuleUndefined.html \
 	$(NULL)
 
 ifneq (1_Linux,$(MOZ_SUITE)_$(OS_ARCH))
 # This test fails in Suite on Linux for some reason, disable it there
 MOCHITEST_FILES += test_2d.composite.uncovered.image.destination-atop.html
 endif
 
 # xor and lighter aren't well handled by cairo; they mostly work, but we don't want
new file mode 100644
--- /dev/null
+++ b/content/canvas/test/test_windingRuleUndefined.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=861938
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 861938</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=861938">Mozilla Bug 861938</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+  <script type="application/javascript">
+
+  /** Test for Bug 861938 **/
+
+  try {
+    var canvas = document.createElement("canvas");
+    canvas.getContext('2d').fill(undefined);
+
+    ok(true, "Should be able to pass undefined to fill()");
+  } catch(e) {
+    ok(false, "Should be able to pass undefined to fill()");
+  }
+  </script>
+</body>
+</html>
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -4242,35 +4242,35 @@ nsDocShell::DisplayLoadError(nsresult aE
                 rv = stss->IsStsURI(aURI, flags, &isStsHost);
                 NS_ENSURE_SUCCESS(rv, rv);
 
                 uint32_t bucketId;
                 if (isStsHost) {
                   cssClass.AssignLiteral("badStsCert");
                   //measuring STS separately allows us to measure click through
                   //rates easily
-                  bucketId = nsISecurityUITelemetry::WARNING_BAD_CERT_STS;
+                  bucketId = nsISecurityUITelemetry::WARNING_BAD_CERT_TOP_STS;
                 } else {
-                  bucketId = nsISecurityUITelemetry::WARNING_BAD_CERT;
+                  bucketId = nsISecurityUITelemetry::WARNING_BAD_CERT_TOP;
                 }
 
 
                 if (Preferences::GetBool(
                         "browser.xul.error_pages.expert_bad_cert", false)) {
                     cssClass.AssignLiteral("expertBadCert");
                 }
 
                 // See if an alternate cert error page is registered
                 nsAdoptingCString alternateErrorPage =
                     Preferences::GetCString(
                         "security.alternate_certificate_error_page");
                 if (alternateErrorPage)
                     errorPage.Assign(alternateErrorPage);
 
-                if (errorPage.EqualsIgnoreCase("certerror")) 
+                if (!IsFrame() && errorPage.EqualsIgnoreCase("certerror")) 
                     mozilla::Telemetry::Accumulate(mozilla::Telemetry::SECURITY_UI, bucketId);
 
             } else {
                 error.AssignLiteral("nssFailure2");
             }
         }
     } else if (NS_ERROR_PHISHING_URI == aError || NS_ERROR_MALWARE_URI == aError) {
         nsAutoCString host;
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -1497,31 +1497,31 @@ Navigator::GetMozTime(nsIDOMMozTimeManag
 }
 #endif
 
 //*****************************************************************************
 //    nsNavigator::nsIDOMNavigatorCamera
 //*****************************************************************************
 
 NS_IMETHODIMP
-Navigator::GetMozCameras(nsIDOMCameraManager** aCameraManager)
+Navigator::GetMozCameras(nsISupports** aCameraManager)
 {
   if (!mCameraManager) {
     nsCOMPtr<nsPIDOMWindow> win = do_QueryReferent(mWindow);
     NS_ENSURE_TRUE(win, NS_ERROR_FAILURE);
 
     if (!win->GetOuterWindow() || win->GetOuterWindow()->GetCurrentInnerWindow() != win) {
       return NS_ERROR_NOT_AVAILABLE;
     }
 
     mCameraManager = nsDOMCameraManager::CheckPermissionAndCreateInstance(win);
     NS_ENSURE_TRUE(mCameraManager, NS_OK);
   }
 
-  nsRefPtr<nsDOMCameraManager> cameraManager = mCameraManager;
+  nsCOMPtr<nsIObserver> cameraManager = mCameraManager.get();
   cameraManager.forget(aCameraManager);
 
   return NS_OK;
 }
 
 size_t
 Navigator::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf) const
 {
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -778,18 +778,16 @@ static nsDOMClassInfoData sClassInfoData
   NS_DEFINE_CLASSINFO_DATA(BluetoothManager, nsEventTargetSH,
                            EVENTTARGET_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(BluetoothAdapter, nsEventTargetSH,
                            EVENTTARGET_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(BluetoothDevice, nsEventTargetSH,
                            EVENTTARGET_SCRIPTABLE_FLAGS)
 #endif
 
-  NS_DEFINE_CLASSINFO_DATA(CameraManager, nsDOMGenericSH,
-                           DOM_DEFAULT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(CameraControl, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(CameraCapabilities, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
   NS_DEFINE_CLASSINFO_DATA(OpenWindowEventDetail, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(AsyncScrollEventDetail, nsDOMGenericSH,
@@ -1961,20 +1959,16 @@ nsDOMClassInfo::Init()
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMBluetoothAdapter)
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(BluetoothDevice, nsIDOMBluetoothDevice)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMBluetoothDevice)
   DOM_CLASSINFO_MAP_END
 #endif
 
-  DOM_CLASSINFO_MAP_BEGIN(CameraManager, nsIDOMCameraManager)
-    DOM_CLASSINFO_MAP_ENTRY(nsIDOMCameraManager)
-  DOM_CLASSINFO_MAP_END
-
   DOM_CLASSINFO_MAP_BEGIN(CameraControl, nsICameraControl)
     DOM_CLASSINFO_MAP_ENTRY(nsICameraControl)
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(CameraCapabilities, nsICameraCapabilities)
     DOM_CLASSINFO_MAP_ENTRY(nsICameraCapabilities)
   DOM_CLASSINFO_MAP_END
 
--- a/dom/base/nsDOMClassInfoClasses.h
+++ b/dom/base/nsDOMClassInfoClasses.h
@@ -177,17 +177,16 @@ DOMCI_CLASS(FMRadio)
 #endif
 
 #ifdef MOZ_B2G_BT
 DOMCI_CLASS(BluetoothManager)
 DOMCI_CLASS(BluetoothAdapter)
 DOMCI_CLASS(BluetoothDevice)
 #endif
 
-DOMCI_CLASS(CameraManager)
 DOMCI_CLASS(CameraControl)
 DOMCI_CLASS(CameraCapabilities)
 
 DOMCI_CLASS(OpenWindowEventDetail)
 DOMCI_CLASS(AsyncScrollEventDetail)
 
 DOMCI_CLASS(LockedFile)
 
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -153,16 +153,21 @@ DOMInterfaces = {
     'workers': True,
 }],
 
 'BatteryManager': {
     'nativeType': 'mozilla::dom::battery::BatteryManager',
     'headerFile': 'BatteryManager.h'
 },
 
+'CameraManager': {
+    'nativeType': 'nsDOMCameraManager',
+    'headerFile': 'DOMCameraManager.h'
+},
+
 'CanvasRenderingContext2D': {
     'implicitJSContext': [
         'createImageData', 'getImageData', 'strokeStyle',
         'fillStyle', 'mozDash'
     ],
     'resultNotAddRefed': [ 'canvas', 'measureText' ],
     'binaryNames': {
         'mozImageSmoothingEnabled': 'imageSmoothingEnabled',
@@ -1642,8 +1647,10 @@ addExternalIface('URI', nativeType='nsIU
                  notflattened=True)
 addExternalIface('UserDataHandler')
 addExternalIface('Window')
 addExternalIface('XPathResult', nativeType='nsISupports')
 addExternalIface('XPathExpression')
 addExternalIface('XPathNSResolver')
 addExternalIface('XULCommandDispatcher')
 addExternalIface('DataTransfer', notflattened=True)
+addExternalIface('GetCameraCallback', nativeType='nsICameraGetCameraCallback', headerFile='nsIDOMCameraManager.h')
+addExternalIface('CameraErrorCallback', nativeType='nsICameraErrorCallback', headerFile='nsIDOMCameraManager.h')
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -2428,17 +2428,18 @@ class JSToNativeConversionInfo():
           ${val} replaced by an expression for the JS::Value in question
           ${valPtr} is a pointer to the JS::Value in question
           ${valHandle} is a handle to the JS::Value in question
           ${valMutableHandle} is a mutable handle to the JS::Value in question
           ${holderName} replaced by the holder's name, if any
           ${declName} replaced by the declaration's name
           ${haveValue} replaced by an expression that evaluates to a boolean
                        for whether we have a JS::Value.  Only used when
-                       defaultValue is not None.
+                       defaultValue is not None or when True is passed for
+                       checkForValue to instantiateJSToNativeConversion.
 
         declType: A CGThing representing the native C++ type we're converting
                   to.  This is allowed to be None if the conversion code is
                   supposed to be used as-is.
 
         holderType: A CGThing representing the type of a "holder" which will
                     hold a possible reference to the C++ thing whose type we
                     returned in #1, or  None if no such holder is needed.
@@ -3192,28 +3193,29 @@ for (uint32_t i = 0; i < length; ++i) {
                                         holderType=holderType)
 
     if type.isString():
         assert not isEnforceRange and not isClamp
 
         treatAs = {
             "Default": "eStringify",
             "EmptyString": "eEmpty",
-            "Null": "eNull"
+            "Null": "eNull",
+            # For Missing it doesn't matter what we use here, since we'll never
+            # call ConvertJSValueToString on undefined in that case.
+            "Missing": "eStringify"
         }
         if type.nullable():
             # For nullable strings null becomes a null string.
             treatNullAs = "Null"
             # For nullable strings undefined becomes a null string unless
             # specified otherwise.
             if treatUndefinedAs == "Default":
                 treatUndefinedAs = "Null"
         nullBehavior = treatAs[treatNullAs]
-        if treatUndefinedAs == "Missing":
-            raise TypeError("We don't support [TreatUndefinedAs=Missing]")
         undefinedBehavior = treatAs[treatUndefinedAs]
 
         def getConversionCode(varName):
             conversionCode = (
                 "if (!ConvertJSValueToString(cx, ${valHandle}, ${valMutableHandle}, %s, %s, %s)) {\n"
                 "%s\n"
                 "}" % (nullBehavior, undefinedBehavior, varName,
                        exceptionCodeIndented.define()))
@@ -3554,32 +3556,31 @@ for (uint32_t i = 0; i < length; ++i) {
                              post=("\n"
                                    "} else {\n"
                                    "  %s = %s;\n"
                                    "}" % (writeLoc, defaultStr))).define()
 
     return JSToNativeConversionInfo(template, declType=declType,
                                     dealWithOptional=isOptional)
 
-def instantiateJSToNativeConversion(info, replacements, argcAndIndex=None):
+def instantiateJSToNativeConversion(info, replacements, checkForValue=False):
     """
     Take a JSToNativeConversionInfo as returned by getJSToNativeConversionInfo
     and a set of replacements as required by the strings in such an object, and
     generate code to convert into stack C++ types.
 
-    If argcAndIndex is not None it must be a dict that can be used to
-    replace ${argc} and ${index}, where ${index} is the index of this
-    argument (0-based) and ${argc} is the total number of arguments.
+    If checkForValue is True, then the conversion will get wrapped in
+    a check for ${haveValue}.
     """
     (templateBody, declType, holderType, dealWithOptional) = (
         info.template, info.declType, info.holderType, info.dealWithOptional)
 
-    if dealWithOptional and argcAndIndex is None:
+    if dealWithOptional and not checkForValue:
         raise TypeError("Have to deal with optional things, but don't know how")
-    if argcAndIndex is not None and declType is None:
+    if checkForValue and declType is None:
         raise TypeError("Need to predeclare optional things, so they will be "
                         "outside the check for big enough arg count!");
 
     # We can't precompute our holder constructor arguments, since
     # those might depend on ${declName}, which we change below.  Just
     # compute arguments at the point when we need them as we go.
     def getArgsCGThing(args):
         return CGGeneric(string.Template(args).substitute(replacements))
@@ -3618,17 +3619,17 @@ def instantiateJSToNativeConversion(info
             CGList([holderType, CGGeneric(" "),
                     CGGeneric(originalHolderName),
                     holderCtorArgs, CGGeneric(";")]))
 
     conversion = CGGeneric(
             string.Template(templateBody).substitute(replacements)
             )
 
-    if argcAndIndex is not None:
+    if checkForValue:
         if dealWithOptional:
             declConstruct = CGIndenter(
                 CGGeneric("%s.Construct(%s);" %
                           (originalDeclName,
                            getArgsCGThing(info.declArgs).define() if
                            info.declArgs else "")))
             if holderType is not None:
                 holderConstruct = CGIndenter(
@@ -3639,18 +3640,18 @@ def instantiateJSToNativeConversion(info
             else:
                 holderConstruct = None
         else:
             declConstruct = None
             holderConstruct = None
 
         conversion = CGList(
             [CGGeneric(
-                    string.Template("if (${index} < ${argc}) {").substitute(
-                        argcAndIndex
+                    string.Template("if (${haveValue}) {").substitute(
+                        replacements
                         )),
              declConstruct,
              holderConstruct,
              CGIndenter(conversion),
              CGGeneric("}")],
             "\n")
 
     result.append(conversion)
@@ -3705,19 +3706,22 @@ class CGArgumentConverter(CGThing):
         self.replacementVariables["valPtr"] = (
             "&" + self.replacementVariables["val"])
         self.replacementVariables["valHandle"] = (
             "JS::Handle<JS::Value>::fromMarkedLocation(%s)" %
             self.replacementVariables["valPtr"])
         self.replacementVariables["valMutableHandle"] = (
             "JS::MutableHandle<JS::Value>::fromMarkedLocation(%s)" %
             self.replacementVariables["valPtr"])
-        if argument.defaultValue:
-            self.replacementVariables["haveValue"] = string.Template(
-                "${index} < ${argc}").substitute(replacer)
+        haveValueCheck = string.Template("${index} < ${argc}").substitute(replacer)
+        if argument.treatUndefinedAs == "Missing":
+            haveValueCheck = (haveValueCheck +
+                              (" && !%s.isUndefined()" %
+                               self.replacementVariables["valHandle"]))
+        self.replacementVariables["haveValue"] = haveValueCheck
         self.descriptorProvider = descriptorProvider
         if self.argument.optional and not self.argument.defaultValue:
             self.argcAndIndex = replacer
         else:
             self.argcAndIndex = None
         self.invalidEnumValueFatal = invalidEnumValueFatal
         self.lenientFloatCode = lenientFloatCode
         self.allowTreatNonCallableAsNull = allowTreatNonCallableAsNull
@@ -3737,17 +3741,17 @@ class CGArgumentConverter(CGThing):
             lenientFloatCode=self.lenientFloatCode,
             isMember="Variadic" if self.argument.variadic else False,
             allowTreatNonCallableAsNull=self.allowTreatNonCallableAsNull)
 
         if not self.argument.variadic:
             return instantiateJSToNativeConversion(
                 typeConversion,
                 self.replacementVariables,
-                self.argcAndIndex).define()
+                self.argcAndIndex is not None).define()
 
         # Variadic arguments get turned into a sequence.
         if typeConversion.dealWithOptional:
             raise TypeError("Shouldn't have optional things in variadics")
         if typeConversion.holderType is not None:
             raise TypeError("Shouldn't need holders for variadics")
 
         replacer = dict(self.argcAndIndex, **self.replacementVariables)
@@ -4702,16 +4706,25 @@ class CGMethodCall(CGThing):
                 code = (
                     "if (argc < %d) {\n"
                     "  return ThrowErrorMessage(cx, MSG_MISSING_ARGUMENTS, %s);\n"
                     "}" % (requiredArgs, methodName))
                 self.cgRoot.prepend(
                     CGWrapper(CGIndenter(CGGeneric(code)), pre="\n", post="\n"))
             return
 
+        # We don't handle [TreatUndefinedAs=Missing] arguments in overload
+        # resolution yet.
+        for (_, sigArgs) in signatures:
+            for arg in sigArgs:
+                if arg.treatUndefinedAs == "Missing":
+                    raise TypeError("No support for [TreatUndefinedAs=Missing] "
+                                    "handling in overload resolution yet: %s" %
+                                    arg.location)
+
         # Need to find the right overload
         maxArgCount = method.maxArgCount
         allowedArgCounts = method.allowedArgCounts
 
         argCountCases = []
         for argCount in allowedArgCounts:
             possibleSignatures = method.signaturesForArgCount(argCount)
             if len(possibleSignatures) == 1:
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -393,35 +393,43 @@ class IDLObjectWithIdentifier(IDLObject)
                     raise WebIDLError("[TreatNullAs] is not allowed for "
                                       "dictionary members", [self.location])
                 if value != 'EmptyString':
                     raise WebIDLError("[TreatNullAs] must take the identifier "
                                       "'EmptyString', not '%s'" % value,
                                       [self.location])
                 self.treatNullAs = value
             elif identifier == "TreatUndefinedAs":
-                if not self.type.isString():
-                    raise WebIDLError("[TreatUndefinedAs] is only allowed on "
-                                      "arguments or attributes whose type is "
-                                      "DOMString or DOMString?",
-                                      [self.location])
                 if isDictionaryMember:
                     raise WebIDLError("[TreatUndefinedAs] is not allowed for "
                                       "dictionary members", [self.location])
-                if value == 'Null':
-                    if not self.type.nullable():
-                        raise WebIDLError("[TreatUndefinedAs=Null] is only "
-                                          "allowed on arguments whose type is "
-                                          "DOMString?", [self.location])
-                elif value == 'Missing':
+                if value == 'Missing':
                     if not isOptional:
                         raise WebIDLError("[TreatUndefinedAs=Missing] is only "
                                           "allowed on optional arguments",
                                           [self.location])
-                elif value != 'EmptyString':
+                elif value == 'Null':
+                    if not self.type.isString():
+                        raise WebIDLError("[TreatUndefinedAs=Null] is only "
+                                          "allowed on arguments or "
+                                          "attributes whose type is "
+                                          "DOMString or DOMString?",
+                                          [self.location])
+                    if not self.type.nullable():
+                        raise WebIDLError("[TreatUndefinedAs=Null] is only "
+                                          "allowed on arguments whose type "
+                                          "is DOMString?", [self.location])
+                elif value == 'EmptyString':
+                    if not self.type.isString():
+                        raise WebIDLError("[TreatUndefinedAs=EmptyString] "
+                                          "is only allowed on arguments or "
+                                          "attributes whose type is "
+                                          "DOMString or DOMString?",
+                                          [self.location])
+                else:
                     raise WebIDLError("[TreatUndefinedAs] must take the "
                                       "identifiers EmptyString or Null or "
                                       "Missing", [self.location])
                 self.treatUndefinedAs = value
             else:
                 unhandledAttrs.append(attr)
 
         return unhandledAttrs
--- a/dom/bindings/test/TestBindingHeader.h
+++ b/dom/bindings/test/TestBindingHeader.h
@@ -151,17 +151,19 @@ public:
 
   // Integer types
   int8_t ReadonlyByte();
   int8_t WritableByte();
   void SetWritableByte(int8_t);
   void PassByte(int8_t);
   int8_t ReceiveByte();
   void PassOptionalByte(const Optional<int8_t>&);
+  void PassOptionalUndefinedMissingByte(const Optional<int8_t>&);
   void PassOptionalByteWithDefault(int8_t);
+  void PassOptionalUndefinedMissingByteWithDefault(int8_t);
   void PassNullableByte(const Nullable<int8_t>&);
   void PassOptionalNullableByte(const Optional< Nullable<int8_t> >&);
   void PassVariadicByte(const Sequence<int8_t>&);
 
   int16_t ReadonlyShort();
   int16_t WritableShort();
   void SetWritableShort(int16_t);
   void PassShort(int16_t);
@@ -395,17 +397,19 @@ public:
   void PassFloat32Array(Float32Array&);
   void PassFloat64Array(Float64Array&);
   JSObject* ReceiveUint8Array(JSContext*);
 
   // String types
   void PassString(const nsAString&);
   void PassNullableString(const nsAString&);
   void PassOptionalString(const Optional<nsAString>&);
+  void PassOptionalUndefinedMissingString(const Optional<nsAString>&);
   void PassOptionalStringWithDefaultValue(const nsAString&);
+  void PassOptionalUndefinedMissingStringWithDefaultValue(const nsAString&);
   void PassOptionalNullableString(const Optional<nsAString>&);
   void PassOptionalNullableStringWithDefaultValue(const nsAString&);
   void PassVariadicString(const Sequence<nsString>&);
 
   // Enumerated types
   void PassEnum(TestEnum);
   void PassNullableEnum(const Nullable<TestEnum>&);
   void PassOptionalEnum(const Optional<TestEnum>&);
--- a/dom/bindings/test/TestCodeGen.webidl
+++ b/dom/bindings/test/TestCodeGen.webidl
@@ -113,17 +113,19 @@ interface OnlyForUseInConstructor {
 interface TestInterface {
   // Integer types
   // XXXbz add tests for throwing versions of all the integer stuff
   readonly attribute byte readonlyByte;
   attribute byte writableByte;
   void passByte(byte arg);
   byte receiveByte();
   void passOptionalByte(optional byte arg);
+  void passOptionalUndefinedMissingByte([TreatUndefinedAs=Missing] optional byte arg);
   void passOptionalByteWithDefault(optional byte arg = 0);
+  void passOptionalUndefinedMissingByteWithDefault([TreatUndefinedAs=Missing] optional byte arg = 0);
   void passNullableByte(byte? arg);
   void passOptionalNullableByte(optional byte? arg);
   void passVariadicByte(byte... arg);
 
   readonly attribute short readonlyShort;
   attribute short writableShort;
   void passShort(short arg);
   short receiveShort();
@@ -363,17 +365,19 @@ interface TestInterface {
   void passFloat32Array(Float32Array arg);
   void passFloat64Array(Float64Array arg);
   Uint8Array receiveUint8Array();
 
   // String types
   void passString(DOMString arg);
   void passNullableString(DOMString? arg);
   void passOptionalString(optional DOMString arg);
+  void passOptionalUndefinedMissingString([TreatUndefinedAs=Missing] optional DOMString arg);
   void passOptionalStringWithDefaultValue(optional DOMString arg = "abc");
+  void passOptionalUndefinedMissingStringWithDefaultValue([TreatUndefinedAs=Missing] optional DOMString arg = "abc");
   void passOptionalNullableString(optional DOMString? arg);
   void passOptionalNullableStringWithDefaultValue(optional DOMString? arg = null);
   void passVariadicString(DOMString... arg);
 
   // Enumerated types
   void passEnum(TestEnum arg);
   void passNullableEnum(TestEnum? arg);
   void passOptionalEnum(optional TestEnum arg);
--- a/dom/bindings/test/TestExampleGen.webidl
+++ b/dom/bindings/test/TestExampleGen.webidl
@@ -18,17 +18,19 @@
 interface TestExampleInterface {
   // Integer types
   // XXXbz add tests for throwing versions of all the integer stuff
   readonly attribute byte readonlyByte;
   attribute byte writableByte;
   void passByte(byte arg);
   byte receiveByte();
   void passOptionalByte(optional byte arg);
+  void passOptionalUndefinedMissingByte([TreatUndefinedAs=Missing] optional byte arg);
   void passOptionalByteWithDefault(optional byte arg = 0);
+  void passOptionalUndefinedMissingByteWithDefault([TreatUndefinedAs=Missing] optional byte arg = 0);
   void passNullableByte(byte? arg);
   void passOptionalNullableByte(optional byte? arg);
   void passVariadicByte(byte... arg);
 
   readonly attribute short readonlyShort;
   attribute short writableShort;
   void passShort(short arg);
   short receiveShort();
@@ -261,17 +263,19 @@ interface TestExampleInterface {
   void passFloat32Array(Float32Array arg);
   void passFloat64Array(Float64Array arg);
   Uint8Array receiveUint8Array();
 
   // String types
   void passString(DOMString arg);
   void passNullableString(DOMString? arg);
   void passOptionalString(optional DOMString arg);
+  void passOptionalUndefinedMissingString([TreatUndefinedAs=Missing] optional DOMString arg);
   void passOptionalStringWithDefaultValue(optional DOMString arg = "abc");
+  void passOptionalUndefinedMissingStringWithDefaultValue([TreatUndefinedAs=Missing] optional DOMString arg = "abc");
   void passOptionalNullableString(optional DOMString? arg);
   void passOptionalNullableStringWithDefaultValue(optional DOMString? arg = null);
   void passVariadicString(DOMString... arg);
 
   // Enumerated types
   void passEnum(TestEnum arg);
   void passNullableEnum(TestEnum? arg);
   void passOptionalEnum(optional TestEnum arg);
--- a/dom/bindings/test/TestJSImplGen.webidl
+++ b/dom/bindings/test/TestJSImplGen.webidl
@@ -30,17 +30,19 @@ enum MyTestEnum {
 interface TestJSImplInterface {
   // Integer types
   // XXXbz add tests for throwing versions of all the integer stuff
   readonly attribute byte readonlyByte;
   attribute byte writableByte;
   void passByte(byte arg);
   byte receiveByte();
   void passOptionalByte(optional byte arg);
+  void passOptionalUndefinedMissingByte([TreatUndefinedAs=Missing] optional byte arg);
   void passOptionalByteWithDefault(optional byte arg = 0);
+  void passOptionalUndefinedMissingByteWithDefault([TreatUndefinedAs=Missing] optional byte arg = 0);
   void passNullableByte(byte? arg);
   void passOptionalNullableByte(optional byte? arg);
   void passVariadicByte(byte... arg);
 
   readonly attribute short readonlyShort;
   attribute short writableShort;
   void passShort(short arg);
   short receiveShort();
@@ -284,17 +286,19 @@ interface TestJSImplInterface {
   //void passFloat32Array(Float32Array arg);
   //void passFloat64Array(Float64Array arg);
   //Uint8Array receiveUint8Array();
 
   // String types
   void passString(DOMString arg);
   void passNullableString(DOMString? arg);
   void passOptionalString(optional DOMString arg);
+  void passOptionalUndefinedMissingString([TreatUndefinedAs=Missing] optional DOMString arg);
   void passOptionalStringWithDefaultValue(optional DOMString arg = "abc");
+  void passOptionalUndefinedMissingStringWithDefaultValue([TreatUndefinedAs=Missing] optional DOMString arg = "abc");
   void passOptionalNullableString(optional DOMString? arg);
   void passOptionalNullableStringWithDefaultValue(optional DOMString? arg = null);
   void passVariadicString(DOMString... arg);
 
   // Enumerated types
   void passEnum(MyTestEnum arg);
   void passNullableEnum(MyTestEnum? arg);
   void passOptionalEnum(optional MyTestEnum arg);
--- a/dom/camera/DOMCameraManager.cpp
+++ b/dom/camera/DOMCameraManager.cpp
@@ -1,36 +1,34 @@
 /* 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 "nsContentUtils.h"
 #include "nsDebug.h"
 #include "nsPIDOMWindow.h"
 #include "mozilla/Services.h"
 #include "nsObserverService.h"
 #include "nsIPermissionManager.h"
 #include "DOMCameraControl.h"
 #include "DOMCameraManager.h"
 #include "nsDOMClassInfo.h"
 #include "DictionaryHelpers.h"
 #include "CameraCommon.h"
+#include "mozilla/dom/CameraManagerBinding.h"
 
 using namespace mozilla;
-using namespace dom;
+using namespace mozilla::dom;
 
-DOMCI_DATA(CameraManager, nsIDOMCameraManager)
-
-NS_IMPL_CYCLE_COLLECTION_1(nsDOMCameraManager,
-                           mCameraThread)
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_1(nsDOMCameraManager, mWindow)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMCameraManager)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
-  NS_INTERFACE_MAP_ENTRY(nsIDOMCameraManager)
   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
-  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CameraManager)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMCameraManager)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMCameraManager)
 
 /**
  * Global camera logging object
  *
@@ -50,22 +48,24 @@ GetCameraLog()
  * nsDOMCameraManager::GetListOfCameras
  * is implementation-specific, and can be found in (e.g.)
  * GonkCameraManager.cpp and FallbackCameraManager.cpp.
  */
 
 WindowTable nsDOMCameraManager::sActiveWindows;
 bool nsDOMCameraManager::sActiveWindowsInitialized = false;
 
-nsDOMCameraManager::nsDOMCameraManager(uint64_t aWindowId)
-  : mWindowId(aWindowId)
+nsDOMCameraManager::nsDOMCameraManager(nsPIDOMWindow* aWindow)
+  : mWindowId(aWindow->WindowID())
   , mCameraThread(nullptr)
+  , mWindow(aWindow)
 {
   /* member initializers and constructor code */
   DOM_CAMERA_LOGT("%s:%d : this=%p, windowId=%llx\n", __func__, __LINE__, this, mWindowId);
+  SetIsDOMBinding();
 }
 
 nsDOMCameraManager::~nsDOMCameraManager()
 {
   /* destructor code */
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   obs->RemoveObserver(this, "xpcom-shutdown");
@@ -88,53 +88,51 @@ nsDOMCameraManager::CheckPermissionAndCr
 
   // Initialize the shared active window tracker
   if (!sActiveWindowsInitialized) {
     sActiveWindows.Init();
     sActiveWindowsInitialized = true;
   }
 
   nsRefPtr<nsDOMCameraManager> cameraManager =
-    new nsDOMCameraManager(aWindow->WindowID());
+    new nsDOMCameraManager(aWindow);
 
   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   obs->AddObserver(cameraManager, "xpcom-shutdown", true);
 
   return cameraManager.forget();
 }
 
-/* [implicit_jscontext] void getCamera ([optional] in jsval aOptions, in nsICameraGetCameraCallback onSuccess, [optional] in nsICameraErrorCallback onError); */
-NS_IMETHODIMP
-nsDOMCameraManager::GetCamera(const JS::Value& aOptions, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, JSContext* cx)
+void
+nsDOMCameraManager::GetCamera(const CameraSelector& aOptions,
+                              nsICameraGetCameraCallback* onSuccess,
+                              const Optional<nsICameraErrorCallback*>& onError,
+                              ErrorResult& aRv)
 {
-  NS_ENSURE_TRUE(onSuccess, NS_ERROR_INVALID_ARG);
-
   uint32_t cameraId = 0;  // back (or forward-facing) camera by default
-  mozilla::idl::CameraSelector selector;
-
-  nsresult rv = selector.Init(cx, &aOptions);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  if (selector.camera.EqualsASCII("front")) {
+  if (aOptions.mCamera.EqualsLiteral("front")) {
     cameraId = 1;
   }
 
   // reuse the same camera thread to conserve resources
   if (!mCameraThread) {
-    rv = NS_NewThread(getter_AddRefs(mCameraThread));
-    NS_ENSURE_SUCCESS(rv, rv);
+    aRv = NS_NewThread(getter_AddRefs(mCameraThread));
+    if (aRv.Failed()) {
+      return;
+    }
   }
 
   DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
 
   // Creating this object will trigger the onSuccess handler
-  nsCOMPtr<nsDOMCameraControl> cameraControl = new nsDOMCameraControl(cameraId, mCameraThread, onSuccess, onError, mWindowId);
+  nsCOMPtr<nsDOMCameraControl> cameraControl =
+    new nsDOMCameraControl(cameraId, mCameraThread,
+                           onSuccess, onError.WasPassed() ? onError.Value() : nullptr, mWindowId);
 
   Register(cameraControl);
-  return NS_OK;
 }
 
 void
 nsDOMCameraManager::Register(nsDOMCameraControl* aDOMCameraControl)
 {
   DOM_CAMERA_LOGI(">>> Register( aDOMCameraControl = %p ) mWindowId = 0x%llx\n", aDOMCameraControl, mWindowId);
   MOZ_ASSERT(NS_IsMainThread());
 
@@ -202,8 +200,14 @@ nsDOMCameraManager::IsWindowStillActive(
   MOZ_ASSERT(NS_IsMainThread());
 
   if (!sActiveWindowsInitialized) {
     return false;
   }
 
   return !!sActiveWindows.Get(aWindowId);
 }
+
+JSObject*
+nsDOMCameraManager::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
+{
+  return CameraManagerBinding::Wrap(aCx, aScope, this);
+}
--- a/dom/camera/DOMCameraManager.h
+++ b/dom/camera/DOMCameraManager.h
@@ -2,72 +2,90 @@
 /* vim: set ts=2 et sw=2 tw=40: */
 /* 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 DOM_CAMERA_DOMCAMERAMANAGER_H
 #define DOM_CAMERA_DOMCAMERAMANAGER_H
 
+#include "mozilla/dom/BindingDeclarations.h"
 #include "nsCOMPtr.h"
 #include "nsAutoPtr.h"
 #include "nsIThread.h"
 #include "nsIObserver.h"
 #include "nsThreadUtils.h"
 #include "nsHashKeys.h"
+#include "nsWrapperCache.h"
 #include "nsWeakReference.h"
 #include "nsClassHashtable.h"
 #include "nsIDOMCameraManager.h"
 #include "nsCycleCollectionParticipant.h"
 #include "mozilla/Attributes.h"
 
 class nsPIDOMWindow;
 
 namespace mozilla {
+  class ErrorResult;
 class nsDOMCameraControl;
+namespace dom {
+class CameraSelector;
+}
 }
 
 typedef nsTArray<nsRefPtr<mozilla::nsDOMCameraControl> > CameraControls;
 typedef nsClassHashtable<nsUint64HashKey, CameraControls> WindowTable;
 
 class nsDOMCameraManager MOZ_FINAL
-  : public nsIDOMCameraManager
-  , public nsIObserver
+  : public nsIObserver
   , public nsSupportsWeakReference
+  , public nsWrapperCache
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsDOMCameraManager, nsIObserver)
-  NS_DECL_NSIDOMCAMERAMANAGER
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsDOMCameraManager,
+                                                         nsIObserver)
   NS_DECL_NSIOBSERVER
 
   static already_AddRefed<nsDOMCameraManager>
     CheckPermissionAndCreateInstance(nsPIDOMWindow* aWindow);
   static bool IsWindowStillActive(uint64_t aWindowId);
 
   void Register(mozilla::nsDOMCameraControl* aDOMCameraControl);
   void OnNavigation(uint64_t aWindowId);
 
   nsresult GetNumberOfCameras(int32_t& aDeviceCount);
   nsresult GetCameraName(uint32_t aDeviceNum, nsCString& aDeviceName);
 
+  // WebIDL
+  void GetCamera(const mozilla::dom::CameraSelector& aOptions,
+                 nsICameraGetCameraCallback* aCallback,
+                 const mozilla::dom::Optional<nsICameraErrorCallback*>& ErrorCallback,
+                 mozilla::ErrorResult& aRv);
+  void GetListOfCameras(nsTArray<nsString>& aList, mozilla::ErrorResult& aRv);
+
+  nsPIDOMWindow* GetParentObject() const { return mWindow; }
+  virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
+    MOZ_OVERRIDE;
+
 protected:
   void XpComShutdown();
   void Shutdown(uint64_t aWindowId);
   ~nsDOMCameraManager();
 
 private:
   nsDOMCameraManager() MOZ_DELETE;
-  nsDOMCameraManager(uint64_t aWindowId);
+  nsDOMCameraManager(nsPIDOMWindow* aWindow);
   nsDOMCameraManager(const nsDOMCameraManager&) MOZ_DELETE;
   nsDOMCameraManager& operator=(const nsDOMCameraManager&) MOZ_DELETE;
 
 protected:
   uint64_t mWindowId;
   nsCOMPtr<nsIThread> mCameraThread;
+  nsCOMPtr<nsPIDOMWindow> mWindow;
   /**
    * 'mActiveWindows' is only ever accessed while in the main thread,
    * so it is not otherwise protected.
    */
   static WindowTable sActiveWindows;
   static bool sActiveWindowsInitialized;
 };
 
--- a/dom/camera/FallbackCameraManager.cpp
+++ b/dom/camera/FallbackCameraManager.cpp
@@ -1,25 +1,28 @@
 /* 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 "DOMCameraManager.h"
 
+#include "mozilla/ErrorResult.h"
+
+using namespace mozilla;
+
 // From nsDOMCameraManager.
 nsresult
 nsDOMCameraManager::GetNumberOfCameras(int32_t& aDeviceCount)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 };
 
 nsresult
 nsDOMCameraManager::GetCameraName(uint32_t aDeviceNum, nsCString& aDeviceName)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
-/* void getListOfCameras ([optional] out unsigned long aCount, [array, size_is (aCount), retval] out string aCameras); */
-NS_IMETHODIMP
-nsDOMCameraManager::GetListOfCameras(uint32_t *aCount, char * **aCameras)
+void
+nsDOMCameraManager::GetListOfCameras(nsTArray<nsString>& aList, ErrorResult& aRv)
 {
-  return NS_ERROR_NOT_IMPLEMENTED;
+  aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
 }
--- a/dom/camera/GonkCameraManager.cpp
+++ b/dom/camera/GonkCameraManager.cpp
@@ -15,16 +15,19 @@
  */
 
 #include <camera/Camera.h>
 
 #include "jsapi.h"
 #include "GonkCameraControl.h"
 #include "DOMCameraManager.h"
 #include "CameraCommon.h"
+#include "mozilla/ErrorResult.h"
+
+using namespace mozilla;
 
 // From nsDOMCameraManager, but gonk-specific!
 nsresult
 nsDOMCameraManager::GetNumberOfCameras(int32_t& aDeviceCount)
 {
   aDeviceCount = android::Camera::getNumberOfCameras();
   return NS_OK;
 }
@@ -58,75 +61,52 @@ nsDOMCameraManager::GetCameraName(uint32
     default:
       aDeviceName.Assign("extra-camera-");
       aDeviceName.AppendInt(aDeviceNum);
       break;
   }
   return NS_OK;
 }
 
-/* void getListOfCameras ([optional] out unsigned long aCount, [array, size_is (aCount), retval] out string aCameras); */
-NS_IMETHODIMP
-nsDOMCameraManager::GetListOfCameras(uint32_t *aCount, char * **aCameras)
+void
+nsDOMCameraManager::GetListOfCameras(nsTArray<nsString>& aList, ErrorResult& aRv)
 {
   int32_t count = android::Camera::getNumberOfCameras();
+  if (count <= 0) {
+    return;
+  }
 
-  DOM_CAMERA_LOGI("GetListOfCameras : getNumberOfCameras() returned %d\n", count);
-  if (count < 1) {
-    *aCameras = nullptr;
-    *aCount = 0;
-    return NS_OK;
-  }
+  DOM_CAMERA_LOGI("getListOfCameras : getNumberOfCameras() returned %d\n", count);
 
   // Allocate 2 extra slots to reserve space for 'front' and 'back' cameras
   // at the front of the array--we will collapse any empty slots below.
-  int32_t arraySize = count + 2;
-  char** cameras = static_cast<char**>(NS_Alloc(arraySize * sizeof(char*)));
-  for (int32_t i = 0; i < arraySize; ++i) {
-    cameras[i] = nullptr;
-  }
-
-  uint32_t extraIndex = 2;
-  bool gotFront = false;
-  bool gotBack = false;
-
-  for (int32_t i = 0; i < count; ++i) {
+  aList.SetLength(2);
+  uint32_t extraIdx = 2;
+  bool gotFront = false, gotBack = false;
+  while (count--) {
     nsCString cameraName;
-    nsresult result = GetCameraName(i, cameraName);
+    nsresult result = GetCameraName(count, cameraName);
     if (result != NS_OK) {
       continue;
     }
 
     // The first camera we find named 'back' gets slot 0; and the first
     // we find named 'front' gets slot 1.  All others appear after these.
-    uint32_t index;
-    if (!gotBack && !cameraName.Compare("back")) {
-      index = 0;
+    if (cameraName.EqualsLiteral("back")) {
+      CopyUTF8toUTF16(cameraName, aList[0]);
       gotBack = true;
-    } else if (!gotFront && !cameraName.Compare("front")) {
-      index = 1;
+    } else if (cameraName.EqualsLiteral("front")) {
+      CopyUTF8toUTF16(cameraName, aList[1]);
       gotFront = true;
     } else {
-      index = extraIndex++;
+      CopyUTF8toUTF16(cameraName, *aList.InsertElementAt(extraIdx));
+      extraIdx++;
     }
-
-    MOZ_ASSERT(index < arraySize);
-    cameras[index] = ToNewCString(cameraName);
   }
 
-  // Make a forward pass over the array to compact it; after this loop,
-  // 'offset' will contain the number of nullptrs in the array, which
-  // we use to adjust the value returned in 'aCount'.
-  int32_t offset = 0;
-  for (int32_t i = 0; i < arraySize; ++i) {
-    if (cameras[i] == nullptr) {
-      offset++;
-    } else if (offset != 0) {
-      cameras[i - offset] = cameras[i];
-      cameras[i] = nullptr;
-    }
+  if (!gotFront) {
+    aList.RemoveElementAt(1);
   }
-  MOZ_ASSERT(offset >= 2);
-
-  *aCameras = cameras;
-  *aCount = arraySize - offset;
-  return NS_OK;
+  
+  if (!gotBack) {
+    aList.RemoveElementAt(0);
+  }
 }
--- a/dom/camera/nsIDOMCameraManager.idl
+++ b/dom/camera/nsIDOMCameraManager.idl
@@ -31,21 +31,16 @@ dictionary CameraRegion {
    'NaN' indicates the information is not available. */
 dictionary CameraPosition {
     double latitude;
     double longitude;
     double altitude;
     double timestamp;
 };
 
-/* Select a camera to use. */
-dictionary CameraSelector {
-    DOMString camera = "back";
-};
-
 [scriptable, uuid(177472c9-f83d-48b5-8782-03b43b27f25d)]
 interface nsICameraCapabilities : nsISupports
 {
     /* an array of objects with 'height' and 'width' properties
        supported for the preview stream */
     [implicit_jscontext]
     readonly attribute jsval        previewSizes;
 
@@ -373,26 +368,8 @@ interface nsICameraControl : nsISupports
     [binaryname(ReleaseHardware)] void release([optional] in nsICameraReleaseCallback onSuccess, [optional] in nsICameraErrorCallback onError);
 };
 
 [scriptable, function, uuid(a267afbc-d91c-413a-8de5-0b94aecffa3e)]
 interface nsICameraGetCameraCallback : nsISupports
 {
     void handleEvent(in nsICameraControl camera);
 };
-
-[scriptable, uuid(f01aabcd-b822-423a-b077-3d0748296428)]
-interface nsIDOMCameraManager : nsISupports
-{
-    /* get a camera instance; options will be used to specify which
-       camera to get from the list returned by getListOfCameras(), e.g.:
-        {
-            camera: "front"
-        }
-    */
-    [implicit_jscontext]
-    void getCamera([optional] in jsval aOptions, in nsICameraGetCameraCallback onSuccess, [optional] in nsICameraErrorCallback onError);
-
-    /* return an array of camera identifiers, e.g.
-        [ "front", "back" ]
-    */
-    void getListOfCameras([optional] out unsigned long aCount, [retval, array, size_is(aCount)] out string aCameras);
-};
--- a/dom/camera/nsIDOMNavigatorCamera.idl
+++ b/dom/camera/nsIDOMNavigatorCamera.idl
@@ -3,13 +3,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
 interface nsIDOMCameraManager;
 
-[scriptable, uuid(bbb2456a-a6c8-42c8-8f52-6de071097e4b)]
+[scriptable,
+uuid(46a5710b-8dfb-430e-8705-90bea31c55aa)]
 interface nsIDOMNavigatorCamera : nsISupports
 {
-  readonly attribute nsIDOMCameraManager mozCameras;
+  readonly attribute nsISupports mozCameras;
 };
--- a/dom/webidl/CameraManager.webidl
+++ b/dom/webidl/CameraManager.webidl
@@ -1,9 +1,10 @@
 /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/.
  */
 
 dictionary CameraPictureOptions {
 
   /* an object with a combination of 'height' and 'width' properties
@@ -41,8 +42,34 @@ dictionary CameraPictureOptions {
 
   /* the number of seconds from January 1, 1970 UTC.  This can be
      different from the positional timestamp (above). */
   // XXXbz this should really accept a date too, no?
   long long dateTime = 0;
 };
 
 // If we start using CameraPictureOptions here, remove it from DummyBinding.
+
+interface GetCameraCallback;
+interface CameraErrorCallback;
+
+/* Select a camera to use. */
+dictionary CameraSelector {
+    DOMString camera = "back";
+};
+
+interface CameraManager {
+    /* get a camera instance; options will be used to specify which
+       camera to get from the list returned by getListOfCameras(), e.g.:
+        {
+            camera: "front"
+        }
+    */
+  [Throws]
+  void getCamera(CameraSelector options, GetCameraCallback callback,
+                 optional CameraErrorCallback errorCallback);
+
+  /* return an array of camera   identifiers, e.g.
+     [ "front", "back" ]
+   */
+  [Throws]
+  sequence<DOMString> getListOfCameras();
+};
--- a/dom/webidl/CanvasRenderingContext2D.webidl
+++ b/dom/webidl/CanvasRenderingContext2D.webidl
@@ -71,30 +71,30 @@ interface CanvasRenderingContext2D {
   void clearRect(double x, double y, double w, double h);
   [LenientFloat]
   void fillRect(double x, double y, double w, double h);
   [LenientFloat]
   void strokeRect(double x, double y, double w, double h);
 
   // path API (see also CanvasPathMethods)
   void beginPath();
-  void fill(optional CanvasWindingRule winding = "nonzero");
+  void fill([TreatUndefinedAs=Missing] optional CanvasWindingRule winding = "nonzero");
 // NOT IMPLEMENTED  void fill(Path path);
   void stroke();
 // NOT IMPLEMENTED  void stroke(Path path);
 // NOT IMPLEMENTED  void drawSystemFocusRing(Element element);
 // NOT IMPLEMENTED  void drawSystemFocusRing(Path path, Element element);
 // NOT IMPLEMENTED  boolean drawCustomFocusRing(Element element);
 // NOT IMPLEMENTED  boolean drawCustomFocusRing(Path path, Element element);
 // NOT IMPLEMENTED  void scrollPathIntoView();
 // NOT IMPLEMENTED  void scrollPathIntoView(Path path);
-  void clip(optional CanvasWindingRule winding = "nonzero");
+  void clip([TreatUndefinedAs=Missing] optional CanvasWindingRule winding = "nonzero");
 // NOT IMPLEMENTED  void clip(Path path);
 // NOT IMPLEMENTED  void resetClip();
-  boolean isPointInPath(unrestricted double x, unrestricted double y, optional CanvasWindingRule winding = "nonzero");
+  boolean isPointInPath(unrestricted double x, unrestricted double y, [TreatUndefinedAs=Missing] optional CanvasWindingRule winding = "nonzero");
 // NOT IMPLEMENTED  boolean isPointInPath(Path path, unrestricted double x, unrestricted double y);
   boolean isPointInStroke(double x, double y);
 
   // text (see also the CanvasDrawingStyles interface)
   [Throws, LenientFloat]
   void fillText(DOMString text, double x, double y, optional double maxWidth);
   [Throws, LenientFloat]
   void strokeText(DOMString text, double x, double y, optional double maxWidth);
--- a/dom/webidl/WebIDL.mk
+++ b/dom/webidl/WebIDL.mk
@@ -23,28 +23,28 @@ webidl_files = \
   AudioParam.webidl \
   AudioStreamTrack.webidl \
   AudioProcessingEvent.webidl \
   BarProp.webidl \
   BatteryManager.webidl \
   BeforeUnloadEvent.webidl \
   BiquadFilterNode.webidl \
   Blob.webidl \
+  CameraManager.webidl \
   CanvasRenderingContext2D.webidl \
   CaretPosition.webidl \
   CDATASection.webidl \
   CFStateChangeEvent.webidl \
   ChannelMergerNode.webidl \
   ChannelSplitterNode.webidl \
   CharacterData.webidl \
   ChildNode.webidl \
   ClientRect.webidl \
   ClientRectList.webidl \
   ClipboardEvent.webidl \
-  CameraManager.webidl \
   CommandEvent.webidl \
   Comment.webidl \
   CompositionEvent.webidl \
   Coordinates.webidl \
   CSS.webidl \
   CSSPrimitiveValue.webidl \
   CSSStyleDeclaration.webidl \
   CSSStyleSheet.webidl \
new file mode 100644
--- /dev/null
+++ b/editor/AsyncSpellCheckTestHelper.jsm
@@ -0,0 +1,86 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = [
+  "onSpellCheck",
+];
+
+const SPELL_CHECK_ENDED_TOPIC = "inlineSpellChecker-spellCheck-ended";
+const SPELL_CHECK_STARTED_TOPIC = "inlineSpellChecker-spellCheck-started";
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+/**
+ * Waits until spell checking has stopped on the given element.
+ *
+ * When a spell check is pending, this waits indefinitely until the spell check
+ * ends.  When a spell check is not pending, it waits a small number of turns of
+ * the event loop: if a spell check begins, it resumes waiting indefinitely for
+ * the end, and otherwise it stops waiting and calls the callback.
+ *
+ * This this can therefore trap spell checks that have not started at the time
+ * of calling, spell checks that have already started, multiple consecutive
+ * spell checks, and the absence of spell checks altogether.
+ *
+ * @param editableElement  The element being spell checked.
+ * @param callback         Called when spell check has completed or enough turns
+ *                         of the event loop have passed to determine it has not
+ *                         started.
+ */
+function onSpellCheck(editableElement, callback) {
+  let editor = editableElement.editor;
+  if (!editor) {
+    let win = editableElement.ownerDocument.defaultView;
+    editor = win.QueryInterface(Ci.nsIInterfaceRequestor).
+                 getInterface(Ci.nsIWebNavigation).
+                 QueryInterface(Ci.nsIInterfaceRequestor).
+                 getInterface(Ci.nsIEditingSession).
+                 getEditorForWindow(win);
+  }
+  if (!editor)
+    throw new Error("Unable to find editor for element " + editableElement);
+
+  try {
+    // False is important here.  Pass false so that the inline spell checker
+    // isn't created if it doesn't already exist.
+    var isc = editor.getInlineSpellChecker(false);
+  }
+  catch (err) {
+    // getInlineSpellChecker throws if spell checking is not enabled instead of
+    // just returning null, which seems kind of lame.  (Spell checking is not
+    // enabled on Android.)  The point here is only to determine whether spell
+    // check is pending, and if getInlineSpellChecker throws, then it's not
+    // pending.
+  }
+  let waitingForEnded = isc && isc.spellCheckPending;
+  let count = 0;
+
+  function observe(subj, topic, data) {
+    if (subj != editor)
+      return;
+    count = 0;
+    let expectedTopic = waitingForEnded ? SPELL_CHECK_ENDED_TOPIC :
+                        SPELL_CHECK_STARTED_TOPIC;
+    if (topic != expectedTopic)
+      Cu.reportError("Expected " + expectedTopic + " but got " + topic + "!");
+    waitingForEnded = !waitingForEnded;
+  }
+
+  let os = Cc["@mozilla.org/observer-service;1"].
+           getService(Ci.nsIObserverService);
+  os.addObserver(observe, SPELL_CHECK_STARTED_TOPIC, false);
+  os.addObserver(observe, SPELL_CHECK_ENDED_TOPIC, false);
+
+  let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+  timer.init(function tick() {
+    // Wait an arbitrarily large number -- 50 -- turns of the event loop before
+    // declaring that no spell checks will start.
+    if (waitingForEnded || ++count < 50)
+      return;
+    timer.cancel();
+    os.removeObserver(observe, SPELL_CHECK_STARTED_TOPIC);
+    os.removeObserver(observe, SPELL_CHECK_ENDED_TOPIC);
+    callback();
+  }, 0, Ci.nsITimer.TYPE_REPEATING_SLACK);
+};
--- a/editor/Makefile.in
+++ b/editor/Makefile.in
@@ -6,10 +6,14 @@
 DEPTH            := @DEPTH@
 topsrcdir        := @top_srcdir@
 srcdir           := @srcdir@
 VPATH            := @srcdir@
 FAIL_ON_WARNINGS := 1
 
 include $(DEPTH)/config/autoconf.mk
 
+EXTRA_JS_MODULES = \
+  AsyncSpellCheckTestHelper.jsm \
+  $(NULL)
+
 include $(topsrcdir)/config/rules.mk
 
--- a/editor/composer/src/nsEditorSpellCheck.cpp
+++ b/editor/composer/src/nsEditorSpellCheck.cpp
@@ -14,16 +14,17 @@
 #include "nsComponentManagerUtils.h"    // for do_CreateInstance
 #include "nsDebug.h"                    // for NS_ENSURE_TRUE, etc
 #include "nsDependentSubstring.h"       // for Substring
 #include "nsEditorSpellCheck.h"
 #include "nsError.h"                    // for NS_ERROR_NOT_INITIALIZED, etc
 #include "nsIChromeRegistry.h"          // for nsIXULChromeRegistry
 #include "nsIContent.h"                 // for nsIContent
 #include "nsIContentPrefService.h"      // for nsIContentPrefService, etc
+#include "nsIContentPrefService2.h"     // for nsIContentPrefService2, etc
 #include "nsIDOMDocument.h"             // for nsIDOMDocument
 #include "nsIDOMElement.h"              // for nsIDOMElement
 #include "nsIDOMRange.h"                // for nsIDOMRange
 #include "nsIDocument.h"                // for nsIDocument
 #include "nsIEditor.h"                  // for nsIEditor
 #include "nsIHTMLEditor.h"              // for nsIHTMLEditor
 #include "nsILoadContext.h"
 #include "nsISelection.h"               // for nsISelection
@@ -57,44 +58,21 @@ class UpdateDictionnaryHolder {
       if (mSpellCheck) {
         mSpellCheck->EndUpdateDictionary();
       }
     }
 };
 
 #define CPS_PREF_NAME NS_LITERAL_STRING("spellcheck.lang")
 
-class LastDictionary MOZ_FINAL {
-public:
-  /**
-   * Store current dictionary for editor document url. Use content pref
-   * service.
-   */
-  NS_IMETHOD StoreCurrentDictionary(nsIEditor* aEditor, const nsAString& aDictionary);
-
-  /**
-   * Get last stored current dictionary for editor document url.
-   */
-  NS_IMETHOD FetchLastDictionary(nsIEditor* aEditor, nsAString& aDictionary);
-
-  /**
-   * Forget last current dictionary stored for editor document url.
-   */
-  NS_IMETHOD ClearCurrentDictionary(nsIEditor* aEditor);
-
-  /**
-   * get uri of editor's document.
-   *
-   */
-  static nsresult GetDocumentURI(nsIEditor* aEditor, nsIURI * *aURI);
-};
-
-// static
-nsresult
-LastDictionary::GetDocumentURI(nsIEditor* aEditor, nsIURI * *aURI)
+/**
+ * Gets the URI of aEditor's document.
+ */
+static nsresult
+GetDocumentURI(nsIEditor* aEditor, nsIURI * *aURI)
 {
   NS_ENSURE_ARG_POINTER(aEditor);
   NS_ENSURE_ARG_POINTER(aURI);
 
   nsCOMPtr<nsIDOMDocument> domDoc;
   aEditor->GetDocument(getter_AddRefs(domDoc));
   NS_ENSURE_TRUE(domDoc, NS_ERROR_FAILURE);
 
@@ -119,100 +97,150 @@ GetLoadContext(nsIEditor* aEditor)
   nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
   NS_ENSURE_TRUE(doc, nullptr);
 
   nsCOMPtr<nsISupports> container = doc->GetContainer();
   nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(container);
   return loadContext.forget();
 }
 
+/**
+ * Fetches the dictionary stored in content prefs and maintains state during the
+ * fetch, which is asynchronous.
+ */
+class DictionaryFetcher MOZ_FINAL : public nsIContentPrefCallback2
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  DictionaryFetcher(nsEditorSpellCheck* aSpellCheck,
+                    nsIEditorSpellCheckCallback* aCallback,
+                    uint32_t aGroup)
+    : mCallback(aCallback), mGroup(aGroup), mSpellCheck(aSpellCheck) {}
+
+  NS_IMETHOD Fetch(nsIEditor* aEditor);
+
+  NS_IMETHOD HandleResult(nsIContentPref* aPref)
+  {
+    nsCOMPtr<nsIVariant> value;
+    nsresult rv = aPref->GetValue(getter_AddRefs(value));
+    NS_ENSURE_SUCCESS(rv, rv);
+    value->GetAsAString(mDictionary);
+    return NS_OK;
+  }
+
+  NS_IMETHOD HandleCompletion(uint16_t reason)
+  {
+    mSpellCheck->DictionaryFetched(this);
+    return NS_OK;
+  }
+
+  NS_IMETHOD HandleError(nsresult error)
+  {
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIEditorSpellCheckCallback> mCallback;
+  uint32_t mGroup;
+  nsString mRootContentLang;
+  nsString mRootDocContentLang;
+  nsString mDictionary;
+
+private:
+  nsCOMPtr<nsEditorSpellCheck> mSpellCheck;
+};
+NS_IMPL_ISUPPORTS1(DictionaryFetcher, nsIContentPrefCallback2)
+
 NS_IMETHODIMP
-LastDictionary::FetchLastDictionary(nsIEditor* aEditor, nsAString& aDictionary)
+DictionaryFetcher::Fetch(nsIEditor* aEditor)
 {
   NS_ENSURE_ARG_POINTER(aEditor);
 
   nsresult rv;
 
   nsCOMPtr<nsIURI> docUri;
   rv = GetDocumentURI(aEditor, getter_AddRefs(docUri));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  nsCOMPtr<nsIContentPrefService> contentPrefService =
+  nsAutoCString docUriSpec;
+  rv = docUri->GetSpec(docUriSpec);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIContentPrefService2> contentPrefService =
     do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
   NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_AVAILABLE);
 
-  nsCOMPtr<nsIWritableVariant> uri = do_CreateInstance(NS_VARIANT_CONTRACTID);
-  NS_ENSURE_TRUE(uri, NS_ERROR_OUT_OF_MEMORY);
-  uri->SetAsISupports(docUri);
-
   nsCOMPtr<nsILoadContext> loadContext = GetLoadContext(aEditor);
-  bool hasPref;
-  if (NS_SUCCEEDED(contentPrefService->HasPref(uri, CPS_PREF_NAME, loadContext, &hasPref)) && hasPref) {
-    nsCOMPtr<nsIVariant> pref;
-    contentPrefService->GetPref(uri, CPS_PREF_NAME, loadContext, nullptr, getter_AddRefs(pref));
-    pref->GetAsAString(aDictionary);
-  } else {
-    aDictionary.Truncate();
-  }
+  rv = contentPrefService->GetByDomainAndName(NS_ConvertUTF8toUTF16(docUriSpec),
+                                              CPS_PREF_NAME, loadContext,
+                                              this);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
-NS_IMETHODIMP
-LastDictionary::StoreCurrentDictionary(nsIEditor* aEditor, const nsAString& aDictionary)
+/**
+ * Stores the current dictionary for aEditor's document URL.
+ */
+static nsresult
+StoreCurrentDictionary(nsIEditor* aEditor, const nsAString& aDictionary)
 {
   NS_ENSURE_ARG_POINTER(aEditor);
 
   nsresult rv;
 
   nsCOMPtr<nsIURI> docUri;
   rv = GetDocumentURI(aEditor, getter_AddRefs(docUri));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  nsCOMPtr<nsIWritableVariant> uri = do_CreateInstance(NS_VARIANT_CONTRACTID);
-  NS_ENSURE_TRUE(uri, NS_ERROR_OUT_OF_MEMORY);
-  uri->SetAsISupports(docUri);
+  nsAutoCString docUriSpec;
+  rv = docUri->GetSpec(docUriSpec);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIWritableVariant> prefValue = do_CreateInstance(NS_VARIANT_CONTRACTID);
   NS_ENSURE_TRUE(prefValue, NS_ERROR_OUT_OF_MEMORY);
   prefValue->SetAsAString(aDictionary);
 
-  nsCOMPtr<nsIContentPrefService> contentPrefService =
+  nsCOMPtr<nsIContentPrefService2> contentPrefService =
     do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
   NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_INITIALIZED);
 
   nsCOMPtr<nsILoadContext> loadContext = GetLoadContext(aEditor);
-  return contentPrefService->SetPref(uri, CPS_PREF_NAME, prefValue, loadContext);
+  return contentPrefService->Set(NS_ConvertUTF8toUTF16(docUriSpec),
+                                 CPS_PREF_NAME, prefValue, loadContext,
+                                 nullptr);
 }
 
-NS_IMETHODIMP
-LastDictionary::ClearCurrentDictionary(nsIEditor* aEditor)
+/**
+ * Forgets the current dictionary stored for aEditor's document URL.
+ */
+static nsresult
+ClearCurrentDictionary(nsIEditor* aEditor)
 {
   NS_ENSURE_ARG_POINTER(aEditor);
 
   nsresult rv;
 
   nsCOMPtr<nsIURI> docUri;
   rv = GetDocumentURI(aEditor, getter_AddRefs(docUri));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  nsCOMPtr<nsIWritableVariant> uri = do_CreateInstance(NS_VARIANT_CONTRACTID);
-  NS_ENSURE_TRUE(uri, NS_ERROR_OUT_OF_MEMORY);
-  uri->SetAsISupports(docUri);
+  nsAutoCString docUriSpec;
+  rv = docUri->GetSpec(docUriSpec);
+  NS_ENSURE_SUCCESS(rv, rv);
 
-  nsCOMPtr<nsIContentPrefService> contentPrefService =
+  nsCOMPtr<nsIContentPrefService2> contentPrefService =
     do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
   NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_INITIALIZED);
 
   nsCOMPtr<nsILoadContext> loadContext = GetLoadContext(aEditor);
-  return contentPrefService->RemovePref(uri, CPS_PREF_NAME, loadContext);
+  return contentPrefService->RemoveByDomainAndName(
+    NS_ConvertUTF8toUTF16(docUriSpec), CPS_PREF_NAME, loadContext, nullptr);
 }
 
-LastDictionary* nsEditorSpellCheck::gDictionaryStore = nullptr;
-
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsEditorSpellCheck)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsEditorSpellCheck)
 
 NS_INTERFACE_MAP_BEGIN(nsEditorSpellCheck)
   NS_INTERFACE_MAP_ENTRY(nsIEditorSpellCheck)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIEditorSpellCheck)
   NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsEditorSpellCheck)
 NS_INTERFACE_MAP_END
@@ -221,16 +249,17 @@ NS_IMPL_CYCLE_COLLECTION_3(nsEditorSpell
                            mEditor,
                            mSpellChecker,
                            mTxtSrvFilter)
 
 nsEditorSpellCheck::nsEditorSpellCheck()
   : mSuggestedWordIndex(0)
   , mDictionaryIndex(0)
   , mEditor(nullptr)
+  , mDictionaryFetcherGroup(0)
   , mUpdateDictionaryRunning(false)
 {
 }
 
 nsEditorSpellCheck::~nsEditorSpellCheck()
 {
   // Make sure we blow the spellchecker away, just in
   // case it hasn't been destroyed already.
@@ -256,29 +285,49 @@ nsEditorSpellCheck::CanSpellCheck(bool* 
   nsTArray<nsString> dictList;
   rv = spellChecker->GetDictionaryList(&dictList);
   NS_ENSURE_SUCCESS(rv, rv);
 
   *_retval = (dictList.Length() > 0);
   return NS_OK;
 }
 
+// Instances of this class can be used as either runnables or RAII helpers.
+class CallbackCaller MOZ_FINAL : public nsRunnable
+{
+public:
+  explicit CallbackCaller(nsIEditorSpellCheckCallback* aCallback)
+    : mCallback(aCallback) {}
+
+  ~CallbackCaller()
+  {
+    Run();
+  }
+
+  NS_IMETHOD Run()
+  {
+    if (mCallback) {
+      mCallback->EditorSpellCheckDone();
+      mCallback = nullptr;
+    }
+    return NS_OK;
+  }
+
+private:
+  nsCOMPtr<nsIEditorSpellCheckCallback> mCallback;
+};
+
 NS_IMETHODIMP    
-nsEditorSpellCheck::InitSpellChecker(nsIEditor* aEditor, bool aEnableSelectionChecking)
+nsEditorSpellCheck::InitSpellChecker(nsIEditor* aEditor, bool aEnableSelectionChecking, nsIEditorSpellCheckCallback* aCallback)
 {
   NS_ENSURE_TRUE(aEditor, NS_ERROR_NULL_POINTER);
   mEditor = aEditor;
 
   nsresult rv;
 
-  if (!gDictionaryStore) {
-    gDictionaryStore = new LastDictionary();
-  }
-
-
   // We can spell check with any editor type
   nsCOMPtr<nsITextServicesDocument>tsDoc =
      do_CreateInstance("@mozilla.org/textservices/textservicesdocument;1", &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   NS_ENSURE_TRUE(tsDoc, NS_ERROR_NULL_POINTER);
 
   tsDoc->SetFilter(mTxtSrvFilter);
@@ -340,17 +389,27 @@ nsEditorSpellCheck::InitSpellChecker(nsI
 
   NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NULL_POINTER);
 
   rv = mSpellChecker->SetDocument(tsDoc, true);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // do not fail if UpdateCurrentDictionary fails because this method may
   // succeed later.
-  UpdateCurrentDictionary();
+  rv = UpdateCurrentDictionary(aCallback);
+  if (NS_FAILED(rv) && aCallback) {
+    // However, if it does fail, we still need to call the callback since we
+    // discard the failure.  Do it asynchronously so that the caller is always
+    // guaranteed async behavior.
+    nsRefPtr<CallbackCaller> caller = new CallbackCaller(aCallback);
+    NS_ENSURE_STATE(caller);
+    rv = NS_DispatchToMainThread(caller);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
   return NS_OK;
 }
 
 NS_IMETHODIMP    
 nsEditorSpellCheck::GetNextMisspelledWord(PRUnichar **aNextMisspelledWord)
 {
   NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
 
@@ -524,35 +583,41 @@ nsEditorSpellCheck::GetCurrentDictionary
 
 NS_IMETHODIMP    
 nsEditorSpellCheck::SetCurrentDictionary(const nsAString& aDictionary)
 {
   NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
 
   nsRefPtr<nsEditorSpellCheck> kungFuDeathGrip = this;
 
+  // The purpose of mUpdateDictionaryRunning is to avoid doing all of this if
+  // UpdateCurrentDictionary's helper method DictionaryFetched, which calls us,
+  // is on the stack.
   if (!mUpdateDictionaryRunning) {
 
+    // Ignore pending dictionary fetchers by increasing this number.
+    mDictionaryFetcherGroup++;
+
     nsDefaultStringComparator comparator;
     nsAutoString langCode;
     int32_t dashIdx = aDictionary.FindChar('-');
     if (dashIdx != -1) {
       langCode.Assign(Substring(aDictionary, 0, dashIdx));
     } else {
       langCode.Assign(aDictionary);
     }
 
     if (mPreferredLang.IsEmpty() || !nsStyleUtil::DashMatchCompare(mPreferredLang, langCode, comparator)) {
       // When user sets dictionary manually, we store this value associated
       // with editor url.
-      gDictionaryStore->StoreCurrentDictionary(mEditor, aDictionary);
+      StoreCurrentDictionary(mEditor, aDictionary);
     } else {
       // If user sets a dictionary matching (even partially), lang defined by
       // document, we consider content pref has been canceled, and we clear it.
-      gDictionaryStore->ClearCurrentDictionary(mEditor);
+      ClearCurrentDictionary(mEditor);
     }
 
     // Also store it in as a preference. It will be used as a default value
     // when everything else fails.
     Preferences::SetString("spellchecker.dictionary", aDictionary);
   }
   return mSpellChecker->SetCurrentDictionary(aDictionary);
 }
@@ -608,58 +673,82 @@ nsresult
 nsEditorSpellCheck::DeleteSuggestedWordList()
 {
   mSuggestedWordList.Clear();
   mSuggestedWordIndex = 0;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsEditorSpellCheck::UpdateCurrentDictionary()
+nsEditorSpellCheck::UpdateCurrentDictionary(nsIEditorSpellCheckCallback* aCallback)
 {
   nsresult rv;
 
   nsRefPtr<nsEditorSpellCheck> kungFuDeathGrip = this;
 
-  UpdateDictionnaryHolder holder(this);
-
   // Get language with html5 algorithm
   nsCOMPtr<nsIContent> rootContent;
   nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(mEditor);
   if (htmlEditor) {
     rootContent = htmlEditor->GetActiveEditingHost();
   } else {
     nsCOMPtr<nsIDOMElement> rootElement;
     rv = mEditor->GetRootElement(getter_AddRefs(rootElement));
     NS_ENSURE_SUCCESS(rv, rv);
     rootContent = do_QueryInterface(rootElement);
   }
   NS_ENSURE_TRUE(rootContent, NS_ERROR_FAILURE);
 
-  mPreferredLang.Truncate();
-  rootContent->GetLang(mPreferredLang);
+  DictionaryFetcher* fetcher = new DictionaryFetcher(this, aCallback,
+                                                     mDictionaryFetcherGroup);
+  rootContent->GetLang(fetcher->mRootContentLang);
+  nsCOMPtr<nsIDocument> doc = rootContent->GetCurrentDoc();
+  NS_ENSURE_STATE(doc);
+  doc->GetContentLanguage(fetcher->mRootDocContentLang);
+
+  rv = fetcher->Fetch(mEditor);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
 
-  // Tell the spellchecker what dictionary to use:
+nsresult
+nsEditorSpellCheck::DictionaryFetched(DictionaryFetcher* aFetcher)
+{
+  nsRefPtr<nsEditorSpellCheck> kungFuDeathGrip = this;
+
+  nsresult rv = NS_OK;
 
-  // First try to get dictionary from content prefs. If we have one, do not got
+  // Important: declare the holder after the callback caller so that the former
+  // is destructed first so that it's not active when the callback is called.
+  CallbackCaller callbackCaller(aFetcher->mCallback);
+  UpdateDictionnaryHolder holder(this);
+
+  if (aFetcher->mGroup < mDictionaryFetcherGroup) {
+    // SetCurrentDictionary was called after the fetch started.  Don't overwrite
+    // that dictionary with the fetched one.
+    return NS_OK;
+  }
+
+  mPreferredLang.Assign(aFetcher->mRootContentLang);
+
+  // If we successfully fetched a dictionary from content prefs, do not go
   // further. Use this exact dictionary.
   nsAutoString dictName;
-  rv = gDictionaryStore->FetchLastDictionary(mEditor, dictName);
-  if (NS_SUCCEEDED(rv) && !dictName.IsEmpty()) {
+  dictName.Assign(aFetcher->mDictionary);
+  if (!dictName.IsEmpty()) {
     if (NS_FAILED(SetCurrentDictionary(dictName))) { 
       // may be dictionary was uninstalled ?
-      gDictionaryStore->ClearCurrentDictionary(mEditor);
+      ClearCurrentDictionary(mEditor);
     }
     return NS_OK;
   }
 
   if (mPreferredLang.IsEmpty()) {
-    nsCOMPtr<nsIDocument> doc = rootContent->GetCurrentDoc();
-    NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
-    doc->GetContentLanguage(mPreferredLang);
+    mPreferredLang.Assign(aFetcher->mRootDocContentLang);
   }
 
   // Then, try to use language computed from element
   if (!mPreferredLang.IsEmpty()) {
     dictName.Assign(mPreferredLang);
   }
 
   // otherwise, get language from preferences
@@ -776,13 +865,8 @@ nsEditorSpellCheck::UpdateCurrentDiction
   // fail silently so that the spellchecker dialog is allowed to come
   // up. The user can manually reset the language to their choice on
   // the dialog if it is wrong.
 
   DeleteSuggestedWordList();
 
   return NS_OK;
 }
-
-void 
-nsEditorSpellCheck::ShutDown() {
-  delete gDictionaryStore;
-}
--- a/editor/composer/src/nsEditorSpellCheck.h
+++ b/editor/composer/src/nsEditorSpellCheck.h
@@ -20,34 +20,32 @@ class nsISpellChecker;
 class nsITextServicesFilter;
 
 #define NS_EDITORSPELLCHECK_CID                     \
 { /* {75656ad9-bd13-4c5d-939a-ec6351eea0cc} */        \
     0x75656ad9, 0xbd13, 0x4c5d,                       \
     { 0x93, 0x9a, 0xec, 0x63, 0x51, 0xee, 0xa0, 0xcc }\
 }
 
-class LastDictionary;
+class DictionaryFetcher;
 
 class nsEditorSpellCheck : public nsIEditorSpellCheck
 {
+  friend class DictionaryFetcher;
+
 public:
   nsEditorSpellCheck();
   virtual ~nsEditorSpellCheck();
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS(nsEditorSpellCheck)
 
   /* Declare all methods in the nsIEditorSpellCheck interface */
   NS_DECL_NSIEDITORSPELLCHECK
 
-  static LastDictionary* gDictionaryStore;
-
-  static void ShutDown();
-
 protected:
   nsCOMPtr<nsISpellChecker> mSpellChecker;
 
   nsTArray<nsString>  mSuggestedWordList;
   int32_t        mSuggestedWordIndex;
 
   // these are the words in the current personal dictionary,
   // GetPersonalDictionary must be called to load them.
@@ -56,18 +54,22 @@ protected:
 
   nsresult       DeleteSuggestedWordList();
 
   nsCOMPtr<nsITextServicesFilter> mTxtSrvFilter;
   nsCOMPtr<nsIEditor> mEditor;
 
   nsString mPreferredLang;
 
+  uint32_t mDictionaryFetcherGroup;
+
   bool mUpdateDictionaryRunning;
 
+  nsresult DictionaryFetched(DictionaryFetcher* aFetchState);
+
 public:
   void BeginUpdateDictionary() { mUpdateDictionaryRunning = true ;}
   void EndUpdateDictionary() { mUpdateDictionaryRunning = false ;}
 };
 
 #endif // nsEditorSpellCheck_h___
 
 
--- a/editor/composer/test/Makefile.in
+++ b/editor/composer/test/Makefile.in
@@ -19,11 +19,12 @@ MOCHITEST_FILES = \
 		bug678842_subframe.html \
 		test_bug738440.html \
 		$(NULL)
 
 MOCHITEST_CHROME_FILES = \
 		test_bug434998.xul \
 		test_bug338427.html \
 		test_bug678842.html \
+		test_async_UpdateCurrentDictionary.html \
 		$(NULL)
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/editor/composer/test/test_async_UpdateCurrentDictionary.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=856270
+-->
+<head>
+  <title>Test for Bug 856270 - Async UpdateCurrentDictionary</title>
+  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=856270">Mozilla Bug 856270</a>
+<p id="display"></p>
+<div id="content">
+<textarea id="editor" spellcheck="true"></textarea>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript;version=1.8">
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(start);
+
+function start() {
+  Components.utils.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm");
+  var textarea = document.getElementById("editor");
+  textarea.focus();
+
+  onSpellCheck(textarea, function () {
+    var isc = textarea.editor.getInlineSpellChecker(false);
+    ok(isc, "Inline spell checker should exist after focus and spell check");
+    var sc = isc.spellChecker;
+    isnot(sc.GetCurrentDictionary(), lang,
+          "Current dictionary should not be set yet.");
+
+    // First, set the lang attribute on the textarea, call Update, and make
+    // sure the spell checker's language was updated appropriately.
+    var lang = "en-US";
+    textarea.setAttribute("lang", lang);
+    sc.UpdateCurrentDictionary(function () {
+      is(sc.GetCurrentDictionary(), lang,
+         "UpdateCurrentDictionary should set the current dictionary.");
+
+      // Second, make some Update calls, but then do a Set.  The Set should
+      // effectively cancel the Updates, but the Updates' callbacks should be
+      // called nonetheless.
+      var numCalls = 3;
+      for (var i = 0; i < numCalls; i++) {
+        sc.UpdateCurrentDictionary(function () {
+          is(sc.GetCurrentDictionary(), "",
+             "No dictionary should be active after Update.");
+          if (--numCalls == 0)
+            SimpleTest.finish();
+        });
+      }
+      try {
+        sc.SetCurrentDictionary("testing-XX");
+      }
+      catch (err) {
+        // Set throws NS_ERROR_NOT_AVAILABLE because "testing-XX" isn't really
+        // an available dictionary.
+      }
+      is(sc.GetCurrentDictionary(), "",
+         "No dictionary should be active after Set.");
+    });
+  });
+}
+
+</script>
+</pre>
+</body>
+</html>
+
--- a/editor/composer/test/test_bug338427.html
+++ b/editor/composer/test/test_bug338427.html
@@ -15,38 +15,41 @@ https://bugzilla.mozilla.org/show_bug.cg
 <textarea id="editor" lang="testing-XX" spellcheck="true"></textarea>
 
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 /** Test for Bug 338427 **/
 function init() {
+    Components.utils.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm");
+
     var textarea = document.getElementById("editor");
     var editor = textarea.editor;
     var spellchecker = editor.getInlineSpellChecker(true);
     spellchecker.enableRealTimeSpell = true;
+    textarea.focus();
 
-    var list = {}, count = {};
-    spellchecker.spellChecker.GetDictionaryList(list, count);
-    if (count.value === 0) {
-        return; // no dictionary, no test possible
-    }
-    var lang = list.value[0];
-    spellchecker.spellChecker.SetCurrentDictionary(lang);
+    onSpellCheck(textarea, function () {
+        var list = {}, count = {};
+        spellchecker.spellChecker.GetDictionaryList(list, count);
+        ok(count.value > 0, "At least one dictionary should be present");
+
+        var lang = list.value[0];
+        spellchecker.spellChecker.SetCurrentDictionary(lang);
 
-    textarea.addEventListener("focus", function() {
-        var dictionary = "";
-        try {
-            dictionary = spellchecker.spellChecker.GetCurrentDictionary();
-        } catch(e) {}
-        is(dictionary, lang, "Unexpected spell check dictionary");
-        SimpleTest.finish();
-    }, false);
-    textarea.focus();
+        onSpellCheck(textarea, function () {
+            try {
+                var dictionary =
+                    spellchecker.spellChecker.GetCurrentDictionary();
+            } catch(e) {}
+            is(dictionary, lang, "Unexpected spell check dictionary");
+            SimpleTest.finish();
+        });
+    });
 }
 
 SimpleTest.waitForExplicitFinish();
 addLoadEvent(init);
 
 </script>
 </pre>
 </body>
--- a/editor/composer/test/test_bug678842.html
+++ b/editor/composer/test/test_bug678842.html
@@ -22,40 +22,43 @@ var content = document.getElementById('c
 // load a subframe containing an editor with a defined unknown lang. At first
 // load, it will set dictionary to en-US. At second load, it will return current
 // dictionary. So, we can check, dictionary is correctly remembered between
 // loads.
 
 var firstLoad = true;
 
 var loadListener = function(evt) {
+  Components.utils.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm");
   var doc = evt.target.contentDocument;
   var elem = doc.getElementById('textarea');
   var editor = elem.QueryInterface(Components.interfaces.nsIDOMNSEditableElement).editor;
   editor.setSpellcheckUserOverride(true);
   var inlineSpellChecker = editor.getInlineSpellChecker(true);
-  var spellchecker = inlineSpellChecker.spellChecker;
-  var currentDictonary = "";
-  try {
-    currentDictonary = spellchecker.GetCurrentDictionary();
-  } catch(e) {}
 
-  if (!currentDictonary) {
-    spellchecker.SetCurrentDictionary('en-US');
-  }
+  onSpellCheck(elem, function () {
+    var spellchecker = inlineSpellChecker.spellChecker;
+    try {
+      var currentDictonary = spellchecker.GetCurrentDictionary();
+    } catch(e) {}
+
+    if (!currentDictonary) {
+      spellchecker.SetCurrentDictionary('en-US');
+    }
 
-  if (firstLoad) {
-    firstLoad = false;
-    is (currentDictonary, "", "unexpected lang " + currentDictonary);
-    content.src = 'http://mochi.test:8888/tests/editor/composer/test/bug678842_subframe.html?firstload=false';
-  } else {
-    is (currentDictonary, "en-US", "unexpected lang " + currentDictonary + " instead of en-US");
-    content.removeEventListener('load', loadListener, false);
-    SimpleTest.finish();
-  }
+    if (firstLoad) {
+      firstLoad = false;
+      is (currentDictonary, "", "unexpected lang " + currentDictonary);
+      content.src = 'http://mochi.test:8888/tests/editor/composer/test/bug678842_subframe.html?firstload=false';
+    } else {
+      is (currentDictonary, "en-US", "unexpected lang " + currentDictonary + " instead of en-US");
+      content.removeEventListener('load', loadListener, false);
+      SimpleTest.finish();
+    }
+  });
 }
 
 content.addEventListener('load', loadListener, false);
 
 content.src = 'http://mochi.test:8888/tests/editor/composer/test/bug678842_subframe.html?firstload=true';
 
 </script>
 </pre>
--- a/editor/idl/nsIEditorSpellCheck.idl
+++ b/editor/idl/nsIEditorSpellCheck.idl
@@ -2,18 +2,19 @@
 /* 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 "nsISupports.idl"
 
 interface nsIEditor;
 interface nsITextServicesFilter;
+interface nsIEditorSpellCheckCallback;
 
-[scriptable, uuid(334946c3-0e93-4aac-b662-e1b56f95d68b)]
+[scriptable, uuid(dd32ef3b-a7d8-43d1-9617-5f2dddbe29eb)]
 interface nsIEditorSpellCheck : nsISupports
 {
 
   /**
    * Call this on any change in installed dictionaries to ensure that the spell
    * checker is not using a current dictionary which is no longer available.
    * If the current dictionary is no longer available, then pick another one.
    */
@@ -25,19 +26,21 @@ interface nsIEditorSpellCheck : nsISuppo
    */
   boolean       canSpellCheck();
 
   /**
    * Turns on the spell checker for the given editor. enableSelectionChecking
    * set means that we only want to check the current selection in the editor,
    * (this controls the behavior of GetNextMisspelledWord). For spellchecking
    * clients with no modal UI (such as inline spellcheckers), this flag doesn't
-   * matter
+   * matter.  Initialization is asynchronous and is not complete until the given
+   * callback is called.
    */
-  void          InitSpellChecker(in nsIEditor editor, in boolean enableSelectionChecking);
+  void          InitSpellChecker(in nsIEditor editor, in boolean enableSelectionChecking,
+                                 [optional] in nsIEditorSpellCheckCallback callback);
 
   /**
    * When interactively spell checking the document, this will return the
    * value of the next word that is misspelled. This also computes the
    * suggestions which you can get by calling GetSuggestedWord.
    *
    * @see nsISpellChecker::GetNextMisspelledWord
    */
@@ -151,13 +154,20 @@ interface nsIEditorSpellCheck : nsISuppo
    * Watch out: this does not clear any suggestions left over from previous
    * calls to CheckCurrentWord, so there may be suggestions, but they will be
    * invalid.
    */
   boolean       CheckCurrentWordNoSuggest(in wstring suggestedWord);
 
   /**
    * Update the dictionary in use to be sure it corresponds to what the editor
-   * needs.
+   * needs.  The update is asynchronous and is not complete until the given
+   * callback is called.
    */
-  void          UpdateCurrentDictionary();
+  void          UpdateCurrentDictionary([optional] in nsIEditorSpellCheckCallback callback);
 
 };
+
+[scriptable, function, uuid(5f0a4bab-8538-4074-89d3-2f0e866a1c0b)]
+interface nsIEditorSpellCheckCallback : nsISupports
+{
+  void editorSpellCheckDone();
+};
--- a/editor/libeditor/html/tests/test_bug366682.html
+++ b/editor/libeditor/html/tests/test_bug366682.html
@@ -43,17 +43,19 @@ function getSpellCheckSelection() {
   var selcon = editor.selectionController;
   return selcon.getSelection(selcon.SELECTION_SPELLCHECK);
 }
 
 function runTest() {
   editDoc().body.innerHTML = "<div>errror and an other errror</div>";
   gMisspeltWords = ["errror", "errror"];
   editDoc().designMode = "on";
-  setTimeout(function() { evalTest(); }, 0);
+
+  Components.utils.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm");
+  onSpellCheck(editDoc().documentElement, evalTest);
 }
 
 function evalTest() {
   is(isSpellingCheckOk(), true, "All misspellings accounted for.");
   SimpleTest.finish();
 }
 
 function isSpellingCheckOk() {
--- a/editor/libeditor/html/tests/test_bug484181.html
+++ b/editor/libeditor/html/tests/test_bug484181.html
@@ -54,27 +54,29 @@ function append(str) {
     synthesizeKey(str[i], {});
   }
 }
 
 function runTest() {
   gMisspeltWords = ["haz", "cheezburger"];
   var edit = document.getElementById("edit");
   edit.focus();
-  SimpleTest.executeSoon(function() {
-      is(isSpellingCheckOk(), true, "All misspellings before editing are accounted for.");
 
-      append(" becaz I'm a lolcat!");
-      SimpleTest.executeSoon(function() {
+  SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm", window);
+  onSpellCheck(edit, function () {
+    is(isSpellingCheckOk(), true, "All misspellings before editing are accounted for.");
+
+    append(" becaz I'm a lolcat!");
+    onSpellCheck(edit, function () {
       gMisspeltWords.push("becaz");
       gMisspeltWords.push("lolcat");
       is(isSpellingCheckOk(), true, "All misspellings after typing are accounted for.");
 
       SimpleTest.finish();
-      });
+    });
   });
 }
 
 function isSpellingCheckOk() {
   netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
 
   var sel = getSpellCheckSelection();
   var numWords = sel.rangeCount;
--- a/editor/libeditor/text/tests/test_bug596333.html
+++ b/editor/libeditor/text/tests/test_bug596333.html
@@ -17,17 +17,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 </div>
 <pre id="test">
 <script type="application/javascript">
 
 /** Test for Bug 596333 **/
 const Ci = SpecialPowers.Ci;
 
 SimpleTest.waitForExplicitFinish();
-addLoadEvent(function() SimpleTest.executeSoon(runTest));
+addLoadEvent(runTest);
 
 var gMisspeltWords;
 
 function getEditor() {
   return SpecialPowers.wrap(document.getElementById("edit")).editor;
 }
 
 function getSpellCheckSelection() {
@@ -66,63 +66,62 @@ function paste(str) {
   s.data = str;
   trans.setTransferData("text/unicode", s, str.length * 2);
 
   getEditor().pasteTransferable(trans);
 }
 
 function runOnFocus() {
   var edit = document.getElementById("edit");
-  edit.removeEventListener("focus", runOnFocus, false);
-  
-  SimpleTest.executeSoon(function() {
-    gMisspeltWords = ["haz", "cheezburger"];
-    is(isSpellingCheckOk(), true, "All misspellings before editing are accounted for.");
-    append(" becaz I'm a lolcat!");
-    SimpleTest.executeSoon(function() {
-      gMisspeltWords.push("becaz");
-      gMisspeltWords.push("lolcat");
-      is(isSpellingCheckOk(), true, "All misspellings after typing are accounted for.");
+
+  gMisspeltWords = ["haz", "cheezburger"];
+  is(isSpellingCheckOk(), true, "All misspellings before editing are accounted for.");
+  append(" becaz I'm a lolcat!");
+  onSpellCheck(edit, function () {
+    gMisspeltWords.push("becaz");
+    gMisspeltWords.push("lolcat");
+    is(isSpellingCheckOk(), true, "All misspellings after typing are accounted for.");
 
-      // Now, type an invalid word, and instead of hitting "space" at the end, just blur
-      // the textarea and see if the spell check after the blur event catches it.
-      append(" workd");
-      edit.blur();
-      SimpleTest.executeSoon(function() {
-        gMisspeltWords.push("workd");
-        is(isSpellingCheckOk(), true, "All misspellings after blur are accounted for.");
+    // Now, type an invalid word, and instead of hitting "space" at the end, just blur
+    // the textarea and see if the spell check after the blur event catches it.
+    append(" workd");
+    edit.blur();
+    onSpellCheck(edit, function () {
+      gMisspeltWords.push("workd");
+      is(isSpellingCheckOk(), true, "All misspellings after blur are accounted for.");
 
-        // Also, test the case when we're entering the first word in a textarea
+      // Also, test the case when we're entering the first word in a textarea
+      gMisspeltWords = ["workd"];
+      edit.value = "";
+      append("workd ");
+      onSpellCheck(edit, function () {
+        is(isSpellingCheckOk(), true, "Misspelling in the first entered word is accounted for.");
+
+        // Make sure that pasting would also trigger spell checking for the previous word
         gMisspeltWords = ["workd"];
         edit.value = "";
-        append("workd ");
-        SimpleTest.executeSoon(function() {
-          is(isSpellingCheckOk(), true, "Misspelling in the first entered word is accounted for.");
+        append("workd");
+        paste("           x");
+        onSpellCheck(edit, function () {
+          is(isSpellingCheckOk(), true, "Misspelling is accounted for after pasting.");
 
-          // Make sure that pasting would also trigger spell checking for the previous word
-          gMisspeltWords = ["workd"];
-          edit.value = "";
-          append("workd");
-          paste("           x");
-          SimpleTest.executeSoon(function() {
-            is(isSpellingCheckOk(), true, "Misspelling is accounted for after pasting.");
-
-            SimpleTest.finish();
-          });
+          SimpleTest.finish();
         });
       });
     });
   });
 }
 
 function runTest()
 {
   var edit = document.getElementById("edit");
-  edit.addEventListener("focus", runOnFocus, false);
   edit.focus();
+
+  SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm", window);
+  onSpellCheck(edit, runOnFocus);
 }
 
 function isSpellingCheckOk() {
   netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
 
   var sel = getSpellCheckSelection();
   var numWords = sel.rangeCount;
 
--- a/editor/libeditor/text/tests/test_bug636465.xul
+++ b/editor/libeditor/text/tests/test_bug636465.xul
@@ -25,48 +25,37 @@ https://bugzilla.mozilla.org/show_bug.cg
   </body>
   <textbox id="x" value="foobarbaz" spellcheck="true"/>
   <script class="testbody" type="application/javascript">
   <![CDATA[
 
   SimpleTest.waitForExplicitFinish();
 
   function runTest() {
+    Components.utils.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm");
     var x = document.getElementById("x");
     x.focus();
-    setTimeout(function(){
+    onSpellCheck(x, function () {
       x.blur();
-      setTimeout(function(){
-        var spellCheckTrue = snapshotWindow(window);
-        x.setAttribute("spellcheck", "false");
-        setTimeout(function(){
-          setTimeout(function(){
-            var spellCheckFalse = snapshotWindow(window);
-            x.setAttribute("spellcheck", "true");
-            x.focus();
-            setTimeout(function(){
-              x.blur();
-              setTimeout(function(){
-                var spellCheckTrueAgain = snapshotWindow(window);
-                x.removeAttribute("spellcheck");
-                setTimeout(function(){
-                  setTimeout(function(){
-                    var spellCheckNone = snapshotWindow(window);
-                    var after = snapshotWindow(window);
-                    ok(compareSnapshots(spellCheckTrue, spellCheckFalse, false)[0],
-                       "Setting the spellcheck attribute to false should work");
-                    ok(compareSnapshots(spellCheckTrue, spellCheckTrueAgain, true)[0],
-                       "Setting the spellcheck attribute back to true should work");
-                    ok(compareSnapshots(spellCheckNone, spellCheckFalse, true)[0],
-                       "Unsetting the spellcheck attribute should work");
-                    SimpleTest.finish();
-                  },0);
-                },0);
-              },0);
-            },0);
-          },0);
-        },0);
-      },0);
-    },0);
+      var spellCheckTrue = snapshotWindow(window);
+      x.setAttribute("spellcheck", "false");
+      var spellCheckFalse = snapshotWindow(window);
+      x.setAttribute("spellcheck", "true");
+      x.focus();
+      onSpellCheck(x, function () {
+        x.blur();
+        var spellCheckTrueAgain = snapshotWindow(window);
+        x.removeAttribute("spellcheck");
+        var spellCheckNone = snapshotWindow(window);
+        var after = snapshotWindow(window);
+        ok(compareSnapshots(spellCheckTrue, spellCheckFalse, false)[0],
+           "Setting the spellcheck attribute to false should work");
+        ok(compareSnapshots(spellCheckTrue, spellCheckTrueAgain, true)[0],
+           "Setting the spellcheck attribute back to true should work");
+        ok(compareSnapshots(spellCheckNone, spellCheckFalse, true)[0],
+           "Unsetting the spellcheck attribute should work");
+        SimpleTest.finish();
+      });
+    });
   }
 ]]>
 </script>
 </window>
--- a/editor/reftests/spellcheck-input-attr-dynamic-inherit.html
+++ b/editor/reftests/spellcheck-input-attr-dynamic-inherit.html
@@ -1,11 +1,11 @@
 <!DOCTYPE html>
 <html>
 <body onload="init()">
-    <input type="text" value="blahblahblah">
+    <input class="spell-checked" type="text" value="blahblahblah">
     <script>
       function init() {
         document.body.setAttribute("spellcheck", "true");
       }
     </script>
 </body>
 </html>
--- a/editor/reftests/spellcheck-input-attr-dynamic-override-inherit.html
+++ b/editor/reftests/spellcheck-input-attr-dynamic-override-inherit.html
@@ -1,11 +1,11 @@
 <!DOCTYPE html>
 <html>
 <body onload="init()" spellcheck="false">
-    <input type="text" value="blahblahblah">
+    <input class="spell-checked" type="text" value="blahblahblah">
     <script>
       function init() {
         document.body.setAttribute("spellcheck", "true");
       }
     </script>
 </body>
 </html>
--- a/editor/reftests/spellcheck-input-attr-dynamic-override.html
+++ b/editor/reftests/spellcheck-input-attr-dynamic-override.html
@@ -1,11 +1,11 @@
 <!DOCTYPE html>
 <html>
 <body onload="init()">
-    <input type="text" spellcheck="false" value="blahblahblah">
+    <input class="spell-checked" type="text" spellcheck="false" value="blahblahblah">
     <script>
       function init() {
         document.querySelector("input").setAttribute("spellcheck", "true");
       }
     </script>
 </body>
 </html>
--- a/editor/reftests/spellcheck-input-attr-dynamic.html
+++ b/editor/reftests/spellcheck-input-attr-dynamic.html
@@ -1,11 +1,11 @@
 <!DOCTYPE html>
 <html>
 <body onload="init()">
-    <input type="text" value="blahblahblah">
+    <input class="spell-checked" type="text" value="blahblahblah">
     <script>
       function init() {
         document.querySelector("input").setAttribute("spellcheck", "true");
       }
     </script>
 </body>
 </html>
--- a/editor/reftests/spellcheck-input-attr-inherit.html
+++ b/editor/reftests/spellcheck-input-attr-inherit.html
@@ -1,6 +1,6 @@
 <!DOCTYPE html>
 <html>
 <body>
-    <span spellcheck="true"><input type="text" value="blahblahblah"></span>
+    <span spellcheck="true"><input class="spell-checked" type="text" value="blahblahblah"></span>
 </body>
 </html>
--- a/editor/reftests/spellcheck-input-property-dynamic-inherit.html
+++ b/editor/reftests/spellcheck-input-property-dynamic-inherit.html
@@ -1,11 +1,11 @@
 <!DOCTYPE html>
 <html>
 <body onload="init()">
-    <input type="text" value="blahblahblah">
+    <input class="spell-checked" type="text" value="blahblahblah">
     <script>
       function init() {
         document.body.spellcheck = true;
       }
     </script>
 </body>
 </html>
--- a/editor/reftests/spellcheck-input-property-dynamic-override-inherit.html
+++ b/editor/reftests/spellcheck-input-property-dynamic-override-inherit.html
@@ -1,11 +1,11 @@
 <!DOCTYPE html>
 <html>
 <body onload="init()" spellcheck="false">
-    <input type="text" value="blahblahblah">
+    <input class="spell-checked" type="text" value="blahblahblah">
     <script>
       function init() {
         document.body.spellcheck = true;
       }
     </script>
 </body>
 </html>
--- a/editor/reftests/spellcheck-input-property-dynamic-override.html
+++ b/editor/reftests/spellcheck-input-property-dynamic-override.html
@@ -1,11 +1,11 @@
 <!DOCTYPE html>
 <html>
 <body onload="init()">
-    <input type="text" spellcheck="false" value="blahblahblah">
+    <input class="spell-checked" type="text" spellcheck="false" value="blahblahblah">
     <script>
       function init() {
         document.querySelector("input").spellcheck = true;
       }
     </script>
 </body>
 </html>
--- a/editor/reftests/spellcheck-input-property-dynamic.html
+++ b/editor/reftests/spellcheck-input-property-dynamic.html
@@ -1,11 +1,11 @@
 <!DOCTYPE html>
 <html>
 <body onload="init()">
-    <input type="text" value="blahblahblah">
+    <input class="spell-checked" type="text" value="blahblahblah">
     <script>
       function init() {
         document.querySelector("input").spellcheck = true;
       }
     </script>
 </body>
 </html>
--- a/editor/txtsvc/public/nsIInlineSpellChecker.idl
+++ b/editor/txtsvc/public/nsIInlineSpellChecker.idl
@@ -5,18 +5,17 @@
 
 #include "nsISupports.idl"
 #include "domstubs.idl"
 
 interface nsISelection;
 interface nsIEditor;
 interface nsIEditorSpellCheck;
 
-[scriptable, uuid(df635540-d073-47b8-8678-18776130691d)]
-
+[scriptable, uuid(b7b7a77c-40c4-4196-b0b7-b0338243b3fe)]
 interface nsIInlineSpellChecker : nsISupports
 {
   readonly attribute nsIEditorSpellCheck spellChecker;
 
   void init(in nsIEditor aEditor);
   void cleanup(in boolean aDestroyingFrames);
 
   attribute boolean enableRealTimeSpell;
@@ -35,15 +34,17 @@ interface nsIInlineSpellChecker : nsISup
   nsIDOMRange getMisspelledWord(in nsIDOMNode aNode, in long aOffset);
   void replaceWord(in nsIDOMNode aNode, in long aOffset, in AString aNewword);
   void addWordToDictionary(in AString aWord);
   void removeWordFromDictionary(in AString aWord);
   
   void ignoreWord(in AString aWord);
   void ignoreWords([array, size_is(aCount)] in wstring aWordsToIgnore, in unsigned long aCount);
   void updateCurrentDictionary();
+
+  readonly attribute boolean spellCheckPending;
 };
 
 %{C++
 
 #define MOZ_INLINESPELLCHECKER_CONTRACTID "@mozilla.org/spellchecker-inline;1"
 
 %}
--- a/extensions/spellcheck/src/mozInlineSpellChecker.cpp
+++ b/extensions/spellcheck/src/mozInlineSpellChecker.cpp
@@ -57,16 +57,18 @@
 #include "nsThreadUtils.h"
 #include "nsUnicharUtils.h"
 #include "nsIContent.h"
 #include "nsEventListenerManager.h"
 #include "nsGUIEvent.h"
 #include "nsRange.h"
 #include "nsContentUtils.h"
 #include "nsEditor.h"
+#include "mozilla/Services.h"
+#include "nsIObserverService.h"
 
 using namespace mozilla::dom;
 
 // Set to spew messages to the console about what is happening.
 //#define DEBUG_INLINESPELL
 
 // the number of milliseconds that we will take at once to do spellchecking
 #define INLINESPELL_CHECK_TIMEOUT 50
@@ -79,16 +81,20 @@ using namespace mozilla::dom;
 
 // This number is the number of checked words a misspelled word counts for
 // when we're checking the time to see if the alloted time is up for
 // spellchecking. Misspelled words take longer to process since we have to
 // create a range, so they count more. The exact number isn't very important
 // since this just controls how often we check the current time.
 #define MISSPELLED_WORD_COUNT_PENALTY 4
 
+// These notifications are broadcast when spell check starts and ends.  STARTED
+// must always be followed by ENDED.
+#define INLINESPELL_STARTED_TOPIC "inlineSpellChecker-spellCheck-started"
+#define INLINESPELL_ENDED_TOPIC "inlineSpellChecker-spellCheck-ended"
 
 static bool ContentIsDescendantOf(nsINode* aPossibleDescendant,
                                     nsINode* aPossibleAncestor);
 
 static const char kMaxSpellCheckSelectionSize[] = "extensions.spellcheck.inline.max-misspellings";
 
 mozInlineSpellStatus::mozInlineSpellStatus(mozInlineSpellChecker* aSpellChecker)
     : mSpellChecker(aSpellChecker), mWordCount(0)
@@ -450,28 +456,38 @@ mozInlineSpellStatus::PositionToCollapse
   return NS_OK;
 }
 
 // mozInlineSpellResume
 
 class mozInlineSpellResume : public nsRunnable
 {
 public:
-  mozInlineSpellResume(const mozInlineSpellStatus& aStatus) : mStatus(aStatus) {}
-  mozInlineSpellStatus mStatus;
+  mozInlineSpellResume(const mozInlineSpellStatus& aStatus,
+                       uint32_t aDisabledAsyncToken)
+    : mDisabledAsyncToken(aDisabledAsyncToken), mStatus(aStatus) {}
+
   nsresult Post()
   {
     return NS_DispatchToMainThread(this);
   }
 
   NS_IMETHOD Run()
   {
-    mStatus.mSpellChecker->ResumeCheck(&mStatus);
+    // Discard the resumption if the spell checker was disabled after the
+    // resumption was scheduled.
+    if (mDisabledAsyncToken == mStatus.mSpellChecker->mDisabledAsyncToken) {
+      mStatus.mSpellChecker->ResumeCheck(&mStatus);
+    }
     return NS_OK;
   }
+
+private:
+  uint32_t mDisabledAsyncToken;
+  mozInlineSpellStatus mStatus;
 };
 
 
 NS_INTERFACE_MAP_BEGIN(mozInlineSpellChecker)
   NS_INTERFACE_MAP_ENTRY(nsIInlineSpellChecker)
   NS_INTERFACE_MAP_ENTRY(nsIEditActionListener)
   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
   NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
@@ -489,16 +505,19 @@ NS_IMPL_CYCLE_COLLECTION_3(mozInlineSpel
 
 mozInlineSpellChecker::SpellCheckingState
   mozInlineSpellChecker::gCanEnableSpellChecking =
   mozInlineSpellChecker::SpellCheck_Uninitialized;
 
 mozInlineSpellChecker::mozInlineSpellChecker() :
     mNumWordsInSpellSelection(0),
     mMaxNumWordsInSpellSelection(250),
+    mNumPendingSpellChecks(0),
+    mNumPendingUpdateCurrentDictionary(0),
+    mDisabledAsyncToken(0),
     mNeedsCheckAfterNavigation(false),
     mFullSpellCheckScheduled(false)
 {
   nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
   if (prefs)
     prefs->GetIntPref(kMaxSpellCheckSelectionSize, &mMaxNumWordsInSpellSelection); 
   mMaxMisspellingsPerCheck = mMaxNumWordsInSpellSelection * 3 / 4;
 }
@@ -645,55 +664,192 @@ mozInlineSpellChecker::UnregisterEventLi
 }
 
 // mozInlineSpellChecker::GetEnableRealTimeSpell
 
 NS_IMETHODIMP
 mozInlineSpellChecker::GetEnableRealTimeSpell(bool* aEnabled)
 {
   NS_ENSURE_ARG_POINTER(aEnabled);
-  *aEnabled = mSpellCheck != nullptr;
+  *aEnabled = mSpellCheck != nullptr || mPendingSpellCheck != nullptr;
   return NS_OK;
 }
 
+// Used as the nsIEditorSpellCheck::InitSpellChecker callback.
+class InitEditorSpellCheckCallback MOZ_FINAL : public nsIEditorSpellCheckCallback
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  explicit InitEditorSpellCheckCallback(mozInlineSpellChecker* aSpellChecker)
+    : mSpellChecker(aSpellChecker) {}
+
+  NS_IMETHOD EditorSpellCheckDone()
+  {
+    return mSpellChecker ? mSpellChecker->EditorSpellCheckInited() : NS_OK;
+  }
+
+  void Cancel()
+  {
+    mSpellChecker = nullptr;
+  }
+
+private:
+  nsRefPtr<mozInlineSpellChecker> mSpellChecker;
+};
+NS_IMPL_ISUPPORTS1(InitEditorSpellCheckCallback, nsIEditorSpellCheckCallback)
+
 // mozInlineSpellChecker::SetEnableRealTimeSpell
 
 NS_IMETHODIMP
 mozInlineSpellChecker::SetEnableRealTimeSpell(bool aEnabled)
 {
   if (!aEnabled) {
     mSpellCheck = nullptr;
-    return Cleanup(false);
+
+    // Hold on to mEditor since Cleanup nulls it out.  See below.
+    nsCOMPtr<nsIEditor> editor = do_QueryReferent(mEditor);
+
+    nsresult rv = Cleanup(false);
+
+    // Notify ENDED observers now.  If we wait to notify as we normally do when
+    // these async operations finish, then in the meantime the editor may create
+    // another inline spell checker and cause more STARTED and ENDED
+    // notifications to be broadcast.  Interleaved notifications for the same
+    // editor but different inline spell checkers could easily confuse
+    // observers.  They may receive two consecutive STARTED notifications for
+    // example, which we guarantee will not happen.  Plus, mEditor must always
+    // be passed to observers.  If we wait to notify, we'd have to hold on to
+    // mEditor because Cleanup nulls it out.
+
+    if (mPendingSpellCheck) {
+      // Cancel the pending editor spell checker initialization.
+      mPendingSpellCheck = nullptr;
+      mPendingInitEditorSpellCheckCallback->Cancel();
+      mPendingInitEditorSpellCheckCallback = nullptr;
+      ChangeNumPendingSpellChecks(-1, editor);
+    }
+
+    // Increment this token so that pending UpdateCurrentDictionary calls and
+    // scheduled spell checks are discarded when they finish.
+    mDisabledAsyncToken++;
+
+    if (mNumPendingUpdateCurrentDictionary > 0) {
+      // Account for pending UpdateCurrentDictionary calls.
+      ChangeNumPendingSpellChecks(-mNumPendingUpdateCurrentDictionary, editor);
+      mNumPendingUpdateCurrentDictionary = 0;
+    }
+    if (mNumPendingSpellChecks > 0) {
+      // If mNumPendingSpellChecks is still > 0 at this point, the remainder is
+      // pending scheduled spell checks.
+      ChangeNumPendingSpellChecks(-mNumPendingSpellChecks, editor);
+    }
+
+    return rv;
+  }
+
+  if (mSpellCheck) {
+    // spellcheck the current contents. SpellCheckRange doesn't supply a created
+    // range to DoSpellCheck, which in our case is the entire range. But this
+    // optimization doesn't matter because there is nothing in the spellcheck
+    // selection when starting, which triggers a better optimization.
+    return SpellCheckRange(nullptr);
   }
 
-  if (!mSpellCheck) {
-    nsresult res = NS_OK;
-    nsCOMPtr<nsIEditorSpellCheck> spellchecker = do_CreateInstance("@mozilla.org/editor/editorspellchecker;1", &res);
-    if (NS_SUCCEEDED(res) && spellchecker)
-    {
-      nsCOMPtr<nsITextServicesFilter> filter = do_CreateInstance("@mozilla.org/editor/txtsrvfiltermail;1", &res);
-      spellchecker->SetFilter(filter);
-      nsCOMPtr<nsIEditor> editor (do_QueryReferent(mEditor));
-      res = spellchecker->InitSpellChecker(editor, false);
-      NS_ENSURE_SUCCESS(res, res);
+  if (mPendingSpellCheck) {
+    // The editor spell checker is already being initialized.
+    return NS_OK;
+  }
+
+  mPendingSpellCheck =
+    do_CreateInstance("@mozilla.org/editor/editorspellchecker;1");
+  NS_ENSURE_STATE(mPendingSpellCheck);
+
+  nsCOMPtr<nsITextServicesFilter> filter =
+    do_CreateInstance("@mozilla.org/editor/txtsrvfiltermail;1");
+  if (!filter) {
+    mPendingSpellCheck = nullptr;
+    NS_ENSURE_STATE(filter);
+  }
+  mPendingSpellCheck->SetFilter(filter);
+
+  mPendingInitEditorSpellCheckCallback = new InitEditorSpellCheckCallback(this);
+  if (!mPendingInitEditorSpellCheckCallback) {
+    mPendingSpellCheck = nullptr;
+    NS_ENSURE_STATE(mPendingInitEditorSpellCheckCallback);
+  }
 
-      mSpellCheck = spellchecker;
+  nsCOMPtr<nsIEditor> editor = do_QueryReferent(mEditor);
+  nsresult rv = mPendingSpellCheck->InitSpellChecker(
+                  editor, false, mPendingInitEditorSpellCheckCallback);
+  if (NS_FAILED(rv)) {
+    mPendingSpellCheck = nullptr;
+    mPendingInitEditorSpellCheckCallback = nullptr;
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  ChangeNumPendingSpellChecks(1);
+
+  return NS_OK;
+}
 
-      // spell checking is enabled, register our event listeners to track navigation
-      RegisterEventListeners();
-    }
-  }
+// Called when nsIEditorSpellCheck::InitSpellChecker completes.
+nsresult
+mozInlineSpellChecker::EditorSpellCheckInited()
+{
+  NS_ASSERTION(mPendingSpellCheck, "Spell check should be pending!");
+
+  // spell checking is enabled, register our event listeners to track navigation
+  RegisterEventListeners();
+
+  mSpellCheck = mPendingSpellCheck;
+  mPendingSpellCheck = nullptr;
+  mPendingInitEditorSpellCheckCallback = nullptr;
+  ChangeNumPendingSpellChecks(-1);
 
   // spellcheck the current contents. SpellCheckRange doesn't supply a created
   // range to DoSpellCheck, which in our case is the entire range. But this
   // optimization doesn't matter because there is nothing in the spellcheck
   // selection when starting, which triggers a better optimization.
   return SpellCheckRange(nullptr);
 }
 
+// Changes the number of pending spell checks by the given delta.  If the number
+// becomes zero or nonzero, observers are notified.  See NotifyObservers for
+// info on the aEditor parameter.
+void
+mozInlineSpellChecker::ChangeNumPendingSpellChecks(int32_t aDelta,
+                                                   nsIEditor* aEditor)
+{
+  int8_t oldNumPending = mNumPendingSpellChecks;
+  mNumPendingSpellChecks += aDelta;
+  NS_ASSERTION(mNumPendingSpellChecks >= 0,
+               "Unbalanced ChangeNumPendingSpellChecks calls!");
+  if (oldNumPending == 0 && mNumPendingSpellChecks > 0) {
+    NotifyObservers(INLINESPELL_STARTED_TOPIC, aEditor);
+  } else if (oldNumPending > 0 && mNumPendingSpellChecks == 0) {
+    NotifyObservers(INLINESPELL_ENDED_TOPIC, aEditor);
+  }
+}
+
+// Broadcasts the given topic to observers.  aEditor is passed to observers if
+// nonnull; otherwise mEditor is passed.
+void
+mozInlineSpellChecker::NotifyObservers(const char* aTopic, nsIEditor* aEditor)
+{
+  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+  if (!os)
+    return;
+  nsCOMPtr<nsIEditor> editor = aEditor;
+  if (!editor) {
+    editor = do_QueryReferent(mEditor);
+  }
+  os->NotifyObservers(editor, aTopic, nullptr);
+}
+
 // mozInlineSpellChecker::SpellCheckAfterEditorChange
 //
 //    Called by the editor when nearly anything happens to change the content.
 //
 //    The start and end positions specify a range for the thing that happened,
 //    but these are usually nullptr, even when you'd think they would be useful
 //    because you want the range (for example, pasting). We ignore them in
 //    this case.
@@ -1122,26 +1278,30 @@ mozInlineSpellChecker::SkipSpellCheckFor
 nsresult
 mozInlineSpellChecker::ScheduleSpellCheck(const mozInlineSpellStatus& aStatus)
 {
   if (mFullSpellCheckScheduled) {
     // Just ignore this; we're going to spell-check everything anyway
     return NS_OK;
   }
 
-  mozInlineSpellResume* resume = new mozInlineSpellResume(aStatus);
+  mozInlineSpellResume* resume =
+    new mozInlineSpellResume(aStatus, mDisabledAsyncToken);
   NS_ENSURE_TRUE(resume, NS_ERROR_OUT_OF_MEMORY);
 
   nsresult rv = resume->Post();
   if (NS_FAILED(rv)) {
     delete resume;
-  } else if (aStatus.IsFullSpellCheck()) {
-    // We're going to check everything.  Suppress further spell-check attempts
-    // until that happens.
-    mFullSpellCheckScheduled = true;
+  } else {
+    if (aStatus.IsFullSpellCheck()) {
+      // We're going to check everything.  Suppress further spell-check attempts
+      // until that happens.
+      mFullSpellCheckScheduled = true;
+    }
+    ChangeNumPendingSpellChecks(1);
   }
   return rv;
 }
 
 // mozInlineSpellChecker::DoSpellCheckSelection
 //
 //    Called to re-check all misspelled words. We iterate over all ranges in
 //    the selection and call DoSpellCheck on them. This is used when a word
@@ -1413,24 +1573,48 @@ nsresult mozInlineSpellChecker::DoSpellC
         return NS_OK;
       }
     }
   }
 
   return NS_OK;
 }
 
+// An RAII helper that calls ChangeNumPendingSpellChecks on destruction.
+class AutoChangeNumPendingSpellChecks
+{
+public:
+  AutoChangeNumPendingSpellChecks(mozInlineSpellChecker* aSpellChecker,
+                                  int32_t aDelta)
+    : mSpellChecker(aSpellChecker), mDelta(aDelta) {}
+
+  ~AutoChangeNumPendingSpellChecks()
+  {
+    mSpellChecker->ChangeNumPendingSpellChecks(mDelta);
+  }
+
+private:
+  nsRefPtr<mozInlineSpellChecker> mSpellChecker;
+  int32_t mDelta;
+};
+
 // mozInlineSpellChecker::ResumeCheck
 //
 //    Called by the resume event when it fires. We will try to pick up where
 //    the last resume left off.
 
 nsresult
 mozInlineSpellChecker::ResumeCheck(mozInlineSpellStatus* aStatus)
 {
+  // Observers should be notified that spell check has ended only after spell
+  // check is done below, but since there are many early returns in this method
+  // and the number of pending spell checks must be decremented regardless of
+  // whether the spell check actually happens, use this RAII object.
+  AutoChangeNumPendingSpellChecks autoChangeNumPending(this, -1);
+
   if (aStatus->IsFullSpellCheck()) {
     // Allow posting new spellcheck resume events from inside
     // ResumeCheck, now that we're actually firing.
     NS_ASSERTION(mFullSpellCheckScheduled,
                  "How could this be false?  The full spell check is "
                  "calling us!!");
     mFullSpellCheckScheduled = false;
   }
@@ -1756,34 +1940,86 @@ nsresult mozInlineSpellChecker::KeyPress
     case nsIDOMKeyEvent::DOM_VK_PAGE_DOWN:
       HandleNavigationEvent(true /* force a spelling correction */);
       break;
   }
 
   return NS_OK;
 }
 
+// Used as the nsIEditorSpellCheck::UpdateCurrentDictionary callback.
+class UpdateCurrentDictionaryCallback MOZ_FINAL : public nsIEditorSpellCheckCallback
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  explicit UpdateCurrentDictionaryCallback(mozInlineSpellChecker* aSpellChecker,
+                                           uint32_t aDisabledAsyncToken)
+    : mSpellChecker(aSpellChecker), mDisabledAsyncToken(aDisabledAsyncToken) {}
+
+  NS_IMETHOD EditorSpellCheckDone()
+  {
+    // Ignore this callback if SetEnableRealTimeSpell(false) was called after
+    // the UpdateCurrentDictionary call that triggered it.
+    return mSpellChecker->mDisabledAsyncToken > mDisabledAsyncToken ?
+           NS_OK :
+           mSpellChecker->CurrentDictionaryUpdated();
+  }
+
+private:
+  nsRefPtr<mozInlineSpellChecker> mSpellChecker;
+  uint32_t mDisabledAsyncToken;
+};
+NS_IMPL_ISUPPORTS1(UpdateCurrentDictionaryCallback, nsIEditorSpellCheckCallback)
+
 NS_IMETHODIMP mozInlineSpellChecker::UpdateCurrentDictionary()
 {
   if (!mSpellCheck) {
     return NS_OK;
   }
 
-  nsAutoString previousDictionary;
-  if (NS_FAILED(mSpellCheck->GetCurrentDictionary(previousDictionary))) {
-    previousDictionary.Truncate();
+  if (NS_FAILED(mSpellCheck->GetCurrentDictionary(mPreviousDictionary))) {
+    mPreviousDictionary.Truncate();
   }
 
-  // This might set mSpellCheck to null (bug 793866)
-  nsresult rv = mSpellCheck->UpdateCurrentDictionary();
+  nsRefPtr<UpdateCurrentDictionaryCallback> cb =
+    new UpdateCurrentDictionaryCallback(this, mDisabledAsyncToken);
+  NS_ENSURE_STATE(cb);
+  nsresult rv = mSpellCheck->UpdateCurrentDictionary(cb);
+  if (NS_FAILED(rv)) {
+    cb = nullptr;
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+  mNumPendingUpdateCurrentDictionary++;
+  ChangeNumPendingSpellChecks(1);
+
+  return NS_OK;
+}
+
+// Called when nsIEditorSpellCheck::UpdateCurrentDictionary completes.
+nsresult mozInlineSpellChecker::CurrentDictionaryUpdated()
+{
+  mNumPendingUpdateCurrentDictionary--;
+  NS_ASSERTION(mNumPendingUpdateCurrentDictionary >= 0,
+               "CurrentDictionaryUpdated called without corresponding "
+               "UpdateCurrentDictionary call!");
+  ChangeNumPendingSpellChecks(-1);
 
   nsAutoString currentDictionary;
   if (!mSpellCheck ||
       NS_FAILED(mSpellCheck->GetCurrentDictionary(currentDictionary))) {
     currentDictionary.Truncate();
   }
 
-  if (!previousDictionary.Equals(currentDictionary)) {
-      rv = SpellCheckRange(nullptr);
+  if (!mPreviousDictionary.Equals(currentDictionary)) {
+    nsresult rv = SpellCheckRange(nullptr);
+    NS_ENSURE_SUCCESS(rv, rv);
   }
 
-  return rv;
+  return NS_OK;
 }
+
+NS_IMETHODIMP
+mozInlineSpellChecker::GetSpellCheckPending(bool* aPending)
+{
+  *aPending = mNumPendingSpellChecks > 0;
+  return NS_OK;
+}
--- a/extensions/spellcheck/src/mozInlineSpellChecker.h
+++ b/extensions/spellcheck/src/mozInlineSpellChecker.h
@@ -23,16 +23,19 @@
 #ifdef KeyPress
 #undef KeyPress
 #endif
 
 class nsIDOMMouseEventListener;
 class mozInlineSpellWordUtil;
 class mozInlineSpellChecker;
 class mozInlineSpellResume;
+class InitEditorSpellCheckCallback;
+class UpdateCurrentDictionaryCallback;
+class mozInlineSpellResume;
 
 class mozInlineSpellStatus
 {
 public:
   mozInlineSpellStatus(mozInlineSpellChecker* aSpellChecker);
 
   nsresult InitForEditorChange(EditAction aAction,
                                nsIDOMNode* aAnchorNode, int32_t aAnchorOffset,
@@ -115,25 +118,30 @@ protected:
 
 class mozInlineSpellChecker : public nsIInlineSpellChecker,
                               public nsIEditActionListener,
                               public nsIDOMEventListener,
                               public nsSupportsWeakReference
 {
 private:
   friend class mozInlineSpellStatus;
+  friend class InitEditorSpellCheckCallback;
+  friend class UpdateCurrentDictionaryCallback;
+  friend class AutoChangeNumPendingSpellChecks;
+  friend class mozInlineSpellResume;
 
   // Access with CanEnableInlineSpellChecking
   enum SpellCheckingState { SpellCheck_Uninitialized = -1,
                             SpellCheck_NotAvailable = 0,
                             SpellCheck_Available = 1};
   static SpellCheckingState gCanEnableSpellChecking;
 
   nsWeakPtr mEditor; 
   nsCOMPtr<nsIEditorSpellCheck> mSpellCheck;
+  nsCOMPtr<nsIEditorSpellCheck> mPendingSpellCheck;
   nsCOMPtr<nsIDOMTreeWalker> mTreeWalker;
   nsCOMPtr<mozISpellI18NUtil> mConverter;
 
   int32_t mNumWordsInSpellSelection;
   int32_t mMaxNumWordsInSpellSelection;
 
   // How many misspellings we can add at once. This is often less than the max
   // total number of misspellings. When you have a large textarea prepopulated
@@ -142,24 +150,45 @@ private:
   // user can also have spellchecking in it.
   int32_t mMaxMisspellingsPerCheck;
 
   // we need to keep track of the current text position in the document
   // so we can spell check the old word when the user clicks around the document.
   nsCOMPtr<nsIDOMNode> mCurrentSelectionAnchorNode;
   int32_t              mCurrentSelectionOffset;
 
+  // Tracks the number of pending spell checks *and* async operations that may
+  // lead to spell checks, like updating the current dictionary.  This is
+  // necessary so that observers can know when to wait for spell check to
+  // complete.
+  int32_t mNumPendingSpellChecks;
+
+  // The number of calls to UpdateCurrentDictionary that haven't finished yet.
+  int32_t mNumPendingUpdateCurrentDictionary;
+
+  // This number is incremented each time the spell checker is disabled so that
+  // pending scheduled spell checks and UpdateCurrentDictionary calls can be
+  // ignored when they finish.
+  uint32_t mDisabledAsyncToken;
+
+  // When mPendingSpellCheck is non-null, this is the callback passed when
+  // it was initialized.
+  nsCOMPtr<InitEditorSpellCheckCallback> mPendingInitEditorSpellCheckCallback;
+
   // Set when we have spellchecked after the last edit operation. See the
   // commment at the top of the .cpp file for more info.
   bool mNeedsCheckAfterNavigation;
 
   // Set when we have a pending mozInlineSpellResume which will check
   // the whole document.
   bool mFullSpellCheckScheduled;
 
+  // Maintains state during the asynchronous UpdateCurrentDictionary call.
+  nsString mPreviousDictionary;
+
 public:
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_NSIEDITACTIONLISTENER
   NS_DECL_NSIINLINESPELLCHECKER
   NS_DECL_NSIDOMEVENTLISTENER
   NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(mozInlineSpellChecker, nsIDOMEventListener)
 
@@ -223,11 +252,23 @@ public:
   nsresult RegisterEventListeners();
   nsresult UnregisterEventListeners();
   nsresult HandleNavigationEvent(bool aForceWordSpellCheck, int32_t aNewPositionOffset = 0);
 
   nsresult GetSpellCheckSelection(nsISelection ** aSpellCheckSelection);
   nsresult SaveCurrentSelectionPosition();
 
   nsresult ResumeCheck(mozInlineSpellStatus* aStatus);
+
+protected:
+
+  // called when async nsIEditorSpellCheck methods complete
+  nsresult EditorSpellCheckInited();
+  nsresult CurrentDictionaryUpdated();
+
+  // track the number of pending spell checks and async operations that may lead
+  // to spell checks, notifying observers accordingly
+  void ChangeNumPendingSpellChecks(int32_t aDelta,
+                                   nsIEditor* aEditor = nullptr);
+  void NotifyObservers(const char* aTopic, nsIEditor* aEditor);
 };
 
 #endif /* __mozinlinespellchecker_h__ */
--- a/extensions/spellcheck/tests/chrome/test_add_remove_dictionaries.xul
+++ b/extensions/spellcheck/tests/chrome/test_add_remove_dictionaries.xul
@@ -59,54 +59,60 @@ function RunTest() {
   hunspell.addDirectory(base);
 
   // install map dictionary
   var map = dir.clone();
   map.append("map");
   ok(map.exists());
   hunspell.addDirectory(map);
 
-  // test that base and map dictionaries are available
-  var dicts = getDictionaryList(editor);
-  isnot(dicts.indexOf("base_utf"), -1, "base is available");
-  isnot(dicts.indexOf("maputf"), -1, "map is available");
+  Components.utils.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm");
+  onSpellCheck(textbox, function () {
 
-  // select base dictionary
-  setCurrentDictionary(editor, "base_utf");
+    // test that base and map dictionaries are available
+    var dicts = getDictionaryList(editor);
+    isnot(dicts.indexOf("base_utf"), -1, "base is available");
+    isnot(dicts.indexOf("maputf"), -1, "map is available");
 
-  SimpleTest.executeSoon(function() {
-    // test that base dictionary is in use
-    is(getMisspelledWords(editor), "Frühstück" + "qwertyu", "base misspellings");
-    is(getCurrentDictionary(editor), "base_utf", "current dictionary");
+    // select base dictionary
+    setCurrentDictionary(editor, "base_utf");
 
-    // select map dictionary
-    setCurrentDictionary(editor, "maputf");
+    onSpellCheck(textbox, function () {
+      // test that base dictionary is in use
+      is(getMisspelledWords(editor), "Frühstück" + "qwertyu", "base misspellings");
+      is(getCurrentDictionary(editor), "base_utf", "current dictionary");
+
+      // select map dictionary
+      setCurrentDictionary(editor, "maputf");
 
-    SimpleTest.executeSoon(function() {
-      // test that map dictionary is in use
-      is(getMisspelledWords(editor), "created" + "imply" + "tomorrow" + "qwertyu", "map misspellings");
-      is(getCurrentDictionary(editor), "maputf", "current dictionary");
+      onSpellCheck(textbox, function () {
+        // test that map dictionary is in use
+        is(getMisspelledWords(editor), "created" + "imply" + "tomorrow" + "qwertyu", "map misspellings");
+        is(getCurrentDictionary(editor), "maputf", "current dictionary");
 
-      // uninstall map dictionary
-      hunspell.removeDirectory(map);
+        // uninstall map dictionary
+        hunspell.removeDirectory(map);
+
+        onSpellCheck(textbox, function () {
+          // test that map dictionary is not in use
+          isnot(getMisspelledWords(editor), "created" + "imply" + "tomorrow" + "qwertyu", "map misspellings");
+          isnot(getCurrentDictionary(editor), "maputf", "current dictionary");
 
-      SimpleTest.executeSoon(function() {
-        // test that map dictionary is not in use
-        isnot(getMisspelledWords(editor), "created" + "imply" + "tomorrow" + "qwertyu", "map misspellings");
-        isnot(getCurrentDictionary(editor), "maputf", "current dictionary");
+          // test that base dictionary is available and map dictionary is unavailable
+          var dicts = getDictionaryList(editor);
+          isnot(dicts.indexOf("base_utf"), -1, "base is available");
+          is(dicts.indexOf("maputf"), -1, "map is unavailable");
 
-        // test that base dictionary is available and map dictionary is unavailable
-        var dicts = getDictionaryList(editor);
-        isnot(dicts.indexOf("base_utf"), -1, "base is available");
-        is(dicts.indexOf("maputf"), -1, "map is unavailable");
+          // uninstall base dictionary
+          hunspell.removeDirectory(base);
 
-        // uninstall base dictionary
-        hunspell.removeDirectory(base);
-
-        SimpleTest.finish();
+          onSpellCheck(textbox, function () {
+            SimpleTest.finish();
+          });
+        });
       });
     });
   });
 }
   ]]>
   </script>
   <textbox id="textbox" spellcheck="true" value="created imply Frühstück tomorrow qwertyu"/>
 </window>
--- a/gfx/layers/LayersTypes.h
+++ b/gfx/layers/LayersTypes.h
@@ -2,37 +2,42 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef GFX_LAYERSTYPES_H
 #define GFX_LAYERSTYPES_H
 
 #include "nsPoint.h"
-
+#ifdef MOZ_WIDGET_GONK
+#include <ui/GraphicBuffer.h>
+#endif
 #if defined(DEBUG) || defined(PR_LOGGING)
 #  include <stdio.h>            // FILE
 #  include "prlog.h"
 #  ifndef MOZ_LAYERS_HAVE_LOG
 #    define MOZ_LAYERS_HAVE_LOG
 #  endif
 #  define MOZ_LAYERS_LOG(_args)                             \
   PR_LOG(LayerManager::GetLog(), PR_LOG_DEBUG, _args)
 #  define MOZ_LAYERS_LOG_IF_SHADOWABLE(layer, _args)         \
   do { if (layer->AsShadowableLayer()) { PR_LOG(LayerManager::GetLog(), PR_LOG_DEBUG, _args); } } while (0)
 #else
 struct PRLogModuleInfo;
 #  define MOZ_LAYERS_LOG(_args)
 #  define MOZ_LAYERS_LOG_IF_SHADOWABLE(layer, _args)
 #endif  // if defined(DEBUG) || defined(PR_LOGGING)
 
+namespace android {
+class GraphicBuffer;
+}
+
 namespace mozilla {
 namespace layers {
 
-class SurfaceDescriptor;
 
 typedef uint32_t TextureFlags;
 
 enum LayersBackend {
   LAYERS_NONE = 0,
   LAYERS_BASIC,
   LAYERS_OPENGL,
   LAYERS_D3D9,
@@ -52,46 +57,65 @@ enum BufferMode {
 enum MaskType {
   MaskNone = 0,   // no mask layer
   Mask2d,         // mask layer for layers with 2D transforms
   Mask3d,         // mask layer for layers with 3D transforms
   NumMaskTypes
 };
 
 // LayerRenderState for Composer2D
+// We currently only support Composer2D using gralloc. If we want to be backed
+// by other surfaces we will need a more generic LayerRenderState.
 enum LayerRenderStateFlags {
   LAYER_RENDER_STATE_Y_FLIPPED = 1 << 0,
   LAYER_RENDER_STATE_BUFFER_ROTATION = 1 << 1
 };
 
+// The 'ifdef MOZ_WIDGET_GONK' sadness here is because we don't want to include
+// android::sp unless we have to.
 struct LayerRenderState {
-  LayerRenderState() : mSurface(nullptr), mFlags(0), mHasOwnOffset(false)
+  LayerRenderState()
+#ifdef MOZ_WIDGET_GONK
+    : mSurface(nullptr), mFlags(0), mHasOwnOffset(false)
+#endif
   {}
 
-  LayerRenderState(SurfaceDescriptor* aSurface, uint32_t aFlags = 0)
+#ifdef MOZ_WIDGET_GONK
+  LayerRenderState(android::GraphicBuffer* aSurface,
+                   const nsIntSize& aSize,
+                   uint32_t aFlags)
     : mSurface(aSurface)
+    , mSize(aSize)
     , mFlags(aFlags)
     , mHasOwnOffset(false)
   {}
 
-  LayerRenderState(SurfaceDescriptor* aSurface, nsIntPoint aOffset, uint32_t aFlags = 0)
-    : mSurface(aSurface)
-    , mFlags(aFlags)
-    , mOffset(aOffset)
-    , mHasOwnOffset(true)
-  {}
-
   bool YFlipped() const
   { return mFlags & LAYER_RENDER_STATE_Y_FLIPPED; }
 
   bool BufferRotated() const
   { return mFlags & LAYER_RENDER_STATE_BUFFER_ROTATION; }
+#endif
 
-  SurfaceDescriptor* mSurface;
+  void SetOffset(const nsIntPoint& aOffset)
+  {
+    mOffset = aOffset;
+    mHasOwnOffset = true;
+  }
+
+#ifdef MOZ_WIDGET_GONK
+  // surface to render
+  android::sp<android::GraphicBuffer> mSurface;
+  // size of mSurface 
+  nsIntSize mSize;
+#endif
+  // see LayerRenderStateFlags
   uint32_t mFlags;
+  // the location of the layer's origin on mSurface
   nsIntPoint mOffset;
+  // true if mOffset is applicable
   bool mHasOwnOffset;
 };
 
 } // namespace
 } // namespace
 
 #endif /* GFX_LAYERSTYPES_H */
--- a/gfx/layers/client/CompositableClient.h
+++ b/gfx/layers/client/CompositableClient.h
@@ -15,16 +15,17 @@ namespace layers {
 
 class CompositableChild;
 class CompositableClient;
 class TextureClient;
 class ImageBridgeChild;
 class ShadowableLayer;
 class CompositableForwarder;
 class CompositableChild;
+class SurfaceDescriptor;
 
 /**
  * CompositableClient manages the texture-specific logic for composite layers,
  * independently of the layer. It is the content side of a ConmpositableClient/
  * CompositableHost pair.
  *
  * CompositableClient's purpose is to send texture data to the compositor side
  * along with any extra information about how the texture is to be composited.
--- a/gfx/layers/composite/ContentHost.h
+++ b/gfx/layers/composite/ContentHost.h
@@ -78,16 +78,17 @@ public:
   }
 
   virtual LayerRenderState GetRenderState() MOZ_OVERRIDE
   {
     LayerRenderState result = mTextureHost->GetRenderState();
 
     result.mFlags = (mBufferRotation != nsIntPoint()) ?
                     LAYER_RENDER_STATE_BUFFER_ROTATION : 0;
+    result.SetOffset(GetOriginOffset());
     return result;
   }
 
   virtual void SetCompositor(Compositor* aCompositor) MOZ_OVERRIDE;
 
 #ifdef MOZ_DUMP_PAINTING
   virtual already_AddRefed<gfxImageSurface> GetAsSurface()
   {
--- a/gfx/layers/composite/ImageHost.h
+++ b/gfx/layers/composite/ImageHost.h
@@ -73,17 +73,20 @@ public:
   virtual void SetPictureRect(const nsIntRect& aPictureRect) MOZ_OVERRIDE
   {
     mPictureRect = aPictureRect;
     mHasPictureRect = true;
   }
 
   virtual LayerRenderState GetRenderState() MOZ_OVERRIDE
   {
-    return mTextureHost->GetRenderState();
+    if (mTextureHost) {
+      return mTextureHost->GetRenderState();
+    }
+    return LayerRenderState();
   }
 
   virtual void SetCompositor(Compositor* aCompositor) MOZ_OVERRIDE;
 
   virtual void Dump(FILE* aFile=NULL,
                     const char* aPrefix="",
                     bool aDumpHtml=false) MOZ_OVERRIDE;
 
--- a/gfx/layers/composite/ImageLayerComposite.cpp
+++ b/gfx/layers/composite/ImageLayerComposite.cpp
@@ -49,20 +49,20 @@ void
 ImageLayerComposite::Disconnect()
 {
   Destroy();
 }
 
 LayerRenderState
 ImageLayerComposite::GetRenderState()
 {
-  if (!mImageHost) {
-    return LayerRenderState();
+  if (mImageHost) {
+    return mImageHost->GetRenderState();
   }
-  return mImageHost->GetRenderState();
+  return LayerRenderState();
 }
 
 Layer*
 ImageLayerComposite::GetLayer()
 {
   return this;
 }
 
--- a/gfx/layers/composite/LayerManagerComposite.cpp
+++ b/gfx/layers/composite/LayerManagerComposite.cpp
@@ -92,18 +92,19 @@ LayerManagerComposite::~LayerManagerComp
 {
   Destroy();
 }
 
 
 bool
 LayerManagerComposite::Initialize()
 {
+  bool result = mCompositor->Initialize();
   mComposer2D = mCompositor->GetWidget()->GetComposer2D();
-  return mCompositor->Initialize();
+  return result;
 }
 
 void
 LayerManagerComposite::Destroy()
 {
   if (!mDestroyed) {
     mCompositor->GetWidget()->CleanupWindowEffects();
     if (mRoot) {
--- a/gfx/layers/composite/TextureHost.h
+++ b/gfx/layers/composite/TextureHost.h
@@ -233,20 +233,19 @@ public:
   {
     return GetIdentifier() == o.GetIdentifier();
   }
   bool operator!= (const TextureHost& o) const
   {
     return GetIdentifier() != o.GetIdentifier();
   }
 
-  LayerRenderState GetRenderState()
+  virtual LayerRenderState GetRenderState()
   {
-    return LayerRenderState(mBuffer,
-                            mFlags & NeedsYFlip ? LAYER_RENDER_STATE_Y_FLIPPED : 0);
+    return LayerRenderState();
   }
 
   virtual already_AddRefed<gfxImageSurface> GetAsSurface() = 0;
 
 #ifdef MOZ_LAYERS_HAVE_LOG
   virtual const char *Name() = 0;
   virtual void PrintInfo(nsACString& aTo, const char* aPrefix);
 #endif
--- a/gfx/layers/ipc/LayerTransactionParent.cpp
+++ b/gfx/layers/ipc/LayerTransactionParent.cpp
@@ -486,24 +486,23 @@ LayerTransactionParent::RecvGetTransform
   return true;
 }
 
 void
 LayerTransactionParent::Attach(ShadowLayerParent* aLayerParent, CompositableParent* aCompositable)
 {
   LayerComposite* layer = aLayerParent->AsLayer()->AsLayerComposite();
   MOZ_ASSERT(layer);
-  LayerComposite* layerComposite = aLayerParent->AsLayer()->AsLayerComposite();
 
   Compositor* compositor
     = static_cast<LayerManagerComposite*>(aLayerParent->AsLayer()->Manager())->GetCompositor();
 
   CompositableHost* compositable = aCompositable->GetCompositableHost();
   MOZ_ASSERT(compositable);
-  layerComposite->SetCompositableHost(compositable);
+  layer->SetCompositableHost(compositable);
   compositable->Attach(aLayerParent->AsLayer(), compositor);
 }
 
 bool
 LayerTransactionParent::RecvClearCachedResources()
 {
   if (mRoot) {
     // NB: |mRoot| here is the *child* context's root.  In this parent
--- a/gfx/layers/opengl/LayerManagerOGL.h
+++ b/gfx/layers/opengl/LayerManagerOGL.h
@@ -445,18 +445,16 @@ public:
 
   /* Do NOT call this from the generic LayerOGL destructor.  Only from the
    * concrete class destructor
    */
   virtual void Destroy() = 0;
 
   virtual Layer* GetLayer() = 0;
 
-  virtual LayerRenderState GetRenderState() { return LayerRenderState(); }
-
   virtual void RenderLayer(int aPreviousFrameBuffer,
                            const nsIntPoint& aOffset) = 0;
 
   typedef mozilla::gl::GLContext GLContext;
 
   LayerManagerOGL* OGLManager() const { return mOGLManager; }
   GLContext *gl() const { return mOGLManager->gl(); }
   virtual void CleanupResources() = 0;
--- a/gfx/layers/opengl/TextureHostOGL.cpp
+++ b/gfx/layers/opengl/TextureHostOGL.cpp
@@ -851,16 +851,27 @@ GrallocTextureHostOGL::SetBuffer(Surface
   mBuffer = aBuffer;
   mDeAllocator = aAllocator;
 
   // only done for hacky fix in gecko 23 for bug 862324.
   // Doing this in SwapTextures is not enough, as the crash could occur right after SetBuffer.
   RegisterTextureHostAtGrallocBufferActor(this, *mBuffer);
 }
 
+LayerRenderState
+GrallocTextureHostOGL::GetRenderState()
+{
+  if (mBuffer && IsSurfaceDescriptorValid(*mBuffer)) {
+    return LayerRenderState(mGraphicBuffer.get(),
+                            mBuffer->get_SurfaceDescriptorGralloc().size(),
+                            mFlags & NeedsYFlip ? LAYER_RENDER_STATE_Y_FLIPPED : 0);
+  }
+
+  return LayerRenderState();
+}
 #endif // MOZ_WIDGET_GONK
 
 already_AddRefed<gfxImageSurface>
 TextureImageTextureHostOGL::GetAsSurface() {
   nsRefPtr<gfxImageSurface> surf = IsValid() ?
     mGL->GetTexImage(mTexture->GetTextureID(),
                      false,
                      mTexture->GetShaderProgramType())
--- a/gfx/layers/opengl/TextureHostOGL.h
+++ b/gfx/layers/opengl/TextureHostOGL.h
@@ -636,16 +636,18 @@ public:
       delete mBuffer;
       mBuffer = nullptr;
     }
 
     mGraphicBuffer = nullptr;
     DeleteTextures();
   }
 
+  virtual LayerRenderState GetRenderState() MOZ_OVERRIDE;
+
 private:
   gl::GLContext* gl() const;
 
   void DeleteTextures();
 
   RefPtr<CompositorOGL> mCompositor;
   android::sp<android::GraphicBuffer> mGraphicBuffer;
   GLenum mTextureTarget;
--- a/gfx/skia/Makefile.in
+++ b/gfx/skia/Makefile.in
@@ -52,28 +52,30 @@ VPATH += \
 	$(srcdir)/src/effects/gradients \
 	$(srcdir)/src/utils \
 	$(srcdir)/src/utils/mac \
 	$(srcdir)/src/sfnt \
 	$(NULL)
 
 ifeq (android,$(MOZ_WIDGET_TOOLKIT))
 OS_CXXFLAGS += $(CAIRO_FT_CFLAGS)
-DEFINES += -DSK_USE_POSIX_THREADS=1
 endif
 
 ifeq (gtk2,$(MOZ_WIDGET_TOOLKIT))
 OS_CXXFLAGS += $(MOZ_PANGO_CFLAGS)
 endif
 
 ifeq (qt,$(MOZ_WIDGET_TOOLKIT))
 OS_CXXFLAGS += $(MOZ_PANGO_CFLAGS)
+ifeq (Linux,$(OS_TARGET))
+DEFINES += -DSK_USE_POSIX_THREADS=1
+endif
 endif
 
-ifeq (Linux,$(OS_TARGET))
+ifeq ($(MOZ_WIDGET_TOOLKIT),$(findstring $(MOZ_WIDGET_TOOLKIT),android gtk2 gonk cocoa))
 DEFINES += -DSK_USE_POSIX_THREADS=1
 endif
 
 ifeq (windows,$(MOZ_WIDGET_TOOLKIT))
 DEFINES += -DSKIA_IMPLEMENTATION=1 -DGR_IMPLEMENTATION=1
 endif
 
 ifneq (,$(INTEL_ARCHITECTURE))
--- a/gfx/skia/moz.build
+++ b/gfx/skia/moz.build
@@ -149,53 +149,61 @@ EXPORTS.skia += [
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
     EXPORTS.skia += [
         'include/ports/SkTypeface_mac.h',
     ]
     CPP_SOURCES += [
         'SkFontHost_mac_coretext.cpp',
         'SkStream_mac.cpp',
+        'SkThread_pthread.cpp',
         'SkTime_Unix.cpp',
     ]
 elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
     EXPORTS.skia += [
         'include/config/sk_stdint.h',
         'include/ports/SkTypeface_win.h',
     ]
     CPP_SOURCES += [
         'SkFontHost_win.cpp',
         'SkFontHost_tables.cpp',
         'SkFontHost_sandbox_none.cpp',
+        'SkThread_win.cpp',
         'SkTime_win.cpp',
     ]
 elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gtk2':
     CPP_SOURCES += [
         'SkFontHost_FreeType.cpp',
         'SkFontHost_FreeType_common.cpp',
         'SkFontHost_linux.cpp',
         'SkFontHost_tables.cpp',
+        'SkThread_pthread.cpp',
         'SkTime_Unix.cpp',
         'SkMMapStream.cpp',
         'SkOSFile.cpp',
     ]
 elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'qt':
     CPP_SOURCES += [
         'SkFontHost_FreeType.cpp',
         'SkFontHost_FreeType_common.cpp',
         'SkFontHost_tables.cpp',
         'SkMMapStream.cpp',
         'SkOSFile.cpp',
     ]
     if CONFIG['OS_TARGET'] == 'Linux':
         CPP_SOURCES += [
             'SkFontHost_linux.cpp',
             'SkFontHost_tables.cpp',
+            'SkThread_pthread.cpp',
             'SkTime_Unix.cpp',
         ]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
+    CPP_SOURCES += [
+        'SkThread_pthread.cpp',
+    ]
 
 
 # Separate 'if' from above, since the else below applies to all != 'android'
 # toolkits.
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
     CPP_SOURCES += [
         'SkDebug_android.cpp',
         'SkFontHost_android_old.cpp',
@@ -204,17 +212,16 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'andr
         'SkFontHost_tables.cpp',
         'SkMMapStream.cpp',
         'SkTime_Unix.cpp',
         'SkThread_pthread.cpp',
     ]
 else:
     CPP_SOURCES += [
         'SkDebug_stdio.cpp',
-        'SkThread_none.cpp',
     ]
 
 if CONFIG['INTEL_ARCHITECTURE']:
     CPP_SOURCES += [
         'SkBitmapProcState_opts_SSE2.cpp',
         'SkBlitRect_opts_SSE2.cpp',
         'SkBlitRow_opts_SSE2.cpp',
         'SkUtils_opts_SSE2.cpp',
--- a/gfx/tests/Makefile.in
+++ b/gfx/tests/Makefile.in
@@ -39,22 +39,16 @@ MOCHITEST_FILES = $(addprefix mochitest/
 ##		gfxColorManagementTest.cpp \
 #
 #
 ## rules.mk will put the CPP_UNIT_TESTS into SIMPLE_PROGRAMS twice if we
 ## define SIMPLE_PROGRAMS based on CPPSRCS directly.
 #CPPSRCS		= $(CPP_DISABLED_UNIT_TESTS)
 #SIMPLE_PROGRAMS	= $(CPP_DISABLED_UNIT_TESTS:.cpp=$(BIN_SUFFIX))
 #
-#ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa)
-#CMMSRCS		= gfxTestCocoaHelper.mm
-#HELPER_OBJS	= gfxTestCocoaHelper.$(OBJ_SUFFIX)
-#EXTRA_DEPS	+= gfxTestCocoaHelper.$(OBJ_SUFFIX)
-#endif
-#
 #LIBS		= \
 #		$(HELPER_OBJS) \
 #		$(call EXPAND_LIBNAME_PATH,thebes,../thebes) \
 #		$(call EXPAND_LIBNAME_PATH,gkgfx,../src) \
 #		$(MOZ_UNICHARUTIL_LIBS) \
 #		$(XPCOM_LIBS) \
 #		$(MOZ_JS_LIBS) \
 #		$(TK_LIBS) \
deleted file mode 100644
--- a/gfx/tests/gfxTestCocoaHelper.h
+++ /dev/null
@@ -1,9 +0,0 @@
-#ifndef GFX_TEST_COCOA_HELPER_H
-#define GFX_TEST_COCOA_HELPER_H
-
-extern "C" {
-    void CocoaPoolInit();
-    void CocoaPoolRelease();
-}
-
-#endif
deleted file mode 100644
--- a/gfx/tests/gfxTestCocoaHelper.mm
+++ /dev/null
@@ -1,19 +0,0 @@
-
-#import <Cocoa/Cocoa.h>
-
-#include "gfxTestCocoaHelper.h"
-
-static NSAutoreleasePool *sPool = NULL;
-
-void
-CocoaPoolInit() {
-  if (sPool)
-    [sPool release];
-  sPool = [[NSAutoreleasePool alloc] init];
-}
-
-void
-CocoaPoolRelease() {
-  [sPool release];
-  sPool = NULL;
-}
--- a/js/examples/jorendb.js
+++ b/js/examples/jorendb.js
@@ -1,384 +1,510 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sw=4 et tw=78:
  *
  * jorendb - A toy command-line debugger for shell-js programs.
  *
  * 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/. */
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
 
 /*
- * jorendb is a simple command-line debugger for shell-js programs.  It is
- * intended as a demo of the Debugger object (as there are no shell js programs to
- * speak of).
+ * jorendb is a simple command-line debugger for shell-js programs. It is
+ * intended as a demo of the Debugger object (as there are no shell js programs
+ * to speak of).
  *
  * To run it: $JS -d path/to/this/file/jorendb.js
  * To run some JS code under it, try:
  *    (jorendb) print load("my-script-to-debug.js")
  * Execution will stop at debugger statements and you'll get a jorendb prompt.
  */
 
-(function () {
-    var debuggerSource = "(" + function () {
-        // Debugger state.
-        var focusedFrame = null;
-        var topFrame = null;
-        var debuggeeValues = [null];
-        var lastExc = null;
+// Debugger state.
+var focusedFrame = null;
+var topFrame = null;
+var debuggeeValues = {};
+var nextDebuggeeValueIndex = 1;
+var lastExc = null;
 
-        // Convert a debuggee value v to a string.
-        function dvToString(v) {
-            return (typeof v !== 'object' || v === null) ? uneval(v) : "[object " + v.class + "]";
-        }
+// Cleanup functions to run when we next re-enter the repl.
+var replCleanups = [];
+
+// Convert a debuggee value v to a string.
+function dvToString(v) {
+    return (typeof v !== 'object' || v === null) ? uneval(v) : "[object " + v.class + "]";
+}
 
-        function showDebuggeeValue(dv) {
-            var dvrepr = dvToString(dv);
-            var i = debuggeeValues.length;
-            debuggeeValues[i] = dv;
-            print("$" + i + " = " + dvrepr);
-        }
+function showDebuggeeValue(dv) {
+    var dvrepr = dvToString(dv);
+    var i = nextDebuggeeValueIndex++;
+    debuggeeValues["$" + i] = dv;
+    print("$" + i + " = " + dvrepr);
+}
 
-        Object.defineProperty(Debugger.Frame.prototype, "num", {
-            configurable: true,
-            enumerable: false,
-            get: function () {
-                    var i = 0;
-                    for (var f = topFrame; f && f !== this; f = f.older)
-                        i++;
-                    return f === null ? undefined : i;
-                }
-            });
+Object.defineProperty(Debugger.Frame.prototype, "num", {
+    configurable: true,
+    enumerable: false,
+    get: function () {
+            var i = 0;
+            for (var f = topFrame; f && f !== this; f = f.older)
+                i++;
+            return f === null ? undefined : i;
+        }
+    });
+
+Debugger.Frame.prototype.frameDescription = function frameDescription() {
+    if (this.type == "call")
+        return ((this.callee.name || '<anonymous>') +
+                "(" + this.arguments.map(dvToString).join(", ") + ")");
+    else
+        return this.type + " code";
+}
 
-        function framePosition(f) {
-            if (!f.script)
-                return f.type + " code";
-            return (f.script.url || f.type + " code") + ":" + f.script.getOffsetLine(f.offset);
-        }
+Debugger.Frame.prototype.positionDescription = function positionDescription() {
+    if (this.script) {
+        var line = this.script.getOffsetLine(this.offset);
+        if (this.script.url)
+            return this.script.url + ":" + line;
+        return "line " + line;
+    }
+    return null;
+}
 
-        function callDescription(f) {
-            return ((f.callee.name || '<anonymous>') + 
-                    "(" + f.arguments.map(dvToString).join(", ") + ")");
-        }
+Debugger.Frame.prototype.fullDescription = function fullDescription() {
+    var fr = this.frameDescription();
+    var pos = this.positionDescription();
+    if (pos)
+        return fr + ", " + pos;
+    return fr;
+}
 
-        function showFrame(f, n) {
-            if (f === undefined || f === null) {
-                f = focusedFrame;
-                if (f === null) {
-                    print("No stack.");
-                    return;
-                }
-            }
-            if (n === undefined) {
-                n = f.num;
-                if (n === undefined)
-                    throw new Error("Internal error: frame not on stack");
-            }
+Object.defineProperty(Debugger.Frame.prototype, "line", {
+        configurable: true,
+        enumerable: false,
+        get: function() {
+            if (this.script)
+                return this.script.getOffsetLine(this.offset);
+            else
+                return null;
+        }
+    });
+
+function callDescription(f) {
+    return ((f.callee.name || '<anonymous>') +
+            "(" + f.arguments.map(dvToString).join(", ") + ")");
+}
 
-            var me = '#' + n;
-            if (f.type === "call")
-                me += ' ' + callDescription(f);
-            me += ' ' + framePosition(f);
-            print(me);
+function showFrame(f, n) {
+    if (f === undefined || f === null) {
+        f = focusedFrame;
+        if (f === null) {
+            print("No stack.");
+            return;
         }
+    }
+    if (n === undefined) {
+        n = f.num;
+        if (n === undefined)
+            throw new Error("Internal error: frame not on stack");
+    }
+
+    print('#' + n + " " + f.fullDescription());
+}
 
-        function saveExcursion(fn) {
-            var tf = topFrame, ff = focusedFrame;
-            try {
-                return fn();
-            } finally {
-                topFrame = tf;
-                focusedFrame = ff;
-            }
-        }
+function saveExcursion(fn) {
+    var tf = topFrame, ff = focusedFrame;
+    try {
+        return fn();
+    } finally {
+        topFrame = tf;
+        focusedFrame = ff;
+    }
+}
+
+// Evaluate an expression in the Debugger global
+function evalCommand(expr) {
+    eval(expr);
+}
+
+function quitCommand() {
+    dbg.enabled = false;
+    quit(0);
+}
 
-        function quitCommand() {
-            dbg.enabled = false;
-            quit(0);
-        }
+function backtraceCommand() {
+    if (topFrame === null)
+        print("No stack.");
+    for (var i = 0, f = topFrame; f; i++, f = f.older)
+        showFrame(f, i);
+}
 
-        function backtraceCommand() {
-            if (topFrame === null)
-                print("No stack.");
-            for (var i = 0, f = topFrame; f; i++, f = f.older)
-                showFrame(f, i);
-        }
+function printCommand(rest) {
+    // This is the real deal.
+    var cv = saveExcursion(
+        () => focusedFrame == null
+              ? debuggeeGlobalWrapper.evalInGlobalWithBindings(rest, debuggeeValues)
+              : focusedFrame.evalWithBindings(rest, debuggeeValues));
+    if (cv === null) {
+        if (!dbg.enabled)
+            return [cv];
+        print("Debuggee died.");
+    } else if ('return' in cv) {
+        if (!dbg.enabled)
+            return [undefined];
+        showDebuggeeValue(cv.return);
+    } else {
+        if (!dbg.enabled)
+            return [cv];
+        print("Exception caught. (To rethrow it, type 'throw'.)");
+        lastExc = cv.throw;
+        showDebuggeeValue(lastExc);
+    }
+}
 
-        function printCommand(rest) {
-            if (focusedFrame === null) {
-                // This is super bogus, need a way to create an env wrapping the debuggeeGlobal
-                // and eval against that.
-                var nonwrappedValue;
-                try {
-                    nonwrappedValue = debuggeeGlobal.eval(rest);
-                } catch (exc) {
-                    print("Exception caught.");
-                    nonwrappedValue = exc;
-                }
-                if (typeof nonwrappedValue !== "object" || nonwrappedValue === null) {
-                    // primitive value, no sweat
-                    print("    " + uneval(nonwrappedValue));
-                } else {
-                    // junk for now
-                    print("    " + Object.prototype.toString.call(nonwrappedValue));
-                }
-            } else {
-                // This is the real deal.
-                var cv = saveExcursion(function () {
-                        return focusedFrame.eval(rest);
-                    });
-                if (cv === null) {
-                    if (!dbg.enabled)
-                        return [cv];
-                    print("Debuggee died.");
-                } else if ('return' in cv) {
-                    if (!dbg.enabled)
-                        return [undefined];
-                    showDebuggeeValue(cv.return);
-                } else {
-                    if (!dbg.enabled)
-                        return [cv];
-                    print("Exception caught. (To rethrow it, type 'throw'.)");
-                    lastExc = cv.throw;
-                    showDebuggeeValue(lastExc);
-                }
-            }
+function detachCommand() {
+    dbg.enabled = false;
+    return [undefined];
+}
+
+function continueCommand() {
+    if (focusedFrame === null) {
+        print("No stack.");
+        return;
+    }
+    return [undefined];
+}
+
+function throwCommand(rest) {
+    var v;
+    if (focusedFrame !== topFrame) {
+        print("To throw, you must select the newest frame (use 'frame 0').");
+        return;
+    } else if (focusedFrame === null) {
+        print("No stack.");
+        return;
+    } else if (rest === '') {
+        return [{throw: lastExc}];
+    } else {
+        var cv = saveExcursion(function () { return focusedFrame.eval(rest); });
+        if (cv === null) {
+            if (!dbg.enabled)
+                return [cv];
+            print("Debuggee died while determining what to throw. Stopped.");
+        } else if ('return' in cv) {
+            return [{throw: cv.return}];
+        } else {
+            if (!dbg.enabled)
+                return [cv];
+            print("Exception determining what to throw. Stopped.");
+            showDebuggeeValue(cv.throw);
         }
+        return;
+    }
+}
 
-        function detachCommand() {
-            dbg.enabled = false;
-            return [undefined];
+function frameCommand(rest) {
+    var n, f;
+    if (rest.match(/[0-9]+/)) {
+        n = +rest;
+        f = topFrame;
+        if (f === null) {
+            print("No stack.");
+            return;
         }
-
-        function continueCommand() {
-            if (focusedFrame === null) {
-                print("No stack.");
+        for (var i = 0; i < n && f; i++) {
+            if (!f.older) {
+                print("There is no frame " + rest + ".");
                 return;
             }
-            return [undefined];
+            f.older.younger = f;
+            f = f.older;
         }
+        focusedFrame = f;
+        showFrame(f, n);
+    } else if (rest !== '') {
+        if (topFrame === null)
+            print("No stack.");
+        else
+            showFrame();
+    } else {
+        print("do what now?");
+    }
+}
+
+function upCommand() {
+    if (focusedFrame === null)
+        print("No stack.");
+    else if (focusedFrame.older === null)
+        print("Initial frame selected; you cannot go up.");
+    else {
+        focusedFrame.older.younger = focusedFrame;
+        focusedFrame = focusedFrame.older;
+        showFrame();
+    }
+}
+
+function downCommand() {
+    if (focusedFrame === null)
+        print("No stack.");
+    else if (!focusedFrame.younger)
+        print("Youngest frame selected; you cannot go down.");
+    else {
+        focusedFrame = focusedFrame.younger;
+        showFrame();
+    }
+}
 
-        function throwCommand(rest) {
-            var v;
-            if (focusedFrame !== topFrame) {
-                print("To throw, you must select the newest frame (use 'frame 0').");
-                return;
-            } else if (focusedFrame === null) {
-                print("No stack.");
-                return;
-            } else if (rest === '') {
-                return [{throw: lastExc}];
-            } else {
-                var cv = saveExcursion(function () { return focusedFrame.eval(rest); });
-                if (cv === null) {
-                    if (!dbg.enabled)
-                        return [cv];
-                    print("Debuggee died while determining what to throw. Stopped.");
-                } else if ('return' in cv) {
-                    return [{throw: cv.return}];
-                } else {
-                    if (!dbg.enabled)
-                        return [cv];
-                    print("Exception determining what to throw. Stopped.");
-                    showDebuggeeValue(cv.throw);
-                }
-                return;
-            }
+function forcereturnCommand(rest) {
+    var v;
+    var f = focusedFrame;
+    if (f !== topFrame) {
+        print("To forcereturn, you must select the newest frame (use 'frame 0').");
+    } else if (f === null) {
+        print("Nothing on the stack.");
+    } else if (rest === '') {
+        return [{return: undefined}];
+    } else {
+        var cv = saveExcursion(function () { return f.eval(rest); });
+        if (cv === null) {
+            if (!dbg.enabled)
+                return [cv];
+            print("Debuggee died while determining what to forcereturn. Stopped.");
+        } else if ('return' in cv) {
+            return [{return: cv.return}];
+        } else {
+            if (!dbg.enabled)
+                return [cv];
+            print("Error determining what to forcereturn. Stopped.");
+            showDebuggeeValue(cv.throw);
+        }
+    }
+}
+
+function printPop(f, c) {
+    var fdesc = f.fullDescription();
+    if (c.return) {
+        print("frame returning (still selected): " + fdesc);
+        showDebuggeeValue(c.return);
+    } else if (c.throw) {
+        print("frame threw exception: " + fdesc);
+        showDebuggeeValue(c.throw);
+        print("(To rethrow it, type 'throw'.)");
+        lastExc = c.throw;
+    } else {
+        print("frame was terminated: " + fdesc);
+    }
+}
+
+// Set |prop| on |obj| to |value|, but then restore its current value
+// when we next enter the repl.
+function setUntilRepl(obj, prop, value) {
+    var saved = obj[prop];
+    obj[prop] = value;
+    replCleanups.push(function () { obj[prop] = saved; });
+}
+
+function doStepOrNext(kind) {
+    var startFrame = topFrame;
+    var startLine = startFrame.line;
+    print("stepping in:   " + startFrame.fullDescription());
+    print("starting line: " + uneval(startLine));
+
+    function stepPopped(completion) {
+        // Note that we're popping this frame; we need to watch for
+        // subsequent step events on its caller.
+        this.reportedPop = true;
+        printPop(this, completion);
+        topFrame = focusedFrame = this;
+        return repl();
+    }
+
+    function stepEntered(newFrame) {
+        print("entered frame: " + newFrame.fullDescription());
+        topFrame = focusedFrame = newFrame;
+        return repl();
+    }
+
+    function stepStepped() {
+        print("stepStepped: " + this.fullDescription());
+        // If we've changed frame or line, then report that.
+        if (this !== startFrame || this.line != startLine) {
+            topFrame = focusedFrame = this;
+            if (focusedFrame != startFrame)
+                print(focusedFrame.fullDescription());
+            return repl();
         }
 
-        function frameCommand(rest) {
-            var n, f;
-            if (rest.match(/[0-9]+/)) {
-                n = +rest;
-                f = topFrame;
-                if (f === null) {
-                    print("No stack.");
-                    return;
-                }
-                for (var i = 0; i < n && f; i++) {
-                    if (!f.older) {
-                        print("There is no frame " + rest + ".");
-                        return;
-                    }
-                    f.older.younger = f;
-                    f = f.older;
-                }
-                focusedFrame = f;
-                showFrame(f, n);
-            } else if (rest !== '') {
-                if (topFrame === null)
-                    print("No stack.");
-                else
-                    showFrame();
-            } else {
-                print("do what now?");
-            }
-        }
+        // Otherwise, let execution continue.
+        return undefined;
+    }
+
+    if (kind.step)
+        setUntilRepl(dbg, 'onEnterFrame', stepEntered);
 
-        function debugmeCommand() {
-            var meta = newGlobal("new-compartment");
-            meta.debuggeeGlobal = this;
-            meta.debuggerSource = debuggerSource;
-            meta.prompt = prompt.replace('(', '(meta-');
-            meta.eval(debuggerSource);
-        }
+    // If we're stepping after an onPop, watch for steps and pops in the
+    // next-older frame; this one is done.
+    var stepFrame = startFrame.reportedPop ? startFrame.older : startFrame;
+    if (!stepFrame || !stepFrame.script)
+        stepFrame = null;
+    if (stepFrame) {
+        setUntilRepl(stepFrame, 'onStep', stepStepped);
+        setUntilRepl(stepFrame, 'onPop',  stepPopped);
+    }
+
+    // Let the program continue!
+    return [undefined];
+}
+
+function stepCommand() { return doStepOrNext({step:true}); }
+function nextCommand() { return doStepOrNext({next:true}); }
 
-        function upCommand() {
-            if (focusedFrame === null)
-                print("No stack.");
-            else if (focusedFrame.older === null)
-                print("Initial frame selected; you cannot go up.");
-            else {
-                focusedFrame.older.younger = focusedFrame;
-                focusedFrame = focusedFrame.older;
-                showFrame();
-            }
-        }
-
-        function downCommand() {
-            if (focusedFrame === null)
-                print("No stack.");
-            else if (!focusedFrame.younger)
-                print("Youngest frame selected; you cannot go down.");
-            else {
-                focusedFrame = focusedFrame.younger;
-                showFrame();
-            }
-        }
+// Build the table of commands.
+var commands = {};
+var commandArray = [
+    backtraceCommand, "bt", "where",
+    continueCommand, "c",
+    detachCommand,
+    downCommand, "d",
+    forcereturnCommand,
+    frameCommand, "f",
+    nextCommand, "n",
+    printCommand, "p",
+    quitCommand, "q",
+    stepCommand, "s",
+    throwCommand, "t",
+    upCommand, "u",
+    helpCommand, "h",
+    evalCommand, "!",
+    ];
+var last = null;
+for (var i = 0; i < commandArray.length; i++) {
+    var cmd = commandArray[i];
+    if (typeof cmd === "string")
+        commands[cmd] = last;
+    else
+        last = commands[cmd.name.replace(/Command$/, '')] = cmd;
+}
 
-        function forcereturnCommand(rest) {
-            var v;
-            var f = focusedFrame;
-            if (f !== topFrame) {
-                print("To forcereturn, you must select the newest frame (use 'frame 0').");
-            } else if (f === null) {
-                print("Nothing on the stack.");
-            } else if (rest === '') {
-                return [{return: undefined}];
-            } else {
-                var cv = saveExcursion(function () { return f.eval(rest); });
-                if (cv === null) {
-                    if (!dbg.enabled)
-                        return [cv];
-                    print("Debuggee died while determining what to forcereturn. Stopped.");
-                } else if ('return' in cv) {
-                    return [{return: cv.return}];
-                } else {
-                    if (!dbg.enabled)
-                        return [cv];
-                    print("Error determining what to forcereturn. Stopped.");
-                    showDebuggeeValue(cv.throw);
-                }
-            }
+function helpCommand(rest) {
+    print("Available commands:");
+    var printcmd = function(group) {
+        print("  " + group.join(", "));
+    }
+
+    var group = [];
+    for (var cmd of commandArray) {
+        if (typeof cmd === "string") {
+            group.push(cmd);
+        } else {
+            if (group.length) printcmd(group);
+            group = [ cmd.name.replace(/Command$/, '') ];
         }
+    }
+    printcmd(group);
+}
+
+// Break cmd into two parts: its first word and everything else. If it begins
+// with punctuation, treat that as a separate word.
+function breakcmd(cmd) {
+    cmd = cmd.trimLeft();
+    if ("!@#$%^&*_+=/?.,<>:;'\"".indexOf(cmd.substr(0, 1)) != -1)
+        return [cmd.substr(0, 1), cmd.substr(1).trimLeft()];
+    var m = /\s/.exec(cmd);
+    if (m === null)
+        return [cmd, ''];
+    return [cmd.slice(0, m.index), cmd.slice(m.index).trimLeft()];
+}
+
+function runcmd(cmd) {
+    var pieces = breakcmd(cmd);
+    if (pieces[0] === "")
+        return undefined;
 
-        // Build the table of commands.
-        var commands = {};
-        var commandArray = [
-            backtraceCommand, "bt", "where",
-            continueCommand, "c",
-            detachCommand,
-            debugmeCommand,
-            downCommand, "d",
-            forcereturnCommand,
-            frameCommand, "f",
-            printCommand, "p",
-            quitCommand, "q",
-            throwCommand, "t",
-            upCommand, "u"
-            ];
-        var last = null;
-        for (var i = 0; i < commandArray.length; i++) {
-            var cmd = commandArray[i];
-            if (typeof cmd === "string")
-                commands[cmd] = last;
-            else
-                last = commands[cmd.name.replace(/Command$/, '')] = cmd;
-        }
+    var first = pieces[0], rest = pieces[1];
+    if (!commands.hasOwnProperty(first)) {
+        print("unrecognized command '" + first + "'");
+        return undefined;
+    }
+
+    var cmd = commands[first];
+    if (cmd.length === 0 && rest !== '') {
+        print("this command cannot take an argument");
+        return undefined;
+    }
+
+    return cmd(rest);
+}
+
+function repl() {
+    while (replCleanups.length > 0)
+        replCleanups.pop()();
 
-        // Break cmd into two parts: its first word and everything else.
-        function breakcmd(cmd) {
-            cmd = cmd.trimLeft();
-            var m = /\s/.exec(cmd);
-            if (m === null)
-                return [cmd, ''];
-            return [cmd.slice(0, m.index), cmd.slice(m.index).trimLeft()];
-        }
-
-        function runcmd(cmd) {
-            var pieces = breakcmd(cmd);
-            if (pieces[0] === "")
-                return undefined;
+    var cmd;
+    for (;;) {
+        putstr("\n" + prompt);
+        cmd = readline();
+        if (cmd === null)
+            return null;
 
-            var first = pieces[0], rest = pieces[1];
-            if (!commands.hasOwnProperty(first)) {
-                print("unrecognized command '" + first + "'");
-                return undefined;
-            }
-
-            var cmd = commands[first];
-            if (cmd.length === 0 && rest !== '') {
-                print("this command cannot take an argument");
-                return undefined;
-            }
-
-            return cmd(rest);
+        try {
+            var result = runcmd(cmd);
+            if (result === undefined)
+                ; // do nothing
+            else if (Array.isArray(result))
+                return result[0];
+            else
+                throw new Error("Internal error: result of runcmd wasn't array or undefined");
+        } catch (exc) {
+            print("*** Internal error: exception in the debugger code.");
+            print("    " + exc);
+            print(exc.stack);
         }
+    }
+}
 
-        function repl() {
-            var cmd;
-            for (;;) {
-                print("\n" + prompt);
-                cmd = readline();
-                if (cmd === null)
-                    break;
-
-                try {
-                    var result = runcmd(cmd);
-                    if (result === undefined)
-                        ; // do nothing
-                    else if (Array.isArray(result))
-                        return result[0];
-                    else
-                        throw new Error("Internal error: result of runcmd wasn't array or undefined");
-                } catch (exc) {
-                    print("*** Internal error: exception in the debugger code.");
-                    print("    " + exc);
-                    var me = prompt.replace(/^\((.*)\)$/, function (a, b) { return b; });
-                    print("Debug " + me + "? (y/n)");
-                    if (readline().match(/^\s*y/i) !== null)
-                        debugMe();
-                    else
-                        print("ok, ignoring error");
-                }
-            }
-        }
+var dbg = new Debugger();
+dbg.onDebuggerStatement = function (frame) {
+    return saveExcursion(function () {
+            topFrame = focusedFrame = frame;
+            print("'debugger' statement hit.");
+            showFrame();
+            return repl();
+        });
+};
+dbg.onThrow = function (frame, exc) {
+    return saveExcursion(function () {
+            topFrame = focusedFrame = frame;
+            print("Unwinding due to exception. (Type 'c' to continue unwinding.)");
+            showFrame();
+            print("Exception value is:");
+            showDebuggeeValue(exc);
+            return repl();
+        });
+};
 
-        var dbg = new Debugger(debuggeeGlobal);
-        dbg.onDebuggerStatement = function (frame) {
-            return saveExcursion(function () {
-                    topFrame = focusedFrame = frame;
-                    print("'debugger' statement hit.");
-                    showFrame();
-                    return repl();
-                });
-        };
-        dbg.onThrow = function (frame, exc) {
-            return saveExcursion(function () {
-                    topFrame = focusedFrame = frame;
-                    print("Unwinding due to exception. (Type 'c' to continue unwinding.)");
-                    showFrame();
-                    print("Exception value is:");
-                    showDebuggeeValue(exc);
-                    return repl();
-                });
-        };
-        repl();
-    } + ")();"
+// The depth of jorendb nesting.
+var jorendbDepth;
+if (typeof jorendbDepth == 'undefined') jorendbDepth = 0;
+
+var debuggeeGlobal = newGlobal("new-compartment");
+debuggeeGlobal.jorendbDepth = jorendbDepth + 1;
+var debuggeeGlobalWrapper = dbg.addDebuggee(debuggeeGlobal);
+
+print("jorendb version -0.0");
+prompt = '(' + Array(jorendbDepth+1).join('meta-') + 'jorendb) ';
 
-    print("jorendb version -0.0");
-    var g = newGlobal("new-compartment");
-    g.debuggeeGlobal = this;
-    g.prompt = '(jorendb)';
-    g.debuggerSource = debuggerSource;
-    g.eval(debuggerSource);
-})();
+var args = arguments;
+while(args.length > 0) {
+    var arg = args.shift();
+    if (arg == '-f') {
+        arg = args.shift();
+        debuggeeGlobal.evaluate(read(arg), { fileName: arg, lineNumber: 1 });
+    } else if (arg == '-e') {
+        arg = args.shift();
+        debuggeeGlobal.eval(arg);
+    } else {
+        throw("jorendb does not implement command-line argument '" + arg + "'");
+    }
+}
+
+repl();
--- a/js/public/CallArgs.h
+++ b/js/public/CallArgs.h
@@ -54,16 +54,18 @@ typedef JSBool
  * |undefined|, use |JS_GetGlobalForObject| to compute the global object.  If
  * the value is some other primitive, use |JS_ValueToObject| to box it.
  */
 extern JS_PUBLIC_API(JS::Value)
 JS_ComputeThis(JSContext *cx, JS::Value *vp);
 
 namespace JS {
 
+extern JS_PUBLIC_DATA(const HandleValue) UndefinedHandleValue;
+
 /*
  * JS::CallReceiver encapsulates access to the callee, |this|, and eventual
  * return value for a function call.  The principal way to create a
  * CallReceiver is using JS::CallReceiverFromVp:
  *
  *   static JSBool
  *   FunctionReturningThis(JSContext *cx, unsigned argc, JS::Value *vp)
  *   {
@@ -294,16 +296,26 @@ class MOZ_STACK_CLASS CallArgsBase :
      * Returns the i-th zero-indexed argument, or |undefined| if there's no
      * such argument.
      */
     Value get(unsigned i) const {
         return i < length() ? this->argv_[i] : UndefinedValue();
     }
 
     /*
+     * Returns the i-th zero-indexed argument as a handle, or |undefined| if
+     * there's no such argument.
+     */
+    HandleValue handleOrUndefinedAt(unsigned i) const {
+        return i < length()
+               ? HandleValue::fromMarkedLocation(&this->argv_[i])
+               : UndefinedHandleValue;
+    }
+
+    /*
      * Returns true if the i-th zero-indexed argument is present and is not
      * |undefined|.
      */
     bool hasDefined(unsigned i) const {
         return i < argc_ && !this->argv_[i].isUndefined();
     }
 
   public:
--- a/js/src/assembler/assembler/X86Assembler.h
+++ b/js/src/assembler/assembler/X86Assembler.h
@@ -38,16 +38,18 @@
 
 #include "AssemblerBuffer.h"
 #include "assembler/wtf/Assertions.h"
 #include "js/Vector.h"
 
 namespace JSC {
 
 inline bool CAN_SIGN_EXTEND_8_32(int32_t value) { return value == (int32_t)(signed char)value; }
+inline bool CAN_ZERO_EXTEND_8_32(int32_t value) { return value == (int32_t)(unsigned char)value; }
+inline bool CAN_ZERO_EXTEND_32_64(int32_t value) { return value >= 0; }
 
 namespace X86Registers {
     typedef enum {
         eax,
         ecx,
         edx,
         ebx,
         esp,
@@ -1086,16 +1088,21 @@ public:
     {
         spew("cmpl       %s0x%x(%s), %s", 
              PRETTY_PRINT_OFFSET(offset), nameIReg(4, base), nameIReg(src));
         m_formatter.oneByteOp(OP_CMP_GvEv, src, base, offset);
     }
 
     void cmpl_ir(int imm, RegisterID dst)
     {
+        if (imm == 0) {
+            testl_rr(dst, dst);
+            return;
+        }
+
         spew("cmpl       $0x%x, %s", imm, nameIReg(4, dst));
         if (CAN_SIGN_EXTEND_8_32(imm)) {
             m_formatter.oneByteOp(OP_GROUP1_EvIb, GROUP1_OP_CMP, dst);
             m_formatter.immediate8(imm);
         } else {
             m_formatter.oneByteOp(OP_GROUP1_EvIz, GROUP1_OP_CMP, dst);
             m_formatter.immediate32(imm);
         }
@@ -1173,16 +1180,21 @@ public:
     {
         spew("cmpq       %d(%s), %s",
              offset, nameIReg(8, base), nameIReg(8, src));
         m_formatter.oneByteOp64(OP_CMP_GvEv, src, base, offset);
     }
 
     void cmpq_ir(int imm, RegisterID dst)
     {
+        if (imm == 0) {
+            testq_rr(dst, dst);
+            return;
+        }
+
         spew("cmpq       $%d, %s",
              imm, nameIReg(8, dst));
         if (CAN_SIGN_EXTEND_8_32(imm)) {
             m_formatter.oneByteOp64(OP_GROUP1_EvIb, GROUP1_OP_CMP, dst);
             m_formatter.immediate8(imm);
         } else {
             m_formatter.oneByteOp64(OP_GROUP1_EvIz, GROUP1_OP_CMP, dst);
             m_formatter.immediate32(imm);
@@ -1265,16 +1277,25 @@ public:
     {
         spew("testb      %s, %s",
              nameIReg(1,src), nameIReg(1,dst));
         m_formatter.oneByteOp(OP_TEST_EbGb, src, dst);
     }
     
     void testl_i32r(int imm, RegisterID dst)
     {
+#if WTF_CPU_X86_64
+        // If the mask fits in an 8-bit immediate, we can use testb with an
+        // 8-bit subreg. This could be extended to handle x86-32 too, but it
+        // would require a check to see if the register supports 8-bit subregs.
+        if (CAN_ZERO_EXTEND_8_32(imm)) {
+            testb_i8r(imm, dst);
+            return;
+        }
+#endif
         spew("testl      $0x%x, %s",
              imm, nameIReg(dst));
         m_formatter.oneByteOp(OP_GROUP3_EvIz, GROUP3_OP_TEST, dst);
         m_formatter.immediate32(imm);
     }
 
     void testl_i32m(int imm, int offset, RegisterID base)
     {
@@ -1308,20 +1329,24 @@ public:
     {
         spew("testq      %s, %s",
              nameIReg(8,src), nameIReg(8,dst));
         m_formatter.oneByteOp64(OP_TEST_EvGv, src, dst);
     }
 
     void testq_i32r(int imm, RegisterID dst)
     {
-        spew("testq      $0x%x, %s",
-             imm, nameIReg(dst));
-        m_formatter.oneByteOp64(OP_GROUP3_EvIz, GROUP3_OP_TEST, dst);
-        m_formatter.immediate32(imm);
+        if (CAN_ZERO_EXTEND_32_64(imm)) {
+            testl_i32r(imm, dst);
+        } else {
+            spew("testq      $0x%x, %s",
+                 imm, nameIReg(dst));
+            m_formatter.oneByteOp64(OP_GROUP3_EvIz, GROUP3_OP_TEST, dst);
+            m_formatter.immediate32(imm);
+        }
     }
 
     void testq_i32m(int imm, int offset, RegisterID base)
     {
         spew("testq      $0x%x, %s0x%x(%s)",
              imm, PRETTY_PRINT_OFFSET(offset), nameIReg(base));
         m_formatter.oneByteOp64(OP_GROUP3_EvIz, GROUP3_OP_TEST, base, offset);
         m_formatter.immediate32(imm);
@@ -1667,33 +1692,33 @@ public:
     {
         spew("movsxd     %s, %s",
              nameIReg(4, src), nameIReg(8, dst));
         m_formatter.oneByteOp64(OP_MOVSXD_GvEv, dst, src);
     }
     
     JmpSrc movl_ripr(RegisterID dst)
     {
-        spew("movl     \?(%%rip), %s",
+        spew("movl       \?(%%rip), %s",
              nameIReg(dst));
         m_formatter.oneByteRipOp(OP_MOV_GvEv, (RegisterID)dst, 0);
         return JmpSrc(m_formatter.size());
     }
 
     JmpSrc movl_rrip(RegisterID src)
     {
-        spew("movl     %s, \?(%%rip)",
+        spew("movl       %s, \?(%%rip)",
              nameIReg(src));
         m_formatter.oneByteRipOp(OP_MOV_EvGv, (RegisterID)src, 0);
         return JmpSrc(m_formatter.size());
     }
 
     JmpSrc movq_ripr(RegisterID dst)
     {
-        spew("movl     \?(%%rip), %s",
+        spew("movl       \?(%%rip), %s",
              nameIReg(dst));
         m_formatter.oneByteRipOp64(OP_MOV_GvEv, dst, 0);
         return JmpSrc(m_formatter.size());
     }
 #else
     void movl_rm(RegisterID src, void* addr)
     {
         spew("movl       %s, 0(%p)",
@@ -1919,23 +1944,23 @@ public:
         spew("jmp        *%s",
              nameIReg(dst));
         m_formatter.oneByteOp(OP_GROUP5_Ev, GROUP5_OP_JMPN, dst);
         return JmpSrc(m_formatter.size());
     }
     
     void jmp_m(int offset, RegisterID base)
     {
-        spew("jmp       *%d(%s)",
+        spew("jmp        *%d(%s)",
              offset, nameIReg(base));
         m_formatter.oneByteOp(OP_GROUP5_Ev, GROUP5_OP_JMPN, base, offset);
     }
 
     void jmp_m(int offset, RegisterID base, RegisterID index, int scale) {
-        spew("jmp       *%d(%s,%s,%d)",
+        spew("jmp        *%d(%s,%s,%d)",
              offset, nameIReg(base), nameIReg(index), 1<<scale);
         m_formatter.oneByteOp(OP_GROUP5_Ev, GROUP5_OP_JMPN, base, index, scale, offset);
     }
 
 #if WTF_CPU_X86_64
     void jmp_rip(int ripOffset) {
         // rip-relative addressing.
         spew("jmp        *%d(%%rip)", ripOffset);
@@ -2097,17 +2122,17 @@ public:
         spew("cvtss2sd   %s, %s",
              nameFPReg(src), nameFPReg(dst));
         m_formatter.prefix(PRE_SSE_F3);
         m_formatter.twoByteOp(OP2_CVTSS2SD_VsdEd, (RegisterID)dst, (RegisterID)src);
     }
 
     void cvtsd2ss_rr(XMMRegisterID src, XMMRegisterID dst)
     {
-        spew("cvtss2sd   %s, %s",
+        spew("cvtsd2ss   %s, %s",
              nameFPReg(src), nameFPReg(dst));
         m_formatter.prefix(PRE_SSE_F2);
         m_formatter.twoByteOp(OP2_CVTSD2SS_VsdEd, (RegisterID)dst, (RegisterID)src);
     }
 
     void cvtsi2sd_rr(RegisterID src, XMMRegisterID dst)
     {
         spew("cvtsi2sd   %s, %s",
@@ -2180,38 +2205,38 @@ public:
     void movd_rr(RegisterID src, XMMRegisterID dst)
     {
         spew("movd       %s, %s",
              nameIReg(src), nameFPReg(dst));
         m_formatter.prefix(PRE_SSE_66);
         m_formatter.twoByteOp(OP2_MOVD_VdEd, (RegisterID)dst, src);
     }
 
-    void psrldq_rr(XMMRegisterID dest, int shift)
-    {
-        spew("psrldq     %s, %d",
-             nameFPReg(dest), shift);
+    void psrldq_ir(int shift, XMMRegisterID dest)
+    {
+        spew("psrldq      $%d, %s",
+             shift, nameFPReg(dest));
         m_formatter.prefix(PRE_SSE_66);
         m_formatter.twoByteOp(OP2_PSRLDQ_Vd, (RegisterID)3, (RegisterID)dest);
         m_formatter.immediate8(shift);
     }
 
-    void psllq_rr(XMMRegisterID dest, int shift)
-    {
-        spew("psllq     %s, %d",
-             nameFPReg(dest), shift);
+    void psllq_ir(int shift, XMMRegisterID dest)
+    {
+        spew("psllq      $%d, %s",
+             shift, nameFPReg(dest));
         m_formatter.prefix(PRE_SSE_66);
         m_formatter.twoByteOp(OP2_PSRLDQ_Vd, (RegisterID)6, (RegisterID)dest);
         m_formatter.immediate8(shift);
     }
 
-    void psrlq_rr(XMMRegisterID dest, int shift)
-    {
-        spew("psrlq     %s, %d",
-             nameFPReg(dest), shift);
+    void psrlq_ir(int shift, XMMRegisterID dest)
+    {
+        spew("psrlq      $%d, %s",
+             shift, nameFPReg(dest));
         m_formatter.prefix(PRE_SSE_66);
         m_formatter.twoByteOp(OP2_PSRLDQ_Vd, (RegisterID)2, (RegisterID)dest);
         m_formatter.immediate8(shift);
     }
 
     void movmskpd_rr(XMMRegisterID src, RegisterID dst)
     {
         spew("movmskpd   %s, %s",
--- a/js/src/builtin/Object.cpp
+++ b/js/src/builtin/Object.cpp
@@ -67,17 +67,17 @@ js::obj_construct(JSContext *cx, unsigne
 /* ES5 15.2.4.7. */
 static JSBool
 obj_propertyIsEnumerable(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     /* Step 1. */
     RootedId id(cx);
-    if (!ValueToId<CanGC>(cx, args.get(0), &id))
+    if (!ValueToId<CanGC>(cx, args.handleOrUndefinedAt(0), &id))
         return false;
 
     /* Step 2. */
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return false;
 
     /* Steps 3. */
@@ -178,17 +178,18 @@ obj_toSource(JSContext *cx, unsigned arg
                 valcnt = 1;
                 gsop[0].set(NULL);
                 if (!JSObject::getGeneric(cx, obj, obj, id, val[0]))
                     return false;
             }
         }
 
         /* Convert id to a linear string. */
-        JSString *s = ToString<CanGC>(cx, IdToValue(id));
+        RootedValue idv(cx, IdToValue(id));
+        JSString *s = ToString<CanGC>(cx, idv);
         if (!s)
             return false;
         Rooted<JSLinearString*> idstr(cx, s->ensureLinear(cx));
         if (!idstr)
             return false;
 
         /*
          * If id is a string that's not an identifier, or if it's a negative
@@ -375,17 +376,17 @@ DefineAccessor(JSContext *cx, unsigned a
     if (args.length() < 2 || !js_IsCallable(args[1])) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
                              JSMSG_BAD_GETTER_OR_SETTER,
                              Type == Getter ? js_getter_str : js_setter_str);
         return false;
     }
 
     RootedId id(cx);
-    if (!ValueToId<CanGC>(cx, args[0], &id))
+    if (!ValueToId<CanGC>(cx, args.handleAt(0), &id))
         return false;
 
     RootedObject descObj(cx, NewBuiltinClassInstance(cx, &ObjectClass));
     if (!descObj)
         return false;
 
     JSAtomState &names = cx->names();
     RootedValue trueVal(cx, BooleanValue(true));
@@ -427,17 +428,17 @@ js::obj_defineSetter(JSContext *cx, unsi
 }
 
 static JSBool
 obj_lookupGetter(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     RootedId id(cx);
-    if (!ValueToId<CanGC>(cx, args.get(0), &id))
+    if (!ValueToId<CanGC>(cx, args.handleOrUndefinedAt(0), &id))
         return JS_FALSE;
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return JS_FALSE;
     if (obj->isProxy()) {
         // The vanilla getter lookup code below requires that the object is
         // native. Handle proxies separately.
         args.rval().setUndefined();
@@ -463,17 +464,17 @@ obj_lookupGetter(JSContext *cx, unsigned
 }
 
 static JSBool
 obj_lookupSetter(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     RootedId id(cx);
-    if (!ValueToId<CanGC>(cx, args.get(0), &id))
+    if (!ValueToId<CanGC>(cx, args.handleOrUndefinedAt(0), &id))
         return JS_FALSE;
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return JS_FALSE;
     if (obj->isProxy()) {
         // The vanilla setter lookup code below requires that the object is
         // native. Handle proxies separately.
         args.rval().setUndefined();
@@ -568,17 +569,17 @@ obj_watch(JSContext *cx, unsigned argc, 
         return false;
     }
 
     RootedObject callable(cx, ValueToCallable(cx, args[1], args.length() - 2));
     if (!callable)
         return false;
 
     RootedId propid(cx);
-    if (!ValueToId<CanGC>(cx, args[0], &propid))
+    if (!ValueToId<CanGC>(cx, args.handleAt(0), &propid))
         return false;
 
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return false;
 
     RootedValue tmp(cx);
     unsigned attrs;
@@ -596,33 +597,33 @@ obj_unwatch(JSContext *cx, unsigned argc
     CallArgs args = CallArgsFromVp(argc, vp);
 
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return false;
     args.rval().setUndefined();
     RootedId id(cx);
     if (argc != 0) {
-        if (!ValueToId<CanGC>(cx, args[0], &id))
+        if (!ValueToId<CanGC>(cx, args.handleAt(0), &id))
             return false;
     } else {
         id = JSID_VOID;
     }
     return JS_ClearWatchPoint(cx, obj, id, NULL, NULL);
 }
 
 #endif /* JS_HAS_OBJ_WATCHPOINT */
 
 /* ECMA 15.2.4.5. */
 static JSBool
 obj_hasOwnProperty(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
-    Value idValue = args.get(0);
+    HandleValue idValue = args.handleOrUndefinedAt(0);
 
     /* Step 1, 2. */
     jsid id;
     if (args.thisv().isObject() && ValueToId<NoGC>(cx, idValue, &id)) {
         JSObject *obj = &args.thisv().toObject(), *obj2;
         Shape *prop;
         if (!obj->isProxy() &&
             HasOwnProperty<NoGC>(cx, obj->getOps()->lookupGeneric, obj, id, &obj2, &prop))
@@ -741,17 +742,17 @@ obj_create(JSContext *cx, unsigned argc,
 static JSBool
 obj_getOwnPropertyDescriptor(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     RootedObject obj(cx);
     if (!GetFirstArgumentAsObject(cx, args, "Object.getOwnPropertyDescriptor", &obj))
         return JS_FALSE;
     RootedId id(cx);
-    if (!ValueToId<CanGC>(cx, args.get(1), &id))
+    if (!ValueToId<CanGC>(cx, args.handleOrUndefinedAt(1), &id))
         return JS_FALSE;
     return GetOwnPropertyDescriptor(cx, obj, id, args.rval());
 }
 
 static JSBool
 obj_keys(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
@@ -846,17 +847,17 @@ static JSBool
 obj_defineProperty(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     RootedObject obj(cx);
     if (!GetFirstArgumentAsObject(cx, args, "Object.defineProperty", &obj))
         return false;
 
     RootedId id(cx);
-    if (!ValueToId<CanGC>(cx, args.get(1), &id))
+    if (!ValueToId<CanGC>(cx, args.handleOrUndefinedAt(1), &id))
         return JS_FALSE;
 
     const Value descval = args.get(2);
 
     JSBool junk;
     if (!js_DefineOwnProperty(cx, obj, id, descval, &junk))
         return false;
 
--- a/js/src/builtin/RegExp.cpp
+++ b/js/src/builtin/RegExp.cpp
@@ -272,17 +272,17 @@ CompileRegExpObject(JSContext *cx, RegEx
 
         source = AtomizeString<CanGC>(cx, str);
         if (!source)
             return false;
     }
 
     RegExpFlag flags = RegExpFlag(0);
     if (args.hasDefined(1)) {
-        RootedString flagStr(cx, ToString<CanGC>(cx, args[1]));
+        RootedString flagStr(cx, ToString<CanGC>(cx, args.handleAt(1)));
         if (!flagStr)
             return false;
         args[1].setString(flagStr);
         if (!ParseRegExpFlags(cx, flagStr, &flags))
             return false;
     }
 
     RootedAtom escapedSourceStr(cx, EscapeNakedForwardSlashes(cx, source));
@@ -597,17 +597,17 @@ js::ExecuteRegExp(JSContext *cx, HandleO
 /* ES5 15.10.6.2 (and 15.10.6.3, which calls 15.10.6.2). */
 static RegExpRunStatus
 ExecuteRegExp(JSContext *cx, CallArgs args, MatchConduit &matches)
 {
     /* Step 1 (a) was performed by CallNonGenericMethod. */
     RootedObject regexp(cx, &args.thisv().toObject());
 
     /* Step 2. */
-    RootedString string(cx, ToString<CanGC>(cx, args.get(0)));
+    RootedString string(cx, ToString<CanGC>(cx, args.handleOrUndefinedAt(0)));
     if (!string)
         return RegExpRunStatus_Error;
 
     return ExecuteRegExp(cx, regexp, string, matches);
 }
 
 /* ES5 15.10.6.2. */
 static bool
@@ -617,17 +617,17 @@ regexp_exec_impl(JSContext *cx, CallArgs
     ScopedMatchPairs matches(&cx->tempLifoAlloc());
     MatchConduit conduit(&matches);
 
     /*
      * Extract arguments to share between ExecuteRegExp()
      * and CreateRegExpMatchResult().
      */
     RootedObject regexp(cx, &args.thisv().toObject());
-    RootedString string(cx, ToString<CanGC>(cx, args.get(0)));
+    RootedString string(cx, ToString<CanGC>(cx, args.handleOrUndefinedAt(0)));
     if (!string)
         return false;
 
     RegExpRunStatus status = ExecuteRegExp(cx, regexp, string, conduit);
 
     if (status == RegExpRunStatus_Error)
         return false;
 
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -6719,21 +6719,23 @@ Parser<ParseHandler>::primaryExpr(TokenK
             VALUE   = 0x4 | GET | SET
         };
 
         pn = handler.newList(PNK_OBJECT, null(), JSOP_NEWINIT);
         if (!pn)
             return null();
 
         RootedAtom atom(context);
+        Value tmp;
         for (;;) {
             TokenKind ltok = tokenStream.getToken(TSF_KEYWORD_IS_NAME);
             switch (ltok) {
               case TOK_NUMBER:
-                atom = ToAtom<CanGC>(context, DoubleValue(tokenStream.currentToken().number()));
+                tmp = DoubleValue(tokenStream.currentToken().number());
+                atom = ToAtom<CanGC>(context, HandleValue::fromMarkedLocation(&tmp));
                 if (!atom)
                     return null();
                 pn3 = handler.newNumber(tokenStream.currentToken());
                 break;
               case TOK_NAME:
                 {
                     atom = tokenStream.currentToken().name();
                     if (atom == context->names().get) {
@@ -6756,27 +6758,29 @@ Parser<ParseHandler>::primaryExpr(TokenK
                     } else if (tt == TOK_STRING) {
                         atom = tokenStream.currentToken().atom();
 
                         uint32_t index;
                         if (atom->isIndex(&index)) {
                             pn3 = handler.newNumber(index);
                             if (!pn3)
                                 return null();
-                            atom = ToAtom<CanGC>(context, DoubleValue(index));
+                            tmp = DoubleValue(index);
+                            atom = ToAtom<CanGC>(context, HandleValue::fromMarkedLocation(&tmp));
                             if (!atom)
                                 return null();
                         } else {
                             pn3 = handler.newName(atom->asPropertyName(), pc, PNK_STRING);
                             if (!pn3)
                                 return null();
                         }
                     } else if (tt == TOK_NUMBER) {
                         double number = tokenStream.currentToken().number();
-                        atom = ToAtom<CanGC>(context, DoubleValue(number));
+                        tmp = DoubleValue(number);
+                        atom = ToAtom<CanGC>(context, HandleValue::fromMarkedLocation(&tmp));
                         if (!atom)
                             return null();
                         pn3 = handler.newNumber(tokenStream.currentToken());
                         if (!pn3)
                             return null();
                     } else {
                         tokenStream.ungetToken();
                         pn3 = handler.newAtom(PNK_NAME, atom);
--- a/js/src/frontend/TokenStream.cpp
+++ b/js/src/frontend/TokenStream.cpp
@@ -660,16 +660,17 @@ TokenStream::reportCompileErrorNumberVA(
 
     CompileError err(cx);
 
     err.report.flags = flags;
     err.report.errorNumber = errorNumber;
     err.report.filename = filename;
     err.report.originPrincipals = originPrincipals;
     err.report.lineno = srcCoords.lineNum(offset);
+    err.report.column = srcCoords.columnIndex(offset);
 
     err.argumentsType = (flags & JSREPORT_UC) ? ArgumentsAreUnicode : ArgumentsAreASCII;
 
     if (!js_ExpandErrorArguments(cx, js_GetErrorMessage, NULL, errorNumber, &err.message,
                                  &err.report, err.argumentsType, args))
     {
         return false;
     }
--- a/js/src/ion/AsmJS.cpp
+++ b/js/src/ion/AsmJS.cpp
@@ -4907,20 +4907,24 @@ CheckFunctionBodiesParallel(ModuleCompil
 
         // Otherwise, the error occurred on the main thread and was already reported.
         return false;
     }
     return true;
 }
 #endif // JS_PARALLEL_COMPILATION
 
-static RegisterSet AllRegs = RegisterSet(GeneralRegisterSet(Registers::AllMask),
-                                         FloatRegisterSet(FloatRegisters::AllMask));
-static RegisterSet NonVolatileRegs = RegisterSet(GeneralRegisterSet(Registers::NonVolatileMask),
-                                                 FloatRegisterSet(FloatRegisters::NonVolatileMask));
+// All registers except the stack pointer.
+static const RegisterSet AllRegsExceptSP =
+    RegisterSet(GeneralRegisterSet(Registers::AllMask &
+                                   ~(uint32_t(1) << Registers::StackPointer)),
+                FloatRegisterSet(FloatRegisters::AllMask));
+static const RegisterSet NonVolatileRegs =
+    RegisterSet(GeneralRegisterSet(Registers::NonVolatileMask),
+                FloatRegisterSet(FloatRegisters::NonVolatileMask));
 
 static void
 LoadAsmJSActivationIntoRegister(MacroAssembler &masm, Register reg)
 {
     masm.movePtr(ImmWord(GetIonContext()->compartment->rt), reg);
     size_t offset = offsetof(JSRuntime, mainThread) +
                     PerThreadData::offsetOfAsmJSActivationStackReadOnly();
     masm.loadPtr(Address(reg, offset), reg);
@@ -5491,37 +5495,37 @@ GenerateStackOverflowExit(ModuleCompiler
 #endif
     void (*pf)(JSContext*) = js_ReportOverRecursed;
     masm.call(ImmWord(JS_FUNC_TO_DATA_PTR(void*, pf)));
     masm.jump(throwLabel);
 }
 
 // The operation-callback exit is called from arbitrarily-interrupted asm.js
 // code. That means we must first save *all* registers and restore *all*
-// registers when we resume. The address to resume to (assuming that
-// js_HandleExecutionInterrupt doesn't indicate that the execution should be
-// aborted) is stored in AsmJSActivation::resumePC_. Unfortunately, loading
-// this requires a scratch register which we don't have after restoring all
-// registers. To hack around this, push the resumePC on the stack so that it
-// can be popped directly into PC.
+// registers (except the stack pointer) when we resume. The address to resume to
+// (assuming that js_HandleExecutionInterrupt doesn't indicate that the
+// execution should be aborted) is stored in AsmJSActivation::resumePC_.
+// Unfortunately, loading this requires a scratch register which we don't have
+// after restoring all registers. To hack around this, push the resumePC on the
+// stack so that it can be popped directly into PC.
 static void
 GenerateOperationCallbackExit(ModuleCompiler &m, Label *throwLabel)
 {
     MacroAssembler &masm = m.masm();
     masm.align(CodeAlignment);
     masm.bind(&m.operationCallbackLabel());
 
 #ifndef JS_CPU_ARM
     // Be very careful here not to perturb the machine state before saving it
     // to the stack. In particular, add/sub instructions may set conditions in
     // the flags register.
     masm.push(Imm32(0));            // space for resumePC
     masm.pushFlags();               // after this we are safe to use sub
     masm.setFramePushed(0);         // set to zero so we can use masm.framePushed() below
-    masm.PushRegsInMask(AllRegs);   // save all GP/FP registers
+    masm.PushRegsInMask(AllRegsExceptSP); // save all GP/FP registers (except SP)
 
     Register activation = ABIArgGenerator::NonArgReturnVolatileReg1;
     Register scratch = ABIArgGenerator::NonArgReturnVolatileReg2;
 
     // Store resumePC into the reserved space.
     LoadAsmJSActivationIntoRegister(masm, activation);
     masm.loadPtr(Address(activation, AsmJSActivation::offsetOfResumePC()), scratch);
     masm.storePtr(scratch, Address(StackPointer, masm.framePushed() + sizeof(void*)));
@@ -5548,17 +5552,17 @@ GenerateOperationCallbackExit(ModuleComp
     JSBool (*pf)(JSContext*) = js_HandleExecutionInterrupt;
     masm.call(ImmWord(JS_FUNC_TO_DATA_PTR(void*, pf)));
     masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel);
 
     // Restore the StackPointer to it's position before the call.
     masm.mov(ABIArgGenerator::NonVolatileReg, StackPointer);
 
     // Restore the machine state to before the interrupt.
-    masm.PopRegsInMask(AllRegs);  // restore all GP/FP registers
+    masm.PopRegsInMask(AllRegsExceptSP); // restore all GP/FP registers (except SP)
     masm.popFlags();              // after this, nothing that sets conditions
     masm.ret();                   // pop resumePC into PC
 #else
     masm.setFramePushed(0);         // set to zero so we can use masm.framePushed() below
     masm.PushRegsInMask(RegisterSet(GeneralRegisterSet(Registers::AllMask & ~(1<<Registers::sp)), FloatRegisterSet(uint32_t(0))));   // save all GP registers,excep sp
 
     // Save both the APSR and FPSCR in non-volatile registers.
     masm.as_mrs(r4);
--- a/js/src/ion/BaselineIC.cpp
+++ b/js/src/ion/BaselineIC.cpp
@@ -6823,17 +6823,23 @@ TryAttachCallStub(JSContext *cx, ICCall_
     if (!callee.isObject())
         return true;
 
     RootedObject obj(cx, &callee.toObject());
     if (!obj->isFunction())
         return true;
 
     RootedFunction fun(cx, obj->toFunction());
+
     if (fun->hasScript()) {
+        // Never attach optimized scripted call stubs for JSOP_FUNAPPLY.
+        // MagicArguments may escape the frame through them.
+        if (op == JSOP_FUNAPPLY)
+            return true;
+
         RootedScript calleeScript(cx, fun->nonLazyScript());
         if (!calleeScript->hasBaselineScript() && !calleeScript->hasIonScript())
             return true;
 
         if (calleeScript->shouldCloneAtCallsite)
             return true;
 
         // Check if this stub chain has already generalized scripted calls.
@@ -6875,37 +6881,41 @@ TryAttachCallStub(JSContext *cx, ICCall_
         return true;
     }
 
     if (fun->isNative() && (!constructing || (constructing && fun->isNativeConstructor()))) {
         // Generalied native call stubs are not here yet!
         JS_ASSERT(!stub->nativeStubsAreGeneralized());
 
         // Check for JSOP_FUNAPPLY
-        if (op == JSOP_FUNAPPLY && fun->maybeNative() == js_fun_apply) {
-            if (!TryAttachFunApplyStub(cx, stub, script, pc, thisv, argc, vp + 2))
-                return false;
-        } else {
-            if (stub->nativeStubCount() >= ICCall_Fallback::MAX_NATIVE_STUBS) {
-                IonSpew(IonSpew_BaselineIC,
-                        "  Too many Call_Native stubs. TODO: add Call_AnyNative!");
-                return true;
-            }
-
-            IonSpew(IonSpew_BaselineIC, "  Generating Call_Native stub (fun=%p, cons=%s)",
-                    fun.get(), constructing ? "yes" : "no");
-            ICCall_Native::Compiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(),
-                                             fun, constructing, pc - script->code);
-            ICStub *newStub = compiler.getStub(compiler.getStubSpace(script));
-            if (!newStub)
-                return false;
-
-            stub->addNewStub(newStub);
+        if (op == JSOP_FUNAPPLY) {
+            if (fun->maybeNative() == js_fun_apply)
+                return TryAttachFunApplyStub(cx, stub, script, pc, thisv, argc, vp + 2);
+
+            // Don't try to attach a "regular" optimized call stubs for FUNAPPLY ops,
+            // since MagicArguments may escape through them.
             return true;
         }
+
+        if (stub->nativeStubCount() >= ICCall_Fallback::MAX_NATIVE_STUBS) {
+            IonSpew(IonSpew_BaselineIC,
+                    "  Too many Call_Native stubs. TODO: add Call_AnyNative!");
+            return true;
+        }
+
+        IonSpew(IonSpew_BaselineIC, "  Generating Call_Native stub (fun=%p, cons=%s)",
+                fun.get(), constructing ? "yes" : "no");
+        ICCall_Native::Compiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(),
+                                         fun, constructing, pc - script->code);
+        ICStub *newStub = compiler.getStub(compiler.getStubSpace(script));
+        if (!newStub)
+            return false;
+
+        stub->addNewStub(newStub);
+        return true;
     }
 
     return true;
 }
 
 static bool
 MaybeCloneFunctionAtCallsite(JSContext *cx, MutableHandleValue callee, HandleScript script,
                              jsbytecode *pc)
--- a/js/src/ion/CodeGenerator.cpp
+++ b/js/src/ion/CodeGenerator.cpp
@@ -2628,16 +2628,31 @@ CodeGenerator::visitNewSlots(LNewSlots *
 
     masm.testPtr(output, output);
     if (!bailoutIf(Assembler::Zero, lir->snapshot()))
         return false;
 
     return true;
 }
 
+bool CodeGenerator::visitAtan2D(LAtan2D *lir)
+{
+    Register temp = ToRegister(lir->temp());
+    FloatRegister y = ToFloatRegister(lir->y());
+    FloatRegister x = ToFloatRegister(lir->x());
+
+    masm.setupUnalignedABICall(2, temp);
+    masm.passABIArg(y);
+    masm.passABIArg(x);
+    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, ecmaAtan2), MacroAssembler::DOUBLE);
+
+    JS_ASSERT(ToFloatRegister(lir->output()) == ReturnFloatReg);
+    return true;
+}
+
 bool
 CodeGenerator::visitNewParallelArray(LNewParallelArray *lir)
 {
     Register objReg = ToRegister(lir->output());
     JSObject *templateObject = lir->mir()->templateObject();
 
     OutOfLineNewParallelArray *ool = new OutOfLineNewParallelArray(lir);
     if (!addOutOfLineCode(ool))
--- a/js/src/ion/CodeGenerator.h
+++ b/js/src/ion/CodeGenerator.h
@@ -149,16 +149,17 @@ class CodeGenerator : public CodeGenerat
                                     Register scratch, const TypedOrValueRegister &output);
     bool visitGetPropertyPolymorphicV(LGetPropertyPolymorphicV *ins);
     bool visitGetPropertyPolymorphicT(LGetPropertyPolymorphicT *ins);
     bool emitSetPropertyPolymorphic(LInstruction *lir, Register obj,
                                     Register scratch, const ConstantOrRegister &value);
     bool visitSetPropertyPolymorphicV(LSetPropertyPolymorphicV *ins);
     bool visitSetPropertyPolymorphicT(LSetPropertyPolymorphicT *ins);
     bool visitAbsI(LAbsI *lir);
+    bool visitAtan2D(LAtan2D *lir);
     bool visitPowI(LPowI *lir);
     bool visitPowD(LPowD *lir);
     bool visitRandom(LRandom *lir);
     bool visitMathFunctionD(LMathFunctionD *ins);
     bool visitModD(LModD *ins);
     bool visitMinMaxI(LMinMaxI *lir);
     bool visitBinaryV(LBinaryV *lir);
     bool emitCompareS(LInstruction *lir, JSOp op, Register left, Register right,
--- a/js/src/ion/IonBuilder.cpp
+++ b/js/src/ion/IonBuilder.cpp
@@ -5256,16 +5256,18 @@ IonBuilder::jsop_initelem_array()
 static bool
 CanEffectlesslyCallLookupGenericOnObject(JSObject *obj)
 {
     while (obj) {
         if (!obj->isNative())
             return false;
         if (obj->getClass()->ops.lookupProperty)
             return false;
+        if (obj->getClass()->resolve != JS_ResolveStub)
+            return false;
         obj = obj->getProto();
     }
     return true;
 }
 
 bool
 IonBuilder::jsop_initprop(HandlePropertyName name)
 {
--- a/js/src/ion/IonBuilder.h
+++ b/js/src/ion/IonBuilder.h
@@ -459,16 +459,17 @@ class IonBuilder : public MIRGenerator
     InliningStatus inlineArrayPush(CallInfo &callInfo);
     InliningStatus inlineArrayConcat(CallInfo &callInfo);
 
     // Math natives.
     InliningStatus inlineMathAbs(CallInfo &callInfo);
     InliningStatus inlineMathFloor(CallInfo &callInfo);
     InliningStatus inlineMathRound(CallInfo &callInfo);
     InliningStatus inlineMathSqrt(CallInfo &callInfo);
+    InliningStatus inlineMathAtan2(CallInfo &callInfo);
     InliningStatus inlineMathMinMax(CallInfo &callInfo, bool max);
     InliningStatus inlineMathPow(CallInfo &callInfo);
     InliningStatus inlineMathRandom(CallInfo &callInfo);
     InliningStatus inlineMathImul(CallInfo &callInfo);
     InliningStatus inlineMathFunction(CallInfo &callInfo, MMathFunction::Function function);
 
     // String natives.
     InliningStatus inlineStringObject(CallInfo &callInfo);
--- a/js/src/ion/LIR-Common.h
+++ b/js/src/ion/LIR-Common.h
@@ -2060,16 +2060,43 @@ class LSqrtD : public LInstructionHelper
 {
   public:
     LIR_HEADER(SqrtD)
     LSqrtD(const LAllocation &num) {
         setOperand(0, num);
     }
 };
 
+class LAtan2D : public LCallInstructionHelper<1, 2, 1>
+{
+  public:
+    LIR_HEADER(Atan2D)
+    LAtan2D(const LAllocation &y, const LAllocation &x, const LDefinition &temp) {
+        setOperand(0, y);
+        setOperand(1, x);
+        setTemp(0, temp);
+    }
+
+    const LAllocation *y() {
+        return getOperand(0);
+    }
+
+    const LAllocation *x() {
+        return getOperand(1);
+    }
+
+    const LDefinition *temp() {
+        return getTemp(0);
+    }
+
+    const LDefinition *output() {
+        return getDef(0);
+    }
+};
+
 // Double raised to an integer power.
 class LPowI : public LCallInstructionHelper<1, 2, 1>
 {
   public:
     LIR_HEADER(PowI)
     LPowI(const LAllocation &value, const LAllocation &power, const LDefinition &temp) {
         setOperand(0, value);
         setOperand(1, power);
--- a/js/src/ion/LOpcodes.h
+++ b/js/src/ion/LOpcodes.h
@@ -87,16 +87,17 @@
     _(EmulatesUndefinedAndBranch)   \
     _(MinMaxI)                      \
     _(MinMaxD)                      \
     _(NegI)                         \
     _(NegD)                         \
     _(AbsI)                         \
     _(AbsD)                         \
     _(SqrtD)                        \
+    _(Atan2D)                       \
     _(PowI)                         \
     _(PowD)                         \
     _(Random)                       \
     _(MathFunctionD)                \
     _(NotI)                         \
     _(NotD)                         \
     _(NotO)                         \
     _(NotV)                         \
--- a/js/src/ion/Lowering.cpp
+++ b/js/src/ion/Lowering.cpp
@@ -1054,16 +1054,29 @@ LIRGenerator::visitSqrt(MSqrt *ins)
 {
     MDefinition *num = ins->num();
     JS_ASSERT(num->type() == MIRType_Double);
     LSqrtD *lir = new LSqrtD(useRegisterAtStart(num));
     return defineReuseInput(lir, ins, 0);
 }
 
 bool
+LIRGenerator::visitAtan2(MAtan2 *ins)
+{
+    MDefinition *y = ins->y();
+    JS_ASSERT(y->type() == MIRType_Double);
+
+    MDefinition *x = ins->x();
+    JS_ASSERT(x->type() == MIRType_Double);
+
+    LAtan2D *lir = new LAtan2D(useRegisterAtStart(y), useRegisterAtStart(x), tempFixed(CallTempReg0));
+    return defineReturn(lir, ins);
+}
+
+bool
 LIRGenerator::visitPow(MPow *ins)
 {
     MDefinition *input = ins->input();
     JS_ASSERT(input->type() == MIRType_Double);
 
     MDefinition *power = ins->power();
     JS_ASSERT(power->type() == MIRType_Int32 || power->type() == MIRType_Double);
 
--- a/js/src/ion/Lowering.h
+++ b/js/src/ion/Lowering.h
@@ -128,16 +128,17 @@ class LIRGenerator : public LIRGenerator
     bool visitLsh(MLsh *ins);
     bool visitRsh(MRsh *ins);
     bool visitUrsh(MUrsh *ins);
     bool visitFloor(MFloor *ins);
     bool visitRound(MRound *ins);
     bool visitMinMax(MMinMax *ins);
     bool visitAbs(MAbs *ins);
     bool visitSqrt(MSqrt *ins);
+    bool visitAtan2(MAtan2 *ins);
     bool visitPow(MPow *ins);
     bool visitRandom(MRandom *ins);
     bool visitMathFunction(MMathFunction *ins);
     bool visitAdd(MAdd *ins);
     bool visitSub(MSub *ins);
     bool visitMul(MMul *ins);
     bool visitDiv(MDiv *ins);
     bool visitMod(MMod *ins);
--- a/js/src/ion/MCallOptimize.cpp
+++ b/js/src/ion/MCallOptimize.cpp
@@ -37,16 +37,18 @@ IonBuilder::inlineNativeCall(CallInfo &c
     if (native == js_math_abs)
         return inlineMathAbs(callInfo);
     if (native == js_math_floor)
         return inlineMathFloor(callInfo);
     if (native == js_math_round)
         return inlineMathRound(callInfo);
     if (native == js_math_sqrt)
         return inlineMathSqrt(callInfo);
+    if (native == math_atan2)
+        return inlineMathAtan2(callInfo);
     if (native == js_math_max)
         return inlineMathMinMax(callInfo, true /* max */);
     if (native == js_math_min)
         return inlineMathMinMax(callInfo, false /* max */);
     if (native == js_math_pow)
         return inlineMathPow(callInfo);
     if (native == js_math_random)
         return inlineMathRandom(callInfo);
@@ -584,16 +586,42 @@ IonBuilder::inlineMathSqrt(CallInfo &cal
 
     MSqrt *sqrt = MSqrt::New(callInfo.getArg(0));
     current->add(sqrt);
     current->push(sqrt);
     return InliningStatus_Inlined;
 }
 
 IonBuilder::InliningStatus
+IonBuilder::inlineMathAtan2(CallInfo &callInfo)
+{
+    if (callInfo.constructing())
+        return InliningStatus_NotInlined;
+
+    if (callInfo.argc() != 2)
+        return InliningStatus_NotInlined;
+
+    if (getInlineReturnType() != MIRType_Double)
+        return InliningStatus_NotInlined;
+
+    MIRType argType0 = callInfo.getArg(0)->type();
+    MIRType argType1 = callInfo.getArg(1)->type();
+
+    if (!IsNumberType(argType0) || !IsNumberType(argType1))
+        return InliningStatus_NotInlined;
+
+    callInfo.unwrapArgs();
+
+    MAtan2 *atan2 = MAtan2::New(callInfo.getArg(0), callInfo.getArg(1));
+    current->add(atan2);
+    current->push(atan2);
+    return InliningStatus_Inlined;
+}
+
+IonBuilder::InliningStatus
 IonBuilder::inlineMathPow(CallInfo &callInfo)
 {
     if (callInfo.constructing())
         return InliningStatus_NotInlined;
 
     if (callInfo.argc() != 2)
         return InliningStatus_NotInlined;
 
--- a/js/src/ion/MIR.h
+++ b/js/src/ion/MIR.h
@@ -3065,16 +3065,55 @@ class MSqrt
         return congruentIfOperandsEqual(ins);
     }
 
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
 };
 
+// Inline implementation of atan2 (arctangent of y/x).
+class MAtan2
+  : public MBinaryInstruction,
+    public MixPolicy<DoublePolicy<0>, DoublePolicy<1> >
+{
+    MAtan2(MDefinition *y, MDefinition *x)
+      : MBinaryInstruction(y, x)
+    {
+        setResultType(MIRType_Double);
+        setMovable();
+    }
+
+  public:
+    INSTRUCTION_HEADER(Atan2)
+    static MAtan2 *New(MDefinition *y, MDefinition *x) {
+        return new MAtan2(y, x);
+    }
+
+    MDefinition *y() const {
+        return getOperand(0);
+    }
+
+    MDefinition *x() const {
+        return getOperand(1);
+    }
+
+    TypePolicy *typePolicy() {
+        return this;
+    }
+
+    bool congruentTo(MDefinition *const &ins) const {
+        return congruentIfOperandsEqual(ins);
+    }
+
+    AliasSet getAliasSet() const {
+        return AliasSet::None();
+    }
+};
+
 // Inline implementation of Math.pow().
 class MPow
   : public MBinaryInstruction,
     public PowPolicy
 {
     MPow(MDefinition *input, MDefinition *power, MIRType powerType)
       : MBinaryInstruction(input, power),
         PowPolicy(powerType)
--- a/js/src/ion/MOpcodes.h
+++ b/js/src/ion/MOpcodes.h
@@ -50,16 +50,17 @@ namespace ion {
     _(BitOr)                                                                \
     _(BitXor)                                                               \
     _(Lsh)                                                                  \
     _(Rsh)                                                                  \
     _(Ursh)                                                                 \
     _(MinMax)                                                               \
     _(Abs)                                                                  \
     _(Sqrt)                                                                 \
+    _(Atan2)                                                                \
     _(Pow)                                                                  \
     _(PowHalf)                                                              \
     _(Random)                                                               \
     _(MathFunction)                                                         \
     _(Add)                                                                  \
     _(Sub)                                                                  \
     _(Mul)                                                                  \
     _(Div)                                                                  \
--- a/js/src/ion/ParallelArrayAnalysis.cpp
+++ b/js/src/ion/ParallelArrayAnalysis.cpp
@@ -146,16 +146,17 @@ class ParallelArrayVisitor : public MIns
     SAFE_OP(BitOr)
     SAFE_OP(BitXor)
     SAFE_OP(Lsh)
     SAFE_OP(Rsh)
     SPECIALIZED_OP(Ursh, PERMIT_NUMERIC)
     SPECIALIZED_OP(MinMax, PERMIT_NUMERIC)
     SAFE_OP(Abs)
     SAFE_OP(Sqrt)
+    UNSAFE_OP(Atan2)
     SAFE_OP(MathFunction)
     SPECIALIZED_OP(Add, PERMIT_NUMERIC)
     SPECIALIZED_OP(Sub, PERMIT_NUMERIC)
     SPECIALIZED_OP(Mul, PERMIT_NUMERIC)
     SPECIALIZED_OP(Div, PERMIT_NUMERIC)
     SPECIALIZED_OP(Mod, PERMIT_NUMERIC)
     UNSAFE_OP(Concat)
     UNSAFE_OP(CharCodeAt)
--- a/js/src/ion/TypePolicy.cpp
+++ b/js/src/ion/TypePolicy.cpp
@@ -351,16 +351,17 @@ DoublePolicy<Op>::staticAdjustInputs(MIn
 
     MToDouble *replace = MToDouble::New(in);
     def->block()->insertBefore(def, replace);
     def->replaceOperand(Op, replace);
     return true;
 }
 
 template bool DoublePolicy<0>::staticAdjustInputs(MInstruction *def);
+template bool DoublePolicy<1>::staticAdjustInputs(MInstruction *def);
 
 template <unsigned Op>
 bool
 BoxPolicy<Op>::staticAdjustInputs(MInstruction *ins)
 {
     MDefinition *in = ins->getOperand(Op);
     if (in->type() == MIRType_Value)
         return true;
--- a/js/src/ion/shared/Assembler-x86-shared.h
+++ b/js/src/ion/shared/Assembler-x86-shared.h
@@ -1095,25 +1095,25 @@ class AssemblerX86Shared
             masm.pinsrd_mr(src.disp(), src.base(), dest.code());
             break;
           default:
             JS_NOT_REACHED("unexpected operand kind");
         }
     }
     void psrldq(Imm32 shift, const FloatRegister &dest) {
         JS_ASSERT(HasSSE2());
-        masm.psrldq_rr(dest.code(), shift.value);
+        masm.psrldq_ir(shift.value, dest.code());
     }
     void psllq(Imm32 shift, const FloatRegister &dest) {
         JS_ASSERT(HasSSE2());
-        masm.psllq_rr(dest.code(), shift.value);
+        masm.psllq_ir(shift.value, dest.code());
     }
     void psrlq(Imm32 shift, const FloatRegister &dest) {
         JS_ASSERT(HasSSE2());
-        masm.psrlq_rr(dest.code(), shift.value);
+        masm.psrlq_ir(shift.value, dest.code());
     }
 
     void cvtsi2sd(const Operand &src, const FloatRegister &dest) {
         JS_ASSERT(HasSSE2());
         switch (src.kind()) {
           case Operand::REG:
             masm.cvtsi2sd_rr(src.reg(), dest.code());
             break;
--- a/js/src/ion/shared/CodeGenerator-x86-shared.cpp
+++ b/js/src/ion/shared/CodeGenerator-x86-shared.cpp
@@ -642,17 +642,17 @@ CodeGeneratorX86Shared::visitAsmJSDivOrM
 
     JS_ASSERT_IF(output == eax, ToRegister(ins->remainder()) == edx);
 
     Label afterDiv;
 
     masm.testl(rhs, rhs);
     Label notzero;
     masm.j(Assembler::NonZero, &notzero);
-    masm.movl(Imm32(0), output);
+    masm.xorl(output, output);
     masm.jmp(&afterDiv);
     masm.bind(&notzero);
 
     masm.xorl(edx, edx);
     masm.udiv(rhs);
 
     masm.bind(&afterDiv);
 
--- a/js/src/ion/x86/CodeGenerator-x86.cpp
+++ b/js/src/ion/x86/CodeGenerator-x86.cpp
@@ -500,18 +500,20 @@ CodeGeneratorX86::visitAsmJSLoadHeap(LAs
     return gen->noteHeapAccess(AsmJSHeapAccess(cmp.offset(), before, after, vt, ToAnyRegister(out)));
 }
 
 bool
 CodeGeneratorX86::visitOutOfLineLoadTypedArrayOutOfBounds(OutOfLineLoadTypedArrayOutOfBounds *ool)
 {
     if (ool->dest().isFloat())
         masm.movsd(&js_NaN, ool->dest().fpu());
-    else
-        masm.movl(Imm32(0), ool->dest().gpr());
+    else {
+        Register destReg = ool->dest().gpr();
+        masm.xorl(destReg, destReg);
+    }
     masm.jmp(ool->rejoin());
     return true;
 }
 
 void
 CodeGeneratorX86::storeViewTypeElement(ArrayBufferView::ViewType vt, const LAllocation *value,
                                        const Address &dstAddr)
 {
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -283,16 +283,17 @@ JS_PUBLIC_API(JSBool)
 JS_ConvertArgumentsVA(JSContext *cx, unsigned argc, jsval *argv, const char *format, va_list ap)
 {
     jsval *sp;
     JSBool required;
     char c;
     double d;
     JSString *str;
     RootedObject obj(cx);
+    RootedValue val(cx);
 
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, JSValueArray(argv - 2, argc + 2));
     sp = argv;
     required = JS_TRUE;
     while ((c = *format++) != '\0') {
         if (isspace(c))
@@ -342,17 +343,18 @@ JS_ConvertArgumentsVA(JSContext *cx, uns
             break;
           case 'I':
             if (!JS_ValueToNumber(cx, *sp, &d))
                 return JS_FALSE;
             *va_arg(ap, double *) = ToInteger(d);
             break;
           case 'S':
           case 'W':
-            str = ToString<CanGC>(cx, *sp);
+            val = *sp;
+            str = ToString<CanGC>(cx, val);
             if (!str)
                 return JS_FALSE;
             *sp = STRING_TO_JSVAL(str);
             if (c == 'W') {
                 JSStableString *stable = str->ensureStable(cx);
                 if (!stable)
                     return JS_FALSE;
                 *va_arg(ap, const jschar **) = stable->chars().get();
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -133,17 +133,18 @@ js::StringIsArrayIndex(JSLinearString *s
 }
 
 bool
 DoubleIndexToId(JSContext *cx, double index, MutableHandleId id)
 {
     if (index == uint32_t(index))
         return IndexToId(cx, uint32_t(index), id);
 
-    return ValueToId<CanGC>(cx, DoubleValue(index), id);
+    Value tmp = DoubleValue(index);
+    return ValueToId<CanGC>(cx, HandleValue::fromMarkedLocation(&tmp), id);
 }
 
 /*
  * If the property at the given index exists, get its value into location
  * pointed by vp and set *hole to false. Otherwise set *hole to true and *vp
  * to JSVAL_VOID. This function assumes that the location pointed by vp is
  * properly rooted and can be used as GC-protected storage for temporaries.
  */
@@ -345,17 +346,18 @@ DeletePropertyOrThrow(JSContext *cx, Han
 {
     JSBool succeeded;
     if (!DeleteArrayElement(cx, obj, index, &succeeded))
         return false;
     if (succeeded)
         return true;
 
     RootedId id(cx);
-    if (!ValueToId<CanGC>(cx, NumberValue(index), &id))
+    RootedValue indexv(cx, NumberValue(index));
+    if (!ValueToId<CanGC>(cx, indexv, &id))
         return false;
     return obj->reportNotConfigurable(cx, id, JSREPORT_ERROR);
 }
 
 JSBool
 js::SetLengthProperty(JSContext *cx, HandleObject obj, double length)
 {
     RootedValue v(cx, NumberValue(length));
@@ -887,17 +889,17 @@ array_join_sub(JSContext *cx, CallArgs &
     // Steps 2 and 3
     uint32_t length;
     if (!GetLengthProperty(cx, obj, &length))
         return false;
 
     // Steps 4 and 5
     RootedString sepstr(cx, NULL);
     if (!locale && args.hasDefined(0)) {
-        sepstr = ToString<CanGC>(cx, args[0]);
+        sepstr = ToString<CanGC>(cx, args.handleAt(0));
         if (!sepstr)
             return false;
     }
     static const jschar comma = ',';
     const jschar *sep;
     size_t seplen;
     if (sepstr) {
         sep = NULL;
@@ -1128,21 +1130,24 @@ InitArrayElements(JSContext *cx, HandleO
     }
 
     if (vector == end)
         return true;
 
     JS_ASSERT(start == MAX_ARRAY_INDEX + 1);
     RootedValue value(cx);
     RootedId id(cx);
+    RootedValue indexv(cx);
     double index = MAX_ARRAY_INDEX + 1;
     do {
         value = *vector++;
-        if (!ValueToId<CanGC>(cx, DoubleValue(index), &id) ||
-            !JSObject::setGeneric(cx, obj, obj, id, &value, true)) {
+        indexv = DoubleValue(index);
+        if (!ValueToId<CanGC>(cx, indexv, &id) ||
+            !JSObject::setGeneric(cx, obj, obj, id, &value, true))
+        {
             return false;
         }
         index += 1;
     } while (vector != end);
 
     return true;
 }
 
--- a/js/src/jsatom.h
+++ b/js/src/jsatom.h
@@ -230,17 +230,17 @@ AtomizeChars(JSContext *cx, const jschar
              js::InternBehavior ib = js::DoNotInternAtom);
 
 template <AllowGC allowGC>
 extern JSAtom *
 AtomizeString(JSContext *cx, JSString *str, js::InternBehavior ib = js::DoNotInternAtom);
 
 template <AllowGC allowGC>
 inline JSAtom *
-ToAtom(JSContext *cx, const js::Value &v);
+ToAtom(JSContext *cx, typename MaybeRooted<Value, allowGC>::HandleType v);
 
 template<XDRMode mode>
 bool
 XDRAtom(XDRState<mode> *xdr, js::MutableHandleAtom atomp);
 
 } /* namespace js */
 
 #endif /* jsatom_h___ */
--- a/js/src/jsatominlines.h
+++ b/js/src/jsatominlines.h
@@ -38,17 +38,17 @@ AtomToId(JSAtom *atom)
     if (atom->isIndex(&index) && index <= JSID_INT_MAX)
         return INT_TO_JSID(int32_t(index));
 
     return JSID_FROM_BITS(size_t(atom));
 }
 
 template <AllowGC allowGC>
 inline JSAtom *
-ToAtom(JSContext *cx, const js::Value &v)
+ToAtom(JSContext *cx, typename MaybeRooted<Value, allowGC>::HandleType v)
 {
     if (!v.isString()) {
         JSString *str = js::ToStringSlow<allowGC>(cx, v);
         if (!str)
             return NULL;
         JS::Anchor<JSString *> anchor(str);
         return AtomizeString<allowGC>(cx, str);
     }
@@ -58,17 +58,18 @@ ToAtom(JSContext *cx, const js::Value &v
         return &str->asAtom();
 
     JS::Anchor<JSString *> anchor(str);
     return AtomizeString<allowGC>(cx, str);
 }
 
 template <AllowGC allowGC>
 inline bool
-ValueToId(JSContext* cx, const Value &v, typename MaybeRooted<jsid, allowGC>::MutableHandleType idp)
+ValueToId(JSContext* cx, typename MaybeRooted<Value, allowGC>::HandleType v,
+          typename MaybeRooted<jsid, allowGC>::MutableHandleType idp)
 {
     int32_t i;
     if (ValueFitsInInt32(v, &i) && INT_FITS_IN_JSID(i)) {
         idp.set(INT_TO_JSID(i));
         return true;
     }
 
     JSAtom *atom = ToAtom<allowGC>(cx, v);
@@ -141,17 +142,18 @@ static JS_ALWAYS_INLINE JSFlatString *
 IdToString(JSContext *cx, jsid id)
 {
     if (JSID_IS_STRING(id))
         return JSID_TO_ATOM(id);
 
     if (JS_LIKELY(JSID_IS_INT(id)))
         return Int32ToString<CanGC>(cx, JSID_TO_INT(id));
 
-    JSString *str = ToStringSlow<CanGC>(cx, IdToValue(id));
+    RootedValue idv(cx, IdToValue(id));
+    JSString *str = ToStringSlow<CanGC>(cx, idv);
     if (!str)
         return NULL;
 
     return str->ensureFlat(cx);
 }
 
 inline
 AtomHasher::Lookup::Lookup(const JSAtom *atom)
--- a/js/src/jscntxtinlines.h
+++ b/js/src/jscntxtinlines.h
@@ -442,18 +442,20 @@ CallJSDeletePropertyOp(JSContext *cx, JS
     assertSameCompartment(cx, receiver, id);
     return op(cx, receiver, id, succeeded);
 }
 
 inline bool
 CallSetter(JSContext *cx, HandleObject obj, HandleId id, StrictPropertyOp op, unsigned attrs,
            unsigned shortid, JSBool strict, MutableHandleValue vp)
 {
-    if (attrs & JSPROP_SETTER)
-        return InvokeGetterOrSetter(cx, obj, CastAsObjectJsval(op), 1, vp.address(), vp.address());
+    if (attrs & JSPROP_SETTER) {
+        RootedValue opv(cx, CastAsObjectJsval(op));
+        return InvokeGetterOrSetter(cx, obj, opv, 1, vp.address(), vp.address());
+    }
 
     if (attrs & JSPROP_GETTER)
         return js_ReportGetterOnlyAssignment(cx);
 
     if (!(attrs & JSPROP_SHORTID))
         return CallJSPropertyOpSetter(cx, op, obj, id, strict, vp);
 
     RootedId nid(cx, INT_TO_JSID(shortid));
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -372,17 +372,17 @@ JSCompartment::wrapId(JSContext *cx, jsi
 {
     MOZ_ASSERT(*idp != JSID_VOID, "JSID_VOID is an out-of-band sentinel value");
     if (JSID_IS_INT(*idp))
         return true;
     RootedValue value(cx, IdToValue(*idp));
     if (!wrap(cx, &value))
         return false;
     RootedId id(cx);
-    if (!ValueToId<CanGC>(cx, value.get(), &id))
+    if (!ValueToId<CanGC>(cx, value, &id))
         return false;
 
     *idp = id;
     return true;
 }
 
 bool
 JSCompartment::wrap(JSContext *cx, PropertyOp *propp)
--- a/js/src/jsdate.cpp
+++ b/js/src/jsdate.cpp
@@ -1205,17 +1205,17 @@ static JSBool
 date_parse(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     if (args.length() == 0) {
         vp->setDouble(js_NaN);
         return true;
     }
 
-    JSString *str = ToString<CanGC>(cx, args[0]);
+    JSString *str = ToString<CanGC>(cx, args.handleAt(0));
     if (!str)
         return false;
 
     JSLinearString *linearStr = str->ensureLinear(cx);
     if (!linearStr)
         return false;
 
     double result;
@@ -2891,17 +2891,17 @@ date_toLocaleFormat_impl(JSContext *cx, 
 #if defined(_WIN32) && !defined(__MWERKS__)
                               "%#c"
 #else
                               "%c"
 #endif
                              , args.rval());
     }
 
-    RootedString fmt(cx, ToString<CanGC>(cx, args[0]));
+    RootedString fmt(cx, ToString<CanGC>(cx, args.handleAt(0)));
     if (!fmt)
         return false;
 
     JSAutoByteString fmtbytes(cx, fmt);
     if (!fmtbytes)
         return false;
 
     return ToLocaleFormatHelper(cx, thisObj, fmtbytes.ptr(), args.rval());
--- a/js/src/jsdbgapi.cpp
+++ b/js/src/jsdbgapi.cpp
@@ -306,17 +306,18 @@ JS_SetWatchPoint(JSContext *cx, JSObject
     RootedId propid(cx);
 
     if (JSID_IS_INT(id)) {
         propid = id;
     } else if (JSID_IS_OBJECT(id)) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_WATCH_PROP);
         return false;
     } else {
-        if (!ValueToId<CanGC>(cx, IdToValue(id), &propid))
+        RootedValue val(cx, IdToValue(id));
+        if (!ValueToId<CanGC>(cx, val, &propid))
             return false;
     }
 
     /*
      * If, by unwrapping and innerizing, we changed the object, check
      * again to make sure that we're allowed to set a watch point.
      */
     if (origobj != obj && !CheckAccess(cx, obj, propid, JSACC_WATCH, &v, &attrs))
@@ -1017,18 +1018,19 @@ class AutoPropertyDescArray
     }
 
     JSPropertyDescArray * operator ->() {
         return &descArray_;
     }
 };
 
 static const char *
-FormatValue(JSContext *cx, const Value &v, JSAutoByteString &bytes)
+FormatValue(JSContext *cx, const Value &vArg, JSAutoByteString &bytes)
 {
+    RootedValue v(cx, vArg);
     JSString *str = ToString<CanGC>(cx, v);
     if (!str)
         return NULL;
     const char *buf = bytes.encodeLatin1(cx, str);
     if (!buf)
         return NULL;
     const char *found = strstr(buf, "function ");
     if (found && (found - buf <= 2))
--- a/js/src/jsexn.cpp
+++ b/js/src/jsexn.cpp
@@ -554,32 +554,32 @@ Exception(JSContext *cx, unsigned argc, 
 
     RootedObject obj(cx, NewObjectWithGivenProto(cx, &ErrorClass, &protov.toObject(), NULL));
     if (!obj)
         return false;
 
     /* Set the 'message' property. */
     RootedString message(cx);
     if (args.hasDefined(0)) {
-        message = ToString<CanGC>(cx, args[0]);
+        message = ToString<CanGC>(cx, args.handleAt(0));
         if (!message)
             return false;
         args[0].setString(message);
     } else {
         message = NULL;
     }
 
     /* Find the scripted caller. */
     NonBuiltinScriptFrameIter iter(cx);
 
     /* Set the 'fileName' property. */
     RootedScript script(cx, iter.done() ? NULL : iter.script());
     RootedString filename(cx);
     if (args.length() > 1) {
-        filename = ToString<CanGC>(cx, args[1]);
+        filename = ToString<CanGC>(cx, args.handleAt(1));
         if (!filename)
             return false;
         args[1].setString(filename);
     } else {
         filename = cx->runtime->emptyString;
         if (!iter.done()) {
             if (const char *cfilename = script->filename()) {
                 filename = FilenameToString(cx, cfilename);
@@ -1078,17 +1078,17 @@ js_ReportUncaughtException(JSContext *cx
                 return false;
         } else if (name) {
             str = name;
         } else if (msg) {
             str = msg;
         }
 
         if (JS_GetProperty(cx, exnObject, filename_str, &roots[4])) {
-            JSString *tmp = ToString<CanGC>(cx, roots[4]);
+            JSString *tmp = ToString<CanGC>(cx, HandleValue::fromMarkedLocation(&roots[4]));
             if (tmp)
                 filename.encodeLatin1(cx, tmp);
         }
 
         uint32_t lineno;
         if (!JS_GetProperty(cx, exnObject, js_lineNumber_str, &roots[5]) ||
             !ToUint32(cx, roots[5], &lineno))
         {
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -630,16 +630,20 @@ js_DumpChars(const jschar *s, size_t n)
     fprintf(stderr, "jschar * (%p) = ", (void *) s);
     JSString::dumpChars(s, n);
     fputc('\n', stderr);
 }
 
 JS_FRIEND_API(void)
 js_DumpObject(JSObject *obj)
 {
+    if (!obj) {
+        fprintf(stderr, "NULL\n");
+        return;
+    }
     obj->dump();
 }
 
 #endif
 
 struct JSDumpHeapTracer : public JSTracer
 {
     FILE   *output;
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -1340,17 +1340,17 @@ js::Function(JSContext *cx, unsigned arg
          * comments and linefeeds.  XXX It might be better to concatenate
          * everything up into a function definition and pass it to the
          * compiler, but doing it this way is less of a delta from the old
          * code.  See ECMA 15.3.2.1.
          */
         size_t args_length = 0;
         for (unsigned i = 0; i < n; i++) {
             /* Collect the lengths for all the function-argument arguments. */
-            arg = ToString<CanGC>(cx, args[i]);
+            arg = ToString<CanGC>(cx, args.handleAt(i));
             if (!arg)
                 return false;
             args[i].setString(arg);
 
             /*
              * Check for overflow.  The < test works because the maximum
              * JSString length fits in 2 fewer bits than size_t has.
              */
@@ -1460,17 +1460,17 @@ js::Function(JSContext *cx, unsigned arg
         JS_ASSERT(str->asAtom().asPropertyName() == formals[i]);
     }
 #endif
 
     RootedString str(cx);
     if (!args.length())
         str = cx->runtime->emptyString;
     else
-        str = ToString<CanGC>(cx, args[args.length() - 1]);
+        str = ToString<CanGC>(cx, args.handleAt(args.length() - 1));
     if (!str)
         return false;
     JSLinearString *linear = str->ensureLinear(cx);
     if (!linear)
         return false;
 
     JS::Anchor<JSString *> strAnchor(str);
     const jschar *chars = linear->chars();
--- a/js/src/jsiter.cpp
+++ b/js/src/jsiter.cpp
@@ -1087,17 +1087,18 @@ SuppressDeletedPropertyHelper(JSContext 
                      */
                     RootedObject proto(cx);
                     if (!JSObject::getProto(cx, obj, &proto))
                         return false;
                     if (proto) {
                         RootedObject obj2(cx);
                         RootedShape prop(cx);
                         RootedId id(cx);
-                        if (!ValueToId<CanGC>(cx, StringValue(*idp), &id))
+                        RootedValue idv(cx, StringValue(*idp));
+                        if (!ValueToId<CanGC>(cx, idv, &id))
                             return false;
                         if (!JSObject::lookupGeneric(cx, proto, id, &obj2, &prop))
                             return false;
                         if (prop) {
                             unsigned attrs;
                             if (obj2->isNative())
                                 attrs = GetShapeAttributes(prop);
                             else if (!JSObject::getGenericAttributes(cx, obj2, id, &attrs))
@@ -1218,17 +1219,18 @@ js_IteratorMore(JSContext *cx, HandleObj
 
     /* We're reentering below and can call anything. */
     JS_CHECK_RECURSION(cx, return false);
 
     /* Fetch and cache the next value from the iterator. */
     if (ni) {
         JS_ASSERT(!ni->isKeyIter());
         RootedId id(cx);
-        if (!ValueToId<CanGC>(cx, StringValue(*ni->current()), &id))
+        RootedValue current(cx, StringValue(*ni->current()));
+        if (!ValueToId<CanGC>(cx, current, &id))
             return false;
         ni->incCursor();
         RootedObject obj(cx, ni->obj);
         if (!JSObject::getGeneric(cx, obj, obj, id, rval))
             return false;
         if ((ni->flags & JSITER_KEYVALUE) && !NewKeyValuePair(cx, id, rval, rval))
             return false;
     } else {
--- a/js/src/jsmath.cpp
+++ b/js/src/jsmath.cpp
@@ -207,43 +207,43 @@ js::math_atan(JSContext *cx, unsigned ar
     if (!mathCache)
         return JS_FALSE;
     z = math_atan_impl(mathCache, x);
     vp->setDouble(z);
     return JS_TRUE;
 }
 
 double
-js::ecmaAtan2(double x, double y)
+js::ecmaAtan2(double y, double x)
 {
 #if defined(_MSC_VER)
     /*
      * MSVC's atan2 does not yield the result demanded by ECMA when both x
      * and y are infinite.
      * - The result is a multiple of pi/4.
-     * - The sign of x determines the sign of the result.
-     * - The sign of y determines the multiplicator, 1 or 3.
+     * - The sign of y determines the sign of the result.
+     * - The sign of x determines the multiplicator, 1 or 3.
      */
-    if (IsInfinite(x) && IsInfinite(y)) {
-        double z = js_copysign(M_PI / 4, x);
-        if (y < 0)
+    if (IsInfinite(y) && IsInfinite(x)) {
+        double z = js_copysign(M_PI / 4, y);
+        if (x < 0)
             z *= 3;
         return z;
     }
 #endif
 
 #if defined(SOLARIS) && defined(__GNUC__)
-    if (x == 0) {
-        if (IsNegativeZero(y))
-            return js_copysign(M_PI, x);
-        if (y == 0)
-            return x;
+    if (y == 0) {
+        if (IsNegativeZero(x))
+            return js_copysign(M_PI, y);
+        if (x == 0)
+            return y;
     }
 #endif
-    return atan2(x, y);
+    return atan2(y, x);
 }
 
 JSBool
 js::math_atan2(JSContext *cx, unsigned argc, Value *vp)
 {
     double x, y, z;
 
     if (argc <= 1) {
--- a/js/src/jsnum.cpp
+++ b/js/src/jsnum.cpp
@@ -262,38 +262,38 @@ num_isFinite(JSContext *cx, unsigned arg
         return JS_FALSE;
     vp->setBoolean(mozilla::IsFinite(x));
     return JS_TRUE;
 }
 
 static JSBool
 num_parseFloat(JSContext *cx, unsigned argc, Value *vp)
 {
-    JSString *str;
-    double d;
-    const jschar *bp, *end, *ep;
+    CallArgs args = CallArgsFromVp(argc, vp);
 
-    if (argc == 0) {
-        vp->setDouble(js_NaN);
+    if (args.length() == 0) {
+        args.rval().setDouble(js_NaN);
         return JS_TRUE;
     }
-    str = ToString<CanGC>(cx, vp[2]);
+    JSString *str = ToString<CanGC>(cx, args.handleAt(0));
     if (!str)
         return JS_FALSE;
-    bp = str->getChars(cx);
+    const jschar *bp = str->getChars(cx);
     if (!bp)
         return JS_FALSE;
-    end = bp + str->length();
+    const jschar *end = bp + str->length();
+    const jschar *ep;
+    double d;
     if (!js_strtod(cx, bp, end, &ep, &d))
         return JS_FALSE;
     if (ep == bp) {
-        vp->setDouble(js_NaN);
+        args.rval().setDouble(js_NaN);
         return JS_TRUE;
     }
-    vp->setNumber(d);
+    args.rval().setDouble(d);
     return JS_TRUE;
 }
 
 /* ES5 15.1.2.2. */
 JSBool
 js::num_parseInt(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
@@ -335,17 +335,17 @@ js::num_parseInt(JSContext *cx, unsigned
             if (d == 0.0) {
                 args.rval().setInt32(0);
                 return true;
             }
         }
     }
 
     /* Step 1. */
-    RootedString inputString(cx, ToString<CanGC>(cx, args[0]));
+    RootedString inputString(cx, ToString<CanGC>(cx, args.handleAt(0)));
     if (!inputString)
         return false;
     args[0].setString(inputString);
 
     /* Steps 6-9. */
     bool stripPrefix = true;
     int32_t radix;
     if (!args.hasDefined(1)) {
--- a/js/src/json.cpp
+++ b/js/src/json.cpp
@@ -629,17 +629,17 @@ js_Stringify(JSContext *cx, MutableHandl
     if (space.isObject()) {
         RootedObject spaceObj(cx, &space.toObject());
         if (ObjectClassIs(spaceObj, ESClass_Number, cx)) {
             double d;
             if (!ToNumber(cx, space, &d))
                 return false;
             space = NumberValue(d);
         } else if (ObjectClassIs(spaceObj, ESClass_String, cx)) {
-            JSString *str = ToStringSlow<CanGC>(cx, space);
+            JSString *str = ToStringSlow<CanGC>(cx, spaceRoot);
             if (!str)
                 return false;
             space = StringValue(str);
         }
     }
 
     StringBuffer gap(cx);
 
@@ -829,17 +829,19 @@ json_toSource(JSContext *cx, unsigned ar
 
 /* ES5 15.12.2. */
 JSBool
 js_json_parse(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     /* Step 1. */
-    JSString *str = (argc >= 1) ? ToString<CanGC>(cx, args[0]) : cx->names().undefined;
+    JSString *str = (args.length() >= 1)
+                    ? ToString<CanGC>(cx, args.handleAt(0))
+                    : cx->names().undefined;
     if (!str)
         return false;
 
     JSStableString *stable = str->ensureStable(cx);
     if (!stable)
         return false;
 
     JS::Anchor<JSString *> anchor(stable);
--- a/js/src/jspropertytree.cpp
+++ b/js/src/jspropertytree.cpp
@@ -262,17 +262,18 @@ Shape::dump(JSContext *cx, FILE *fp) con
     if (JSID_IS_INT(propid)) {
         fprintf(fp, "[%ld]", (long) JSID_TO_INT(propid));
     } else {
         JSLinearString *str;
         if (JSID_IS_ATOM(propid)) {
             str = JSID_TO_ATOM(propid);
         } else {
             JS_ASSERT(JSID_IS_OBJECT(propid));
-            JSString *s = ToStringSlow<CanGC>(cx, IdToValue(propid));
+            RootedValue v(cx, IdToValue(propid));
+            JSString *s = ToStringSlow<CanGC>(cx, v);
             fputs("object ", fp);
             str = s ? s->ensureLinear(cx) : NULL;
         }
         if (!str)
             fputs("<error>", fp);
         else
             FileEscapedString(fp, str, '"');
     }
--- a/js/src/jsproxy.cpp
+++ b/js/src/jsproxy.cpp
@@ -670,29 +670,31 @@ Trap(JSContext *cx, HandleObject handler
      MutableHandleValue rval)
 {
     return Invoke(cx, ObjectValue(*handler), fval, argc, argv, rval.address());
 }
 
 static bool
 Trap1(JSContext *cx, HandleObject handler, HandleValue fval, HandleId id, MutableHandleValue rval)
 {
-    JSString *str = ToString<CanGC>(cx, IdToValue(id));
+    rval.set(IdToValue(id)); // Re-use out-param to avoid Rooted overhead.
+    JSString *str = ToString<CanGC>(cx, rval);
     if (!str)
         return false;
     rval.setString(str);
     return Trap(cx, handler, fval, 1, rval.address(), rval);
 }
 
 static bool
 Trap2(JSContext *cx, HandleObject handler, HandleValue fval, HandleId id, Value v_,
       MutableHandleValue rval)
 {
     RootedValue v(cx, v_);
-    JSString *str = ToString<CanGC>(cx, IdToValue(id));
+    rval.set(IdToValue(id)); // Re-use out-param to avoid Rooted overhead.
+    JSString *str = ToString<CanGC>(cx, rval);
     if (!str)
         return false;
     rval.setString(str);
     Value argv[2] = { rval.get(), v };
     AutoValueArray ava(cx, argv, 2);
     return Trap(cx, handler, fval, 2, argv, rval);
 }
 
@@ -943,17 +945,18 @@ ScriptedIndirectProxyHandler::hasOwn(JSC
            ValueToBool(value, bp);
 }
 
 bool
 ScriptedIndirectProxyHandler::get(JSContext *cx, HandleObject proxy, HandleObject receiver,
                                   HandleId id, MutableHandleValue vp)
 {
     RootedObject handler(cx, GetIndirectProxyHandlerObject(proxy));
-    JSString *str = ToString<CanGC>(cx, IdToValue(id));
+    RootedValue idv(cx, IdToValue(id));
+    JSString *str = ToString<CanGC>(cx, idv);
     if (!str)
         return false;
     RootedValue value(cx, StringValue(str));
     Value argv[] = { ObjectOrNullValue(receiver), value };
     AutoValueArray ava(cx, argv, 2);
     RootedValue fval(cx);
     if (!GetDerivedTrap(cx, handler, cx->names().get, &fval))
         return false;
@@ -962,17 +965,18 @@ ScriptedIndirectProxyHandler::get(JSCont
     return Trap(cx, handler, fval, 2, argv, vp);
 }
 
 bool
 ScriptedIndirectProxyHandler::set(JSContext *cx, HandleObject proxy, HandleObject receiver,
                                   HandleId id, bool strict, MutableHandleValue vp)
 {
     RootedObject handler(cx, GetIndirectProxyHandlerObject(proxy));
-    JSString *str = ToString<CanGC>(cx, IdToValue(id));
+    RootedValue idv(cx, IdToValue(id));
+    JSString *str = ToString<CanGC>(cx, idv);
     if (!str)
         return false;
     RootedValue value(cx, StringValue(str));
     Value argv[] = { ObjectOrNullValue(receiver), value, vp.get() };
     AutoValueArray ava(cx, argv, 3);
     RootedValue fval(cx);
     if (!GetDerivedTrap(cx, handler, cx->names().set, &fval))
         return false;
@@ -1333,17 +1337,18 @@ HasOwn(JSContext *cx, HandleObject obj, 
         return false;
     *bp = (desc.obj == obj);
     return true;
 }
 
 static bool
 IdToValue(JSContext *cx, HandleId id, MutableHandleValue value)
 {
-    JSString *name = ToString<CanGC>(cx, IdToValue(id));
+    value.set(IdToValue(id)); // Re-use out-param to avoid Rooted overhead.
+    JSString *name = ToString<CanGC>(cx, value);
     if (!name)
         return false;
     value.set(StringValue(name));
     return true;
 }
 
 // TrapGetOwnProperty(O, P)
 static bool
--- a/js/src/jsreflect.cpp
+++ b/js/src/jsreflect.cpp
@@ -2966,33 +2966,35 @@ ASTSerializer::moduleOrFunctionBody(Pars
     }
 
     return builder.blockStatement(elts, pos, dst);
 }
 
 static JSBool
 reflect_parse(JSContext *cx, uint32_t argc, jsval *vp)
 {
-    if (argc < 1) {
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    if (args.length() < 1) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_MORE_ARGS_NEEDED,
                              "Reflect.parse", "0", "s");
         return JS_FALSE;
     }
 
-    RootedString src(cx, ToString<CanGC>(cx, JS_ARGV(cx, vp)[0]));
+    RootedString src(cx, ToString<CanGC>(cx, args.handleAt(0)));
     if (!src)
         return JS_FALSE;
 
     ScopedJSFreePtr<char> filename;
     uint32_t lineno = 1;
     bool loc = true;
 
     RootedObject builder(cx);
 
-    RootedValue arg(cx, argc > 1 ? JS_ARGV(cx, vp)[1] : UndefinedValue());
+    RootedValue arg(cx, args.get(1));
 
     if (!arg.isNullOrUndefined()) {
         if (!arg.isObject()) {
             js_ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_UNEXPECTED_TYPE,
                                      JSDVG_SEARCH_STACK, arg, NullPtr(), "not an object", NULL);
             return JS_FALSE;
         }
 
@@ -3076,21 +3078,21 @@ reflect_parse(JSContext *cx, uint32_t ar
     serialize.setParser(&parser);
 
     ParseNode *pn = parser.parse(NULL);
     if (!pn)
         return JS_FALSE;
 
     RootedValue val(cx);
     if (!serialize.program(pn, &val)) {
-        JS_SET_RVAL(cx, vp, JSVAL_NULL);
+        args.rval().setNull();
         return JS_FALSE;
     }
 
-    JS_SET_RVAL(cx, vp, val);
+    args.rval().set(val);
     return JS_TRUE;
 }
 
 static const JSFunctionSpec static_methods[] = {
     JS_FN("parse", reflect_parse, 1, 0),
     JS_FS_END
 };
 
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -71,22 +71,21 @@ using mozilla::PodEqual;
 typedef Handle<JSLinearString*> HandleLinearString;
 
 static JSLinearString *
 ArgToRootedString(JSContext *cx, CallArgs &args, unsigned argno)
 {
     if (argno >= args.length())
         return cx->names().undefined;
 
-    Value &arg = args[argno];
-    JSString *str = ToString<CanGC>(cx, arg);
+    JSString *str = ToString<CanGC>(cx, args.handleAt(argno));
     if (!str)
         return NULL;
 
-    arg = StringValue(str);
+    args[argno] = StringValue(str);
     return str->ensureLinear(cx);
 }
 
 /*
  * Forward declarations for URI encode/decode and helper routines
  */
 static JSBool
 str_decodeURI(JSContext *cx, unsigned argc, Value *vp);
@@ -821,18 +820,17 @@ str_toLocaleUpperCase(JSContext *cx, uns
 static JSBool
 str_localeCompare(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     RootedString str(cx, ThisToStringForStringProto(cx, args));
     if (!str)
         return false;
 
-    Value thatValue = args.length() > 0 ? args[0] : UndefinedValue();
-    RootedString thatStr(cx, ToString<CanGC>(cx, thatValue));
+    RootedString thatStr(cx, ToString<CanGC>(cx, args.handleOrUndefinedAt(0)));
     if (!thatStr)
         return false;
 
     if (cx->runtime->localeCallbacks && cx->runtime->localeCallbacks->localeCompare) {
         RootedValue result(cx);
         if (!cx->runtime->localeCallbacks->localeCompare(cx, str, thatStr, &result))
             return false;
 
@@ -1715,17 +1713,17 @@ class StringRegExpGuard
     bool normalizeRegExp(JSContext *cx, bool flat, unsigned optarg, CallArgs args)
     {
         if (re_.initialized())
             return true;
 
         /* Build RegExp from pattern string. */
         RootedString opt(cx);
         if (optarg < args.length()) {
-            opt = ToString<CanGC>(cx, args[optarg]);
+            opt = ToString<CanGC>(cx, args.handleAt(optarg));
             if (!opt)
                 return false;
         } else {
             opt = NULL;
         }
 
         Rooted<JSAtom *> patstr(cx);
         if (flat) {
@@ -3176,20 +3174,20 @@ static JSBool
 str_concat(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     JSString *str = ThisToStringForStringProto(cx, args);
     if (!str)
         return false;
 
     for (unsigned i = 0; i < args.length(); i++) {
-        JSString *argStr = ToString<NoGC>(cx, args[i]);
+        JSString *argStr = ToString<NoGC>(cx, args.handleAt(i));
         if (!argStr) {
             RootedString strRoot(cx, str);
-            argStr = ToString<CanGC>(cx, args[i]);
+            argStr = ToString<CanGC>(cx, args.handleAt(i));
             if (!argStr)
                 return false;
             str = strRoot;
         }
 
         JSString *next = ConcatStrings<NoGC>(cx, str, argStr);
         if (next) {
             str = next;
@@ -3503,17 +3501,17 @@ static const JSFunctionSpec string_metho
 
 JSBool
 js_String(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     RootedString str(cx);
     if (args.length() > 0) {
-        str = ToString<CanGC>(cx, args[0]);
+        str = ToString<CanGC>(cx, args.handleAt(0));
         if (!str)
             return false;
     } else {
         str = cx->runtime->emptyString;
     }
 
     if (IsConstructing(args)) {
         StringObject *strobj = StringObject::create(cx, str);
@@ -3739,34 +3737,35 @@ js_NewStringCopyZ(JSContext *cx, const c
 
 template JSFlatString *
 js_NewStringCopyZ<CanGC>(JSContext *cx, const char *s);
 
 template JSFlatString *
 js_NewStringCopyZ<NoGC>(JSContext *cx, const char *s);
 
 const char *
-js_ValueToPrintable(JSContext *cx, const Value &v, JSAutoByteString *bytes, bool asSource)
+js_ValueToPrintable(JSContext *cx, const Value &vArg, JSAutoByteString *bytes, bool asSource)
 {
+    RootedValue v(cx, vArg);
     JSString *str;
     if (asSource)
         str = ValueToSource(cx, v);
     else
         str = ToString<CanGC>(cx, v);
     if (!str)
         return NULL;
     str = js_QuoteString(cx, str, 0);
     if (!str)
         return NULL;
     return bytes->encodeLatin1(cx, str);
 }
 
 template <AllowGC allowGC>
 JSString *
-js::ToStringSlow(JSContext *cx, const Value &arg)
+js::ToStringSlow(JSContext *cx, typename MaybeRooted<Value, allowGC>::HandleType arg)
 {
     /* As with ToObjectSlow, callers must verify that |arg| isn't a string. */
     JS_ASSERT(!arg.isString());
 
     Value v = arg;
     if (!v.isPrimitive()) {
         if (!allowGC)
             return NULL;
@@ -3789,20 +3788,20 @@ js::ToStringSlow(JSContext *cx, const Va
         str = cx->names().null;
     } else {
         str = cx->names().undefined;
     }
     return str;
 }
 
 template JSString *
-js::ToStringSlow<CanGC>(JSContext *cx, const Value &arg);
+js::ToStringSlow<CanGC>(JSContext *cx, HandleValue arg);
 
 template JSString *
-js::ToStringSlow<NoGC>(JSContext *cx, const Value &arg);
+js::ToStringSlow<NoGC>(JSContext *cx, Value arg);
 
 JSString *
 js::ValueToSource(JSContext *cx, const Value &v)
 {
     JS_CHECK_RECURSION(cx, return NULL);
     assertSameCompartment(cx, v);
 
     if (v.isUndefined())
@@ -3812,17 +3811,18 @@ js::ValueToSource(JSContext *cx, const V
     if (v.isPrimitive()) {
         /* Special case to preserve negative zero, _contra_ toString. */
         if (v.isDouble() && IsNegativeZero(v.toDouble())) {
             /* NB: _ucNstr rather than _ucstr to indicate non-terminated. */
             static const jschar js_negzero_ucNstr[] = {'-', '0'};
 
             return js_NewStringCopyN<CanGC>(cx, js_negzero_ucNstr, 2);
         }
-        return ToString<CanGC>(cx, v);
+        RootedValue vRoot(cx, v);
+        return ToString<CanGC>(cx, vRoot);
     }
 
     RootedValue rval(cx, NullValue());
     RootedValue fval(cx);
     RootedObject obj(cx, &v.toObject());
     if (!JSObject::getProperty(cx, obj, obj, cx->names().toSource, &fval))
         return NULL;
     if (js_IsCallable(fval)) {
--- a/js/src/jsstr.h
+++ b/js/src/jsstr.h
@@ -121,26 +121,26 @@ js_ValueToPrintable(JSContext *cx, const
 namespace js {
 
 /*
  * Convert a non-string value to a string, returning null after reporting an
  * error, otherwise returning a new string reference.
  */
 template <AllowGC allowGC>
 extern JSString *
-ToStringSlow(JSContext *cx, const Value &v);
+ToStringSlow(JSContext *cx, typename MaybeRooted<Value, allowGC>::HandleType arg);
 
 /*
  * Convert the given value to a string.  This method includes an inline
  * fast-path for the case where the value is already a string; if the value is
  * known not to be a string, use ToStringSlow instead.
  */
 template <AllowGC allowGC>
 static JS_ALWAYS_INLINE JSString *
-ToString(JSContext *cx, const js::Value &v)
+ToString(JSContext *cx, JS::HandleValue v)
 {
 #ifdef DEBUG
     if (allowGC) {
         SkipRoot skip(cx, &v);
         MaybeCheckStackRoots(cx);
     }
 #endif
 
--- a/js/src/jswrapper.cpp
+++ b/js/src/jswrapper.cpp
@@ -384,17 +384,18 @@ Reify(JSContext *cx, JSCompartment *orig
     size_t length = ni->numKeys();
     bool isKeyIter = ni->isKeyIter();
     AutoIdVector keys(cx);
     if (length > 0) {
         if (!keys.reserve(length))
             return false;
         for (size_t i = 0; i < length; ++i) {
             RootedId id(cx);
-            if (!ValueToId<CanGC>(cx, StringValue(ni->begin()[i]), &id))
+            RootedValue v(cx, StringValue(ni->begin()[i]));
+            if (!ValueToId<CanGC>(cx, v, &id))
                 return false;
             keys.infallibleAppend(id);
             if (!origin->wrapId(cx, &keys[i]))
                 return false;
         }
     }
 
     close.clear();
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -3562,17 +3562,17 @@ GetSelfHostedValue(JSContext *cx, unsign
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     if (argc != 1 || !args[0].isString()) {
         JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_INVALID_ARGS,
                              "getSelfHostedValue");
         return false;
     }
-    RootedAtom srcAtom(cx, ToAtom<CanGC>(cx, args[0]));
+    RootedAtom srcAtom(cx, ToAtom<CanGC>(cx, args.handleAt(0)));
     if (!srcAtom)
         return false;
     RootedPropertyName srcName(cx, srcAtom->asPropertyName());
     return cx->runtime->cloneSelfHostedValue(cx, srcName, args.rval());
 }
 
 static JSFunctionSpecWithHelp shell_functions[] = {
     JS_FN_HELP("version", Version, 0, 0,
@@ -5321,16 +5321,17 @@ main(int argc, char **argv, char **envp)
         || !op.addBoolOption('\0', "no-ggc", "Disable Generational GC")
 #endif
     )
     {
         return EXIT_FAILURE;
     }
 
     op.setArgTerminatesOptions("script", true);
+    op.setArgCapturesRest("scriptArgs");
 
     switch (op.parseArgs(argc, argv)) {
       case OptionParser::ParseHelp:
         return EXIT_SUCCESS;
       case OptionParser::ParseError:
         op.printHelp(argv[0]);
         return EXIT_FAILURE;
       case OptionParser::Fail:
--- a/js/src/shell/jsoptparse.cpp
+++ b/js/src/shell/jsoptparse.cpp
@@ -54,16 +54,24 @@ OPTION_CONVERT_IMPL(Int)
 OPTION_CONVERT_IMPL(MultiString)
 
 void
 OptionParser::setArgTerminatesOptions(const char *name, bool enabled)
 {
     findArgument(name)->setTerminatesOptions(enabled);
 }
 
+void
+OptionParser::setArgCapturesRest(const char *name)
+{
+    MOZ_ASSERT(restArgument == -1, "only one argument may be set to capture the rest");
+    restArgument = findArgumentIndex(name);
+    MOZ_ASSERT(restArgument != -1, "unknown argument name passed to setArgCapturesRest");
+}
+
 OptionParser::Result
 OptionParser::error(const char *fmt, ...)
 {
     va_list args;
     va_start(args, fmt);
     fprintf(stderr, "Error: ");
     vfprintf(stderr, fmt, args);
     va_end(args);
@@ -345,41 +353,44 @@ OptionParser::parseArgs(int inputArgc, c
     for (size_t i = 1; i < argc; ++i) {
         char *arg = argv[i];
         Result r;
         /* Note: solo dash option is actually a 'stdin' argument. */
         if (arg[0] == '-' && arg[1] != '\0' && optionsAllowed) {
             /* Option. */
             Option *opt;
             if (arg[1] == '-') {
-                /* Long option. */
-                opt = findOption(arg + 2);
-                if (!opt)
-                    return error("Invalid long option: %s", arg);
+                if (arg[2] == '\0') {
+                    /* End of options */
+                    optionsAllowed = false;
+                    nextArgument = restArgument;
+                    continue;
+                } else {
+                    /* Long option. */
+                    opt = findOption(arg + 2);
+                    if (!opt)
+                        return error("Invalid long option: %s", arg);
+                }
             } else {
                 /* Short option */
                 if (arg[2] != '\0')
                     return error("Short option followed by junk: %s", arg);
                 opt = findOption(arg[1]);
                 if (!opt)
                     return error("Invalid short option: %s", arg);
             }
 
             r = handleOption(opt, argc, argv, &i, &optionsAllowed);
         } else {
             /* Argument. */
             r = handleArg(argc, argv, &i, &optionsAllowed);
         }
 
-        switch (r) {
-          case Okay:
-            break;
-          default:
+        if (r != Okay)
             return r;
-        }
     }
     return Okay;
 }
 
 void
 OptionParser::setHelpOption(char shortflag, const char *longflag, const char *help)
 {
     helpOption.setFlagInfo(shortflag, longflag, help);
@@ -493,31 +504,39 @@ OptionParser::findOption(const char *lon
 const Option *
 OptionParser::findOption(const char *longflag) const
 {
     return const_cast<OptionParser *>(this)->findOption(longflag);
 }
 
 /* Argument accessors */
 
+int
+OptionParser::findArgumentIndex(const char *name) const
+{
+    for (Option * const *it = arguments.begin(); it != arguments.end(); ++it) {
+        const char *target = (*it)->longflag;
+        if (strcmp(target, name) == 0)
+            return it - arguments.begin();
+    }
+    return -1;
+}
+
 Option *
 OptionParser::findArgument(const char *name)
 {
-    for (Option **it = arguments.begin(), **end = arguments.end(); it != end; ++it) {
-        const char *target = (*it)->longflag;
-        if (strcmp(target, name) == 0)
-            return *it;
-    }
-    return NULL;
+    int index = findArgumentIndex(name);
+    return (index == -1) ? NULL : arguments[index];
 }
 
 const Option *
 OptionParser::findArgument(const char *name) const
 {
-    return const_cast<OptionParser *>(this)->findArgument(name);
+    int index = findArgumentIndex(name);
+    return (index == -1) ? NULL : arguments[index];
 }
 
 const char *
 OptionParser::getStringArg(const char *name) const
 {
     return findArgument(name)->asStringOption()->value;
 }
 
--- a/js/src/shell/jsoptparse.h
+++ b/js/src/shell/jsoptparse.h
@@ -206,49 +206,57 @@ class OptionParser
     BoolOption  helpOption;
     const char  *usage;
     const char  *ver;
     const char  *descr;
     size_t      descrWidth;
     size_t      helpWidth;
     size_t      nextArgument;
 
+    // If '--' is passed, all remaining arguments should be interpreted as the
+    // argument at index 'restArgument'. Defaults to the next unassigned
+    // argument.
+    int         restArgument;
+
     static const char prognameMeta[];
 
     Option *findOption(char shortflag);
     const Option *findOption(char shortflag) const;
     Option *findOption(const char *longflag);
     const Option *findOption(const char *longflag) const;
+    int findArgumentIndex(const char *name) const;
     Option *findArgument(const char *name);
     const Option *findArgument(const char *name) const;
 
     Result error(const char *fmt, ...);
     Result extractValue(size_t argc, char **argv, size_t *i, char **value);
     Result handleArg(size_t argc, char **argv, size_t *i, bool *optsAllowed);
     Result handleOption(Option *opt, size_t argc, char **argv, size_t *i, bool *optsAllowed);
 
   public:
     explicit OptionParser(const char *usage)
       : helpOption('h', "help", "Display help information"),
-        usage(usage), ver(NULL), descr(NULL), descrWidth(80), helpWidth(80), nextArgument(0)
+        usage(usage), ver(NULL), descr(NULL), descrWidth(80), helpWidth(80),
+        nextArgument(0), restArgument(-1)
     {}
 
     ~OptionParser();
 
     Result parseArgs(int argc, char **argv);
     Result printHelp(const char *progname);
 
     /* Metadata */
 
     void setVersion(const char *version) { ver = version; }
     void setHelpWidth(size_t width) { helpWidth = width; }
     void setDescriptionWidth(size_t width) { descrWidth = width; }
     void setDescription(const char *description) { descr = description; }
     void setHelpOption(char shortflag, const char *longflag, const char *help);
     void setArgTerminatesOptions(const char *name, bool enabled);
+    void setArgCapturesRest(const char *name);
 
     /* Arguments: no further arguments may be added after a variadic argument. */
 
     bool addOptionalStringArg(const char *name, const char *help);
     bool addOptionalMultiStringArg(const char *name, const char *help);
 
     const char *getStringArg(const char *name) const;
     MultiStringRange getMultiStringArg(const char *name) const;
--- a/js/src/tests/jstests.list
+++ b/js/src/tests/jstests.list
@@ -1,11 +1,104 @@
 # Manifest entries for imported test suites whose individual test cases
 # we don't want to change.
 
 skip-if(!this.hasOwnProperty("Intl")) include test262/intl402/jstests.list # Intl is not enabled in all builds
 
+##################################################
+# Test262 tests skipped due to SpiderMonkey bugs #
+##################################################
+
+# These tests all make assumptions about global-object property enumeration
+# behavior, but because of bug 866222 these assumptions don't hold in the
+# browser.
+skip-if(!xulRuntime.shell) script test262/ch10/10.4/10.4.2/S10.4.2_A1.1_T1.js
+skip-if(!xulRuntime.shell) script test262/ch10/10.4/10.4.2/S10.4.2_A1.1_T2.js
+skip-if(!xulRuntime.shell) script test262/ch10/10.4/10.4.2/S10.4.2_A1.1_T3.js
+skip-if(!xulRuntime.shell) script test262/ch10/10.4/10.4.2/S10.4.2_A1.1_T4.js
+skip-if(!xulRuntime.shell) script test262/ch10/10.4/10.4.2/S10.4.2_A1.1_T5.js
+skip-if(!xulRuntime.shell) script test262/ch10/10.4/10.4.2/S10.4.2_A1.1_T6.js
+skip-if(!xulRuntime.shell) script test262/ch10/10.4/10.4.2/S10.4.2_A1.1_T7.js
+skip-if(!xulRuntime.shell) script test262/ch10/10.4/10.4.2/S10.4.2_A1.1_T8.js
+skip-if(!xulRuntime.shell) script test262/ch10/10.4/10.4.2/S10.4.2_A1.1_T9.js
+skip-if(!xulRuntime.shell) script test262/ch10/10.4/10.4.2/S10.4.2_A1.1_T10.js
+skip-if(!xulRuntime.shell) script test262/ch10/10.4/10.4.2/S10.4.2_A1.1_T11.js
+skip-if(!xulRuntime.shell) script test262/ch10/10.4/10.4.2/S10.4.2_A1.2_T1.js
+skip-if(!xulRuntime.shell) script test262/ch10/10.4/10.4.2/S10.4.2_A1.2_T2.js
+skip-if(!xulRuntime.shell) script test262/ch10/10.4/10.4.2/S10.4.2_A1.2_T3.js
+skip-if(!xulRuntime.shell) script test262/ch10/10.4/10.4.2/S10.4.2_A1.2_T4.js
+skip-if(!xulRuntime.shell) script test262/ch10/10.4/10.4.2/S10.4.2_A1.2_T5.js
+skip-if(!xulRuntime.shell) script test262/ch10/10.4/10.4.2/S10.4.2_A1.2_T6.js
+skip-if(!xulRuntime.shell) script test262/ch10/10.4/10.4.2/S10.4.2_A1.2_T7.js
+skip-if(!xulRuntime.shell) script test262/ch10/10.4/10.4.2/S10.4.2_A1.2_T8.js
+skip-if(!xulRuntime.shell) script test262/ch10/10.4/10.4.2/S10.4.2_A1.2_T9.js
+skip-if(!xulRuntime.shell) script test262/ch10/10.4/10.4.2/S10.4.2_A1.2_T10.js
+skip-if(!xulRuntime.shell) script test262/ch10/10.4/10.4.2/S10.4.2_A1.2_T11.js
+
+skip script test262/ch10/10.4/10.4.3/10.4.3-1-104.js # bug 603201
+skip script test262/ch10/10.4/10.4.3/10.4.3-1-106.js # bug 603201
+
 skip script test262/intl402/ch10/10.1/10.1.1_a.js # bug 854320
 skip script test262/intl402/ch10/10.1/10.1.1_13.js # bug 853704
 skip script test262/intl402/ch10/10.1/10.1.1_19_c.js # bug 853704
 skip script test262/intl402/ch11/11.1/11.1.1_a.js # bug 854320
 skip script test262/intl402/ch11/11.3/11.3.2_TRP.js # bug 853706
 skip script test262/intl402/ch12/12.1/12.1.1_a.js # bug 854320
+
+#######################################################################
+# Tests disabled due to jstest limitations wrt imported test262 tests #
+#######################################################################
+
+# These tests are disabled because jstest doesn't understand @negative (without
+# a pattern) yet.
+skip script test262/ch11/11.1/11.1.1/S11.1.1_A1.js
+skip script test262/ch11/11.2/11.2.4/S11.2.4_A1.3_T1.js
+skip script test262/ch11/11.3/11.3.1/11.3.1-2-1gs.js
+skip script test262/ch11/11.3/11.3.1/S11.3.1_A1.1_T1.js
+skip script test262/ch11/11.3/11.3.1/S11.3.1_A1.1_T2.js
+skip script test262/ch11/11.3/11.3.1/S11.3.1_A1.1_T3.js
+skip script test262/ch11/11.3/11.3.1/S11.3.1_A1.1_T4.js
+skip script test262/ch11/11.3/11.3.1/S11.3.1_A2.1_T3.js
+skip script test262/ch11/11.3/11.3.2/S11.3.2_A1.1_T1.js
+skip script test262/ch11/11.3/11.3.2/S11.3.2_A1.1_T2.js
+skip script test262/ch11/11.3/11.3.2/S11.3.2_A1.1_T3.js
+skip script test262/ch11/11.3/11.3.2/S11.3.2_A1.1_T4.js
+skip script test262/ch11/11.3/11.3.2/S11.3.2_A2.1_T3.js
+skip script test262/ch11/11.4/11.4.1/11.4.1-5-a-5gs.js
+skip script test262/ch11/11.4/11.4.2/S11.4.2_A2_T2.js
+skip script test262/ch11/11.4/11.4.4/S11.4.4_A2.1_T3.js
+skip script test262/ch11/11.4/11.4.5/S11.4.5_A2.1_T3.js
+skip script test262/ch11/11.13/11.13.1/S11.13.1_A2.1_T3.js
+skip script test262/ch11/11.13/11.13.2/11.13.2-6-1gs.js
+skip script test262/ch11/11.13/11.13.2/S11.13.2_A2.2_T1.js
+skip script test262/ch11/11.13/11.13.2/S11.13.2_A2.2_T2.js
+skip script test262/ch11/11.13/11.13.2/S11.13.2_A2.2_T3.js
+skip script test262/ch11/11.13/11.13.2/S11.13.2_A2.2_T4.js
+skip script test262/ch11/11.13/11.13.2/S11.13.2_A2.2_T5.js
+skip script test262/ch11/11.13/11.13.2/S11.13.2_A2.2_T6.js
+skip script test262/ch11/11.13/11.13.2/S11.13.2_A2.2_T7.js
+skip script test262/ch11/11.13/11.13.2/S11.13.2_A2.2_T8.js
+skip script test262/ch11/11.13/11.13.2/S11.13.2_A2.2_T9.js
+skip script test262/ch11/11.13/11.13.2/S11.13.2_A2.2_T10.js
+skip script test262/ch11/11.13/11.13.2/S11.13.2_A2.2_T11.js
+skip script test262/ch13/13.0/S13_A7_T3.js
+
+# These tests are disabled because jstest doesn't understand @negative with a
+# pattern yet.
+skip script test262/ch10/10.1/10.1.1/10.1.1-2gs.js
+skip script test262/ch10/10.1/10.1.1/10.1.1-5gs.js
+skip script test262/ch10/10.1/10.1.1/10.1.1-8gs.js
+skip script test262/ch10/10.4/10.4.2/10.4.2.1-1gs.js
+skip script test262/ch10/10.5/10.5-1gs.js
+skip script test262/ch10/10.6/10.6-2gs.js
+skip script test262/ch11/11.1/11.1.5/11.1.5-1gs.js
+skip script test262/ch11/11.1/11.1.5/11.1.5-2gs.js
+skip script test262/ch11/11.4/11.4.5/11.4.5-2-2gs.js
+skip script test262/ch11/11.13/11.13.1/11.13.1-4-28gs.js
+skip script test262/ch11/11.13/11.13.1/11.13.1-4-29gs.js
+skip script test262/ch13/13.0/13.0_4-5gs.js
+skip script test262/ch13/13.0/13.0_4-17gs.js
+skip script test262/ch13/13.1/13.1-1gs.js
+skip script test262/ch13/13.1/13.1-4gs.js
+skip script test262/ch13/13.1/13.1-5gs.js
+skip script test262/ch13/13.1/13.1-8gs.js
+skip script test262/ch13/13.1/13.1-13gs.js
+skip script test262/ch13/13.2/13.2-19-b-3gs.js
--- a/js/src/tests/jstests.py
+++ b/js/src/tests/jstests.py
@@ -1,16 +1,17 @@
 #!/usr/bin/env python
 """
 The JS Shell Test Harness.
 
 See the adjacent README.txt for more details.
 """
 
 import os, sys, textwrap
+from os.path import abspath, dirname, realpath
 from copy import copy
 from subprocess import list2cmdline, call
 
 from lib.results import NullTestOutput
 from lib.tests import TestCase
 from lib.results import ResultsSink
 from lib.progressbar import ProgressBar
 
@@ -84,16 +85,18 @@ def parse_args():
     harness_og.add_option('-t', '--timeout', type=float, default=150.0,
                           help='Set maximum time a test is allows to run (in seconds).')
     harness_og.add_option('-a', '--args', dest='shell_args', default='',
                           help='Extra args to pass to the JS shell.')
     harness_og.add_option('--jitflags', default='',
                           help='Example: --jitflags=m,amd to run each test with -m, -a -m -d [default=%default]')
     harness_og.add_option('-g', '--debug', action='store_true', help='Run a test in debugger.')
     harness_og.add_option('--debugger', default='gdb -q --args', help='Debugger command.')
+    harness_og.add_option('-J', '--jorendb', action='store_true', help='Run under JS debugger.')
+    harness_og.add_option('--passthrough', action='store_true', help='Run tests with stdin/stdout attached to caller.')
     harness_og.add_option('--valgrind', action='store_true', help='Run tests in valgrind.')
     harness_og.add_option('--valgrind-args', default='', help='Extra args to pass to valgrind.')
     op.add_option_group(harness_og)
 
     input_og = OptionGroup(op, "Inputs", "Change what tests are run.")
     input_og.add_option('-f', '--file', dest='test_file', action='append',
                         help='Get tests from the given file.')
     input_og.add_option('-x', '--exclude-file', action='append',
@@ -130,17 +133,17 @@ def parse_args():
                           help='Generate reftest manifest files.')
     op.add_option_group(special_og)
     options, args = op.parse_args()
 
     # Acquire the JS shell given on the command line.
     options.js_shell = None
     requested_paths = set()
     if len(args) > 0:
-        options.js_shell = os.path.abspath(args[0])
+        options.js_shell = abspath(args[0])
         requested_paths |= set(args[1:])
 
     # If we do not have a shell, we must be in a special mode.
     if options.js_shell is None and not options.make_manifests:
         op.error('missing JS_SHELL argument')
 
     # Valgrind and gdb are mutually exclusive.
     if options.valgrind and options.debug:
@@ -148,17 +151,25 @@ def parse_args():
 
     # Fill the debugger field, as needed.
     prefix = options.debugger.split() if options.debug else []
     if options.valgrind:
         prefix = ['valgrind'] + options.valgrind_args.split()
         if os.uname()[0] == 'Darwin':
             prefix.append('--dsymutil=yes')
         options.show_output = True
-    TestCase.set_js_cmd_prefix(options.js_shell, options.shell_args.split(), prefix)
+
+    js_cmd_args = options.shell_args.split()
+    if options.jorendb:
+        options.passthrough = True
+        options.hide_progress = True
+        options.worker_count = 1
+        debugger_path = realpath(os.path.join(abspath(dirname(abspath(__file__))), '..', '..', 'examples', 'jorendb.js'))
+        js_cmd_args.extend([ '-d', '-f', debugger_path, '--' ])
+    TestCase.set_js_cmd_prefix(options.js_shell, js_cmd_args, prefix)
 
     # If files with lists of tests to run were specified, add them to the
     # requested tests set.
     if options.test_file:
         for test_file in options.test_file:
             requested_paths |= set([line.strip() for line in open(test_file).readlines()])
 
     # If files with lists of tests to exclude were specified, add them to the
@@ -219,17 +230,17 @@ def load_tests(options, requested_paths,
         if options.xul_info_src is None:
             xul_info = manifest.XULInfo.create(options.js_shell)
         else:
             xul_abi, xul_os, xul_debug = options.xul_info_src.split(r':')
             xul_debug = xul_debug.lower() is 'true'
             xul_info = manifest.XULInfo(xul_abi, xul_os, xul_debug)
         xul_tester = manifest.XULInfoTester(xul_info, options.js_shell)
 
-    test_dir = os.path.dirname(os.path.abspath(__file__))
+    test_dir = dirname(abspath(__file__))
     test_list = manifest.load(test_dir, xul_tester)
     skip_list = []
 
     if options.make_manifests:
         manifest.make_manifests(options.make_manifests, test_list)
         sys.exit()
 
     # Create a new test list. Apply each JIT configuration to every test.
@@ -284,17 +295,17 @@ def load_tests(options, requested_paths,
 def main():
     options, requested_paths, excluded_paths = parse_args()
     skip_list, test_list = load_tests(options, requested_paths, excluded_paths)
 
     if not test_list:
         print 'no tests selected'
         return 1
 
-    test_dir = os.path.dirname(os.path.abspath(__file__))
+    test_dir = dirname(abspath(__file__))
 
     if options.debug:
         if len(test_list) > 1:
             print('Multiple tests match command line arguments, debugger can only run one')
             for tc in test_list:
                 print('    %s'%tc.path)
             return 2
 
--- a/js/src/tests/lib/manifest.py
+++ b/js/src/tests/lib/manifest.py
@@ -339,17 +339,18 @@ def load(location, xul_tester, reldir = 
     - an external manifest entry for a containing directory,
     - most commonly: the header of the test case itself.
     """
     # The list of tests that we are collecting.
     tests = []
 
     # Any file whose basename matches something in this set is ignored.
     EXCLUDED = set(('browser.js', 'shell.js', 'jsref.js', 'template.js',
-                    'user.js', 'test262-browser.js', 'test262-shell.js',
+                    'user.js', 'sta.js',
+                    'test262-browser.js', 'test262-shell.js',
                     'test402-browser.js', 'test402-shell.js',
                     'testBuiltInObject.js', 'testIntl.js',
                     'js-test-driver-begin.js', 'js-test-driver-end.js'))
 
     manifestFile = os.path.join(location, 'jstests.list')
     externalManifestEntries = _parse_external_manifest(manifestFile, '')
 
     for root, basename in _find_all_js_files(location, location):
--- a/js/src/tests/lib/tasks_unix.py
+++ b/js/src/tests/lib/tasks_unix.py
@@ -12,35 +12,36 @@ class Task(object):
         self.cmd = test.get_command(test.js_cmd_prefix)
         self.pid = pid
         self.stdout = stdout
         self.stderr = stderr
         self.start = datetime.now()
         self.out = []
         self.err = []
 
-def spawn_test(test):
+def spawn_test(test, passthrough = False):
     """Spawn one child, return a task struct."""
-    (rout, wout) = os.pipe()
-    (rerr, werr) = os.pipe()
+    if not passthrough:
+        (rout, wout) = os.pipe()
+        (rerr, werr) = os.pipe()
 
-    rv = os.fork()
+        rv = os.fork()
 
-    # Parent.
-    if rv:
-        os.close(wout)
-        os.close(werr)
-        return Task(test, rv, rout, rerr)
+        # Parent.
+        if rv:
+            os.close(wout)
+            os.close(werr)
+            return Task(test, rv, rout, rerr)
 
-    # Child.
-    os.close(rout)
-    os.close(rerr)
+        # Child.
+        os.close(rout)
+        os.close(rerr)
 
-    os.dup2(wout, 1)
-    os.dup2(werr, 2)
+        os.dup2(wout, 1)
+        os.dup2(werr, 2)
 
     cmd = test.get_command(test.js_cmd_prefix)
     os.execvp(cmd[0], cmd)
 
 def total_seconds(td):
     """
     Return the total number of seconds contained in the duration as a float
     """
@@ -183,17 +184,17 @@ def run_all_tests(tests, results, option
     tests = tests[:]
     tests.reverse()
 
     # The set of currently running tests.
     tasks = []
 
     while len(tests) or len(tasks):
         while len(tests) and len(tasks) < options.worker_count:
-            tasks.append(spawn_test(tests.pop()))
+            tasks.append(spawn_test(tests.pop(), options.passthrough))
 
         timeout = get_max_wait(tasks, results, options.timeout)
         read_input(tasks, timeout)
 
         kill_undead(tasks, results, options.timeout)
         tasks = reap_zombies(tasks, results, options.timeout)
 
         results.pb.poke()
new file mode 100644
--- /dev/null
+++ b/js/src/tests/supporting/sta.js
@@ -0,0 +1,901 @@
+/// Copyright (c) 2012 Ecma International.  All rights reserved. 
+/// Ecma International makes this code available under the terms and conditions set
+/// forth on http://hg.ecmascript.org/tests/test262/raw-file/tip/LICENSE (the 
+/// "Use Terms").   Any redistribution of this code must retain the above 
+/// copyright and this notice and otherwise comply with the Use Terms.
+
+//-----------------------------------------------------------------------------
+function compareArray(aExpected, aActual) {
+    if (aActual.length != aExpected.length) {
+        return false;
+    }
+
+    aExpected.sort();
+    aActual.sort();
+
+    var s;
+    for (var i = 0; i < aExpected.length; i++) {
+        if (aActual[i] !== aExpected[i]) {
+            return false;
+        }
+    }
+    return true;
+}
+
+//-----------------------------------------------------------------------------
+function arrayContains(arr, expected) {
+    var found;
+    for (var i = 0; i < expected.length; i++) {
+        found = false;
+        for (var j = 0; j < arr.length; j++) {
+            if (expected[i] === arr[j]) {
+                found = true;
+                break;
+            }
+        }
+        if (!found) {
+            return false;
+        }
+    }
+    return true;
+}
+
+//-----------------------------------------------------------------------------
+var supportsArrayIndexGettersOnArrays = undefined;
+function fnSupportsArrayIndexGettersOnArrays() {
+    if (typeof supportsArrayIndexGettersOnArrays !== "undefined") {
+        return supportsArrayIndexGettersOnArrays;
+    }
+
+    supportsArrayIndexGettersOnArrays = false;
+
+    if (fnExists(Object.defineProperty)) {
+        var arr = [];
+        Object.defineProperty(arr, "0", {
+            get: function() {
+                supportsArrayIndexGettersOnArrays = true;
+                return 0;
+            }
+        });
+        var res = arr[0];
+    }
+
+    return supportsArrayIndexGettersOnArrays;
+}
+
+//-----------------------------------------------------------------------------
+var supportsArrayIndexGettersOnObjects = undefined;
+function fnSupportsArrayIndexGettersOnObjects() {
+    if (typeof supportsArrayIndexGettersOnObjects !== "undefined")
+        return supportsArrayIndexGettersOnObjects;
+
+    supportsArrayIndexGettersOnObjects = false;
+
+    if (fnExists(Object.defineProperty)) {
+        var obj = {};
+        Object.defineProperty(obj, "0", {
+            get: function() {
+                supportsArrayIndexGettersOnObjects = true;
+                return 0;
+            }
+        });
+        var res = obj[0];
+    }
+
+    return supportsArrayIndexGettersOnObjects;
+}
+
+//-----------------------------------------------------------------------------
+function ConvertToFileUrl(pathStr) {
+    return "file:" + pathStr.replace(/\\/g, "/");
+}
+
+//-----------------------------------------------------------------------------
+function fnExists(/*arguments*/) {
+    for (var i = 0; i < arguments.length; i++) {
+        if (typeof (arguments[i]) !== "function") return false;
+    }
+    return true;
+}
+
+//-----------------------------------------------------------------------------
+var __globalObject = Function("return this;")();
+function fnGlobalObject() {
+     return __globalObject;
+}
+
+//-----------------------------------------------------------------------------
+function fnSupportsStrict() {
+    "use strict";
+    try {
+        eval('with ({}) {}');
+        return false;
+    } catch (e) {
+        return true;
+    }
+}
+
+//-----------------------------------------------------------------------------
+//Verify all attributes specified data property of given object:
+//value, writable, enumerable, configurable
+//If all attribute values are expected, return true, otherwise, return false
+function dataPropertyAttributesAreCorrect(obj,
+                                          name,
+                                          value,
+                                          writable,
+                                          enumerable,
+                                          configurable) {
+    var attributesCorrect = true;
+
+    if (obj[name] !== value) {
+        if (typeof obj[name] === "number" &&
+            isNaN(obj[name]) &&
+            typeof value === "number" &&
+            isNaN(value)) {
+            // keep empty
+        } else {
+            attributesCorrect = false;
+        }
+    }
+
+    try {
+        if (obj[name] === "oldValue") {
+            obj[name] = "newValue";
+        } else {
+            obj[name] = "OldValue";
+        }
+    } catch (we) {
+    }
+
+    var overwrited = false;
+    if (obj[name] !== value) {
+        if (typeof obj[name] === "number" &&
+            isNaN(obj[name]) &&
+            typeof value === "number" &&
+            isNaN(value)) {
+            // keep empty
+        } else {
+            overwrited = true;
+        }
+    }
+    if (overwrited !== writable) {
+        attributesCorrect = false;
+    }
+
+    var enumerated = false;
+    for (var prop in obj) {
+        if (obj.hasOwnProperty(prop) && prop === name) {
+            enumerated = true;
+        }
+    }
+
+    if (enumerated !== enumerable) {
+        attributesCorrect = false;
+    }
+
+
+    var deleted = false;
+
+    try {
+        delete obj[name];
+    } catch (de) {
+    }
+    if (!obj.hasOwnProperty(name)) {
+        deleted = true;
+    }
+    if (deleted !== configurable) {
+        attributesCorrect = false;
+    }
+
+    return attributesCorrect;
+}
+
+//-----------------------------------------------------------------------------
+//Verify all attributes specified accessor property of given object:
+//get, set, enumerable, configurable
+//If all attribute values are expected, return true, otherwise, return false
+function accessorPropertyAttributesAreCorrect(obj,
+                                              name,
+                                              get,
+                                              set,
+                                              setVerifyHelpProp,
+                                              enumerable,
+                                              configurable) {
+    var attributesCorrect = true;
+
+    if (get !== undefined) {
+        if (obj[name] !== get()) {
+            if (typeof obj[name] === "number" &&
+                isNaN(obj[name]) &&
+                typeof get() === "number" &&
+                isNaN(get())) {
+                // keep empty
+            } else {
+                attributesCorrect = false;
+            }
+        }
+    } else {
+        if (obj[name] !== undefined) {
+            attributesCorrect = false;
+        }
+    }
+
+    try {
+        var desc = Object.getOwnPropertyDescriptor(obj, name);
+        if (typeof desc.set === "undefined") {
+            if (typeof set !== "undefined") {
+                attributesCorrect = false;
+            }
+        } else {
+            obj[name] = "toBeSetValue";
+            if (obj[setVerifyHelpProp] !== "toBeSetValue") {
+                attributesCorrect = false;
+            }
+        }
+    } catch (se) {
+        throw se;
+    }
+
+
+    var enumerated = false;
+    for (var prop in obj) {
+        if (obj.hasOwnProperty(prop) && prop === name) {
+            enumerated = true;
+        }
+    }
+
+    if (enumerated !== enumerable) {
+        attributesCorrect = false;
+    }
+
+
+    var deleted = false;
+    try {
+        delete obj[name];
+    } catch (de) {
+        throw de;
+    }
+    if (!obj.hasOwnProperty(name)) {
+        deleted = true;
+    }
+    if (deleted !== configurable) {
+        attributesCorrect = false;
+    }
+
+    return attributesCorrect;
+}
+
+//-----------------------------------------------------------------------------
+var NotEarlyErrorString = "NotEarlyError";
+var EarlyErrorRePat = "^((?!" + NotEarlyErrorString + ").)*$";
+var NotEarlyError = new Error(NotEarlyErrorString);
+
+//-----------------------------------------------------------------------------
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+function Test262Error(message) {
+    if (message) this.message = message;
+}
+
+Test262Error.prototype.toString = function () {
+    return "Test262 Error: " + this.message;
+};
+
+function testFailed(message) {
+    throw new Test262Error(message);
+}
+
+
+function testPrint(message) {
+
+}
+
+
+//adaptors for Test262 framework
+function $PRINT(message) {
+
+}
+
+function $INCLUDE(message) { }
+function $ERROR(message) {
+    testFailed(message);
+}
+
+function $FAIL(message) {
+    testFailed(message);
+}
+
+
+
+//Sputnik library definitions
+//Ultimately these should be namespaced some how and only made
+//available to tests that explicitly include them.
+//For now, we just define the globally
+
+//math_precision.js
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+function getPrecision(num) {
+    //TODO: Create a table of prec's,
+    //      because using Math for testing Math isn't that correct.
+
+    var log2num = Math.log(Math.abs(num)) / Math.LN2;
+    var pernum = Math.ceil(log2num);
+    return (2 * Math.pow(2, -52 + pernum));
+    //return(0);
+}
+
+
+//math_isequal.js
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+var prec;
+function isEqual(num1, num2) {
+    if ((num1 === Infinity) && (num2 === Infinity)) {
+        return (true);
+    }
+    if ((num1 === -Infinity) && (num2 === -Infinity)) {
+        return (true);
+    }
+    prec = getPrecision(Math.min(Math.abs(num1), Math.abs(num2)));
+    return (Math.abs(num1 - num2) <= prec);
+    //return(num1 === num2);
+}
+
+//numeric_conversion.js
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+function ToInteger(p) {
+    var x = Number(p);
+
+    if (isNaN(x)) {
+        return +0;
+    }
+
+    if ((x === +0)
+  || (x === -0)
+  || (x === Number.POSITIVE_INFINITY)
+  || (x === Number.NEGATIVE_INFINITY)) {
+        return x;
+    }
+
+    var sign = (x < 0) ? -1 : 1;
+
+    return (sign * Math.floor(Math.abs(x)));
+}
+
+//Date_constants.js
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+var HoursPerDay = 24;
+var MinutesPerHour = 60;
+var SecondsPerMinute = 60;
+
+var msPerDay = 86400000;
+var msPerSecond = 1000;
+var msPerMinute = 60000;
+var msPerHour = 3600000;
+
+var date_1899_end = -2208988800001;
+var date_1900_start = -2208988800000;
+var date_1969_end = -1;
+var date_1970_start = 0;
+var date_1999_end = 946684799999;
+var date_2000_start = 946684800000;
+var date_2099_end = 4102444799999;
+var date_2100_start = 4102444800000;
+
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+//the following values are normally generated by the sputnik.py driver
+var $LocalTZ,
+    $DST_start_month,
+    $DST_start_sunday,
+    $DST_start_hour,
+    $DST_start_minutes,
+    $DST_end_month,
+    $DST_end_sunday,
+    $DST_end_hour,
+    $DST_end_minutes;
+
+(function () {
+    /**
+      * Finds the first date, starting from |start|, where |predicate|
+      * holds.
+      */
+    var findNearestDateBefore = function(start, predicate) {
+        var current = start;
+        var month = 1000 * 60 * 60 * 24 * 30;
+        for (var step = month; step > 0; step = Math.floor(step / 3)) {
+            if (!predicate(current)) {
+                while (!predicate(current))
+                    current = new Date(current.getTime() + step);
+                    current = new Date(current.getTime() - step);
+                }
+        }
+        while (!predicate(current)) {
+            current = new Date(current.getTime() + 1);
+        }
+        return current;
+    };
+
+    var juneDate = new Date(2000, 5, 20, 0, 0, 0, 0);
+    var decemberDate = new Date(2000, 11, 20, 0, 0, 0, 0);
+    var juneOffset = juneDate.getTimezoneOffset();
+    var decemberOffset = decemberDate.getTimezoneOffset();
+    var isSouthernHemisphere = (juneOffset > decemberOffset);
+    var winterTime = isSouthernHemisphere ? juneDate : decemberDate;
+    var summerTime = isSouthernHemisphere ? decemberDate : juneDate;
+
+    var dstStart = findNearestDateBefore(winterTime, function (date) {
+        return date.getTimezoneOffset() == summerTime.getTimezoneOffset();
+    });
+    $DST_start_month = dstStart.getMonth();
+    $DST_start_sunday = dstStart.getDate() > 15 ? '"last"' : '"first"';
+    $DST_start_hour = dstStart.getHours();
+    $DST_start_minutes = dstStart.getMinutes();
+
+    var dstEnd = findNearestDateBefore(summerTime, function (date) {
+        return date.getTimezoneOffset() == winterTime.getTimezoneOffset();
+    });
+    $DST_end_month = dstEnd.getMonth();
+    $DST_end_sunday = dstEnd.getDate() > 15 ? '"last"' : '"first"';
+    $DST_end_hour = dstEnd.getHours();
+    $DST_end_minutes = dstEnd.getMinutes();
+
+    return;
+})();
+
+
+//Date.library.js
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+//15.9.1.2 Day Number and Time within Day
+function Day(t) {
+  return Math.floor(t/msPerDay);
+}
+
+function TimeWithinDay(t) {
+  return t%msPerDay;
+}
+
+//15.9.1.3 Year Number
+function DaysInYear(y){
+  if(y%4 != 0) return 365;
+  if(y%4 == 0 && y%100 != 0) return 366;
+  if(y%100 == 0 && y%400 != 0) return 365;
+  if(y%400 == 0) return 366;
+}
+
+function DayFromYear(y) {
+  return (365*(y-1970)
+          + Math.floor((y-1969)/4)
+          - Math.floor((y-1901)/100)
+          + Math.floor((y-1601)/400));
+}
+
+function TimeFromYear(y){
+  return msPerDay*DayFromYear(y);
+}
+
+function YearFromTime(t) {
+  t = Number(t);
+  var sign = ( t < 0 ) ? -1 : 1;
+  var year = ( sign < 0 ) ? 1969 : 1970;
+
+  for(var time = 0;;year += sign){
+    time = TimeFromYear(year);
+
+    if(sign > 0 && time > t){
+      year -= sign;
+      break;
+    }
+    else if(sign < 0 && time <= t){
+      break;
+    }
+  };
+  return year;
+}
+
+function InLeapYear(t){
+  if(DaysInYear(YearFromTime(t)) == 365)
+    return 0;
+
+  if(DaysInYear(YearFromTime(t)) == 366)
+    return 1;
+}
+
+function DayWithinYear(t) {
+  return Day(t)-DayFromYear(YearFromTime(t));
+}
+
+//15.9.1.4 Month Number
+function MonthFromTime(t){
+  var day = DayWithinYear(t);
+  var leap = InLeapYear(t);
+
+  if((0 <= day) && (day < 31)) return 0;
+  if((31 <= day) && (day < (59+leap))) return 1;
+  if(((59+leap) <= day) && (day < (90+leap))) return 2;
+  if(((90+leap) <= day) && (day < (120+leap))) return 3;
+  if(((120+leap) <= day) && (day < (151+leap))) return 4;
+  if(((151+leap) <= day) && (day < (181+leap))) return 5;
+  if(((181+leap) <= day) && (day < (212+leap))) return 6;
+  if(((212+leap) <= day) && (day < (243+leap))) return 7;
+  if(((243+leap) <= day) && (day < (273+leap))) return 8;
+  if(((273+leap) <= day) && (day < (304+leap))) return 9;
+  if(((304+leap) <= day) && (day < (334+leap))) return 10;
+  if(((334+leap) <= day) && (day < (365+leap))) return 11;
+}
+
+//15.9.1.5 Date Number
+function DateFromTime(t) {
+  var day = DayWithinYear(t);
+  var month = MonthFromTime(t);
+  var leap = InLeapYear(t);
+
+  if(month == 0) return day+1;
+  if(month == 1) return day-30;
+  if(month == 2) return day-58-leap;
+  if(month == 3) return day-89-leap;
+  if(month == 4) return day-119-leap;
+  if(month == 5) return day-150-leap;
+  if(month == 6) return day-180-leap;
+  if(month == 7) return day-211-leap;
+  if(month == 8) return day-242-leap;
+  if(month == 9) return day-272-leap;
+  if(month == 10) return day-303-leap;
+  if(month == 11) return day-333-leap;
+}
+
+//15.9.1.6 Week Day
+function WeekDay(t) {
+  var weekday = (Day(t)+4)%7;
+  return (weekday < 0 ? 7+weekday : weekday);
+}
+
+//15.9.1.9 Daylight Saving Time Adjustment
+$LocalTZ = (new Date()).getTimezoneOffset() / -60;
+if (DaylightSavingTA((new Date()).valueOf()) !== 0) {
+   $LocalTZ -= 1;
+}
+var LocalTZA = $LocalTZ*msPerHour;
+
+function DaysInMonth(m, leap) {
+  m = m%12;
+
+  //April, June, Sept, Nov
+  if(m == 3 || m == 5 || m == 8 || m == 10 ) {
+    return 30;
+  }
+
+  //Jan, March, May, July, Aug, Oct, Dec
+  if(m == 0 || m == 2 || m == 4 || m == 6 || m == 7 || m == 9 || m == 11){
+    return 31;
+  }
+
+  //Feb
+  return 28+leap;
+}
+
+function GetSundayInMonth(t, m, count){
+    var year = YearFromTime(t);
+    var tempDate;
+
+    if (count==='"first"') {
+        for (var d=1; d <= DaysInMonth(m, InLeapYear(t)); d++) {
+            tempDate = new Date(year, m, d);
+            if (tempDate.getDay()===0) {
+                return tempDate.valueOf();
+            }
+        }
+    } else if(count==='"last"') {
+        for (var d=DaysInMonth(m, InLeapYear(t)); d>0; d--) {
+            tempDate = new Date(year, m, d);
+            if (tempDate.getDay()===0) {
+                return tempDate.valueOf();
+            }
+        }
+    }
+    throw new Error("Unsupported 'count' arg:" + count);
+}
+/*
+function GetSundayInMonth(t, m, count){
+  var year = YearFromTime(t);
+  var leap = InLeapYear(t);
+  var day = 0;
+
+  if(m >= 1) day += DaysInMonth(0, leap);
+  if(m >= 2) day += DaysInMonth(1, leap);
+  if(m >= 3) day += DaysInMonth(2, leap);
+  if(m >= 4) day += DaysInMonth(3, leap);
+  if(m >= 5) day += DaysInMonth(4, leap);
+  if(m >= 6) day += DaysInMonth(5, leap);
+  if(m >= 7) day += DaysInMonth(6, leap);
+  if(m >= 8) day += DaysInMonth(7, leap);
+  if(m >= 9) day += DaysInMonth(8, leap);
+  if(m >= 10) day += DaysInMonth(9, leap);
+  if(m >= 11) day += DaysInMonth(10, leap);
+
+  var month_start = TimeFromYear(year)+day*msPerDay;
+  var sunday = 0;
+
+  if(count === "last"){
+    for(var last_sunday = month_start+DaysInMonth(m, leap)*msPerDay;
+      WeekDay(last_sunday)>0;
+      last_sunday -= msPerDay
+    ){};
+    sunday = last_sunday;
+  }
+  else {
+    for(var first_sunday = month_start;
+      WeekDay(first_sunday)>0;
+      first_sunday += msPerDay
+    ){};
+    sunday = first_sunday+7*msPerDay*(count-1);
+  }
+
+  return sunday;
+}*/
+
+function DaylightSavingTA(t) {
+//  t = t-LocalTZA;
+
+  var DST_start = GetSundayInMonth(t, $DST_start_month, $DST_start_sunday) +
+                  $DST_start_hour*msPerHour +
+                  $DST_start_minutes*msPerMinute;
+
+  var k = new Date(DST_start);
+
+  var DST_end   = GetSundayInMonth(t, $DST_end_month, $DST_end_sunday) +
+                  $DST_end_hour*msPerHour +
+                  $DST_end_minutes*msPerMinute;
+
+  if ( t >= DST_start && t < DST_end ) {
+    return msPerHour;
+  } else {
+    return 0;
+  }
+}
+
+//15.9.1.9 Local Time
+function LocalTime(t){
+  return t+LocalTZA+DaylightSavingTA(t);
+}
+
+function UTC(t) {
+  return t-LocalTZA-DaylightSavingTA(t-LocalTZA);
+}
+
+//15.9.1.10 Hours, Minutes, Second, and Milliseconds
+function HourFromTime(t){
+  return Math.floor(t/msPerHour)%HoursPerDay;
+}
+
+function MinFromTime(t){
+  return Math.floor(t/msPerMinute)%MinutesPerHour;
+}
+
+function SecFromTime(t){
+  return Math.floor(t/msPerSecond)%SecondsPerMinute;
+}
+
+function msFromTime(t){
+  return t%msPerSecond;
+}
+
+//15.9.1.11 MakeTime (hour, min, sec, ms)
+function MakeTime(hour, min, sec, ms){
+  if ( !isFinite(hour) || !isFinite(min) || !isFinite(sec) || !isFinite(ms)) {
+    return Number.NaN;
+  }
+
+  hour = ToInteger(hour);
+  min  = ToInteger(min);
+  sec  = ToInteger(sec);
+  ms   = ToInteger(ms);
+
+  return ((hour*msPerHour) + (min*msPerMinute) + (sec*msPerSecond) + ms);
+}
+
+//15.9.1.12 MakeDay (year, month, date)
+function MakeDay(year, month, date) {
+  if ( !isFinite(year) || !isFinite(month) || !isFinite(date)) {
+    return Number.NaN;
+  }
+
+  year = ToInteger(year);
+  month = ToInteger(month);
+  date = ToInteger(date );
+
+  var result5 = year + Math.floor(month/12);
+  var result6 = month%12;
+
+  var sign = ( year < 1970 ) ? -1 : 1;
+  var t =    ( year < 1970 ) ? 1 :  0;
+  var y =    ( year < 1970 ) ? 1969 : 1970;
+
+  if( sign == -1 ){
+    for ( y = 1969; y >= year; y += sign ) {
+      t += sign * DaysInYear(y)*msPerDay;
+    }
+  } else {
+    for ( y = 1970 ; y < year; y += sign ) {
+      t += sign * DaysInYear(y)*msPerDay;
+    }
+  }
+
+  var leap = 0;
+  for ( var m = 0; m < month; m++ ) {
+    //if year is changed, than we need to recalculate leep
+    leap = InLeapYear(t);
+    t += DaysInMonth(m, leap)*msPerDay;
+  }
+
+  if ( YearFromTime(t) != result5 ) {
+    return Number.NaN;
+  }
+  if ( MonthFromTime(t) != result6 ) {
+    return Number.NaN;
+  }
+  if ( DateFromTime(t) != 1 ) {
+    return Number.NaN;
+  }
+
+  return Day(t)+date-1;
+}
+
+//15.9.1.13 MakeDate (day, time)
+function MakeDate( day, time ) {
+  if(!isFinite(day) || !isFinite(time)) {
+    return Number.NaN;
+  }
+
+  return day*msPerDay+time;
+}
+
+//15.9.1.14 TimeClip (time)
+function TimeClip(time) {
+  if(!isFinite(time) || Math.abs(time) > 8.64e15){
+    return Number.NaN;
+  }
+
+  return ToInteger(time);
+}
+
+//Test Functions
+//ConstructDate is considered deprecated, and should not be used directly from
+//test262 tests as it's incredibly sensitive to DST start/end dates that 
+//vary with geographic location.
+function ConstructDate(year, month, date, hours, minutes, seconds, ms){
+  /*
+   * 1. Call ToNumber(year)
+   * 2. Call ToNumber(month)
+   * 3. If date is supplied use ToNumber(date); else use 1
+   * 4. If hours is supplied use ToNumber(hours); else use 0
+   * 5. If minutes is supplied use ToNumber(minutes); else use 0
+   * 6. If seconds is supplied use ToNumber(seconds); else use 0
+   * 7. If ms is supplied use ToNumber(ms); else use 0
+   * 8. If Result(1) is not NaN and 0 <= ToInteger(Result(1)) <= 99, Result(8) is
+   * 1900+ToInteger(Result(1)); otherwise, Result(8) is Result(1)
+   * 9. Compute MakeDay(Result(8), Result(2), Result(3))
+   * 10. Compute MakeTime(Result(4), Result(5), Result(6), Result(7))
+   * 11. Compute MakeDate(Result(9), Result(10))
+   * 12. Set the [[Value]] property of the newly constructed object to TimeClip(UTC(Result(11)))
+   */
+  var r1 = Number(year);
+  var r2 = Number(month);
+  var r3 = ((date && arguments.length > 2) ? Number(date) : 1);
+  var r4 = ((hours && arguments.length > 3) ? Number(hours) : 0);
+  var r5 = ((minutes && arguments.length > 4) ? Number(minutes) : 0);
+  var r6 = ((seconds && arguments.length > 5) ? Number(seconds) : 0);
+  var r7 = ((ms && arguments.length > 6) ? Number(ms) : 0);
+
+  var r8 = r1;
+
+  if(!isNaN(r1) && (0 <= ToInteger(r1)) && (ToInteger(r1) <= 99))
+    r8 = 1900+r1;
+
+  var r9 = MakeDay(r8, r2, r3);
+  var r10 = MakeTime(r4, r5, r6, r7);
+  var r11 = MakeDate(r9, r10);
+
+  var retVal = TimeClip(UTC(r11));
+  return retVal;
+}
+
+
+
+/**** Python code for initialize the above constants
+// We may want to replicate the following in JavaScript.
+// However, using JS date operations to generate parameters that are then used to
+// test those some date operations seems unsound.  However, it isn't clear if there
+//is a good interoperable alternative.
+
+# Copyright 2009 the Sputnik authors.  All rights reserved.
+# This code is governed by the BSD license found in the LICENSE file.
+
+def GetDaylightSavingsTimes():
+# Is the given floating-point time in DST?
+def IsDst(t):
+return time.localtime(t)[-1]
+# Binary search to find an interval between the two times no greater than
+# delta where DST switches, returning the midpoint.
+def FindBetween(start, end, delta):
+while end - start > delta:
+middle = (end + start) / 2
+if IsDst(middle) == IsDst(start):
+start = middle
+else:
+end = middle
+return (start + end) / 2
+now = time.time()
+one_month = (30 * 24 * 60 * 60)
+# First find a date with different daylight savings.  To avoid corner cases
+# we try four months before and after today.
+after = now + 4 * one_month
+before = now - 4 * one_month
+if IsDst(now) == IsDst(before) and IsDst(now) == IsDst(after):
+logger.warning("Was unable to determine DST info.")
+return None
+# Determine when the change occurs between now and the date we just found
+# in a different DST.
+if IsDst(now) != IsDst(before):
+first = FindBetween(before, now, 1)
+else:
+first = FindBetween(now, after, 1)
+# Determine when the change occurs between three and nine months from the
+# first.
+second = FindBetween(first + 3 * one_month, first + 9 * one_month, 1)
+# Find out which switch is into and which if out of DST
+if IsDst(first - 1) and not IsDst(first + 1):
+start = second
+end = first
+else:
+start = first
+end = second
+return (start, end)
+
+
+def GetDaylightSavingsAttribs():
+times = GetDaylightSavingsTimes()
+if not times:
+return None
+(start, end) = times
+def DstMonth(t):
+return time.localtime(t)[1] - 1
+def DstHour(t):
+return time.localtime(t - 1)[3] + 1
+def DstSunday(t):
+if time.localtime(t)[2] > 15:
+return "'last'"
+else:
+return "'first'"
+def DstMinutes(t):
+return (time.localtime(t - 1)[4] + 1) % 60
+attribs = { }
+attribs['start_month'] = DstMonth(start)
+attribs['end_month'] = DstMonth(end)
+attribs['start_sunday'] = DstSunday(start)
+attribs['end_sunday'] = DstSunday(end)
+attribs['start_hour'] = DstHour(start)
+attribs['end_hour'] = DstHour(end)
+attribs['start_minutes'] = DstMinutes(start)
+attribs['end_minutes'] = DstMinutes(end)
+return attribs
+
+*********/
+
+//--Test case registration-----------------------------------------------------
+function runTestCase(testcase) {
+    if (testcase() !== true) {
+        $ERROR("Test case returned non-true value!");
+    }
+}
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/ch09/9.1/S9.1_A1_T1.js
@@ -0,0 +1,23 @@
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/**
+ * Result of primitive conversion from object is a default value for the Object
+ *
+ * @path ch09/9.1/S9.1_A1_T1.js
+ * @description Using operator Number. The operator calls ToPrimitive with hint Number
+ */
+
+// CHECK#1
+var object = {valueOf: function() {return "1"}, toString: function() {return 0}};
+if (Number(object) !== 1) {
+  $ERROR('#1: var object = {valueOf: function() {return "1"}, toString: function() {return 0}}; Number(object) === 1. Actual: ' + (Number(object)));
+}
+
+// CHECK#2
+var object = {valueOf: function() {return {}}, toString: function() {return "0"}};
+if (Number(object) !== 0) {
+  $ERROR('#2: var object = {valueOf: function() {return {}}, toString: function() {return "0"}}; Number(object) === 0. Actual: ' + (Number(object)));
+}
+
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/ch09/9.1/S9.1_A1_T2.js
@@ -0,0 +1,23 @@
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/**
+ * Result of primitive conversion from object is a default value for the Object
+ *
+ * @path ch09/9.1/S9.1_A1_T2.js
+ * @description Using operator Number. This operator calls ToPrimitive with hint Number
+ */
+
+// CHECK#1
+var object = {valueOf: function() {return 0}, toString: function() {return 1}};
+if (String(object) !== "1") {
+  $ERROR('#1: var object = {valueOf: function() {return 0}, toString: function() {return 1}}; String(object) === "1". Actual: ' + (String(object)));
+}
+
+// CHECK#2
+var object = {valueOf: function() {return 0}, toString: function() {return {}}};
+if (String(object) !== "0") {
+  $ERROR('#2: var object = {valueOf: function() {return 0}, toString: function() {return {}}}; String(object) === "0". Actual: ' + (String(object)));
+}
+
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/ch09/9.1/S9.1_A1_T3.js
@@ -0,0 +1,23 @@
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/**
+ * Result of primitive conversion from object is a default value for the Object
+ *
+ * @path ch09/9.1/S9.1_A1_T3.js
+ * @description Using operator "+". This operator firstly calls ToPrimitive and then calls ToString or ToNumber
+ */
+
+// CHECK#1
+var object = {valueOf: function() {return 1}, toString: function() {return 0}};
+if (object + "" !== "1") {
+  $ERROR('#1: var object = {valueOf: function() {return 1}, toString: function() {return 0}}; object + "" === "1". Actual: ' + (object + ""));
+}
+
+// CHECK#2
+var object = {valueOf: function() {return "1"}, toString: function() {return 0}};
+if (object + 0 !== "10") {
+  $ERROR('#2: var object = {valueOf: function() {return "1"}, toString: function() {return 0}}; object + 0 === "10". Actual: ' + (object + 0));
+}
+
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/ch09/9.1/S9.1_A1_T4.js
@@ -0,0 +1,23 @@
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/**
+ * Result of primitive conversion from object is a default value for the Object
+ *
+ * @path ch09/9.1/S9.1_A1_T4.js
+ * @description Using operator "<". The operator firstly calls ToPrimitive and then calls ToString or ToNumber
+ */
+
+// CHECK#1
+var object = {valueOf: function() {return -2}, toString: function() {return "-2"}};
+if ("-1" < object) {
+  $ERROR('#1: var object = {valueOf: function() {return -2}, toString: function() {return "-2"}}; "-1" < object');
+}
+
+// CHECK#2
+var object = {valueOf: function() {return "-2"}, toString: function() {return -2}};
+if (object < "-1") {
+  $ERROR('#2: var object = {valueOf: function() {return "-2"}, toString: function() {return -2}}; object < "-1"');
+}
+
+
new file mode 100644
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/ch09/9.2/S9.2_A1_T1.js
@@ -0,0 +1,30 @@
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/**
+ * Result of boolean conversion from undefined value is false
+ *
+ * @path ch09/9.2/S9.2_A1_T1.js
+ * @description Undefined, void and others are converted to Boolean by explicit transformation
+ */
+
+// CHECK#1
+if (Boolean(undefined) !== false) {
+  $ERROR('#1: Boolean(undefined) === false. Actual: ' + (Boolean(undefined)));
+}
+
+// CHECK#2
+if (Boolean(void 0) !== false) {
+  $ERROR('#2: Boolean(undefined) === false. Actual: ' + (Boolean(undefined)));
+}
+
+// CHECK#3
+if (Boolean(eval("var x")) !== false) {
+  $ERROR('#3: Boolean(eval("var x")) === false. Actual: ' + (Boolean(eval("var x"))));
+}
+
+// CHECK#4
+if (Boolean() !== false) {
+  $ERROR('#4: Boolean() === false. Actual: ' + (Boolean()));
+}
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/ch09/9.2/S9.2_A1_T2.js
@@ -0,0 +1,25 @@
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/**
+ * Result of boolean conversion from undefined value is false
+ *
+ * @path ch09/9.2/S9.2_A1_T2.js
+ * @description Undefined, void and others are converted to Boolean by implicit transformation
+ */
+
+// CHECK#1
+if (!(undefined) !== true) {
+  $ERROR('#1: !(undefined) === true. Actual: ' + (!(undefined)));
+}
+
+// CHECK#2
+if (!(void 0) !== true) {
+  $ERROR('#2: !(undefined) === true. Actual: ' + (!(undefined)));
+}
+
+// CHECK#3
+if (!(eval("var x")) !== true) {
+  $ERROR('#3: !(eval("var x")) === true. Actual: ' + (!(eval("var x"))));
+}
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/ch09/9.2/S9.2_A2_T1.js
@@ -0,0 +1,15 @@
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/**
+ * Result of boolean conversion from null value is false
+ *
+ * @path ch09/9.2/S9.2_A2_T1.js
+ * @description null convert to Boolean by explicit transformation
+ */
+
+// CHECK#1
+if (Boolean(null) !== false) {
+  $ERROR('#1: Boolean(null) === false. Actual: ' + (Boolean(null))); 
+}
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/ch09/9.2/S9.2_A2_T2.js
@@ -0,0 +1,15 @@
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/**
+ * Result of boolean conversion from null value is false
+ *
+ * @path ch09/9.2/S9.2_A2_T2.js
+ * @description null convert to Boolean by implicit transformation
+ */
+
+// CHECK#1
+if (!(null) !== true) {
+  $ERROR('#1: !(null) === true. Actual: ' + (!(null))); 
+}
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/ch09/9.2/S9.2_A3_T1.js
@@ -0,0 +1,20 @@
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/**
+ * Result of boolean conversion from boolean value is no conversion
+ *
+ * @path ch09/9.2/S9.2_A3_T1.js
+ * @description true and false convert to Boolean by explicit transformation
+ */
+
+// CHECK#1 
+if (Boolean(true) !== true) {
+  $ERROR('#1: Boolean(true) === true. Actual: ' + (Boolean(true)));	
+}
+
+// CHECK#2
+if (Boolean(false) !== false) {
+  $ERROR('#2: Boolean(false) === false. Actual: ' + (Boolean(false)));
+}
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/ch09/9.2/S9.2_A3_T2.js
@@ -0,0 +1,20 @@
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/**
+ * Result of boolean conversion from boolean value is no conversion
+ *
+ * @path ch09/9.2/S9.2_A3_T2.js
+ * @description true and false convert to Boolean by implicit transformation
+ */
+
+// CHECK#1 
+if (!(true) !== false) {
+  $ERROR('#1: !(true) === false. Actual: ' + (!(true)));	
+}
+
+// CHECK#2
+if (!(false) !== true) {
+  $ERROR('#2: !(false) === true. Actual: ' + (!(false)));
+}
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/ch09/9.2/S9.2_A4_T1.js
@@ -0,0 +1,25 @@
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/**
+ * Result of boolean conversion from number value is false if the argument is +0, -0, or NaN; otherwise, is true
+ *
+ * @path ch09/9.2/S9.2_A4_T1.js
+ * @description +0, -0 and NaN convert to Boolean by explicit transformation
+ */
+
+// CHECK#1
+if (Boolean(+0) !== false) {
+  $ERROR('#1: Boolean(+0) === false. Actual: ' + (Boolean(+0))); 	 
+}
+
+// CHECK#2
+if (Boolean(-0) !== false) {
+  $ERROR('#2: Boolean(-0) === false. Actual: ' + (Boolean(-0)));
+}
+
+// CHECK#3
+if (Boolean(Number.NaN) !== false) {
+  $ERROR('#3: Boolean(Number.NaN) === false. Actual: ' + (Boolean(Number.NaN)));
+}
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/ch09/9.2/S9.2_A4_T2.js
@@ -0,0 +1,25 @@
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/**
+ * Result of boolean conversion from number value is false if the argument is +0, -0, or NaN; otherwise, is true
+ *
+ * @path ch09/9.2/S9.2_A4_T2.js
+ * @description +0, -0 and NaN convert to Boolean by implicit transformation
+ */
+
+// CHECK#1
+if (!(+0) !== true) {
+  $ERROR('#1: !(+0) === true. Actual: ' + (!(+0))); 	 
+}
+
+// CHECK#2
+if (!(-0) !== true) {
+  $ERROR('#2: !(-0) === true. Actual: ' + (!(-0)));
+}
+
+// CHECK#3
+if (!(Number.NaN) !== true) {
+  $ERROR('#3: !(Number.NaN) === true. Actual: ' + (!(Number.NaN)));
+}
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/ch09/9.2/S9.2_A4_T3.js
@@ -0,0 +1,51 @@
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/**
+ * Result of boolean conversion from number value is false if the argument is +0, -0, or NaN; otherwise, is true
+ *
+ * @path ch09/9.2/S9.2_A4_T3.js
+ * @description Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY,
+ * Number.MAX_VALUE, Number.MIN_VALUE and some numbers convert to Boolean by explicit transformation
+ */
+
+// CHECK#1
+if (Boolean(Number.POSITIVE_INFINITY) !== true) {
+  $ERROR('#1: Boolean(+Infinity) === true. Actual: ' + (Boolean(+Infinity))); 	
+}
+
+// CHECK#2;
+if (Boolean(Number.NEGATIVE_INFINITY) !== true) {
+  $ERROR('#2: Boolean(-Infinity) === true. Actual: ' + (Boolean(-Infinity))); 	
+}
+
+// CHECK#3
+if (Boolean(Number.MAX_VALUE) !== true) {
+  $ERROR('#3: Boolean(Number.MAX_VALUE) === true. Actual: ' + (Boolean(Number.MAX_VALUE))); 	
+}
+
+// CHECK#4
+if (Boolean(Number.MIN_VALUE) !== true) {
+  $ERROR('#4: Boolean(Number.MIN_VALUE) === true. Actual: ' + (Boolean(Number.MIN_VALUE))); 	
+}
+
+// CHECK#5
+if (Boolean(13) !== true) {
+  $ERROR('#5: Boolean(13) === true. Actual: ' + (Boolean(13)));	
+}
+
+// CHECK#6
+if (Boolean(-13) !== true) {
+  $ERROR('#6: Boolean(-13) === true. Actual: ' + (Boolean(-13)));	
+}
+
+// CHECK#7
+if (Boolean(1.3) !== true) {
+  $ERROR('#7: Boolean(1.3) === true. Actual: ' + (Boolean(1.3)));	
+}
+
+// CHECK#8
+if (Boolean(-1.3) !== true) {
+  $ERROR('#8: Boolean(-1.3) === true. Actual: ' + (Boolean(-1.3)));	
+}	
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/ch09/9.2/S9.2_A4_T4.js
@@ -0,0 +1,51 @@
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/**
+ * Result of boolean conversion from number value is false if the argument is +0, -0, or NaN; otherwise, is true
+ *
+ * @path ch09/9.2/S9.2_A4_T4.js
+ * @description Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY,
+ * Number.MAX_VALUE, Number.MIN_VALUE and some other numbers are converted to Boolean by implicit transformation
+ */
+
+// CHECK#1
+if (!(Number.POSITIVE_INFINITY) !== false) {
+  $ERROR('#1: !(+Infinity) === false. Actual: ' + (!(+Infinity))); 	
+}
+
+// CHECK#2;
+if (!(Number.NEGATIVE_INFINITY) !== false) {
+  $ERROR('#2: !(-Infinity) === false. Actual: ' + (!(-Infinity))); 	
+}
+
+// CHECK#3
+if (!(Number.MAX_VALUE) !== false) {
+  $ERROR('#3: !(Number.MAX_VALUE) === false. Actual: ' + (!(Number.MAX_VALUE))); 	
+}
+
+// CHECK#4
+if (!(Number.MIN_VALUE) !== false) {
+  $ERROR('#4: !(Number.MIN_VALUE) === false. Actual: ' + (!(Number.MIN_VALUE))); 	
+}
+
+// CHECK#5
+if (!(13) !== false) {
+  $ERROR('#5: !(13) === false. Actual: ' + (!(13)));	
+}
+
+// CHECK#6
+if (!(-13) !== false) {
+  $ERROR('#6: !(-13) === false. Actual: ' + (!(-13)));	
+}
+
+// CHECK#7
+if (!(1.3) !== false) {
+  $ERROR('#7: !(1.3) === false. Actual: ' + (!(1.3)));	
+}
+
+// CHECK#8
+if (!(-1.3) !== false) {
+  $ERROR('#8: !(-1.3) === false. Actual: ' + (!(-1.3)));	
+}	
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/ch09/9.2/S9.2_A5_T1.js
@@ -0,0 +1,15 @@
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/**
+ * Result of boolean conversion from nonempty string value (length is not zero) is true; from empty String (length is zero) is false
+ *
+ * @path ch09/9.2/S9.2_A5_T1.js
+ * @description "" is converted to Boolean by explicit transformation
+ */
+
+// CHECK#1
+if (Boolean("") !== false) {
+  $ERROR('#1: Boolean("") === false. Actual: ' + (Boolean("")));
+}
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/ch09/9.2/S9.2_A5_T2.js
@@ -0,0 +1,15 @@
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/**
+ * Result of boolean conversion from nonempty string value (length is not zero) is true; from empty String (length is zero) is false
+ *
+ * @path ch09/9.2/S9.2_A5_T2.js
+ * @description "" convert to Boolean by implicit transformation
+ */
+
+// CHECK#1
+if (!("") !== true) {
+  $ERROR('#1: !("") === true. Actual: ' + (!("")));
+}
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/ch09/9.2/S9.2_A5_T3.js
@@ -0,0 +1,20 @@
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/**
+ * Result of boolean conversion from nonempty string value (length is not zero) is true; from empty String (length is zero) is false
+ *
+ * @path ch09/9.2/S9.2_A5_T3.js
+ * @description Any nonempty string convert to Boolean by explicit transformation
+ */
+
+// CHECK#1
+if (Boolean(" ") !== true) {
+  $ERROR('#1: Boolean(" ") === true. Actual: ' + (Boolean(" ")));	
+}
+
+// CHECK#2
+if (Boolean("Nonempty String") !== true) {
+  $ERROR('#2: Boolean("Nonempty String") === true. Actual: ' + (Boolean("Nonempty String")));	
+}
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/ch09/9.2/S9.2_A5_T4.js
@@ -0,0 +1,20 @@
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/**
+ * Result of boolean conversion from nonempty string value (length is not zero) is true; from empty String (length is zero) is false
+ *
+ * @path ch09/9.2/S9.2_A5_T4.js
+ * @description Any nonempty string convert to Boolean by implicit transformation
+ */
+
+// CHECK#1
+if (!(" ") !== false) {
+  $ERROR('#1: !(" ") === false. Actual: ' + (!(" ")));	
+}
+
+// CHECK#2
+if (!("Nonempty String") !== false) {
+  $ERROR('#2: !("Nonempty String") === false. Actual: ' + (!("Nonempty String")));	
+}
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/ch09/9.2/S9.2_A6_T1.js
@@ -0,0 +1,105 @@
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/**
+ * Result of boolean conversion from object is true
+ *
+ * @path ch09/9.2/S9.2_A6_T1.js
+ * @description Different objects convert to Boolean by explicit transformation
+ */
+
+// CHECK#1
+if (Boolean(new Object()) !== true) {
+  $ERROR('#1: Boolean(new Object()) === true. Actual: ' + (Boolean(new Object())));	
+}
+
+// CHECK#2
+if (Boolean(new String("")) !== true) {
+  $ERROR('#2: Boolean(new String("")) === true. Actual: ' + (Boolean(new String(""))));	
+}
+
+// CHECK#3
+if (Boolean(new String()) !== true) {
+  $ERROR('#3: Boolean(new String()) === true. Actual: ' + (Boolean(new String())));	
+}
+
+// CHECK#4
+if (Boolean(new Boolean(true)) !== true) {
+  $ERROR('#4: Boolean(new Boolean(true)) === true. Actual: ' + (Boolean(new Boolean(true))));	
+}
+
+// CHECK#5
+if (Boolean(new Boolean(false)) !== true) {
+  $ERROR('#5: Boolean(new Boolean(false)) === true. Actual: ' + (Boolean(new Boolean(false))));	
+}
+
+// CHECK#6
+if (Boolean(new Boolean()) !== true) {
+  $ERROR('#6: Boolean(new Boolean()) === true. Actual: ' + (Boolean(new Boolean())));	
+}
+
+// CHECK#7
+if (Boolean(new Array()) !== true) {
+  $ERROR('#7: Boolean(new Array()) === true. Actual: ' + (Boolean(new Array())));	
+}
+
+// CHECK#8
+if (Boolean(new Number()) !== true) {
+  $ERROR('#8: Boolean(new Number()) === true. Actual: ' + (Boolean(new Number())));	
+}
+
+// CHECK#9
+if (Boolean(new Number(-0)) !== true) {
+  $ERROR('#9: Boolean(new Number(-0)) === true. Actual: ' + (Boolean(new Number(-0))));	
+}
+
+// CHECK#10
+if (Boolean(new Number(0)) !== true) {
+  $ERROR('#10: Boolean(new Number(0)) === true. Actual: ' + (Boolean(new Number(0))));	
+}
+
+// CHECK#11
+if (Boolean(new Number()) !== true) {
+  $ERROR('#11: Boolean(new Number()) === true. Actual: ' + (Boolean(new Number())));	
+}
+
+// CHECK#12
+if (Boolean(new Number(Number.NaN)) !== true) {
+  $ERROR('#12: Boolean(new Number(Number.NaN)) === true. Actual: ' + (Boolean(new Number(Number.NaN))));	
+}
+
+// CHECK#13
+if (Boolean(new Number(-1)) !== true) {
+  $ERROR('#13: Boolean(new Number(-1)) === true. Actual: ' + (Boolean(new Number(-1))));	
+}
+
+// CHECK#14
+if (Boolean(new Number(1)) !== true) {
+  $ERROR('#14: Boolean(new Number(1)) === true. Actual: ' + (Boolean(new Number(1))));	
+}
+
+// CHECK#15
+if (Boolean(new Number(Number.POSITIVE_INFINITY)) !== true) {
+  $ERROR('#15: Boolean(new Number(Number.POSITIVE_INFINITY)) === true. Actual: ' + (Boolean(new Number(Number.POSITIVE_INFINITY))));	
+}
+
+// CHECK#16
+if (Boolean(new Number(Number.NEGATIVE_INFINITY)) !== true) {
+  $ERROR('#16: Boolean(new Number(Number.NEGATIVE_INFINITY)) === true. Actual: ' + (Boolean(new Number(Number.NEGATIVE_INFINITY))));	
+}
+
+// CHECK#17
+if (Boolean(new Function()) !== true) {
+  $ERROR('#17: Boolean(new Function()) === true. Actual: ' + (Boolean(new Function())));	
+}
+
+// CHECK#18
+if (Boolean(new Date()) !== true) {
+  $ERROR('#18: Boolean(new Date()) === true. Actual: ' + (Boolean(new Date())));	
+}
+
+// CHECK#19
+if (Boolean(new Date(0)) !== true) {
+  $ERROR('#19: Boolean(new Date(0)) === true. Actual: ' + (Boolean(new Date(0))));	
+}
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/ch09/9.2/S9.2_A6_T2.js
@@ -0,0 +1,105 @@
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/**
+ * Result of boolean conversion from object is true
+ *
+ * @path ch09/9.2/S9.2_A6_T2.js
+ * @description Different objects convert to Boolean by implicit transformation
+ */
+
+// CHECK#1
+if (!(new Object()) !== false) {
+  $ERROR('#1: !(new Object()) === false. Actual: ' + (!(new Object())));	
+}
+
+// CHECK#2
+if (!(new String("")) !== false) {
+  $ERROR('#2: !(new String("")) === false. Actual: ' + (!(new String(""))));	
+}
+
+// CHECK#3
+if (!(new String()) !== false) {
+  $ERROR('#3: !(new String()) === false. Actual: ' + (!(new String())));	
+}
+
+// CHECK#4
+if (!(new Boolean(true)) !== false) {
+  $ERROR('#4: !(new Boolean(true)) === false. Actual: ' + (!(new Boolean(true))));	
+}
+
+// CHECK#5
+if (!(new Boolean(false)) !== false) {
+  $ERROR('#5: !(new Boolean(false)) === false. Actual: ' + (!(new Boolean(false))));	
+}
+
+// CHECK#6
+if (!(new Boolean()) !== false) {
+  $ERROR('#6: !(new Boolean()) === false. Actual: ' + (!(new Boolean())));	
+}
+
+// CHECK#7
+if (!(new Array()) !== false) {
+  $ERROR('#7: !(new Array()) === false. Actual: ' + (!(new Array())));	
+}
+
+// CHECK#8
+if (!(new Number()) !== false) {
+  $ERROR('#8: !(new Number()) === false. Actual: ' + (!(new Number())));	
+}
+
+// CHECK#9
+if (!(new Number(-0)) !== false) {
+  $ERROR('#9: !(new Number(-0)) === false. Actual: ' + (!(new Number(-0))));	
+}
+
+// CHECK#10
+if (!(new Number(0)) !== false) {
+  $ERROR('#10: !(new Number(0)) === false. Actual: ' + (!(new Number(0))));	
+}
+
+// CHECK#11
+if (!(new Number()) !== false) {
+  $ERROR('#11: !(new Number()) === false. Actual: ' + (!(new Number())));	
+}
+
+// CHECK#12
+if (!(new Number(Number.NaN)) !== false) {
+  $ERROR('#12: !(new Number(Number.NaN)) === false. Actual: ' + (!(new Number(Number.NaN))));	
+}
+
+// CHECK#13
+if (!(new Number(-1)) !== false) {
+  $ERROR('#13: !(new Number(-1)) === false. Actual: ' + (!(new Number(-1))));	
+}
+
+// CHECK#14
+if (!(new Number(1)) !== false) {
+  $ERROR('#14: !(new Number(1)) === false. Actual: ' + (!(new Number(1))));	
+}
+
+// CHECK#15
+if (!(new Number(Number.POSITIVE_INFINITY)) !== false) {
+  $ERROR('#15: !(new Number(Number.POSITIVE_INFINITY)) === false. Actual: ' + (!(new Number(Number.POSITIVE_INFINITY))));	
+}
+
+// CHECK#16
+if (!(new Number(Number.NEGATIVE_INFINITY)) !== false) {
+  $ERROR('#16: !(new Number(Number.NEGATIVE_INFINITY)) === false. Actual: ' + (!(new Number(Number.NEGATIVE_INFINITY))));	
+}
+
+// CHECK#17
+if (!(new Function()) !== false) {
+  $ERROR('#17: !(new Function()) === false. Actual: ' + (!(new Function())));	
+}
+
+// CHECK#18
+if (!(new Date()) !== false) {
+  $ERROR('#18: !(new Date()) === false. Actual: ' + (!(new Date())));	
+}
+
+// CHECK#19
+if (!(new Date(0)) !== false) {
+  $ERROR('#19: !(new Date(0)) === false. Actual: ' + (!(new Date(0))));	
+}
+
new file mode 100644
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/ch09/9.3/9.3.1/S9.3.1_A1.js
@@ -0,0 +1,19 @@
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/**
+ * The MV of StringNumericLiteral ::: [empty] is 0
+ *
+ * @path ch09/9.3/9.3.1/S9.3.1_A1.js
+ * @description Number('') convert to Number by explicit transformation
+ */
+
+// CHECK#1
+if (Number("") !== 0) {
+  $ERROR('#1.1: Number("") === 0. Actual: ' + (Number("")));
+} else {
+  if (1/Number("") !== Number.POSITIVE_INFINITY) {
+    $ERROR('#1.2: Number("") == +0. Actual: -0');
+  }
+}
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/ch09/9.3/9.3.1/S9.3.1_A10.js
@@ -0,0 +1,17 @@
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/**
+ * The MV of StrUnsignedDecimalLiteral:::. DecimalDigits is the
+ * MV of DecimalDigits times 10<sup><small>-n</small></sup>, where n is the
+ * number of characters in DecimalDigits
+ *
+ * @path ch09/9.3/9.3.1/S9.3.1_A10.js
+ * @description Compare Number('.12345') with +('12345')*1e-5
+ */
+
+// CHECK#1
+if (Number(".12345") !== +("12345")*1e-5) {
+  $ERROR('#1: Number(".12345") === +("12345")*1e-5');
+}
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/ch09/9.3/9.3.1/S9.3.1_A11.js
@@ -0,0 +1,23 @@
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/**
+ * The MV of StrUnsignedDecimalLiteral:::. DecimalDigits ExponentPart
+ * is the MV of DecimalDigits times 10<sup><small>e-n</small></sup>, where n is
+ * the number of characters in DecimalDigits and e is the MV of ExponentPart
+ *
+ * @path ch09/9.3/9.3.1/S9.3.1_A11.js
+ * @description Compare Number('.12345e6') with +('12345')*1e1,
+ * and Number('.12345e-3') !== Number('12345')*1e-8
+ */
+
+// CHECK#1
+if (Number(".12345e6") !== +("12345")*1e1)  {
+  $ERROR('#1: Number(".12345e6") === +("12345")*1e1');
+}
+
+// CHECK#2
+if (Number(".12345e-3") !== Number("12345")*1e-8)  {
+  $ERROR('#2: Number(".12345e-3") === Number("12345")*1e-8');
+}
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/ch09/9.3/9.3.1/S9.3.1_A12.js
@@ -0,0 +1,22 @@
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/**
+ * The MV of StrUnsignedDecimalLiteral::: DecimalDigits ExponentPart
+ * is the MV of DecimalDigits times 10<sup><small>e</small></sup>, where e is the MV of ExponentPart
+ *
+ * @path ch09/9.3/9.3.1/S9.3.1_A12.js
+ * @description Compare Number('12345e6') with +('12345')*1e1,
+ * and Number('12345e-6') !== Number('12345')*1e-6
+ */
+
+// CHECK#1
+if (Number("12345e6") !== +("12345")*1e6)  {
+  $ERROR('#1: Number("12345e6") === +("12345")*1e6');
+}
+
+// CHECK#2
+if (Number("12345e-6") !== Number("12345")*1e-6)  {
+  $ERROR('#2: Number("12345e-6") === Number("12345")*1e-6');
+}
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/ch09/9.3/9.3.1/S9.3.1_A13.js
@@ -0,0 +1,26 @@
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/**
+ * The MV of DecimalDigits ::: DecimalDigits DecimalDigit is
+ * (the MV of DecimalDigits times 10) plus the MV of DecimalDigit
+ *
+ * @path ch09/9.3/9.3.1/S9.3.1_A13.js
+ * @description Compare '12' with Number("1")*10+Number("2") and analogous
+ */
+
+// CHECK#1
+if (+("12") !== Number("1")*10+Number("2"))  {
+  $ERROR('#1: +("12") === Number("1")*10+Number("2")');
+}
+
+// CHECK#2
+if (Number("123") !== Number("12")*10+Number("3"))  {
+  $ERROR('#2: Number("123") === Number("12")*10+Number("3")');
+}
+
+// CHECK#2
+if (Number("1234") !== Number("123")*10+Number("4"))  {
+  $ERROR('#2: Number("1234") === Number("123")*10+Number("4")');
+}
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/ch09/9.3/9.3.1/S9.3.1_A14.js
@@ -0,0 +1,15 @@
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/**
+ * The MV of SignedInteger ::: + DecimalDigits is the MV of DecimalDigits
+ *
+ * @path ch09/9.3/9.3.1/S9.3.1_A14.js
+ * @description Compare Number('+1234567890') with +('1234567890')
+ */
+
+// CHECK#1
+if (Number("+1234567890") !== +("1234567890"))  {
+  $ERROR('#1: Number("+1234567890") === +("1234567890")');
+}
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/ch09/9.3/9.3.1/S9.3.1_A15.js
@@ -0,0 +1,15 @@
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/**
+ * The MV of SignedInteger ::: - DecimalDigits is the negative of the MV of DecimalDigits
+ *
+ * @path ch09/9.3/9.3.1/S9.3.1_A15.js
+ * @description Compare -Number('1234567890') with ('-1234567890')
+ */
+
+// CHECK#1
+if (+("-1234567890") !== -Number("1234567890"))  {
+  $ERROR('#1: +("-1234567890") === -Number("1234567890")');
+}
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/ch09/9.3/9.3.1/S9.3.1_A16.js
@@ -0,0 +1,25 @@
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/**
+ * The MV of DecimalDigit ::: 0 or of HexDigit ::: 0 is 0
+ *
+ * @path ch09/9.3/9.3.1/S9.3.1_A16.js
+ * @description Compare Number('0x0') and Number('0X0') with 0
+ */
+
+// CHECK#1
+if (Number("0") !== 0) {
+  $ERROR('#1: Number("0") === 0. Actual: ' + (Number("0")));
+}
+
+// CHECK#2
+if (+("0x0") !== 0) {
+  $ERROR('#2: +("0x0") === 0. Actual: ' + (+("0x0")));
+}
+
+// CHECK#3
+if (Number("0X0") !== 0) {
+  $ERROR('#3: Number("0X0") === 0. Actual: ' + (Number("0X0")));
+}
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/ch09/9.3/9.3.1/S9.3.1_A17.js
@@ -0,0 +1,25 @@
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/**
+ * The MV of DecimalDigit ::: 1 or of HexDigit ::: 1 is 1
+ *
+ * @path ch09/9.3/9.3.1/S9.3.1_A17.js
+ * @description Compare Number('0x1') and Number('0X1') with 1
+ */
+
+// CHECK#1
+if (Number("1") !== 1)  {
+  $ERROR('#1: Number("1") === 1. Actual: ' + (Number("1")));
+}
+
+// CHECK#2
+if (Number("0x1") !== 1)  {
+  $ERROR('#2: Number("0x1") === 1. Actual: ' + (Number("0x1")));
+}
+
+// CHECK#3
+if (+("0X1") !== 1)  {
+  $ERROR('#3: +("0X1") === 1. Actual: ' + (+("0X1")));
+}
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/ch09/9.3/9.3.1/S9.3.1_A18.js
@@ -0,0 +1,25 @@
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/**
+ * The MV of DecimalDigit ::: 2 or of HexDigit ::: 2 is 2
+ *
+ * @path ch09/9.3/9.3.1/S9.3.1_A18.js
+ * @description Compare Number('0x2') and Number('0X2') with 2
+ */
+
+// CHECK#1
+if (+("2") !== 2)  {
+  $ERROR('#1: +("2") === 2. Actual: ' + (+("2")));
+}
+
+// CHECK#2
+if (Number("0x2") !== 2)  {
+  $ERROR('#2: Number("0x2") === 2. Actual: ' + (Number("0x2")));
+}
+
+// CHECK#3
+if (Number("0X2") !== 2)  {
+  $ERROR('#3: Number("0X2") === 2. Actual: ' + (Number("0X2")));
+}
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/ch09/9.3/9.3.1/S9.3.1_A19.js
@@ -0,0 +1,25 @@
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/**
+ * The MV of DecimalDigit ::: 3 or of HexDigit ::: 3 is 3
+ *
+ * @path ch09/9.3/9.3.1/S9.3.1_A19.js
+ * @description Compare Number('0x3') and Number('0X3') with 3
+ */
+
+// CHECK#1
+if (Number("3") !== 3)  {
+  $ERROR('#1: Number("3") === 3. Actual: ' + (Number("3")));
+}
+
+// CHECK#2
+if (+("0x3") !== 3)  {
+  $ERROR('#2: +("0x3") === 3. Actual: ' + (+("0x3")));
+}
+
+// CHECK#3
+if (Number("0X3") !== 3)  {
+  $ERROR('#3: Number("0X3") === 3. Actual: ' + (Number("0X3")));
+}
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/test262/ch09/9.3/9.3.1/S9.3.1_A2.js
@@ -0,0 +1,289 @@
+// Copyright 2009 the Sputnik authors.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/**
+ * The MV of StringNumericLiteral ::: StrWhiteSpace is 0
+ *
+ * @path ch09/9.3/9.3.1/S9.3.1_A2.js
+ * @description Strings with various WhiteSpaces convert to Number by explicit transformation
+ */
+
+// CHECK#1
+if (Number("\u0009\u000C\u0020\u00A0\u000B\u000A\u000D\u2028\u2029\u