Bug 897655 - Use off thread parsing when loading scripts from XUL documents, r=billm,bz,luke.
authorBrian Hackett <bhackett1024@gmail.com>
Thu, 15 Aug 2013 14:14:43 -0700
changeset 155705 b5e301863e69b7d0228339e56d9889306292e0e7
parent 155704 d52251e9123c7c99315437e8550144b63561d891
child 155706 4cfdb0d28f20ec2f50b60fc82ae43c53df879f61
push id2961
push userlsblakk@mozilla.com
push dateMon, 28 Oct 2013 21:59:28 +0000
treeherdermozilla-beta@73ef4f13486f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbillm, bz, luke
bugs897655
milestone26.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
Bug 897655 - Use off thread parsing when loading scripts from XUL documents, r=billm,bz,luke.
content/xul/content/src/nsXULElement.cpp
content/xul/content/src/nsXULElement.h
content/xul/document/src/XULDocument.cpp
content/xul/document/src/XULDocument.h
content/xul/document/src/nsXULPrototypeCache.cpp
dom/base/nsIScriptContext.h
dom/tests/mochitest/ajax/offline/Makefile.in
js/src/frontend/BytecodeCompiler.cpp
js/src/frontend/FoldConstants.cpp
js/src/frontend/Parser.cpp
js/src/gc/Zone.cpp
js/src/jit/VMFunctions.h
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jsatom.cpp
js/src/jscntxt.h
js/src/jscntxtinlines.h
js/src/jscompartment.cpp
js/src/jscompartment.h
js/src/jsgc.cpp
js/src/jsgc.h
js/src/jsobj.cpp
js/src/jsobj.h
js/src/jspubtd.h
js/src/jsworkers.cpp
js/src/jsworkers.h
js/src/shell/js.cpp
js/src/vm/RegExpObject.cpp
js/src/vm/RegExpObject.h
js/src/vm/Runtime.cpp
js/src/vm/Runtime.h
js/src/vm/Shape.h
js/src/vm/String.cpp
js/src/vm/String.h
--- a/content/xul/content/src/nsXULElement.cpp
+++ b/content/xul/content/src/nsXULElement.cpp
@@ -2543,23 +2543,66 @@ nsXULPrototypeScript::DeserializeOutOfLi
                 if (rv != NS_ERROR_NOT_AVAILABLE)
                     cache->AbortCaching();
             }
         }
     }
     return rv;
 }
 
