Bug 906371 - Use off thread JS parsing when loading async scripts, r=bz,billm.
authorBrian Hackett <bhackett1024@gmail.com>
Wed, 11 Sep 2013 17:42:09 -0600
changeset 159633 3ca22e239a1dbdb25993a49a7bba63ad4143e832
parent 159632 6d30f146c472553eae0881024c9504cdf1f0670a
child 159634 b9552f5d4476f4f079c80a8ff4f4c8312fe71b3d
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)
reviewersbz, billm
bugs906371
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 906371 - Use off thread JS parsing when loading async scripts, r=bz,billm.
content/base/src/nsScriptLoader.cpp
content/base/src/nsScriptLoader.h
dom/base/nsIScriptContext.h
dom/base/nsJSEnvironment.cpp
dom/base/nsJSEnvironment.h
dom/base/nsJSUtils.cpp
dom/base/nsJSUtils.h
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jsatom.cpp
js/src/jscntxt.h
js/src/jscntxtinlines.h
js/src/jsopcode.cpp
js/src/jsopcode.h
js/src/jsstr.h
js/src/jsworkers.cpp
--- a/content/base/src/nsScriptLoader.cpp
+++ b/content/base/src/nsScriptLoader.cpp
@@ -12,16 +12,17 @@
 #include "jsfriendapi.h"
 #include "nsScriptLoader.h"
 #include "nsICharsetConverterManager.h"
 #include "nsIUnicodeDecoder.h"
 #include "nsIContent.h"
 #include "mozilla/dom/Element.h"
 #include "nsGkAtoms.h"
 #include "nsNetUtil.h"
+#include "nsIJSRuntimeService.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsIScriptContext.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIPrincipal.h"
 #include "nsJSPrincipals.h"
 #include "nsContentPolicyUtils.h"
 #include "nsIHttpChannel.h"
 #include "nsIHttpChannelInternal.h"
@@ -93,16 +94,17 @@ public:
 
   nsCOMPtr<nsIScriptElement> mElement;
   bool mLoading;             // Are we still waiting for a load to complete?
   bool mIsInline;            // Is the script inline or loaded?
   nsString mScriptText;              // Holds script for loaded scripts
   uint32_t mJSVersion;
   nsCOMPtr<nsIURI> mURI;
   nsCOMPtr<nsIPrincipal> mOriginPrincipal;
+  nsAutoCString mURL;   // Keep the URI's filename alive during off thread parsing.
   int32_t mLineNo;
   const CORSMode mCORSMode;
 };
 
 // The nsScriptLoadRequest is passed as the context to necko, and thus
 // it needs to be threadsafe. Necko won't do anything with this
 // context, but it will AddRef and Release it on other threads.
 NS_IMPL_ISUPPORTS0(nsScriptLoadRequest)
@@ -686,22 +688,132 @@ nsScriptLoader::ProcessScriptElement(nsI
   // there's no document.write currently on the call stack. However,
   // this way matches IE more closely than checking if document.write
   // is on the call stack.
   NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
       "Not safe to run a parser-inserted script?");
   return ProcessRequest(request) == NS_ERROR_HTMLPARSER_BLOCK;
 }
 
+namespace {
+
+class NotifyOffThreadScriptLoadCompletedRunnable : public nsRunnable
+{
+    nsRefPtr<nsScriptLoader> mLoader;
+    void *mToken;
+
+public:
+    NotifyOffThreadScriptLoadCompletedRunnable(already_AddRefed<nsScriptLoader> aLoader,
+                                               void *aToken)
+      : mLoader(aLoader), mToken(aToken)
+    {}
+
+    NS_DECL_NSIRUNNABLE
+};
+
+} /* anonymous namespace */
+
 nsresult