+class NotifyOffThreadScriptCompletedRunnable : public nsRunnable
+{
+    nsRefPtr<nsIOffThreadScriptReceiver> mReceiver;
+
+    // Note: there is no need to root the script, it is protected against GC
+    // until FinishOffThreadScript is called on it.
+    JSScript *mScript;
+
+public:
+    NotifyOffThreadScriptCompletedRunnable(already_AddRefed<nsIOffThreadScriptReceiver> aReceiver,
+                                           JSScript *aScript)
+      : mReceiver(aReceiver), mScript(aScript)
+    {}
+
+    NS_DECL_NSIRUNNABLE
+};
+
+NS_IMETHODIMP
+NotifyOffThreadScriptCompletedRunnable::Run()
+{
+    MOZ_ASSERT(NS_IsMainThread());
+
+    // Note: this unroots mScript so that it is available to be collected by the
+    // JS GC. The receiver needs to root the script before performing a call that
+    // could GC.
+    JS::FinishOffThreadScript(nsJSRuntime::sRuntime, mScript);
+
+    return mReceiver->OnScriptCompileComplete(mScript, mScript ? NS_OK : NS_ERROR_FAILURE);
+}
+
+static void
+OffThreadScriptReceiverCallback(JSScript *script, void *ptr)
+{
+    // Be careful not to adjust the refcount on the receiver, as this callback
+    // may be invoked off the main thread.
+    nsIOffThreadScriptReceiver* aReceiver = static_cast<nsIOffThreadScriptReceiver*>(ptr);
+    nsRefPtr<NotifyOffThreadScriptCompletedRunnable> notify =
+        new NotifyOffThreadScriptCompletedRunnable(
+            already_AddRefed<nsIOffThreadScriptReceiver>(aReceiver), script);
+    NS_DispatchToMainThread(notify);
+}
+
 nsresult
 nsXULPrototypeScript::Compile(const PRUnichar* aText,
                               int32_t aTextLength,
                               nsIURI* aURI,
                               uint32_t aLineNo,
                               nsIDocument* aDocument,
-                              nsIScriptGlobalObject* aGlobal)
+                              nsIScriptGlobalObject* aGlobal,
+                              nsIOffThreadScriptReceiver *aOffThreadReceiver /* = nullptr */)
 {
     // We'll compile the script using the prototype document's special
     // script object as the parent. This ensures that we won't end up
     // with an uncollectable reference.
     //
     // Compiling it using (for example) the first document's global
     // object would cause JS to keep a reference via the __proto__ or
     // parent pointer to the first document's global. If that happened,
@@ -2599,21 +2642,33 @@ nsXULPrototypeScript::Compile(const PRUn
            .setVersion(JSVersion(mLangVersion));
     // If the script was inline, tell the JS parser to save source for
     // Function.prototype.toSource(). If it's out of line, we retrieve the
     // source from the files on demand.
     options.setSourcePolicy(mOutOfLine ? JS::CompileOptions::LAZY_SOURCE
                                        : JS::CompileOptions::SAVE_SOURCE);
     JS::RootedObject scope(cx, JS::CurrentGlobalOrNull(cx));
     xpc_UnmarkGrayObject(scope);
-    JSScript* script = JS::Compile(cx, scope, options,
+
+    if (aOffThreadReceiver && JS::CanCompileOffThread(cx, options)) {
+        if (!JS::CompileOffThread(cx, scope, options,
+                                  static_cast<const jschar*>(aText), aTextLength,
+                                  OffThreadScriptReceiverCallback,
+                                  static_cast<void*>(aOffThreadReceiver))) {
+            return NS_ERROR_OUT_OF_MEMORY;
+        }
+        // This reference will be consumed by the NotifyOffThreadScriptCompletedRunnable.
+        NS_ADDREF(aOffThreadReceiver);
+    } else {
+        JSScript* script = JS::Compile(cx, scope, options,
                                    static_cast<const jschar*>(aText), aTextLength);
-    if (!script)
-        return NS_ERROR_OUT_OF_MEMORY;
-    Set(script);
+        if (!script)
+            return NS_ERROR_OUT_OF_MEMORY;
+        Set(script);
+    }
     return NS_OK;
 }
 
 void
 nsXULPrototypeScript::UnlinkJSObjects()
 {
     if (mScriptObject) {
         mScriptObject = nullptr;
--- a/content/xul/content/src/nsXULElement.h
+++ b/content/xul/content/src/nsXULElement.h
@@ -226,17 +226,18 @@ public:
                                  nsIURI* aDocumentURI,
                                  const nsCOMArray<nsINodeInfo> *aNodeInfos) MOZ_OVERRIDE;
     nsresult DeserializeOutOfLine(nsIObjectInputStream* aInput,
                                   nsIScriptGlobalObject* aGlobal);
 
     nsresult Compile(const PRUnichar* aText, int32_t aTextLength,
                      nsIURI* aURI, uint32_t aLineNo,
                      nsIDocument* aDocument,
-                     nsIScriptGlobalObject* aGlobal);
+                     nsIScriptGlobalObject* aGlobal,
+                     nsIOffThreadScriptReceiver *aOffThreadReceiver = nullptr);
 
     void UnlinkJSObjects();
 
     void Set(JSScript* aObject);
 
     // It's safe to return a handle because we trace mScriptObject, no one ever
     // uses the handle (or the script object) past the point at which the
     // nsXULPrototypeScript dies, and we can't get memmoved so the
--- a/content/xul/document/src/XULDocument.cpp
+++ b/content/xul/document/src/XULDocument.cpp
@@ -349,19 +349,19 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_IN
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_ADDREF_INHERITED(XULDocument, XMLDocument)
 NS_IMPL_RELEASE_INHERITED(XULDocument, XMLDocument)
 
 
 // QueryInterface implementation for XULDocument
 NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(XULDocument)
-    NS_INTERFACE_TABLE_INHERITED4(XULDocument, nsIXULDocument,
+    NS_INTERFACE_TABLE_INHERITED5(XULDocument, nsIXULDocument,
                                   nsIDOMXULDocument, nsIStreamLoaderObserver,
-                                  nsICSSLoaderObserver)
+                                  nsICSSLoaderObserver, nsIOffThreadScriptReceiver)
 NS_INTERFACE_TABLE_TAIL_INHERITING(XMLDocument)
 
 
 //----------------------------------------------------------------------
 //
 // nsIDocument interface
 //
 
@@ -3460,17 +3460,16 @@ XULDocument::LoadScript(nsXULPrototypeSc
         aScriptProto->mSrcLoading = true;
     }
 
     // Block until OnStreamComplete resumes us.
     *aBlock = true;
     return NS_OK;
 }
 
-
 NS_IMETHODIMP
 XULDocument::OnStreamComplete(nsIStreamLoader* aLoader,
                               nsISupports* context,
                               nsresult aStatus,
                               uint32_t stringLen,
                               const uint8_t* string)
 {
     nsCOMPtr<nsIRequest> request;
@@ -3500,107 +3499,141 @@ XULDocument::OnStreamComplete(nsIStreamL
 
     NS_ASSERTION(mCurrentScriptProto && mCurrentScriptProto->mSrcLoading,
                  "script source not loading on unichar stream complete?");
     if (!mCurrentScriptProto) {
         // XXX Wallpaper for bug 270042
         return NS_OK;
     }
 
-    // Clear mCurrentScriptProto now, but save it first for use below in
-    // the compile/execute code, and in the while loop that resumes walks
-    // of other documents that raced to load this script
-    nsXULPrototypeScript* scriptProto = mCurrentScriptProto;
-    mCurrentScriptProto = nullptr;
-
-    // Clear the prototype's loading flag before executing the script or
-    // resuming document walks, in case any of those control flows starts a
-    // new script load.
-    scriptProto->mSrcLoading = false;
-
     if (NS_SUCCEEDED(aStatus)) {
         // If the including XUL document is a FastLoad document, and we're
         // compiling an out-of-line script (one with src=...), then we must
         // be writing a new FastLoad file.  If we were reading this script
         // from the FastLoad file, XULContentSinkImpl::OpenScript (over in
         // nsXULContentSink.cpp) would have already deserialized a non-null
         // script->mScriptObject, causing control flow at the top of LoadScript
         // not to reach here.
-        nsCOMPtr<nsIURI> uri = scriptProto->mSrcURI;
+        nsCOMPtr<nsIURI> uri = mCurrentScriptProto->mSrcURI;
 
         // XXX should also check nsIHttpChannel::requestSucceeded
 
-        nsString stringStr;
+        MOZ_ASSERT(!mOffThreadCompiling && mOffThreadCompileString.Length() == 0,
+                   "XULDocument can't load multiple scripts at once");
+
         rv = nsScriptLoader::ConvertToUTF16(channel, string, stringLen,
-                                            EmptyString(), this, stringStr);
-        if (NS_SUCCEEDED(rv)) {
-            rv = scriptProto->Compile(stringStr.get(), stringStr.Length(),
-                                      uri, 1, this,
-                                      mCurrentPrototype->GetScriptGlobalObject());
-        }
-
-        aStatus = rv;
+                                            EmptyString(), this, mOffThreadCompileString);
         if (NS_SUCCEEDED(rv)) {
-            rv = ExecuteScript(scriptProto);
-
-            // If the XUL cache is enabled, save the script object there in
-            // case different XUL documents source the same script.
-            //
-            // But don't save the script in the cache unless the master XUL
-            // document URL is a chrome: URL.  It is valid for a URL such as
-            // about:config to translate into a master document URL, whose
-            // prototype document nodes -- including prototype scripts that
-            // hold GC roots protecting their mJSObject pointers -- are not
-            // cached in the XUL prototype cache.  See StartDocumentLoad,
-            // the fillXULCache logic.
-            //
-            // A document such as about:config is free to load a script via
-            // a URL such as chrome://global/content/config.js, and we must
-            // not cache that script object without a prototype cache entry
-            // containing a companion nsXULPrototypeScript node that owns a
-            // GC root protecting the script object.  Otherwise, the script
-            // cache entry will dangle once the uncached prototype document
-            // is released when its owning XULDocument is unloaded.
+            rv = mCurrentScriptProto->Compile(mOffThreadCompileString.get(),
+                                              mOffThreadCompileString.Length(),
+                                              uri, 1, this,
+                                              mCurrentPrototype->GetScriptGlobalObject(),
+                                              this);
+            if (NS_SUCCEEDED(rv) && !mCurrentScriptProto->GetScriptObject()) {
+                // We will be notified via OnOffThreadCompileComplete when the
+                // compile finishes. Keep the contents of the compiled script
+                // alive until the compilation finishes.
+                mOffThreadCompiling = true;
+                BlockOnload();
+                return NS_OK;
+            }
+            mOffThreadCompileString.Truncate();
+        }
+    }
+
+    return OnScriptCompileComplete(mCurrentScriptProto->GetScriptObject(), rv);
+}
+
+NS_IMETHODIMP
+XULDocument::OnScriptCompileComplete(JSScript* aScript, nsresult aStatus)
+{
+    // Allow load events to be fired once off thread compilation finishes.
+    if (mOffThreadCompiling) {
+        mOffThreadCompiling = false;
+        UnblockOnload(false);
+    }
+
+    // After compilation finishes the script's characters are no longer needed.
+    mOffThreadCompileString.Truncate();
+
+    // When compiling off thread the script will not have been attached to the
+    // script proto yet.
+    if (aScript && !mCurrentScriptProto->GetScriptObject())
+        mCurrentScriptProto->Set(aScript);
+
+    // Clear mCurrentScriptProto now, but save it first for use below in
+    // the execute code, and in the while loop that resumes walks of other
+    // documents that raced to load this script.
+    nsXULPrototypeScript* scriptProto = mCurrentScriptProto;
+    mCurrentScriptProto = nullptr;
+
+    // Clear the prototype's loading flag before executing the script or
+    // resuming document walks, in case any of those control flows starts a
+    // new script load.
+    scriptProto->mSrcLoading = false;
+
+    nsresult rv = aStatus;
+    if (NS_SUCCEEDED(rv)) {
+        rv = ExecuteScript(scriptProto);
+
+        // If the XUL cache is enabled, save the script object there in
+        // case different XUL documents source the same script.
+        //
+        // But don't save the script in the cache unless the master XUL
+        // document URL is a chrome: URL.  It is valid for a URL such as
+        // about:config to translate into a master document URL, whose
+        // prototype document nodes -- including prototype scripts that
+        // hold GC roots protecting their mJSObject pointers -- are not
+        // cached in the XUL prototype cache.  See StartDocumentLoad,
+        // the fillXULCache logic.
+        //
+        // A document such as about:config is free to load a script via
+        // a URL such as chrome://global/content/config.js, and we must
+        // not cache that script object without a prototype cache entry
+        // containing a companion nsXULPrototypeScript node that owns a
+        // GC root protecting the script object.  Otherwise, the script
+        // cache entry will dangle once the uncached prototype document
+        // is released when its owning XULDocument is unloaded.
+        //
+        // (See http://bugzilla.mozilla.org/show_bug.cgi?id=98207 for
+        // the true crime story.)
+        bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled();
+  
+        if (useXULCache && IsChromeURI(mDocumentURI) && scriptProto->GetScriptObject()) {
+            nsXULPrototypeCache::GetInstance()->PutScript(
+                               scriptProto->mSrcURI,
+                               scriptProto->GetScriptObject());
+        }
+
+        if (mIsWritingFastLoad && mCurrentPrototype != mMasterPrototype) {
+            // If we are loading an overlay script, try to serialize
+            // it to the FastLoad file here.  Master scripts will be
+            // serialized when the master prototype document gets
+            // written, at the bottom of ResumeWalk.  That way, master
+            // out-of-line scripts are serialized in the same order that
+            // they'll be read, in the FastLoad file, which reduces the
+            // number of seeks that dump the underlying stream's buffer.
             //
-            // (See http://bugzilla.mozilla.org/show_bug.cgi?id=98207 for
-            // the true crime story.)
-            bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled();
-  
-            if (useXULCache && IsChromeURI(mDocumentURI)) {
-                nsXULPrototypeCache::GetInstance()->PutScript(
-                                   scriptProto->mSrcURI,
-                                   scriptProto->GetScriptObject());
-            }
-
-            if (mIsWritingFastLoad && mCurrentPrototype != mMasterPrototype) {
-                // If we are loading an overlay script, try to serialize
-                // it to the FastLoad file here.  Master scripts will be
-                // serialized when the master prototype document gets
-                // written, at the bottom of ResumeWalk.  That way, master
-                // out-of-line scripts are serialized in the same order that
-                // they'll be read, in the FastLoad file, which reduces the
-                // number of seeks that dump the underlying stream's buffer.
-                //
-                // Ignore the return value, as we don't need to propagate
-                // a failure to write to the FastLoad file, because this
-                // method aborts that whole process on error.
-                nsIScriptGlobalObject* global =
-                    mCurrentPrototype->GetScriptGlobalObject();
-
-                NS_ASSERTION(global != nullptr, "master prototype w/o global?!");
-                if (global) {
-                    nsIScriptContext *scriptContext = \
-                          global->GetScriptContext();
-                    NS_ASSERTION(scriptContext != nullptr,
-                                 "Failed to get script context for language");
-                    if (scriptContext)
-                        scriptProto->SerializeOutOfLine(nullptr, global);
-                }
+            // Ignore the return value, as we don't need to propagate
+            // a failure to write to the FastLoad file, because this
+            // method aborts that whole process on error.
+            nsIScriptGlobalObject* global =
+                mCurrentPrototype->GetScriptGlobalObject();
+
+            NS_ASSERTION(global != nullptr, "master prototype w/o global?!");
+            if (global) {
+                nsIScriptContext *scriptContext =
+                    global->GetScriptContext();
+                NS_ASSERTION(scriptContext != nullptr,
+                             "Failed to get script context for language");
+                if (scriptContext)
+                    scriptProto->SerializeOutOfLine(nullptr, global);
             }
         }
+
         // ignore any evaluation errors
     }
 
     rv = ResumeWalk();
 
     // Load a pointer to the prototype-script's list of XULDocuments who
     // raced to load the same script
     XULDocument** docp = &scriptProto->mSrcLoadWaiters;
@@ -3623,17 +3656,16 @@ XULDocument::OnStreamComplete(nsIStreamL
         }
         doc->ResumeWalk();
         NS_RELEASE(doc);
     }
 
     return rv;
 }
 
-
 nsresult
 XULDocument::ExecuteScript(nsIScriptContext * aContext,
                            JS::Handle<JSScript*> aScriptObject)
 {
     NS_PRECONDITION(aScriptObject != nullptr && aContext != nullptr, "null ptr");
     if (! aScriptObject || ! aContext)
         return NS_ERROR_NULL_POINTER;
 
--- a/content/xul/document/src/XULDocument.h
+++ b/content/xul/document/src/XULDocument.h
@@ -83,17 +83,18 @@ private:
 
 namespace mozilla {
 namespace dom {
 
 class XULDocument MOZ_FINAL : public XMLDocument,
                               public nsIXULDocument,
                               public nsIDOMXULDocument,
                               public nsIStreamLoaderObserver,
-                              public nsICSSLoaderObserver
+                              public nsICSSLoaderObserver,
+                              public nsIOffThreadScriptReceiver
 {
 public:
     XULDocument();
     virtual ~XULDocument();
 
     // nsISupports interface
     NS_DECL_ISUPPORTS_INHERITED
     NS_DECL_NSISTREAMLOADEROBSERVER
@@ -168,16 +169,18 @@ public:
     virtual bool IsDocumentRightToLeft() MOZ_OVERRIDE;
 
     virtual void ResetDocumentDirection() MOZ_OVERRIDE;
 
     virtual int GetDocumentLWTheme() MOZ_OVERRIDE;
 
     virtual void ResetDocumentLWTheme() MOZ_OVERRIDE { mDocLWTheme = Doc_Theme_Uninitialized; }
 
+    NS_IMETHOD OnScriptCompileComplete(JSScript* aScript, nsresult aStatus) MOZ_OVERRIDE;
+
     static bool
     MatchAttribute(nsIContent* aContent,
                    int32_t aNameSpaceID,
                    nsIAtom* aAttrName,
                    void* aData);
 
     NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(XULDocument, XMLDocument)
 
@@ -435,16 +438,28 @@ protected:
      * The prototype-script of the current transcluded script that is being
      * loaded.  For document.write('<script src="nestedwrite.js"><\/script>')
      * to work, these need to be in a stack element type, and we need to hold
      * the top of stack here.
      */
     nsXULPrototypeScript* mCurrentScriptProto;
 
     /**
+     * Whether the current transcluded script is being compiled off thread.
+     * The load event is blocked while this is in progress.
+     */
+    bool mOffThreadCompiling;
+
+    /**
+     * If the current transcluded script is being compiled off thread, the
+     * source for that script.
+     */
+    nsString mOffThreadCompileString;
+
+    /**
      * Check if a XUL template builder has already been hooked up.
      */
     static nsresult
     CheckTemplateBuilderHookup(nsIContent* aElement, bool* aNeedsHookup);
 
     /**
      * Create a XUL template builder on the specified node.
      */
--- a/content/xul/document/src/nsXULPrototypeCache.cpp
+++ b/content/xul/document/src/nsXULPrototypeCache.cpp
@@ -199,16 +199,18 @@ nsXULPrototypeCache::GetScript(nsIURI* a
 {
     return mScriptTable.Get(aURI);
 }
 
 nsresult
 nsXULPrototypeCache::PutScript(nsIURI* aURI,
                                JS::Handle<JSScript*> aScriptObject)
 {
+    MOZ_ASSERT(aScriptObject, "Need a non-NULL script");
+
 #ifdef DEBUG
     if (mScriptTable.Get(aURI)) {
         nsAutoCString scriptName;
         aURI->GetSpec(scriptName);
         nsAutoCString message("Loaded script ");
         message += scriptName;
         message += " twice (bug 392650)";
         NS_WARNING(message.get());
--- a/dom/base/nsIScriptContext.h
+++ b/dom/base/nsIScriptContext.h
@@ -30,16 +30,18 @@ class nsIURI;
 #define NS_ISCRIPTCONTEXT_IID \
 { 0x6219173f, 0x4a61, 0x4c99, \
   { 0xb1, 0xfd, 0x8e, 0x7a, 0xf0, 0xdc, 0xe0, 0x56 } }
 
 /* This MUST match JSVERSION_DEFAULT.  This version stuff if we don't
    know what language we have is a little silly... */
 #define SCRIPTVERSION_DEFAULT JSVERSION_DEFAULT
 
+class nsIOffThreadScriptReceiver;
+
 /**
  * It is used by the application to initialize a runtime and run scripts.
  * A script runtime would implement this interface.
  */
 class nsIScriptContext : public nsISupports
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISCRIPTCONTEXT_IID)
@@ -171,10 +173,29 @@ public:
   /**
    * Tell the context we're done reinitializing it.
    */
   virtual void DidInitializeContext() = 0;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsIScriptContext, NS_ISCRIPTCONTEXT_IID)
 
+#define NS_IOFFTHREADSCRIPTRECEIVER_IID \
+{0x3a980010, 0x878d, 0x46a9,            \
+  {0x93, 0xad, 0xbc, 0xfd, 0xd3, 0x8e, 0xa0, 0xc2}}
+
+class nsIOffThreadScriptReceiver : public nsISupports
+{
+public:
+  NS_DECLARE_STATIC_IID_ACCESSOR(NS_IOFFTHREADSCRIPTRECEIVER_IID)
+
+  /**
+   * Notify this object that a previous CompileScript call specifying this as
+   * aOffThreadReceiver has completed. The script being passed in must be
+   * rooted before any call which could trigger GC.
+   */
+  NS_IMETHOD OnScriptCompileComplete(JSScript* aScript, nsresult aStatus) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIOffThreadScriptReceiver, NS_IOFFTHREADSCRIPTRECEIVER_IID)
+
 #endif // nsIScriptContext_h__
 
--- a/dom/tests/mochitest/ajax/offline/Makefile.in
+++ b/dom/tests/mochitest/ajax/offline/Makefile.in
@@ -8,97 +8,18 @@ topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 relativesrcdir	= @relativesrcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 MOCHITEST_FILES	= \
 	offlineTests.js \
-	test_badManifestMagic.html \
-	test_bypass.html \
-	test_missingFile.html \
-	test_noManifest.html \
-	test_simpleManifest.html \
-	test_identicalManifest.html \
-	test_changingManifest.html \
-	test_refetchManifest.html \
-	test_offlineIFrame.html \
 	test_bug445544.html \
-	test_bug460353.html \
-	test_bug474696.html \
-	test_bug544462.html \
-	test_bug744719.html \
-	744719.cacheManifest \
-	744719.cacheManifest^headers^ \
-	test_bug765203.html \
-	unknownSection.cacheManifest \
-	unknownSection.cacheManifest^headers^ \
-	test_bug744719-cancel.html \
-	744719-cancel.cacheManifest \
-	744719-cancel.cacheManifest^headers^ \
-	subresource744719.html \
-	test_foreign.html \
-	test_fallback.html \
-	test_overlap.html \
-	test_redirectManifest.html \
-	test_redirectUpdateItem.html \
-	overlap.cacheManifest \
-	overlap.cacheManifest^headers^ \
-	test_updatingManifest.html \
-	test_updateCheck.html \
 	445544_part1.html \
 	445544_part2.html \
 	445544.cacheManifest \
 	445544.cacheManifest^headers^ \
-	460353_iframe_nomanifest.html \
-	460353_iframe_ownmanifest.html \
-	460353_iframe_samemanifest.html \
-	test_obsolete.html \
-	obsolete.html \
-	obsoletingManifest.sjs \
-	badManifestMagic.cacheManifest \
-	badManifestMagic.cacheManifest^headers^ \
-	bypass.cacheManifest \
-	bypass.cacheManifest^headers^ \
-	bypass.html \
-	dynamicRedirect.sjs \
-	explicitRedirect.sjs \
-	fallback.html \
-	fallback2.html \
-	fallbackTop.html \
-	fallback.cacheManifest \
-	fallback.cacheManifest^headers^ \
-	foreign1.cacheManifest \
-	foreign1.cacheManifest^headers^ \
-	foreign2.cacheManifest \
-	foreign2.cacheManifest^headers^ \
-	foreign2.html \
-	notonwhitelist.html \
-	onwhitelist.html \
-	onwhitelist.html^headers^ \
-	updatingIframe.sjs \
-	updatingImplicit.html \
-	manifestRedirect.sjs \
-	missingFile.cacheManifest \
-	missingFile.cacheManifest^headers^ \
-	redirects.sjs \
-	simpleManifest.cacheManifest \
-	simpleManifest.cacheManifest^headers^ \
-	wildcardManifest.cacheManifest \
-	wildcardManifest.cacheManifest^headers^ \
-	updatingManifest.sjs \
-	changing1Sec.sjs \
-	changing1Hour.sjs \
-	changingManifest.sjs \
-	offlineChild.html \
-	test_xhtmlManifest.xhtml \
-	test_missingManifest.html \
-	missing.html \
-	jupiter.jpg \
-	test_cancelOfflineCache.html \
-	test_lowDeviceStorage.html \
-	test_lowDeviceStorageDuringUpdate.html \
 	$(NULL)
 
 # test_offlineMode.html disabled due to bug 656943
 
 include $(topsrcdir)/config/rules.mk
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -189,17 +189,18 @@ frontend::CompileScript(ExclusiveContext
     if (options.filename && !ss->setFilename(cx, options.filename))
         return NULL;
 
     JS::RootedScriptSource sourceObject(cx, ScriptSourceObject::create(cx, ss));
     if (!sourceObject)
         return NULL;
 
     // Saving source is not yet supported when parsing off thread.
-    JS_ASSERT_IF(!cx->isJSContext(), !extraSct && options.sourcePolicy == CompileOptions::NO_SOURCE);
+    JS_ASSERT_IF(!cx->isJSContext(),
+                 !extraSct && options.sourcePolicy != CompileOptions::SAVE_SOURCE);
 
     SourceCompressionToken *sct = extraSct;
     Maybe<SourceCompressionToken> mysct;
     if (cx->isJSContext() && !sct) {
         mysct.construct(cx->asJSContext());
         sct = mysct.addr();
     }
 
--- a/js/src/frontend/FoldConstants.cpp
+++ b/js/src/frontend/FoldConstants.cpp
@@ -608,17 +608,17 @@ Fold(ExclusiveContext *cx, ParseNode **p
         JS_ASSERT(pn->isArity(PN_BINARY));
         if (pn1->isKind(PNK_STRING) || pn2->isKind(PNK_STRING)) {
             if (!FoldType(cx, !pn1->isKind(PNK_STRING) ? pn1 : pn2, PNK_STRING))
                 return false;
             if (!pn1->isKind(PNK_STRING) || !pn2->isKind(PNK_STRING))
                 return true;
             RootedString left(cx, pn1->pn_atom);
             RootedString right(cx, pn2->pn_atom);
-            RootedString str(cx, ConcatStrings<CanGC>(cx->asJSContext(), left, right));
+            RootedString str(cx, ConcatStrings<CanGC>(cx, left, right));
             if (!str)
                 return false;
             pn->pn_atom = AtomizeString<CanGC>(cx, str);
             if (!pn->pn_atom)
                 return false;
             pn->setKind(PNK_STRING);
             pn->setOp(JSOP_STRING);
             pn->setArity(PN_NULLARY);
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -1227,37 +1227,31 @@ Parser<ParseHandler>::newFunction(Generi
      * function's parent slot to pc->sc->as<GlobalObject>()->scopeChain. If the
      * global context is a compile-and-go one, we leave the pre-set parent
      * intact; otherwise we clear parent and proto.
      */
     while (pc->parent)
         pc = pc->parent;
 
     RootedObject parent(context);
-    parent = pc->sc->isFunctionBox() ? NULL : pc->sc->asGlobalSharedContext()->scopeChain();
+    if (!pc->sc->isFunctionBox() && options().compileAndGo)
+        parent = pc->sc->asGlobalSharedContext()->scopeChain();
 
     RootedFunction fun(context);
     JSFunction::Flags flags = (kind == Expression)
                               ? JSFunction::INTERPRETED_LAMBDA
                               : (kind == Arrow)
                                 ? JSFunction::INTERPRETED_LAMBDA_ARROW
                                 : JSFunction::INTERPRETED;
     fun = NewFunction(context, NullPtr(), NULL, 0, flags, parent, atom,
                       JSFunction::FinalizeKind, MaybeSingletonObject);
+    if (!fun)
+        return NULL;
     if (options().selfHostingMode)
         fun->setIsSelfHostedBuiltin();
-    if (fun && !options().compileAndGo) {
-        if (!context->shouldBeJSContext())
-            return NULL;
-        if (!JSObject::clearParent(context->asJSContext(), fun))
-            return NULL;
-        if (!JSObject::clearType(context->asJSContext(), fun))
-            return NULL;
-        fun->setEnvironment(NULL);
-    }
     return fun;
 }
 
 static bool
 MatchOrInsertSemicolon(TokenStream &ts)
 {
     TokenKind tt = ts.peekTokenSameLine(TokenStream::Operand);
     if (tt == TOK_ERROR)
--- a/js/src/gc/Zone.cpp
+++ b/js/src/gc/Zone.cpp
@@ -68,16 +68,19 @@ Zone::setNeedsBarrier(bool needs, Should
 {
 #ifdef JS_ION
     if (updateIon == UpdateIon && needs != ionUsingBarriers_) {
         ion::ToggleBarriers(this, needs);
         ionUsingBarriers_ = needs;
     }
 #endif
 
+    if (needs && runtimeFromMainThread()->isAtomsZone(this))
+        JS_ASSERT(!runtimeFromMainThread()->exclusiveThreadsPresent());
+
     needsBarrier_ = needs;
 }
 
 void
 Zone::markTypes(JSTracer *trc)
 {
     /*
      * Mark all scripts, type objects and singleton JS objects in the
--- a/js/src/jit/VMFunctions.h
+++ b/js/src/jit/VMFunctions.h
@@ -382,16 +382,19 @@ template <> struct OutParamToRootType<Mu
 template <> struct OutParamToRootType<MutableHandleString> {
     static const VMFunction::RootType result = VMFunction::RootString;
 };
 
 template <class> struct MatchContext { };
 template <> struct MatchContext<JSContext *> {
     static const ExecutionMode execMode = SequentialExecution;
 };
+template <> struct MatchContext<ExclusiveContext *> {
+    static const ExecutionMode execMode = SequentialExecution;
+};
 template <> struct MatchContext<ForkJoinSlice *> {
     static const ExecutionMode execMode = ParallelExecution;
 };
 template <> struct MatchContext<ThreadSafeContext *> {
     // ThreadSafeContext functions can be called from either mode, but for
     // calling from parallel they need to be wrapped first to return a
     // ParallelResult, so we default to SequentialExecution here.
     static const ExecutionMode execMode = SequentialExecution;
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4836,16 +4836,79 @@ JS::Compile(JSContext *cx, HandleObject 
     AutoFile file;
     if (!file.open(cx, filename))
         return NULL;
     options = options.setFileAndLine(filename, 1);
     JSScript *script = Compile(cx, obj, options, file.fp());
     return script;
 }
 
+JS_PUBLIC_API(bool)
+JS::CanCompileOffThread(JSContext *cx, const CompileOptions &options)
+{
+#if defined(JS_THREADSAFE) && defined(JS_ION)
+    if (!cx->runtime()->useHelperThreads() || !cx->runtime()->helperThreadCount())
+        return false;
+
+    // Off thread compilation can't occur during incremental collections on the
+    // atoms compartment, to avoid triggering barriers. Outside the atoms
+    // compartment, the compilation will use a new zone which doesn't require
+    // barriers itself.
+    if (cx->runtime()->atomsZoneNeedsBarrier())
+        return false;
+
+    // Blacklist filenames which cause mysterious assertion failures in
+    // graphics code on OS X. These seem to tickle some preexisting race
+    // condition unrelated to off thread compilation. See bug 897655.
+    static const char *blacklist[] = {
+#ifdef XP_MACOSX
+        "chrome://browser/content/places/editBookmarkOverlay.js",
+        "chrome://browser/content/nsContextMenu.js",
+        "chrome://browser/content/newtab/newTab.js",
+        "chrome://browser/content/places/browserPlacesViews.js",
+#endif
+        NULL
+    };
+
+    const char *filename = options.filename;
+    for (const char **ptest = blacklist; *ptest; ptest++) {
+        if (!strcmp(*ptest, filename))
+            return false;
+    }
+
+    return true;
+#else
+    return false;
+#endif
+}
+
+JS_PUBLIC_API(bool)
+JS::CompileOffThread(JSContext *cx, Handle<JSObject*> obj, CompileOptions options,
+                     const jschar *chars, size_t length,
+                     OffThreadCompileCallback callback, void *callbackData)
+{
+#if defined(JS_THREADSAFE) && defined(JS_ION)
+    JS_ASSERT(CanCompileOffThread(cx, options));
+    return StartOffThreadParseScript(cx, options, chars, length, obj, callback, callbackData);
+#else
+    MOZ_ASSUME_UNREACHABLE("Off thread compilation is only available with JS_ION");
+#endif
+}
+
+JS_PUBLIC_API(void)
+JS::FinishOffThreadScript(JSRuntime *rt, JSScript *script)
+{
+#if defined(JS_THREADSAFE) && defined(JS_ION)
+    JS_ASSERT(CurrentThreadCanAccessRuntime(rt));
+    rt->workerThreadState->finishParseTaskForScript(script);
+#else
+    MOZ_ASSUME_UNREACHABLE("Off thread compilation is only available with JS_ION");
+#endif
+}
+
 JS_PUBLIC_API(JSScript *)
 JS_CompileUCScriptForPrincipals(JSContext *cx, JSObject *objArg, JSPrincipals *principals,
                                 const jschar *chars, size_t length,
                                 const char *filename, unsigned lineno)
 {
     RootedObject obj(cx, objArg);
     CompileOptions options(cx);
     options.setPrincipals(principals)
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -4114,16 +4114,43 @@ Compile(JSContext *cx, JS::Handle<JSObje
         const jschar *chars, size_t length);
 
 extern JS_PUBLIC_API(JSScript *)
 Compile(JSContext *cx, JS::Handle<JSObject*> obj, CompileOptions options, FILE *file);
 
 extern JS_PUBLIC_API(JSScript *)
 Compile(JSContext *cx, JS::Handle<JSObject*> obj, CompileOptions options, const char *filename);
 
+extern JS_PUBLIC_API(bool)
+CanCompileOffThread(JSContext *cx, const CompileOptions &options);
+
+/*
+ * Off thread compilation control flow.
+ *
+ * After successfully triggering an off thread compile of a script, the
+ * callback will eventually be invoked with the specified data and the result
+ * script or NULL. The callback will be invoked while off the main thread, so
+ * must ensure that its operations are thread safe. Afterwards,
+ * FinishOffThreadScript must be invoked on the main thread to make the script
+ * usable (correct compartment/zone); this method must be invoked even if the
+ * off thread compilation produced a NULL script.
+ *
+ * The characters passed in to CompileOffThread must remain live until the
+ * callback is invoked, and the resulting script will be rooted until the call
+ * to FinishOffThreadScript.
+ */
+
+extern JS_PUBLIC_API(bool)
+CompileOffThread(JSContext *cx, Handle<JSObject*> obj, CompileOptions options,
+                 const jschar *chars, size_t length,
+                 OffThreadCompileCallback callback, void *callbackData);
+
+extern JS_PUBLIC_API(void)
+FinishOffThreadScript(JSRuntime *rt, JSScript *script);
+
 extern JS_PUBLIC_API(JSFunction *)
 CompileFunction(JSContext *cx, JS::Handle<JSObject*> obj, CompileOptions options,
                 const char *name, unsigned nargs, const char **argnames,
                 const char *bytes, size_t length);
 
 extern JS_PUBLIC_API(JSFunction *)
 CompileFunction(JSContext *cx, JS::Handle<JSObject*> obj, CompileOptions options,
                 const char *name, unsigned nargs, const char **argnames,
--- a/js/src/jsatom.cpp
+++ b/js/src/jsatom.cpp
@@ -345,26 +345,19 @@ js::AtomizeString(ExclusiveContext *cx, 
         AtomSet::Ptr p = cx->atoms().lookup(AtomHasher::Lookup(&atom));
         JS_ASSERT(p); /* Non-static atom must exist in atom state set. */
         JS_ASSERT(p->asPtr() == &atom);
         JS_ASSERT(ib == InternAtom);
         p->setTagged(bool(ib));
         return &atom;
     }
 
-    const jschar *chars;
-    if (str->isLinear()) {
-        chars = str->asLinear().chars();
-    } else {
-        if (!cx->shouldBeJSContext())
-            return NULL;
-        chars = str->getChars(cx->asJSContext());
-        if (!chars)
-            return NULL;
-    }
+    const jschar *chars = str->getChars(cx);
+    if (!chars)
+        return NULL;
 
     if (JSAtom *atom = AtomizeAndCopyChars<NoGC>(cx, chars, str->length(), ib))
         return atom;
 
     if (!cx->isJSContext() || !allowGC)
         return NULL;
 
     JSLinearString *linear = str->ensureLinear(cx->asJSContext());
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -272,17 +272,16 @@ struct ThreadSafeContext : ContextFriend
     }
 };
 
 struct WorkerThread;
 
 class ExclusiveContext : public ThreadSafeContext
 {
     friend class gc::ArenaLists;
-    friend class CompartmentChecker;
     friend class AutoCompartment;
     friend class AutoLockForExclusiveAccess;
     friend struct StackBaseShape;
     friend void JSScript::initCompartment(ExclusiveContext *cx);
 
     // The worker on which this context is running, if this is not a JSContext.
     WorkerThread *workerThread;
 
--- a/js/src/jscntxtinlines.h
+++ b/js/src/jscntxtinlines.h
@@ -23,17 +23,17 @@ namespace js {
 
 #ifdef JS_CRASH_DIAGNOSTICS
 class CompartmentChecker
 {
     JSCompartment *compartment;
 
   public:
     explicit CompartmentChecker(ExclusiveContext *cx)
-      : compartment(cx->compartment_)
+      : compartment(cx->compartment())
     {}
 
     /*
      * Set a breakpoint here (break js::CompartmentChecker::fail) to debug
      * compartment mismatches.
      */
     static void fail(JSCompartment *c1, JSCompartment *c2) {
         printf("*** Compartment mismatch %p vs. %p\n", (void *) c1, (void *) c2);
@@ -42,24 +42,24 @@ class CompartmentChecker
 
     static void fail(JS::Zone *z1, JS::Zone *z2) {
         printf("*** Zone mismatch %p vs. %p\n", (void *) z1, (void *) z2);
         MOZ_CRASH();
     }
 
     /* Note: should only be used when neither c1 nor c2 may be the atoms compartment. */
     static void check(JSCompartment *c1, JSCompartment *c2) {
-        JS_ASSERT(!c1->runtimeFromMainThread()->isAtomsCompartment(c1));
-        JS_ASSERT(!c2->runtimeFromMainThread()->isAtomsCompartment(c2));
+        JS_ASSERT(!c1->runtimeFromAnyThread()->isAtomsCompartment(c1));
+        JS_ASSERT(!c2->runtimeFromAnyThread()->isAtomsCompartment(c2));
         if (c1 != c2)
             fail(c1, c2);
     }
 
     void check(JSCompartment *c) {
-        if (c && !compartment->runtimeFromMainThread()->isAtomsCompartment(c)) {
+        if (c && !compartment->runtimeFromAnyThread()->isAtomsCompartment(c)) {
             if (!compartment)
                 compartment = c;
             else if (c != compartment)
                 fail(compartment, c);
         }
     }
 
     void checkZone(JS::Zone *z) {
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -612,16 +612,44 @@ JSCompartment::sweepCrossCompartmentWrap
 }
 
 void
 JSCompartment::purge()
 {
     dtoaCache.purge();
 }
 
+void
+JSCompartment::clearTables()
+{
+    global_ = NULL;
+
+    regExps.clearTables();
+
+    // No scripts should have run in this compartment. This is used when
+    // merging a compartment that has been used off thread into another
+    // compartment and zone.
+    JS_ASSERT(crossCompartmentWrappers.empty());
+    JS_ASSERT_IF(callsiteClones.initialized(), callsiteClones.empty());
+    JS_ASSERT(!ionCompartment_);
+    JS_ASSERT(!debugScopes);
+    JS_ASSERT(!gcWeakMapList);
+    JS_ASSERT(!analysisLifoAlloc.used());
+    JS_ASSERT(enumerators->next() == enumerators);
+
+    if (baseShapes.initialized())
+        baseShapes.clear();
+    if (initialShapes.initialized())
+        initialShapes.clear();
+    if (newTypeObjects.initialized())
+        newTypeObjects.clear();
+    if (lazyTypeObjects.initialized())
+        lazyTypeObjects.clear();
+}
+
 bool
 JSCompartment::hasScriptsOnStack()
 {
     for (ActivationIterator iter(runtimeFromMainThread()); !iter.done(); ++iter) {
         if (iter.activation()->compartment() == this)
             return true;
     }
 
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -305,16 +305,17 @@ struct JSCompartment
         WrapperEnum(JSCompartment *c) : js::WrapperMap::Enum(c->crossCompartmentWrappers) {}
     };
 
     void mark(JSTracer *trc);
     bool isDiscardingJitCode(JSTracer *trc);
     void sweep(js::FreeOp *fop, bool releaseTypes);
     void sweepCrossCompartmentWrappers();
     void purge();
+    void clearTables();
 
     void findOutgoingEdges(js::gc::ComponentFinder<JS::Zone> &finder);
 
     js::DtoaCache dtoaCache;
 
     /* Random number generator state, used by jsmath.cpp. */
     uint64_t rngState;
 
@@ -395,16 +396,22 @@ struct JSCompartment
 };
 
 inline bool
 JSRuntime::isAtomsZone(JS::Zone *zone)
 {
     return zone == atomsCompartment_->zone();
 }
 
+inline bool
+JSRuntime::atomsZoneNeedsBarrier()
+{
+    return atomsCompartment_->zone()->needsBarrier();
+}
+
 // For use when changing the debug mode flag on one or more compartments.
 // Do not run scripts in any compartment that is scheduled for GC using this
 // object. See comment in updateForDebugMode.
 //
 class js::AutoDebugModeGC
 {
     JSRuntime *rt;
     bool needGC;
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -1075,16 +1075,22 @@ js::AddStringRoot(JSContext *cx, JSStrin
 
 extern bool
 js::AddObjectRoot(JSContext *cx, JSObject **rp, const char *name)
 {
     return AddRoot(cx, rp, name, JS_GC_ROOT_OBJECT_PTR);
 }
 
 extern bool
+js::AddObjectRoot(JSRuntime *rt, JSObject **rp, const char *name)
+{
+    return AddRoot(rt, rp, name, JS_GC_ROOT_OBJECT_PTR);
+}
+
+extern bool
 js::AddScriptRoot(JSContext *cx, JSScript **rp, const char *name)
 {
     return AddRoot(cx, rp, name, JS_GC_ROOT_SCRIPT_PTR);
 }
 
 extern JS_FRIEND_API(bool)
 js_AddObjectRoot(JSRuntime *rt, JSObject **objp)
 {
@@ -1213,17 +1219,17 @@ ArenaLists::allocateFromArenaInline(Zone
 #ifdef JS_THREADSAFE
     volatile uintptr_t *bfs = &backgroundFinalizeState[thingKind];
     if (*bfs != BFS_DONE) {
         /*
          * We cannot search the arena list for free things while the
          * background finalization runs and can modify head or cursor at any
          * moment. So we always allocate a new arena in that case.
          */
-        maybeLock.lock(zone->runtimeFromMainThread());
+        maybeLock.lock(zone->runtimeFromAnyThread());
         if (*bfs == BFS_RUN) {
             JS_ASSERT(!*al->cursor);
             chunk = PickChunk(zone);
             if (!chunk) {
                 /*
                  * Let the caller to wait for the background allocation to
                  * finish and restart the allocation attempt.
                  */
@@ -4764,16 +4770,60 @@ js::NewCompartment(JSContext *cx, Zone *
         return NULL;
     }
 
     zoneHolder.forget();
     return compartment.forget();
 }
 
 void
+gc::MergeCompartments(JSCompartment *source, JSCompartment *target)
+{
+    JSRuntime *rt = source->runtimeFromMainThread();
+    AutoPrepareForTracing prepare(rt);
+
+    // Cleanup tables and other state in the source compartment that will be
+    // meaningless after merging into the target compartment.
+
+    source->clearTables();
+
+    // Fixup compartment pointers in source to refer to target.
+
+    for (CellIter iter(source->zone(), FINALIZE_SCRIPT); !iter.done(); iter.next()) {
+        JSScript *script = iter.get<JSScript>();
+        JS_ASSERT(script->compartment() == source);
+        script->compartment_ = target;
+    }
+
+    for (CellIter iter(source->zone(), FINALIZE_BASE_SHAPE); !iter.done(); iter.next()) {
+        BaseShape *base = iter.get<BaseShape>();
+        JS_ASSERT(base->compartment() == source);
+        base->compartment_ = target;
+    }
+
+    // Fixup zone pointers in source's zone to refer to target's zone.
+
+    for (size_t thingKind = 0; thingKind != FINALIZE_LIMIT; thingKind++) {
+        for (ArenaIter aiter(source->zone(), AllocKind(thingKind)); !aiter.done(); aiter.next()) {
+            ArenaHeader *aheader = aiter.get();
+            aheader->zone = target->zone();
+        }
+    }
+
+    // The source should be the only compartment in its zone.
+    for (CompartmentsInZoneIter c(source->zone()); !c.done(); c.next())
+        JS_ASSERT(c.get() == source);
+
+    // Merge the allocator in source's zone into target's zone.
+    target->zone()->allocator.arenas.adoptArenas(rt, &source->zone()->allocator.arenas);
+    target->zone()->gcBytes += source->zone()->gcBytes;
+    source->zone()->gcBytes = 0;
+}
+
+void
 gc::RunDebugGC(JSContext *cx)
 {
 #ifdef JS_GC_ZEAL
     JSRuntime *rt = cx->runtime();
 
     if (rt->mainThread.suppressGC)
         return;
 
@@ -5037,16 +5087,17 @@ ArenaLists::adoptArenas(JSRuntime *rt, A
         ArenaList *toList = &arenaLists[thingKind];
         while (fromList->head != NULL) {
             ArenaHeader *fromHeader = fromList->head;
             fromList->head = fromHeader->next;
             fromHeader->next = NULL;
 
             toList->insert(fromHeader);
         }
+        fromList->cursor = &fromList->head;
     }
 }
 
 bool
 ArenaLists::containsArena(JSRuntime *rt, ArenaHeader *needle)
 {
     AutoLockGC lock(rt);
     size_t allocKind = needle->getAllocKind();
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -404,17 +404,17 @@ class ArenaLists
     };
 
     volatile uintptr_t backgroundFinalizeState[FINALIZE_LIMIT];
 
   public:
     /* For each arena kind, a list of arenas remaining to be swept. */
     ArenaHeader *arenaListsToSweep[FINALIZE_LIMIT];
 
-    /* Shape areneas to be swept in the foreground. */
+    /* Shape arenas to be swept in the foreground. */
     ArenaHeader *gcShapeArenasToSweep;
 
   public:
     ArenaLists() {
         for (size_t i = 0; i != FINALIZE_LIMIT; ++i)
             freeLists[i].initAsEmpty();
         for (size_t i = 0; i != FINALIZE_LIMIT; ++i)
             backgroundFinalizeState[i] = BFS_DONE;
@@ -658,16 +658,19 @@ AddValueRootRT(JSRuntime *rt, js::Value 
 
 extern bool
 AddStringRoot(JSContext *cx, JSString **rp, const char *name);
 
 extern bool
 AddObjectRoot(JSContext *cx, JSObject **rp, const char *name);
 
 extern bool
+AddObjectRoot(JSRuntime *rt, JSObject **rp, const char *name);
+
+extern bool
 AddScriptRoot(JSContext *cx, JSScript **rp, const char *name);
 
 } /* namespace js */
 
 extern bool
 js_InitGC(JSRuntime *rt, uint32_t maxbytes);
 
 extern void
@@ -1330,16 +1333,23 @@ SetValidateGC(JSContext *cx, bool enable
 
 void
 SetFullCompartmentChecks(JSContext *cx, bool enabled);
 
 /* Wait for the background thread to finish sweeping if it is running. */
 void
 FinishBackgroundFinalize(JSRuntime *rt);
 
+/*
+ * Merge all contents of source into target. This can only be used if source is
+ * the only compartment in its zone.
+ */
+void
+MergeCompartments(JSCompartment *source, JSCompartment *target);
+
 const int ZealPokeValue = 1;
 const int ZealAllocValue = 2;
 const int ZealFrameGCValue = 3;
 const int ZealVerifierPreValue = 4;
 const int ZealFrameVerifierPreValue = 5;
 // These two values used to be distinct.  They no longer are, but both were
 // kept to avoid breaking fuzz tests.  Avoid using ZealStackRootingValue__2.
 const int ZealStackRootingValue = 6;
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -5100,33 +5100,42 @@ js::IsDelegate(JSContext *cx, HandleObje
         }
         if (obj2 == obj) {
             *result = true;
             return true;
         }
     }
 }
 
+JSObject *
+js::GetClassPrototypePure(GlobalObject *global, JSProtoKey protoKey)
+{
+    JS_ASSERT(JSProto_Null <= protoKey);
+    JS_ASSERT(protoKey < JSProto_LIMIT);
+
+    if (protoKey != JSProto_Null) {
+        const Value &v = global->getReservedSlot(JSProto_LIMIT + protoKey);
+        if (v.isObject())
+            return &v.toObject();
+    }
+
+    return NULL;
+}
+
 /*
  * The first part of this function has been hand-expanded and optimized into
  * NewBuiltinClassInstance in jsobjinlines.h.
  */
 bool
 js_GetClassPrototype(ExclusiveContext *cx, JSProtoKey protoKey,
                      MutableHandleObject protop, Class *clasp)
 {
-    JS_ASSERT(JSProto_Null <= protoKey);
-    JS_ASSERT(protoKey < JSProto_LIMIT);
-
-    if (protoKey != JSProto_Null) {
-        const Value &v = cx->global()->getReservedSlot(JSProto_LIMIT + protoKey);
-        if (v.isObject()) {
-            protop.set(&v.toObject());
-            return true;
-        }
+    if (JSObject *proto = GetClassPrototypePure(cx->global(), protoKey)) {
+        protop.set(proto);
+        return true;
     }
 
     RootedValue v(cx);
     if (!js_FindClassObject(cx, protoKey, &v, clasp))
         return false;
 
     if (IsFunctionObject(v)) {
         RootedObject ctor(cx, &v.get().toObject());
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -1435,16 +1435,19 @@ js_InferFlags(JSContext *cx, unsigned de
  * methods instead.
  */
 extern bool
 js_GetClassPrototype(js::ExclusiveContext *cx, JSProtoKey protoKey, js::MutableHandleObject protop,
                      js::Class *clasp = NULL);
 
 namespace js {
 
+JSObject *
+GetClassPrototypePure(GlobalObject *global, JSProtoKey protoKey);
+
 extern bool
 SetClassAndProto(JSContext *cx, HandleObject obj,
                  Class *clasp, Handle<TaggedProto> proto, bool checkForCycles);
 
 extern JSObject *
 NonNullObject(JSContext *cx, const Value &v);
 
 extern const char *
--- a/js/src/jspubtd.h
+++ b/js/src/jspubtd.h
@@ -203,16 +203,19 @@ class                                   
 #ifdef JS_THREADSAFE
 typedef struct PRCallOnceType   JSCallOnceType;
 #else
 typedef bool                    JSCallOnceType;
 #endif
 typedef bool                    (*JSInitCallback)(void);
 
 namespace JS {
+
+typedef void (*OffThreadCompileCallback)(JSScript *script, void *callbackData);
+
 namespace shadow {
 
 struct Runtime
 {
     /* Restrict zone access during Minor GC. */
     bool needsBarrier_;
 
 #ifdef JSGC_GENERATIONAL
--- a/js/src/jsworkers.cpp
+++ b/js/src/jsworkers.cpp
@@ -12,16 +12,17 @@
 #include "prmjtime.h"
 
 #include "frontend/BytecodeCompiler.h"
 #include "jit/ExecutionModeInlines.h"
 #include "jit/IonBuilder.h"
 
 #include "jscntxtinlines.h"
 #include "jscompartmentinlines.h"
+#include "jsobjinlines.h"
 
 using namespace js;
 
 using mozilla::DebugOnly;
 
 bool
 js::EnsureWorkerThreadsInitialized(JSRuntime *rt)
 {
@@ -160,86 +161,112 @@ js::CancelOffThreadIonCompile(JSCompartm
 static JSClass workerGlobalClass = {
     "internal-worker-global", JSCLASS_GLOBAL_FLAGS,
     JS_PropertyStub,  JS_DeletePropertyStub,
     JS_PropertyStub,  JS_StrictPropertyStub,
     JS_EnumerateStub, JS_ResolveStub,
     JS_ConvertStub,   NULL
 };
 
-ParseTask::ParseTask(JSRuntime *rt, ExclusiveContext *cx, const CompileOptions &options,
-                     const jschar *chars, size_t length)
-  : runtime(rt), cx(cx), options(options), chars(chars), length(length),
-    alloc(JSRuntime::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE), script(NULL)
+ParseTask::ParseTask(Zone *zone, ExclusiveContext *cx, const CompileOptions &options,
+                     const jschar *chars, size_t length, JSObject *scopeChain,
+                     JS::OffThreadCompileCallback callback, void *callbackData)
+  : zone(zone), cx(cx), options(options), chars(chars), length(length),
+    alloc(JSRuntime::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE), scopeChain(scopeChain),
+    callback(callback), callbackData(callbackData), script(NULL)
 {
+    JSRuntime *rt = zone->runtimeFromMainThread();
+
     if (options.principals())
         JS_HoldPrincipals(options.principals());
     if (options.originPrincipals())
         JS_HoldPrincipals(options.originPrincipals());
+    if (!AddObjectRoot(rt, &this->scopeChain, "ParseTask::scopeChain"))
+        MOZ_CRASH();
 }
 
 ParseTask::~ParseTask()
 {
+    JSRuntime *rt = zone->runtimeFromMainThread();
+
     if (options.principals())
-        JS_DropPrincipals(runtime, options.principals());
+        JS_DropPrincipals(rt, options.principals());
     if (options.originPrincipals())
-        JS_DropPrincipals(runtime, options.originPrincipals());
+        JS_DropPrincipals(rt, options.originPrincipals());
+    JS_RemoveObjectRootRT(rt, &scopeChain);
 
     // ParseTask takes over ownership of its input exclusive context.
     js_delete(cx);
 }
 
 bool
 js::StartOffThreadParseScript(JSContext *cx, const CompileOptions &options,
-                              const jschar *chars, size_t length)
+                              const jschar *chars, size_t length, HandleObject scopeChain,
+                              JS::OffThreadCompileCallback callback, void *callbackData)
 {
+    // Suppress GC so that calls below do not trigger a new incremental GC
+    // which could require barriers on the atoms compartment.
+    gc::AutoSuppressGC suppress(cx);
+
     frontend::MaybeCallSourceHandler(cx, options, chars, length);
 
     JSRuntime *rt = cx->runtime();
     if (!EnsureWorkerThreadsInitialized(rt))
         return false;
 
     JS::CompartmentOptions compartmentOptions(cx->compartment()->options());
     compartmentOptions.setZone(JS::FreshZone);
 
     JSObject *global = JS_NewGlobalObject(cx, &workerGlobalClass, NULL,
                                           JS::FireOnNewGlobalHook, compartmentOptions);
     if (!global)
         return false;
 
-    // For now, type inference is always disabled in exclusive zones.
-    // This restriction would be fairly easy to lift.
+    // For now, type inference is always disabled in exclusive zones, as type
+    // inference data is not merged between zones when finishing the off thread
+    // parse. This restriction would be fairly easy to lift.
+    JS_ASSERT(!cx->typeInferenceEnabled());
     global->zone()->types.inferenceEnabled = false;
 
+    JS_SetCompartmentPrincipals(global->compartment(), cx->compartment()->principals);
+
+    RootedObject obj(cx);
+
     // Initialize all classes needed for parsing while we are still on the main
-    // thread.
+    // thread. Do this for both the target and the new global so that prototype
+    // pointers can be changed infallibly after parsing finishes.
+    if (!js_GetClassObject(cx, cx->global(), JSProto_Function, &obj) ||
+        !js_GetClassObject(cx, cx->global(), JSProto_Array, &obj) ||
+        !js_GetClassObject(cx, cx->global(), JSProto_RegExp, &obj))
+    {
+        return false;
+    }
     {
         AutoCompartment ac(cx, global);
-
-        RootedObject obj(cx);
         if (!js_GetClassObject(cx, global, JSProto_Function, &obj) ||
             !js_GetClassObject(cx, global, JSProto_Array, &obj) ||
             !js_GetClassObject(cx, global, JSProto_RegExp, &obj))
         {
             return false;
         }
     }
 
-    global->zone()->usedByExclusiveThread = true;
+    cx->runtime()->setUsedByExclusiveThread(global->zone());
 
     ScopedJSDeletePtr<ExclusiveContext> workercx(
         cx->new_<ExclusiveContext>(cx->runtime(), (PerThreadData *) NULL,
                                    ThreadSafeContext::Context_Exclusive));
     if (!workercx)
         return false;
 
     workercx->enterCompartment(global->compartment());
 
     ScopedJSDeletePtr<ParseTask> task(
-        cx->new_<ParseTask>(cx->runtime(), workercx.get(), options, chars, length));
+        cx->new_<ParseTask>(global->zone(), workercx.get(), options, chars, length,
+                            scopeChain, callback, callbackData));
     if (!task)
         return false;
 
     workercx.forget();
 
     WorkerThreadState &state = *cx->runtime()->workerThreadState;
     JS_ASSERT(state.numThreads);
 
@@ -425,16 +452,92 @@ WorkerThreadState::canStartIonCompile()
         return false;
     for (size_t i = 0; i < numThreads; i++) {
         if (threads[i].ionBuilder)
             return false;
     }
     return true;
 }
 
+bool
+WorkerThreadState::canStartParseTask()
+{
+    // Don't allow simultaneous off thread parses, to reduce contention on the
+    // atoms table. Note that asm.js compilation depends on this to avoid
+    // stalling the worker thread, as off thread parse tasks can trigger and
+    // block on other off thread asm.js compilation tasks.
+    JS_ASSERT(isLocked());
+    if (parseWorklist.empty())
+        return false;
+    for (size_t i = 0; i < numThreads; i++) {
+        if (threads[i].parseTask)
+            return false;
+    }
+    return true;
+}
+
+void
+WorkerThreadState::finishParseTaskForScript(JSScript *script)
+{
+    JSRuntime *rt = script->compartment()->runtimeFromMainThread();
+    ParseTask *parseTask = NULL;
+
+    {
+        AutoLockWorkerThreadState lock(rt);
+        for (size_t i = 0; i < parseFinishedList.length(); i++) {
+            if (parseFinishedList[i]->script == script) {
+                parseTask = parseFinishedList[i];
+                parseFinishedList[i] = parseFinishedList.back();
+                parseFinishedList.popBack();
+                break;
+            }
+        }
+    }
+    JS_ASSERT(parseTask);
+
+    // Mark the zone as no longer in use by an ExclusiveContext, and available
+    // to be collected by the GC.
+    rt->clearUsedByExclusiveThread(parseTask->zone);
+
+    if (!script) {
+        // Parsing failed and there is nothing to finish, but there still may
+        // be lingering ParseTask instances holding roots which need to be
+        // cleaned up. The ParseTask which we picked might not be the right
+        // one but this is ok as finish calls will be 1:1 with calls that
+        // create a ParseTask.
+        js_delete(parseTask);
+        return;
+    }
+
+    // Point the prototypes of any objects in the script's compartment to refer
+    // to the corresponding prototype in the new compartment. This will briefly
+    // create cross compartment pointers, which will be fixed by the
+    // MergeCompartments call below.
+    for (gc::CellIter iter(parseTask->zone, gc::FINALIZE_TYPE_OBJECT); !iter.done(); iter.next()) {
+        types::TypeObject *object = iter.get<types::TypeObject>();
+        JSObject *proto = object->proto;
+        if (!proto)
+            continue;
+
+        JSProtoKey key = js_IdentifyClassPrototype(proto);
+        if (key == JSProto_Null)
+            continue;
+
+        JSObject *newProto = GetClassPrototypePure(&parseTask->scopeChain->global(), key);
+        JS_ASSERT(newProto);
+
+        object->proto = newProto;
+    }
+
+    // Move the parsed script and all its contents into the desired compartment.
+    gc::MergeCompartments(parseTask->script->compartment(), parseTask->scopeChain->compartment());
+
+    js_delete(parseTask);
+}
+
 void
 WorkerThread::destroy()
 {
     WorkerThreadState &state = *runtime->workerThreadState;
 
     if (thread) {
         {
             AutoLockWorkerThreadState lock(runtime);
@@ -543,31 +646,37 @@ ExclusiveContext::setWorkerThread(Worker
     this->workerThread = workerThread;
     this->perThreadData = workerThread->threadData.addr();
 }
 
 void
 WorkerThread::handleParseWorkload(WorkerThreadState &state)
 {
     JS_ASSERT(state.isLocked());
-    JS_ASSERT(!state.parseWorklist.empty());
+    JS_ASSERT(state.canStartParseTask());
     JS_ASSERT(idle());
 
     parseTask = state.parseWorklist.popCopy();
     parseTask->cx->setWorkerThread(this);
 
     {
         AutoUnlockWorkerThreadState unlock(runtime);
         parseTask->script = frontend::CompileScript(parseTask->cx, &parseTask->alloc,
                                                     NullPtr(), NullPtr(),
                                                     parseTask->options,
                                                     parseTask->chars, parseTask->length);
     }
 
+    // The callback is invoked while we are still off the main thread.
+    parseTask->callback(parseTask->script, parseTask->callbackData);
+
+    // FinishOffThreadScript will need to be called on the script to
+    // migrate it into the correct compartment.
     state.parseFinishedList.append(parseTask);
+
     parseTask = NULL;
 
     // Notify the main thread in case it is waiting for the parse/emit to finish.
     state.notify(WorkerThreadState::MAIN);
 }
 
 void
 WorkerThread::threadLoop()
@@ -578,31 +687,31 @@ WorkerThread::threadLoop()
     js::TlsPerThreadData.set(threadData.addr());
 
     while (true) {
         JS_ASSERT(!ionBuilder && !asmData);
 
         // Block until a task is available.
         while (!state.canStartIonCompile() &&
                !state.canStartAsmJSCompile() &&
-               state.parseWorklist.empty())
+               !state.canStartParseTask())
         {
             if (state.shouldPause)
                 pause();
             if (terminate)
                 return;
             state.wait(WorkerThreadState::WORKER);
         }
 
         // Dispatch tasks, prioritizing AsmJS work.
         if (state.canStartAsmJSCompile())
             handleAsmJSWorkload(state);
         else if (state.canStartIonCompile())
             handleIonWorkload(state);
-        else if (!state.parseWorklist.empty())
+        else if (state.canStartParseTask())
             handleParseWorkload(state);
     }
 }
 
 AutoPauseWorkersForGC::AutoPauseWorkersForGC(JSRuntime *rt MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
   : runtime(rt), needsUnpause(false), oldExclusiveThreadsPaused(rt->exclusiveThreadsPaused)
 {
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
@@ -690,17 +799,18 @@ js::StartOffThreadIonCompile(JSContext *
 
 void
 js::CancelOffThreadIonCompile(JSCompartment *compartment, JSScript *script)
 {
 }
 
 bool
 js::StartOffThreadParseScript(JSContext *cx, const CompileOptions &options,
-                              const jschar *chars, size_t length)
+                              const jschar *chars, size_t length, HandleObject scopeChain,
+                              JS::OffThreadCompileCallback callback, void *callbackData)
 {
     MOZ_ASSUME_UNREACHABLE("Off thread compilation not available in non-THREADSAFE builds");
 }
 
 void
 js::WaitForOffThreadParsingToFinish(JSRuntime *rt)
 {
 }
--- a/js/src/jsworkers.h
+++ b/js/src/jsworkers.h
@@ -84,16 +84,17 @@ class WorkerThreadState
 # endif
 
     void wait(CondVar which, uint32_t timeoutMillis = 0);
     void notify(CondVar which);
     void notifyAll(CondVar which);
 
     bool canStartAsmJSCompile();
     bool canStartIonCompile();
+    bool canStartParseTask();
 
     uint32_t harvestFailedAsmJSJobs() {
         JS_ASSERT(isLocked());
         uint32_t n = numAsmJSFailedJobs;
         numAsmJSFailedJobs = 0;
         return n;
     }
     void noteAsmJSFailure(void *func) {
@@ -109,16 +110,18 @@ class WorkerThreadState
     void resetAsmJSFailureState() {
         numAsmJSFailedJobs = 0;
         asmJSFailedFunction = NULL;
     }
     void *maybeAsmJSFailedFunction() const {
         return asmJSFailedFunction;
     }
 
+    void finishParseTaskForScript(JSScript *script);
+
   private:
 
     /*
      * Lock protecting all mutable shared state accessed by helper threads, and
      * used by all condition variables.
      */
     PRLock *workerLock;
 
@@ -219,17 +222,18 @@ void
 CancelOffThreadIonCompile(JSCompartment *compartment, JSScript *script);
 
 /*
  * Start a parse/emit cycle for a stream of source. The characters must stay
  * alive until the compilation finishes.
  */
 bool
 StartOffThreadParseScript(JSContext *cx, const CompileOptions &options,
-                          const jschar *chars, size_t length);
+                          const jschar *chars, size_t length, HandleObject scopeChain,
+                          JS::OffThreadCompileCallback callback, void *callbackData);
 
 /* Block until in progress and pending off thread parse jobs have finished. */
 void
 WaitForOffThreadParsingToFinish(JSRuntime *rt);
 
 class AutoLockWorkerThreadState
 {
     JSRuntime *rt;
@@ -327,25 +331,39 @@ struct AsmJSParallelTask
         this->mir = mir;
         this->lir = NULL;
     }
 };
 #endif
 
 struct ParseTask
 {
-    JSRuntime *runtime;
+    Zone *zone;
     ExclusiveContext *cx;
     CompileOptions options;
     const jschar *chars;
     size_t length;
     LifoAlloc alloc;
 
+    // Rooted pointer to the scope in the target compartment which the
+    // resulting script will be merged into. This is not safe to use off the
+    // main thread.
+    JSObject *scopeChain;
+
+    // Callback invoked off the main thread when the parse finishes.
+    JS::OffThreadCompileCallback callback;
+    void *callbackData;
+
+    // Holds the final script between the invocation of the callback and the
+    // point where FinishOffThreadScript is called, which will destroy the
+    // ParseTask.
     JSScript *script;
 
-    ParseTask(JSRuntime *rt, ExclusiveContext *cx, const CompileOptions &options,
-              const jschar *chars, size_t length);
+    ParseTask(Zone *zone, ExclusiveContext *cx, const CompileOptions &options,
+              const jschar *chars, size_t length, JSObject *scopeChain,
+              JS::OffThreadCompileCallback callback, void *callbackData);
+
     ~ParseTask();
 };
 
 } /* namespace js */
 
 #endif /* jsworkers_h */
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -3230,16 +3230,23 @@ SyntaxParse(JSContext *cx, unsigned argc
     }
 
     args.rval().setBoolean(succeeded);
     return true;
 }
 
 #ifdef JS_THREADSAFE
 
+static void
+OffThreadCompileScriptCallback(JSScript *script, void *callbackData)
+{
+    // This callback is invoked off the main thread and there isn't a good way
+    // to pass the script on to the main thread. Just let the script leak.
+}
+
 static bool
 OffThreadCompileScript(JSContext *cx, unsigned argc, jsval *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     if (args.length() < 1) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_MORE_ARGS_NEEDED,
                              "offThreadCompileScript", "0", "s");
@@ -3266,18 +3273,21 @@ OffThreadCompileScript(JSContext *cx, un
     // but since the compiled script is never consumed there isn't much choice.
     JSString **permanentRoot = cx->new_<JSString *>();
     if (!permanentRoot)
         return false;
     *permanentRoot = scriptContents;
     if (!JS_AddStringRoot(cx, permanentRoot))
         return false;
 
-    if (!StartOffThreadParseScript(cx, options, chars, length))
+    if (!StartOffThreadParseScript(cx, options, chars, length, cx->global(),
+                                   OffThreadCompileScriptCallback, NULL))
+    {
         return false;
+    }
 
     args.rval().setUndefined();
     return true;
 }
 
 #endif // JS_THREADSAFE
 
 struct FreeOnReturn
--- a/js/src/vm/RegExpObject.cpp
+++ b/js/src/vm/RegExpObject.cpp
@@ -676,16 +676,23 @@ RegExpCompartment::sweep(JSRuntime *rt)
         RegExpShared *shared = e.front();
         if (shared->activeUseCount == 0 && shared->gcNumberWhenUsed < rt->gcStartNumber) {
             js_delete(shared);
             e.removeFront();
         }
     }
 }
 
+void
+RegExpCompartment::clearTables()
+{
+    JS_ASSERT(inUse_.empty());
+    map_.clear();
+}
+
 bool
 RegExpCompartment::get(ExclusiveContext *cx, JSAtom *source, RegExpFlag flags, RegExpGuard *g)
 {
     Key key(source, flags);
     Map::AddPtr p = map_.lookupForAdd(key);
     if (p) {
         g->init(*p->value);
         return true;
--- a/js/src/vm/RegExpObject.h
+++ b/js/src/vm/RegExpObject.h
@@ -314,16 +314,17 @@ class RegExpCompartment
     PendingSet inUse_;
 
   public:
     RegExpCompartment(JSRuntime *rt);
     ~RegExpCompartment();
 
     bool init(JSContext *cx);
     void sweep(JSRuntime *rt);
+    void clearTables();
 
     bool get(ExclusiveContext *cx, JSAtom *source, RegExpFlag flags, RegExpGuard *g);
 
     /* Like 'get', but compile 'maybeOpt' (if non-null). */
     bool get(JSContext *cx, HandleAtom source, JSString *maybeOpt, RegExpGuard *g);
 
     size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf);
 };
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -708,16 +708,32 @@ JSRuntime::onOutOfMemory(void *p, size_t
         return p;
     if (cx)
         js_ReportOutOfMemory(cx);
     return NULL;
 }
 
 #ifdef JS_THREADSAFE
 
+void
+JSRuntime::setUsedByExclusiveThread(Zone *zone)
+{
+    JS_ASSERT(!zone->usedByExclusiveThread);
+    zone->usedByExclusiveThread = true;
+    numExclusiveThreads++;
+}
+
+void
+JSRuntime::clearUsedByExclusiveThread(Zone *zone)
+{
+    JS_ASSERT(zone->usedByExclusiveThread);
+    zone->usedByExclusiveThread = false;
+    numExclusiveThreads--;
+}
+
 bool
 js::CurrentThreadCanAccessRuntime(JSRuntime *rt)
 {
     PerThreadData *pt = js::TlsPerThreadData.get();
     JS_ASSERT(pt && pt->associatedWith(rt));
     return rt->ownerThread_ == PR_GetCurrentThread() || InExclusiveParallelSection();
 }
 
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -769,16 +769,19 @@ struct JSRuntime : public JS::shadow::Ru
 
     /* Number of non-main threads with an ExclusiveContext. */
     size_t numExclusiveThreads;
 
     friend class js::AutoLockForExclusiveAccess;
     friend class js::AutoPauseWorkersForGC;
 
   public:
+    void setUsedByExclusiveThread(JS::Zone *zone);
+    void clearUsedByExclusiveThread(JS::Zone *zone);
+
 #endif // JS_THREADSAFE
 
     bool currentThreadHasExclusiveAccess() {
 #if defined(JS_THREADSAFE) && defined(DEBUG)
         return (!numExclusiveThreads && mainThreadHasExclusiveAccess) ||
             exclusiveThreadsPaused ||
             exclusiveAccessOwner == PR_GetCurrentThread();
 #else
@@ -1293,17 +1296,17 @@ struct JSRuntime : public JS::shadow::Ru
     /* Client opaque pointers */
     void                *data;
 
     /* Synchronize GC heap access between main thread and GCHelperThread. */
     PRLock              *gcLock;
 
     js::GCHelperThread  gcHelperThread;
 
-#ifdef XP_MACOSX
+#if defined(XP_MACOSX) && defined(JS_ION)
     js::AsmJSMachExceptionHandler asmJSMachExceptionHandler;
 #endif
 
 #ifdef JS_THREADSAFE
 # ifdef JS_ION
     js::WorkerThreadState *workerThreadState;
 # endif
 
@@ -1398,16 +1401,18 @@ struct JSRuntime : public JS::shadow::Ru
 
     bool isAtomsCompartment(JSCompartment *comp) {
         return comp == atomsCompartment_;
     }
 
     // The atoms compartment is the only one in its zone.
     inline bool isAtomsZone(JS::Zone *zone);
 
+    inline bool atomsZoneNeedsBarrier();
+
     union {
         /*
          * Cached pointers to various interned property names, initialized in
          * order from first to last via the other union arm.
          */
         JSAtomState atomState;
 
         js::FixedHeapPtr<js::PropertyName> firstCachedName;
--- a/js/src/vm/Shape.h
+++ b/js/src/vm/Shape.h
@@ -225,22 +225,27 @@ struct ShapeTable {
  * taken to call JSObject::canRemoveLastProperty when unwinding an object to
  * an earlier property, however.
  */
 
 class Shape;
 class UnownedBaseShape;
 struct StackBaseShape;
 
+namespace gc {
+void MergeCompartments(JSCompartment *source, JSCompartment *target);
+}
+
 class BaseShape : public js::gc::Cell
 {
   public:
     friend class Shape;
     friend struct StackBaseShape;
     friend struct StackShape;
+    friend void gc::MergeCompartments(JSCompartment *source, JSCompartment *target);
 
     enum Flag {
         /* Owned by the referring shape. */
         OWNED_SHAPE        = 0x1,
 
         /* getterObj/setterObj are active in unions below. */
         HAS_GETTER_OBJECT  = 0x2,
         HAS_SETTER_OBJECT  = 0x4,
--- a/js/src/vm/String.cpp
+++ b/js/src/vm/String.cpp
@@ -246,17 +246,17 @@ JSRope::getCharsNonDestructiveInternal(T
         s[n] = 0;
 
     out.reset(s);
     return true;
 }
 
 template<JSRope::UsingBarrier b>
 JSFlatString *
-JSRope::flattenInternal(JSContext *maybecx)
+JSRope::flattenInternal(ExclusiveContext *maybecx)
 {
     /*
      * Perform a depth-first dag traversal, splatting each node's characters
      * into a contiguous buffer. Visit each rope node three times:
      *   1. record position in the buffer and recurse into left child;
      *   2. recurse into the right child;
      *   3. transform the node into a dependent string.
      * To avoid maintaining a stack, tree nodes are mutated to indicate how many
@@ -388,17 +388,17 @@ JSRope::flattenInternal(JSContext *maybe
         if (progress == 0x200)
             goto visit_right_child;
         JS_ASSERT(progress == 0x300);
         goto finish_node;
     }
 }
 
 JSFlatString *
-JSRope::flatten(JSContext *maybecx)
+JSRope::flatten(ExclusiveContext *maybecx)
 {
 #if JSGC_INCREMENTAL
     if (zone()->needsBarrier())
         return flattenInternal<WithIncrementalBarrier>(maybecx);
     else
         return flattenInternal<NoBarrier>(maybecx);
 #else
     return flattenInternal<NoBarrier>(maybecx);
@@ -466,17 +466,17 @@ JSDependentString::getCharsZNonDestructi
     PodCopy(s, chars(), n);
     s[n] = 0;
 
     out.reset(s);
     return true;
 }
 
 JSFlatString *
-JSDependentString::undepend(JSContext *cx)
+JSDependentString::undepend(ExclusiveContext *cx)
 {
     JS_ASSERT(JSString::isDependent());
 
     /*
      * We destroy the base() pointer in undepend, so we need a pre-barrier. We
      * don't need a post-barrier because there aren't any outgoing pointers
      * afterwards.
      */
@@ -497,17 +497,17 @@ JSDependentString::undepend(JSContext *c
      * for the benefit of any other dependent string that depends on *this.
      */
     d.lengthAndFlags = buildLengthAndFlags(n, UNDEPENDED_FLAGS);
 
     return &this->asFlat();
 }
 
 JSStableString *
-JSInlineString::uninline(JSContext *maybecx)
+JSInlineString::uninline(ExclusiveContext *maybecx)
 {
     JS_ASSERT(isInline());
     size_t n = length();
     jschar *news = maybecx ? maybecx->pod_malloc<jschar>(n + 1) : js_pod_malloc<jschar>(n + 1);
     if (!news)
         return NULL;
     js_strncpy(news, d.inlineStorage, n);
     news[n] = 0;
@@ -566,18 +566,18 @@ JSFlatString::isIndexSlow(uint32_t *inde
 }
 
 bool
 ScopedThreadSafeStringInspector::ensureChars(ThreadSafeContext *cx)
 {
     if (chars_)
         return true;
 
-    if (cx->isJSContext()) {
-        JSLinearString *linear = str_->ensureLinear(cx->asJSContext());
+    if (cx->isExclusiveContext()) {
+        JSLinearString *linear = str_->ensureLinear(cx->asExclusiveContext());
         if (!linear)
             return false;
         chars_ = linear->chars();
     } else {
         chars_ = str_->maybeChars();
         if (!chars_) {
             if (!str_->getCharsNonDestructive(cx, scopedChars_))
                 return false;
--- a/js/src/vm/String.h
+++ b/js/src/vm/String.h
@@ -261,19 +261,19 @@ class JSString : public js::gc::Cell
         return d.lengthAndFlags <= FLAGS_MASK;
     }
 
     /*
      * All strings have a fallible operation to get an array of chars.
      * getCharsZ additionally ensures the array is null terminated.
      */
 
-    inline const jschar *getChars(JSContext *cx);
-    inline const jschar *getCharsZ(JSContext *cx);
-    inline bool getChar(JSContext *cx, size_t index, jschar *code);
+    inline const jschar *getChars(js::ExclusiveContext *cx);
+    inline const jschar *getCharsZ(js::ExclusiveContext *cx);
+    inline bool getChar(js::ExclusiveContext *cx, size_t index, jschar *code);
 
     /*
      * Returns chars() if the string is already linear or flat. Otherwise
      * returns NULL if a new array of chars must be allocated.
      */
     inline const jschar *maybeChars() const;
     inline const jschar *maybeCharsZ() const;
 
@@ -284,21 +284,21 @@ class JSString : public js::gc::Cell
 
     inline bool getCharsNonDestructive(js::ThreadSafeContext *cx,
                                        js::ScopedJSFreePtr<jschar> &out) const;
     inline bool getCharsZNonDestructive(js::ThreadSafeContext *cx,
                                         js::ScopedJSFreePtr<jschar> &out) const;
 
     /* Fallible conversions to more-derived string types. */
 
-    inline JSLinearString *ensureLinear(JSContext *cx);
-    inline JSFlatString *ensureFlat(JSContext *cx);
-    inline JSStableString *ensureStable(JSContext *cx);
+    inline JSLinearString *ensureLinear(js::ExclusiveContext *cx);
+    inline JSFlatString *ensureFlat(js::ExclusiveContext *cx);
+    inline JSStableString *ensureStable(js::ExclusiveContext *cx);
 
-    static bool ensureLinear(JSContext *cx, JSString *str) {
+    static bool ensureLinear(js::ExclusiveContext *cx, JSString *str) {
         return str->ensureLinear(cx) != NULL;
     }
 
     /* Type query and debug-checked casts */
 
     JS_ALWAYS_INLINE
     bool isRope() const {
         return (d.lengthAndFlags & FLAGS_MASK) == ROPE_FLAGS;
@@ -460,20 +460,20 @@ class JSRope : public JSString
 
     bool getCharsNonDestructive(js::ThreadSafeContext *cx,
                                 js::ScopedJSFreePtr<jschar> &out) const;
     bool getCharsZNonDestructive(js::ThreadSafeContext *cx,
                                  js::ScopedJSFreePtr<jschar> &out) const;
 
     enum UsingBarrier { WithIncrementalBarrier, NoBarrier };
     template<UsingBarrier b>
-    JSFlatString *flattenInternal(JSContext *cx);
+    JSFlatString *flattenInternal(js::ExclusiveContext *cx);
 
     friend class JSString;
-    JSFlatString *flatten(JSContext *cx);
+    JSFlatString *flatten(js::ExclusiveContext *cx);
 
     void init(js::ThreadSafeContext *cx, JSString *left, JSString *right, size_t length);
 
   public:
     template <js::AllowGC allowGC>
     static inline JSRope *new_(js::ThreadSafeContext *cx,
                                typename js::MaybeRooted<JSString*, allowGC>::HandleType left,
                                typename js::MaybeRooted<JSString*, allowGC>::HandleType right,
@@ -526,17 +526,17 @@ class JSLinearString : public JSString
 JS_STATIC_ASSERT(sizeof(JSLinearString) == sizeof(JSString));
 
 class JSDependentString : public JSLinearString
 {
     bool getCharsZNonDestructive(js::ThreadSafeContext *cx,
                                  js::ScopedJSFreePtr<jschar> &out) const;
 
     friend class JSString;
-    JSFlatString *undepend(JSContext *cx);
+    JSFlatString *undepend(js::ExclusiveContext *cx);
 
     void init(js::ThreadSafeContext *cx, JSLinearString *base, const jschar *chars,
               size_t length);
 
     /* Vacuous and therefore unimplemented. */
     bool isDependent() const MOZ_DELETE;
     JSDependentString &asDependent() const MOZ_DELETE;
 
@@ -698,17 +698,17 @@ class JSInlineString : public JSFlatStri
     static const size_t MAX_INLINE_LENGTH = NUM_INLINE_CHARS - 1;
 
   public:
     template <js::AllowGC allowGC>
     static inline JSInlineString *new_(js::ThreadSafeContext *cx);
 
     inline jschar *init(size_t length);
 
-    JSStableString *uninline(JSContext *cx);
+    JSStableString *uninline(js::ExclusiveContext *cx);
 
     inline void resetLength(size_t length);
 
     static bool lengthFits(size_t length) {
         return length <= MAX_INLINE_LENGTH;
     }
 
     static size_t offsetOfInlineStorage() {
@@ -1004,25 +1004,25 @@ class AutoNameVector : public AutoVector
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
 } /* namespace js */
 
 /* Avoid requiring vm/String-inl.h just to call getChars. */
 
 JS_ALWAYS_INLINE const jschar *
-JSString::getChars(JSContext *cx)
+JSString::getChars(js::ExclusiveContext *cx)
 {
     if (JSLinearString *str = ensureLinear(cx))
         return str->chars();
     return NULL;
 }
 
 JS_ALWAYS_INLINE bool
-JSString::getChar(JSContext *cx, size_t index, jschar *code)
+JSString::getChar(js::ExclusiveContext *cx, size_t index, jschar *code)
 {
     JS_ASSERT(index < length());
 
     /*
      * Optimization for one level deep ropes.
      * This is common for the following pattern:
      *
      * while() {
@@ -1046,17 +1046,17 @@ JSString::getChar(JSContext *cx, size_t 
     if (!chars)
         return false;
 
     *code = chars[index];
     return true;
 }
 
 JS_ALWAYS_INLINE const jschar *
-JSString::getCharsZ(JSContext *cx)
+JSString::getCharsZ(js::ExclusiveContext *cx)
 {
     if (JSFlatString *str = ensureFlat(cx))
         return str->chars();
     return NULL;
 }
 
 JS_ALWAYS_INLINE const jschar *
 JSString::maybeChars() const
@@ -1090,35 +1090,35 @@ JSString::getCharsZNonDestructive(js::Th
     /* If string is already flat, use maybeCharsZ instead. */
     JS_ASSERT(!isFlat());
     if (isDependent())
         return asDependent().getCharsZNonDestructive(cx, out);
     return asRope().getCharsZNonDestructive(cx, out);
 }
 
 JS_ALWAYS_INLINE JSLinearString *
-JSString::ensureLinear(JSContext *cx)
+JSString::ensureLinear(js::ExclusiveContext *cx)
 {
     return isLinear()
            ? &asLinear()
            : asRope().flatten(cx);
 }
 
 JS_ALWAYS_INLINE JSFlatString *
-JSString::ensureFlat(JSContext *cx)
+JSString::ensureFlat(js::ExclusiveContext *cx)
 {
     return isFlat()
            ? &asFlat()
            : isDependent()
              ? asDependent().undepend(cx)
              : asRope().flatten(cx);
 }
 
 JS_ALWAYS_INLINE JSStableString *
-JSString::ensureStable(JSContext *maybecx)
+JSString::ensureStable(js::ExclusiveContext *maybecx)
 {
     if (isRope()) {
         JSFlatString *flat = asRope().flatten(maybecx);
         if (!flat)
             return NULL;
         JS_ASSERT(!flat->isInline());
         return &flat->asStable();
     }