-nsScriptLoader::ProcessRequest(nsScriptLoadRequest* aRequest)
+nsScriptLoader::ProcessOffThreadRequest(void **aOffThreadToken)
+{
+    nsCOMPtr<nsScriptLoadRequest> request = mOffThreadScriptRequest;
+    mOffThreadScriptRequest = nullptr;
+    mDocument->UnblockOnload(false);
+
+    return ProcessRequest(request, aOffThreadToken);
+}
+
+NS_IMETHODIMP
+NotifyOffThreadScriptLoadCompletedRunnable::Run()
+{
+    MOZ_ASSERT(NS_IsMainThread());
+
+    nsresult rv = mLoader->ProcessOffThreadRequest(&mToken);
+
+    if (mToken) {
+      // The result of the off thread parse was not actually needed to process
+      // the request (disappearing window, some other error, ...). Finish the
+      // request to avoid leaks in the JS engine.
+      nsCOMPtr<nsIJSRuntimeService> svc = do_GetService("@mozilla.org/js/xpc/RuntimeService;1");
+      NS_ENSURE_TRUE(svc, NS_ERROR_FAILURE);
+      JSRuntime *rt;
+      svc->GetRuntime(&rt);
+      NS_ENSURE_TRUE(rt, NS_ERROR_FAILURE);
+      JS::FinishOffThreadScript(nullptr, rt, mToken);
+    }
+
+    return rv;
+}
+
+static void
+OffThreadScriptLoaderCallback(void *aToken, void *aCallbackData)
+{
+    // Be careful not to adjust the refcount on the loader, as this callback
+    // may be invoked off the main thread.
+    nsScriptLoader* aLoader = static_cast<nsScriptLoader*>(aCallbackData);
+    nsRefPtr<NotifyOffThreadScriptLoadCompletedRunnable> notify =
+        new NotifyOffThreadScriptLoadCompletedRunnable(
+            already_AddRefed<nsScriptLoader>(aLoader), aToken);
+    NS_DispatchToMainThread(notify);
+}
+
+nsresult
+nsScriptLoader::AttemptAsyncScriptParse(nsScriptLoadRequest* aRequest)
+{
+  if (!aRequest->mElement->GetScriptAsync() || aRequest->mIsInline) {
+    return NS_ERROR_FAILURE;
+  }
+
+  if (mOffThreadScriptRequest) {
+    return NS_ERROR_FAILURE;
+  }
+
+  JSObject *unrootedGlobal;
+  nsCOMPtr<nsIScriptContext> context = GetScriptContext(&unrootedGlobal);
+  if (!context) {
+    return NS_ERROR_FAILURE;
+  }
+  AutoPushJSContext cx(context->GetNativeContext());
+  JS::Rooted<JSObject*> global(cx, unrootedGlobal);
+
+  JS::CompileOptions options(cx);
+  FillCompileOptionsForRequest(aRequest, &options);
+
+  if (!JS::CanCompileOffThread(cx, options)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  mOffThreadScriptRequest = aRequest;
+  if (!JS::CompileOffThread(cx, global, options,
+                            aRequest->mScriptText.get(), aRequest->mScriptText.Length(),
+                            OffThreadScriptLoaderCallback,
+                            static_cast<void*>(this))) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  // This reference will be consumed by the NotifyOffThreadScriptLoadCompletedRunnable.
+  NS_ADDREF(this);
+
+  mDocument->BlockOnload();
+
+  return NS_OK;
+}
+
+nsresult
+nsScriptLoader::ProcessRequest(nsScriptLoadRequest* aRequest, void **aOffThreadToken)
 {
   NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
                "Processing requests when running scripts is unsafe.");
 
+  if (!aOffThreadToken) {
+    nsresult rv = AttemptAsyncScriptParse(aRequest);
+    if (rv != NS_ERROR_FAILURE)
+      return rv;
+  }
+
   NS_ENSURE_ARG(aRequest);
   nsAFlatString* script;
   nsAutoString textData;
 
   nsCOMPtr<nsIDocument> doc;
 
   nsCOMPtr<nsINode> scriptElem = do_QueryInterface(aRequest->mElement);
 
@@ -746,17 +858,17 @@ nsScriptLoader::ProcessRequest(nsScriptL
   }
 
   nsresult rv = NS_OK;
   if (runScript) {
     if (doc) {
       doc->BeginEvaluatingExternalScript();
     }
     aRequest->mElement->BeginEvaluating();
-    rv = EvaluateScript(aRequest, *script);
+    rv = EvaluateScript(aRequest, *script, aOffThreadToken);
     aRequest->mElement->EndEvaluating();
     if (doc) {
       doc->EndEvaluatingExternalScript();
     }
 
     nsContentUtils::DispatchTrustedEvent(scriptElem->OwnerDoc(),
                                          scriptElem,
                                          NS_LITERAL_STRING("afterscriptexecute"),
@@ -794,80 +906,97 @@ nsScriptLoader::FireScriptEvaluated(nsre
     nsCOMPtr<nsIScriptLoaderObserver> obs = mObservers[i];
     obs->ScriptEvaluated(aResult, aRequest->mElement,
                          aRequest->mIsInline);
   }
 
   aRequest->FireScriptEvaluated(aResult);
 }
 
+nsIScriptContext *
+nsScriptLoader::GetScriptContext(JSObject **aGlobal)
+{
+  nsPIDOMWindow *pwin = mDocument->GetInnerWindow();
+  NS_ASSERTION(pwin, "shouldn't be called with a null inner window");
+
+  nsCOMPtr<nsIScriptGlobalObject> globalObject = do_QueryInterface(pwin);
+  NS_ASSERTION(globalObject, "windows must be global objects");
+
+  // and make sure we are setup for this type of script.
+  nsresult rv = globalObject->EnsureScriptEnvironment();
+  if (NS_FAILED(rv)) {
+    return nullptr;
+  }
+
+  *aGlobal = globalObject->GetGlobalJSObject();
+  return globalObject->GetScriptContext();
+}
+
+void
+nsScriptLoader::FillCompileOptionsForRequest(nsScriptLoadRequest *aRequest,
+                                             JS::CompileOptions *aOptions)
+{
+  // It's very important to use aRequest->mURI, not the final URI of the channel
+  // aRequest ended up getting script data from, as the script filename.
+  nsContentUtils::GetWrapperSafeScriptFilename(mDocument, aRequest->mURI, aRequest->mURL);
+
+  aOptions->setFileAndLine(aRequest->mURL.get(), aRequest->mLineNo);
+  aOptions->setVersion(JSVersion(aRequest->mJSVersion));
+  if (aRequest->mOriginPrincipal) {
+    aOptions->setOriginPrincipals(nsJSPrincipals::get(aRequest->mOriginPrincipal));
+  }
+}
+
 nsresult
 nsScriptLoader::EvaluateScript(nsScriptLoadRequest* aRequest,
-                               const nsAFlatString& aScript)
+                               const nsAFlatString& aScript,
+                               void** aOffThreadToken)
 {
   nsresult rv = NS_OK;
 
   // We need a document to evaluate scripts.
   if (!mDocument) {
     return NS_ERROR_FAILURE;
   }
 
   nsCOMPtr<nsIContent> scriptContent(do_QueryInterface(aRequest->mElement));
   nsIDocument* ownerDoc = scriptContent->OwnerDoc();
   if (ownerDoc != mDocument) {
     // Willful violation of HTML5 as of 2010-12-01
     return NS_ERROR_FAILURE;
   }
 
-  nsPIDOMWindow *pwin = mDocument->GetInnerWindow();
-  NS_ASSERTION(pwin, "shouldn't be called with a null inner window");
-
-  nsCOMPtr<nsIScriptGlobalObject> globalObject = do_QueryInterface(pwin);
-  NS_ASSERTION(globalObject, "windows must be global objects");
-
   // Get the script-type to be used by this element.
   NS_ASSERTION(scriptContent, "no content - what is default script-type?");
 
-  // and make sure we are setup for this type of script.
-  rv = globalObject->EnsureScriptEnvironment();
-  if (NS_FAILED(rv))
-    return rv;
-
   // Make sure context is a strong reference since we access it after
   // we've executed a script, which may cause all other references to
   // the context to go away.
-  nsCOMPtr<nsIScriptContext> context = globalObject->GetScriptContext();
+  JSObject *unrootedGlobal;
+  nsCOMPtr<nsIScriptContext> context = GetScriptContext(&unrootedGlobal);
   if (!context) {
     return NS_ERROR_FAILURE;
   }
   AutoPushJSContext cx(context->GetNativeContext());
+  JS::Rooted<JSObject*> global(cx, unrootedGlobal);
 
   bool oldProcessingScriptTag = context->GetProcessingScriptTag();
   context->SetProcessingScriptTag(true);
 
   // Update our current script.
   nsCOMPtr<nsIScriptElement> oldCurrent = mCurrentScript;
   mCurrentScript = aRequest->mElement;
 
-  // It's very important to use aRequest->mURI, not the final URI of the channel
-  // aRequest ended up getting script data from, as the script filename.
-  nsAutoCString url;
-  nsContentUtils::GetWrapperSafeScriptFilename(mDocument, aRequest->mURI, url);
-
   JSVersion version = JSVersion(aRequest->mJSVersion);
   if (version != JSVERSION_UNKNOWN) {
     JS::CompileOptions options(cx);
-    options.setFileAndLine(url.get(), aRequest->mLineNo)
-           .setVersion(JSVersion(aRequest->mJSVersion));
-    if (aRequest->mOriginPrincipal) {
-      options.setOriginPrincipals(nsJSPrincipals::get(aRequest->mOriginPrincipal));
-    }
-    JS::Rooted<JSObject*> global(cx, globalObject->GetGlobalJSObject());
+    FillCompileOptionsForRequest(aRequest, &options);
     rv = context->EvaluateString(aScript, global,
-                                 options, /* aCoerceToString = */ false, nullptr);
+                                 options, /* aCoerceToString = */ false, nullptr,
+                                 aOffThreadToken);
   }
 
   // Put the old script back in case it wants to do anything else.
   mCurrentScript = oldCurrent;
 
   context->SetProcessingScriptTag(oldProcessingScriptTag);
   return rv;
 }
--- a/content/base/src/nsScriptLoader.h
+++ b/content/base/src/nsScriptLoader.h
@@ -202,16 +202,22 @@ public:
    *                     Void if not present.
    * @param aScriptFromHead Whether or not the script was a child of head
    */
   virtual void PreloadURI(nsIURI *aURI, const nsAString &aCharset,
                           const nsAString &aType,
                           const nsAString &aCrossOrigin,
                           bool aScriptFromHead);
 
+  /**
+   * Process a request that was deferred so that the script could be compiled
+   * off thread.
+   */
+  nsresult ProcessOffThreadRequest(void **aOffThreadToken);
+
 private:
   /**
    * Unblocks the creator parser of the parser-blocking scripts.
    */
   void UnblockParser(nsScriptLoadRequest* aParserBlockingRequest);
 
   /**
    * Asynchronously resumes the creator parser of the parser-blocking scripts.
@@ -256,24 +262,31 @@ private:
   bool SelfReadyToExecuteScripts()
   {
     return mEnabled && !mBlockerCount;
   }
 
   bool AddPendingChildLoader(nsScriptLoader* aChild) {
     return mPendingChildLoaders.AppendElement(aChild) != nullptr;
   }
-  
-  nsresult ProcessRequest(nsScriptLoadRequest* aRequest);
+
+  nsresult AttemptAsyncScriptParse(nsScriptLoadRequest* aRequest);
+  nsresult ProcessRequest(nsScriptLoadRequest* aRequest,
+                          void **aOffThreadToken = nullptr);
   void FireScriptAvailable(nsresult aResult,
                            nsScriptLoadRequest* aRequest);
   void FireScriptEvaluated(nsresult aResult,
                            nsScriptLoadRequest* aRequest);
   nsresult EvaluateScript(nsScriptLoadRequest* aRequest,
-                          const nsAFlatString& aScript);
+                          const nsAFlatString& aScript,
+                          void **aOffThreadToken);
+
+  nsIScriptContext *GetScriptContext(JSObject **aGlobal);
+  void FillCompileOptionsForRequest(nsScriptLoadRequest *aRequest,
+                                    JS::CompileOptions *aOptions);
 
   nsresult PrepareLoadedRequest(nsScriptLoadRequest* aRequest,
                                 nsIStreamLoader* aLoader,
                                 nsresult aStatus,
                                 uint32_t aStringLen,
                                 const uint8_t* aString);
 
   nsIDocument* mDocument;                   // [WEAK]
@@ -297,16 +310,17 @@ private:
       return aRequest == aPi.mRequest;
     }
   };
   struct PreloadURIComparator {
     bool Equals(const PreloadInfo &aPi, nsIURI * const &aURI) const;
   };
   nsTArray<PreloadInfo> mPreloads;
 
+  nsCOMPtr<nsScriptLoadRequest> mOffThreadScriptRequest;
   nsCOMPtr<nsIScriptElement> mCurrentScript;
   nsCOMPtr<nsIScriptElement> mCurrentParserInsertedScript;
   // XXXbz do we want to cycle-collect these or something?  Not sure.
   nsTArray< nsRefPtr<nsScriptLoader> > mPendingChildLoaders;
   uint32_t mBlockerCount;
   bool mEnabled;
   bool mDeferEnabled;
   bool mDocumentParsingDone;
--- a/dom/base/nsIScriptContext.h
+++ b/dom/base/nsIScriptContext.h
@@ -53,22 +53,25 @@ public:
    * @param aOptions an options object. You probably want to at least set
    *                 filename and line number. The principal is computed
    *                 internally, though 'originPrincipals' may be passed.
    * @param aCoerceToString if the return value is not JSVAL_VOID, convert it
    *                        to a string before returning.
    * @param aRetValue the result of executing the script.  Pass null if you
    *                  don't care about the result.  Note that asking for a
    *                  result will deoptimize your script somewhat in many cases.
+   * @param aOffThreadToken if specified, the result of compiling the script
+   *                        on another thread.
    */
   virtual nsresult EvaluateString(const nsAString& aScript,
                                   JS::Handle<JSObject*> aScopeObject,
                                   JS::CompileOptions& aOptions,
                                   bool aCoerceToString,
-                                  JS::Value* aRetValue) = 0;
+                                  JS::Value* aRetValue,
+                                  void **aOffThreadToken = nullptr) = 0;
 
   /**
    * Bind an already-compiled event handler function to the given
    * target.  Scripting languages with static scoping must re-bind the
    * scope chain for aHandler to begin (after the activation scope for
    * aHandler itself, typically) with aTarget's scope.
    *
    * The result of the bind operation is a new handler object, with
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -975,28 +975,30 @@ nsJSContext::GetCCRefcnt()
   return refcnt;
 }
 
 nsresult
 nsJSContext::EvaluateString(const nsAString& aScript,
                             JS::Handle<JSObject*> aScopeObject,
                             JS::CompileOptions& aCompileOptions,
                             bool aCoerceToString,
-                            JS::Value* aRetValue)
+                            JS::Value* aRetValue,
+                            void **aOffThreadToken)
 {
   NS_ENSURE_TRUE(mIsInitialized, NS_ERROR_NOT_INITIALIZED);
   if (!mScriptsEnabled) {
     return NS_OK;
   }
 
   AutoCxPusher pusher(mContext);
   nsJSUtils::EvaluateOptions evalOptions;
   evalOptions.setCoerceToString(aCoerceToString);
   return nsJSUtils::EvaluateString(mContext, aScript, aScopeObject,
-                                   aCompileOptions, evalOptions, aRetValue);
+                                   aCompileOptions, evalOptions, aRetValue,
+                                   aOffThreadToken);
 }
 
 #ifdef DEBUG
 bool
 AtomIsEventHandlerName(nsIAtom *aName)
 {
   const PRUnichar *name = aName->GetUTF16String();
 
--- a/dom/base/nsJSEnvironment.h
+++ b/dom/base/nsJSEnvironment.h
@@ -39,17 +39,18 @@ public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsJSContext,
                                                          nsIScriptContext)
 
   virtual nsresult EvaluateString(const nsAString& aScript,
                                   JS::Handle<JSObject*> aScopeObject,
                                   JS::CompileOptions &aOptions,
                                   bool aCoerceToString,
-                                  JS::Value* aRetValue) MOZ_OVERRIDE;
+                                  JS::Value* aRetValue,
+                                  void **aOffThreadToken = nullptr) MOZ_OVERRIDE;
 
   virtual nsresult BindCompiledEventHandler(nsISupports *aTarget,
                                             JS::Handle<JSObject*> aScope,
                                             JS::Handle<JSObject*> aHandler,
                                             JS::MutableHandle<JSObject*> aBoundHandler) MOZ_OVERRIDE;
 
   virtual nsIScriptGlobalObject *GetGlobalObject() MOZ_OVERRIDE;
   inline nsIScriptGlobalObject *GetGlobalObjectRef() { return mGlobalObjectRef; }
--- a/dom/base/nsJSUtils.cpp
+++ b/dom/base/nsJSUtils.cpp
@@ -218,17 +218,18 @@ public:
 };
 
 nsresult
 nsJSUtils::EvaluateString(JSContext* aCx,
                           const nsAString& aScript,
                           JS::Handle<JSObject*> aScopeObject,
                           JS::CompileOptions& aCompileOptions,
                           EvaluateOptions& aEvaluateOptions,
-                          JS::Value* aRetValue)
+                          JS::Value* aRetValue,
+                          void **aOffThreadToken)
 {
   PROFILER_LABEL("JS", "EvaluateString");
   MOZ_ASSERT_IF(aCompileOptions.versionSet,
                 aCompileOptions.version != JSVERSION_UNKNOWN);
   MOZ_ASSERT_IF(aEvaluateOptions.coerceToString, aRetValue);
   MOZ_ASSERT_IF(!aEvaluateOptions.reportUncaught, aRetValue);
   MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext());
 
@@ -260,19 +261,30 @@ nsJSUtils::EvaluateString(JSContext* aCx
   }
 
   // Scope the JSAutoCompartment so that we can later wrap the return value
   // into the caller's cx.
   {
     JSAutoCompartment ac(aCx, aScopeObject);
 
     JS::RootedObject rootedScope(aCx, aScopeObject);
-    ok = JS::Evaluate(aCx, rootedScope, aCompileOptions,
-                      PromiseFlatString(aScript).get(),
-                      aScript.Length(), aRetValue);
+    if (aOffThreadToken) {
+      JSScript *script = JS::FinishOffThreadScript(aCx, JS_GetRuntime(aCx), *aOffThreadToken);
+      *aOffThreadToken = nullptr; // Mark the token as having been finished.
+      if (script) {
+        ok = JS_ExecuteScript(aCx, rootedScope, script, aRetValue);
+      } else {
+        ok = false;
+      }
+    } else {
+      ok = JS::Evaluate(aCx, rootedScope, aCompileOptions,
+                        PromiseFlatString(aScript).get(),
+                        aScript.Length(), aRetValue);
+    }
+
     if (ok && aEvaluateOptions.coerceToString && !aRetValue->isUndefined()) {
       JSString* str = JS_ValueToString(aCx, *aRetValue);
       ok = !!str;
       *aRetValue = ok ? JS::StringValue(str) : JS::UndefinedValue();
     }
   }
 
   if (!ok) {
--- a/dom/base/nsJSUtils.h
+++ b/dom/base/nsJSUtils.h
@@ -80,17 +80,18 @@ public:
     }
   };
 
   static nsresult EvaluateString(JSContext* aCx,
                                  const nsAString& aScript,
                                  JS::Handle<JSObject*> aScopeObject,
                                  JS::CompileOptions &aCompileOptions,
                                  EvaluateOptions& aEvaluateOptions,
-                                 JS::Value* aRetValue);
+                                 JS::Value* aRetValue,
+                                 void **aOffThreadToken = nullptr);
 
 };
 
 
 class nsDependentJSString : public nsDependentString
 {
 public:
   /**
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -6332,8 +6332,19 @@ JS_PreventExtensions(JSContext *cx, JS::
 {
     bool extensible;
     if (!JSObject::isExtensible(cx, obj, &extensible))
         return false;
     if (!extensible)
         return true;
     return JSObject::preventExtensions(cx, obj);
 }
+
+char *
+JSAutoByteString::encodeLatin1(ExclusiveContext *cx, JSString *str)
+{
+    JSLinearString *linear = str->ensureLinear(cx);
+    if (!linear)
+        return NULL;
+
+    mBytes = LossyTwoByteCharsToNewLatin1CharsZ(cx, linear->range()).c_str();
+    return mBytes;
+}
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -3844,17 +3844,17 @@ class JSAutoByteString
 
     char *encodeLatin1(JSContext *cx, JSString *str) {
         JS_ASSERT(!mBytes);
         JS_ASSERT(cx);
         mBytes = JS_EncodeString(cx, str);
         return mBytes;
     }
 
-    char *encodeLatin1(js::ContextFriendFields *cx, JSString *str);
+    char *encodeLatin1(js::ExclusiveContext *cx, JSString *str);
 
     char *encodeUtf8(JSContext *cx, JSString *str) {
         JS_ASSERT(!mBytes);
         JS_ASSERT(cx);
         mBytes = JS_EncodeStringToUTF8(cx, str);
         return mBytes;
     }
 
--- a/js/src/jsatom.cpp
+++ b/js/src/jsatom.cpp
@@ -33,21 +33,20 @@ using namespace js::gc;
 
 using mozilla::ArrayEnd;
 using mozilla::ArrayLength;
 using mozilla::RangedPtr;
 
 const char *
 js::AtomToPrintableString(ExclusiveContext *cx, JSAtom *atom, JSAutoByteString *bytes)
 {
-    // The only uses for this method when running off the main thread are for
-    // parse errors/warnings, which will not actually be reported in such cases.
-    if (!cx->isJSContext())
-        return "";
-    return js_ValueToPrintable(cx->asJSContext(), StringValue(atom), bytes);
+    JSString *str = js_QuoteString(cx, atom, 0);
+    if (!str)
+        return NULL;
+    return bytes->encodeLatin1(cx, str);
 }
 
 const char * const js::TypeStrings[] = {
     js_undefined_str,
     js_object_str,
     js_function_str,
     js_string_str,
     js_number_str,
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -289,23 +289,23 @@ class ExclusiveContext : public ThreadSa
     friend class gc::ArenaLists;
     friend class AutoCompartment;
     friend class AutoLockForExclusiveAccess;
     friend struct StackBaseShape;
     friend void JSScript::initCompartment(ExclusiveContext *cx);
     friend class jit::IonContext;
 
     // The worker on which this context is running, if this is not a JSContext.
-    WorkerThread *workerThread;
+    WorkerThread *workerThread_;
 
   public:
 
     ExclusiveContext(JSRuntime *rt, PerThreadData *pt, ContextKind kind)
       : ThreadSafeContext(rt, pt, kind),
-        workerThread(NULL),
+        workerThread_(NULL),
         enterCompartmentDepth_(0)
     {}
 
     /*
      * "Entering" a compartment changes cx->compartment (which changes
      * cx->global). Note that this does not push any StackFrame which means
      * that it is possible for cx->fp()->compartment() != cx->compartment.
      * This is not a problem since, in general, most places in the VM cannot
@@ -331,16 +331,17 @@ class ExclusiveContext : public ThreadSa
         return enterCompartmentDepth_;
     }
 #endif
 
     inline void enterCompartment(JSCompartment *c);
     inline void leaveCompartment(JSCompartment *oldCompartment);
 
     void setWorkerThread(WorkerThread *workerThread);
+    WorkerThread *workerThread() const { return workerThread_; }
 
     // If required, pause this thread until notified to continue by the main thread.
     inline void maybePause() const;
 
     // Threads with an ExclusiveContext may freely access any data in their
     // compartment and zone.
     JSCompartment *compartment() const {
         JS_ASSERT_IF(runtime_->isAtomsCompartment(compartment_),
--- a/js/src/jscntxtinlines.h
+++ b/js/src/jscntxtinlines.h
@@ -347,18 +347,18 @@ GetNativeStackLimit(ThreadSafeContext *c
     }
     return cx->perThreadData->nativeStackLimit[kind];
 }
 
 inline void
 ExclusiveContext::maybePause() const
 {
 #ifdef JS_WORKER_THREADS
-    if (workerThread)
-        workerThread->maybePause();
+    if (workerThread())
+        workerThread()->maybePause();
 #endif
 }
 
 class AutoLockForExclusiveAccess
 {
 #ifdef JS_WORKER_THREADS
     JSRuntime *runtime;
 
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -727,17 +727,17 @@ Sprinter::realloc_(size_t newSize)
         return false;
     }
     base = newBuf;
     size = newSize;
     base[size - 1] = 0;
     return true;
 }
 
-Sprinter::Sprinter(JSContext *cx)
+Sprinter::Sprinter(ExclusiveContext *cx)
   : context(cx),
 #ifdef DEBUG
     initialized(false),
 #endif
     base(NULL), size(0), offset(0), reportedOOM(false)
 { }
 
 Sprinter::~Sprinter()
@@ -862,17 +862,17 @@ Sprinter::putString(JSString *s)
     size_t size = length;
     if (size == (size_t) -1)
         return -1;
 
     ptrdiff_t oldOffset = offset;
     char *buffer = reserve(size);
     if (!buffer)
         return -1;
-    DeflateStringToBuffer(context, chars, length, buffer, &size);
+    DeflateStringToBuffer(NULL, chars, length, buffer, &size);
     buffer[size] = 0;
 
     return oldOffset;
 }
 
 int
 Sprinter::printf(const char *fmt, ...)
 {
@@ -1018,24 +1018,25 @@ QuoteString(Sprinter *sp, JSString *str,
      */
     if (offset == sp->getOffset() && Sprint(sp, "") < 0)
         return NULL;
 
     return sp->stringAt(offset);
 }
 
 JSString *
-js_QuoteString(JSContext *cx, JSString *str, jschar quote)
+js_QuoteString(ExclusiveContext *cx, JSString *str, jschar quote)
 {
     Sprinter sprinter(cx);
     if (!sprinter.init())
         return NULL;
     char *bytes = QuoteString(&sprinter, str, quote);
-    JSString *escstr = bytes ? JS_NewStringCopyZ(cx, bytes) : NULL;
-    return escstr;
+    if (!bytes)
+        return NULL;
+    return js_NewStringCopyZ<CanGC>(cx, bytes);
 }
 
 /************************************************************************/
 
 static int ReconstructPCStack(JSContext*, JSScript*, jsbytecode*, jsbytecode**);
 
 static unsigned
 StackDepth(JSScript *script)
--- a/js/src/jsopcode.h
+++ b/js/src/jsopcode.h
@@ -228,17 +228,17 @@ extern const char       js_EscapeMap[];
 #endif
 
 /*
  * Return a GC'ed string containing the chars in str, with any non-printing
  * chars or quotes (' or " as specified by the quote argument) escaped, and
  * with the quote character at the beginning and end of the result string.
  */
 extern JSString *
-js_QuoteString(JSContext *cx, JSString *str, jschar quote);
+js_QuoteString(js::ExclusiveContext *cx, JSString *str, jschar quote);
 
 namespace js {
 
 static inline bool
 IsJumpOpcode(JSOp op)
 {
     uint32_t type = JOF_TYPE(js_CodeSpec[op].format);
 
@@ -417,32 +417,32 @@ class Sprinter
             parent->checkInvariants();
         }
 
         ~InvariantChecker() {
             parent->checkInvariants();
         }
     };
 
-    JSContext               *context;       /* context executing the decompiler */
+    ExclusiveContext        *context;       /* context executing the decompiler */
 
   private:
     static const size_t     DefaultSize;
 #ifdef DEBUG
     bool                    initialized;    /* true if this is initialized, use for debug builds */
 #endif
     char                    *base;          /* malloc'd buffer address */
     size_t                  size;           /* size of buffer allocated at base */
     ptrdiff_t               offset;         /* offset of next free char in buffer */
     bool                    reportedOOM;    /* this sprinter has reported OOM in string ops */
 
     bool realloc_(size_t newSize);
 
   public:
-    explicit Sprinter(JSContext *cx);
+    explicit Sprinter(ExclusiveContext *cx);
     ~Sprinter();
 
     /* Initialize this sprinter, returns false on error */
     bool init();
 
     void checkInvariants() const;
 
     const char *string() const;
--- a/js/src/jsstr.h
+++ b/js/src/jsstr.h
@@ -261,17 +261,17 @@ InflateStringToBuffer(JSContext *maybecx
 
 /*
  * Deflate JS chars to bytes into a buffer. 'bytes' must be large enough for
  * 'length chars. The buffer is NOT null-terminated. The destination length
  * must to be initialized with the buffer size and will contain on return the
  * number of copied bytes.
  */
 extern bool
-DeflateStringToBuffer(JSContext *cx, const jschar *chars,
+DeflateStringToBuffer(JSContext *maybecx, const jschar *chars,
                       size_t charsLength, char *bytes, size_t *length);
 
 /*
  * The String.prototype.replace fast-native entry point is exported for joined
  * function optimization in js{interp,tracer}.cpp.
  */
 extern bool
 str_replace(JSContext *cx, unsigned argc, js::Value *vp);
--- a/js/src/jsworkers.cpp
+++ b/js/src/jsworkers.cpp
@@ -716,27 +716,27 @@ WorkerThread::handleIonWorkload(WorkerTh
     // this incorporation can be delayed indefinitely without affecting
     // performance as long as the main thread is actually executing Ion code.
     runtime->triggerOperationCallback(JSRuntime::TriggerCallbackAnyThreadDontStopIon);
 }
 
 void
 ExclusiveContext::setWorkerThread(WorkerThread *workerThread)
 {
-    this->workerThread = workerThread;
-    this->perThreadData = workerThread->threadData.addr();
+    workerThread_ = workerThread;
+    perThreadData = workerThread->threadData.addr();
 }
 
 frontend::CompileError &
 ExclusiveContext::addPendingCompileError()
 {
     frontend::CompileError *error = js_new<frontend::CompileError>();
     if (!error)
         MOZ_CRASH();
-    if (!workerThread->parseTask->errors.append(error))
+    if (!workerThread()->parseTask->errors.append(error))
         MOZ_CRASH();
     return *error;
 }
 
 void
 WorkerThread::handleParseWorkload(WorkerThreadState &state)
 {
     JS_ASSERT(state.isLocked());
@@ -824,19 +824,35 @@ WorkerThreadState::compressionInProgress
 bool
 SourceCompressionTask::complete()
 {
     JS_ASSERT_IF(!ss, !chars);
     if (active()) {
         WorkerThreadState &state = *cx->workerThreadState();
         AutoLockWorkerThreadState lock(state);
 
+        // If this compression task is itself being completed on a worker
+        // thread, treat the thread as paused while waiting for the completion.
+        // Otherwise we will not wake up and mark this as paused due to the
+        // loop in AutoPauseWorkersForGC.
+        if (cx->workerThread()) {
+            state.numPaused++;
+            if (state.numPaused == state.numThreads)
+                state.notify(WorkerThreadState::MAIN);
+        }
+
         while (state.compressionInProgress(this))
             state.wait(WorkerThreadState::MAIN);
 
+        if (cx->workerThread()) {
+            state.numPaused--;
+            if (state.shouldPause)
+                cx->workerThread()->pause();
+        }
+
         ss->ready_ = true;
 
         // Update memory accounting.
         if (!oom)
             cx->updateMallocCounter(ss->computedSizeOfData());
 
         ss = NULL;
         chars = NULL;