Bug 1342012 - Refactor nsJSUtils::ExecutionContext to separate compilation and execution steps and allow extraction of compiled JSScript r=nbp r=smaug
authorJon Coppeard <jcoppeard@mozilla.com>
Thu, 06 Dec 2018 16:52:16 -0500
changeset 509503 92863bfc60c0a9592580b4166aeaf9c013a8b8d5
parent 509502 4001453082049c7f1729f464baff027eb079a8a8
child 509504 7b1979745763b8c70b35431349892807c6a408f7
push id10547
push userffxbld-merge
push dateMon, 21 Jan 2019 13:03:58 +0000
treeherdermozilla-beta@24ec1916bffe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnbp, smaug
bugs1342012
milestone66.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 1342012 - Refactor nsJSUtils::ExecutionContext to separate compilation and execution steps and allow extraction of compiled JSScript r=nbp r=smaug
dom/base/nsGlobalWindowInner.cpp
dom/base/nsJSUtils.cpp
dom/base/nsJSUtils.h
dom/jsurl/nsJSProtocolHandler.cpp
dom/plugins/base/nsNPAPIPlugin.cpp
dom/script/ScriptLoader.cpp
dom/xbl/nsXBLProtoImplField.cpp
--- a/dom/base/nsGlobalWindowInner.cpp
+++ b/dom/base/nsGlobalWindowInner.cpp
@@ -5996,17 +5996,18 @@ bool nsGlobalWindowInner::RunTimeoutHand
       AutoEntryScript aes(this, reason, true);
       JS::CompileOptions options(aes.cx());
       options.setFileAndLine(filename, lineNo);
       options.setNoScriptRval(true);
       JS::Rooted<JSObject*> global(aes.cx(), FastGetGlobalJSObject());
       nsresult rv;
       {
         nsJSUtils::ExecutionContext exec(aes.cx(), global);
-        rv = exec.CompileAndExec(options, script);
+        exec.Compile(options, script);
+        rv = exec.ExecScript();
       }
 
       if (rv == NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW_UNCATCHABLE) {
         abortIntervalHandler = true;
       }
     } else {
       // Hold strong ref to ourselves while we call the callback.
       nsCOMPtr<nsISupports> me(static_cast<nsIDOMWindow*>(this));
--- a/dom/base/nsJSUtils.cpp
+++ b/dom/base/nsJSUtils.cpp
@@ -119,24 +119,26 @@ nsJSUtils::ExecutionContext::ExecutionCo
       mAutoProfilerLabel("nsJSUtils::ExecutionContext",
                          /* dynamicStr */ nullptr,
                          js::ProfilingStackFrame::Category::JS),
 #endif
       mCx(aCx),
       mRealm(aCx, aGlobal),
       mRetValue(aCx),
       mScopeChain(aCx),
+      mScript(aCx),
       mRv(NS_OK),
       mSkip(false),
       mCoerceToString(false),
       mEncodeBytecode(false)
 #ifdef DEBUG
       ,
       mWantsReturnValue(false),
-      mExpectScopeChain(false)
+      mExpectScopeChain(false),
+      mScriptUsed(false)
 #endif
 {
   MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext());
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(CycleCollectedJSContext::Get() &&
              CycleCollectedJSContext::Get()->MicroTaskLevel());
   MOZ_ASSERT(mRetValue.isUndefined());
 
@@ -169,308 +171,294 @@ void nsJSUtils::ExecutionContext::SetSco
     if (!JS_WrapObject(mCx, mScopeChain[i])) {
       mSkip = true;
       mRv = NS_ERROR_OUT_OF_MEMORY;
       return;
     }
   }
 }
 
-nsresult nsJSUtils::ExecutionContext::JoinAndExec(
-    JS::OffThreadToken** aOffThreadToken,
-    JS::MutableHandle<JSScript*> aScript) {
+nsresult nsJSUtils::ExecutionContext::JoinCompile(
+    JS::OffThreadToken** aOffThreadToken) {
   if (mSkip) {
     return mRv;
   }
 
   MOZ_ASSERT(!mWantsReturnValue);
   MOZ_ASSERT(!mExpectScopeChain);
-  aScript.set(JS::FinishOffThreadScript(mCx, *aOffThreadToken));
+  MOZ_ASSERT(!mScript);
+  mScript.set(JS::FinishOffThreadScript(mCx, *aOffThreadToken));
   *aOffThreadToken = nullptr;  // Mark the token as having been finished.
-  if (!aScript) {
+  if (!mScript) {
     mSkip = true;
     mRv = EvaluationExceptionToNSResult(mCx);
     return mRv;
   }
 
-  if (mEncodeBytecode && !StartIncrementalEncoding(mCx, aScript)) {
-    mSkip = true;
-    mRv = EvaluationExceptionToNSResult(mCx);
-    return mRv;
-  }
-
-  if (!JS_ExecuteScript(mCx, mScopeChain, aScript)) {
+  if (mEncodeBytecode && !StartIncrementalEncoding(mCx, mScript)) {
     mSkip = true;
     mRv = EvaluationExceptionToNSResult(mCx);
     return mRv;
   }
 
   return NS_OK;
 }
 
-nsresult nsJSUtils::ExecutionContext::CompileAndExec(
-    JS::CompileOptions& aCompileOptions, JS::SourceText<char16_t>& aSrcBuf,
-    JS::MutableHandle<JSScript*> aScript) {
+nsresult nsJSUtils::ExecutionContext::Compile(
+    JS::CompileOptions& aCompileOptions, JS::SourceText<char16_t>& aSrcBuf) {
   if (mSkip) {
     return mRv;
   }
 
   MOZ_ASSERT(aSrcBuf.get());
   MOZ_ASSERT(mRetValue.isUndefined());
 #ifdef DEBUG
   mWantsReturnValue = !aCompileOptions.noScriptRval;
 #endif
 
+  MOZ_ASSERT(!mScript);
   bool compiled = true;
   if (mScopeChain.length() == 0) {
-    compiled = JS::Compile(mCx, aCompileOptions, aSrcBuf, aScript);
+    compiled = JS::Compile(mCx, aCompileOptions, aSrcBuf, &mScript);
   } else {
-    compiled =
-        JS::CompileForNonSyntacticScope(mCx, aCompileOptions, aSrcBuf, aScript);
+    compiled = JS::CompileForNonSyntacticScope(mCx, aCompileOptions, aSrcBuf,
+                                               &mScript);
   }
 
-  MOZ_ASSERT_IF(compiled, aScript);
+  MOZ_ASSERT_IF(compiled, mScript);
   if (!compiled) {
     mSkip = true;
     mRv = EvaluationExceptionToNSResult(mCx);
     return mRv;
   }
 
-  if (mEncodeBytecode && !StartIncrementalEncoding(mCx, aScript)) {
-    mSkip = true;
-    mRv = EvaluationExceptionToNSResult(mCx);
-    return mRv;
-  }
-
-  MOZ_ASSERT(!mCoerceToString || mWantsReturnValue);
-  if (!JS_ExecuteScript(mCx, mScopeChain, aScript, &mRetValue)) {
+  if (mEncodeBytecode && !StartIncrementalEncoding(mCx, mScript)) {
     mSkip = true;
     mRv = EvaluationExceptionToNSResult(mCx);
     return mRv;
   }
 
   return NS_OK;
 }
 
-nsresult nsJSUtils::ExecutionContext::CompileAndExec(
+nsresult nsJSUtils::ExecutionContext::Compile(
     JS::CompileOptions& aCompileOptions, const nsAString& aScript) {
-  MOZ_ASSERT(!mEncodeBytecode,
-             "A JSScript is needed for calling FinishIncrementalEncoding");
   if (mSkip) {
     return mRv;
   }
 
   const nsPromiseFlatString& flatScript = PromiseFlatString(aScript);
   JS::SourceText<char16_t> srcBuf;
   if (!srcBuf.init(mCx, flatScript.get(), flatScript.Length(),
                    JS::SourceOwnership::Borrowed)) {
     mSkip = true;
     mRv = EvaluationExceptionToNSResult(mCx);
     return mRv;
   }
 
-  JS::Rooted<JSScript*> script(mCx);
-  return CompileAndExec(aCompileOptions, srcBuf, &script);
+  return Compile(aCompileOptions, srcBuf);
 }
 
-nsresult nsJSUtils::ExecutionContext::DecodeAndExec(
+nsresult nsJSUtils::ExecutionContext::Decode(
     JS::CompileOptions& aCompileOptions, mozilla::Vector<uint8_t>& aBytecodeBuf,
     size_t aBytecodeIndex) {
-  MOZ_ASSERT(!mEncodeBytecode,
-             "A JSScript is needed for calling FinishIncrementalEncoding");
   if (mSkip) {
     return mRv;
   }
 
   MOZ_ASSERT(!mWantsReturnValue);
-  JS::Rooted<JSScript*> script(mCx);
   JS::TranscodeResult tr =
-      JS::DecodeScript(mCx, aBytecodeBuf, &script, aBytecodeIndex);
+      JS::DecodeScript(mCx, aBytecodeBuf, &mScript, aBytecodeIndex);
   // These errors are external parameters which should be handled before the
   // decoding phase, and which are the only reasons why you might want to
   // fallback on decoding failures.
   MOZ_ASSERT(tr != JS::TranscodeResult_Failure_BadBuildId &&
              tr != JS::TranscodeResult_Failure_WrongCompileOption);
   if (tr != JS::TranscodeResult_Ok) {
     mSkip = true;
     mRv = NS_ERROR_DOM_JS_DECODING_ERROR;
     return mRv;
   }
 
-  if (!JS_ExecuteScript(mCx, mScopeChain, script)) {
-    mSkip = true;
-    mRv = EvaluationExceptionToNSResult(mCx);
-    return mRv;
-  }
-
   return mRv;
 }
 
-nsresult nsJSUtils::ExecutionContext::DecodeJoinAndExec(
+nsresult nsJSUtils::ExecutionContext::JoinDecode(
     JS::OffThreadToken** aOffThreadToken) {
   if (mSkip) {
     return mRv;
   }
 
   MOZ_ASSERT(!mWantsReturnValue);
   MOZ_ASSERT(!mExpectScopeChain);
-  JS::Rooted<JSScript*> script(mCx);
-  script.set(JS::FinishOffThreadScriptDecoder(mCx, *aOffThreadToken));
+  mScript.set(JS::FinishOffThreadScriptDecoder(mCx, *aOffThreadToken));
   *aOffThreadToken = nullptr;  // Mark the token as having been finished.
-  if (!script || !JS_ExecuteScript(mCx, mScopeChain, script)) {
+  if (!mScript) {
     mSkip = true;
     mRv = EvaluationExceptionToNSResult(mCx);
     return mRv;
   }
 
   return NS_OK;
 }
 
-nsresult nsJSUtils::ExecutionContext::DecodeBinASTJoinAndExec(
-    JS::OffThreadToken** aOffThreadToken,
-    JS::MutableHandle<JSScript*> aScript) {
+nsresult nsJSUtils::ExecutionContext::JoinDecodeBinAST(
+    JS::OffThreadToken** aOffThreadToken) {
 #ifdef JS_BUILD_BINAST
   if (mSkip) {
     return mRv;
   }
 
   MOZ_ASSERT(!mWantsReturnValue);
   MOZ_ASSERT(!mExpectScopeChain);
 
-  aScript.set(JS::FinishOffThreadBinASTDecode(mCx, *aOffThreadToken));
+  mScript.set(JS::FinishOffThreadBinASTDecode(mCx, *aOffThreadToken));
   *aOffThreadToken = nullptr;  // Mark the token as having been finished.
 
-  if (!aScript) {
+  if (!mScript) {
     mSkip = true;
     mRv = EvaluationExceptionToNSResult(mCx);
     return mRv;
   }
 
-  if (mEncodeBytecode && !StartIncrementalEncoding(mCx, aScript)) {
-    mSkip = true;
-    mRv = EvaluationExceptionToNSResult(mCx);
-    return mRv;
-  }
-
-  if (!JS_ExecuteScript(mCx, mScopeChain, aScript)) {
+  if (mEncodeBytecode && !StartIncrementalEncoding(mCx, mScript)) {
     mSkip = true;
     mRv = EvaluationExceptionToNSResult(mCx);
     return mRv;
   }
 
   return NS_OK;
 #else
   return NS_ERROR_NOT_IMPLEMENTED;
 #endif
 }
 
-nsresult nsJSUtils::ExecutionContext::DecodeBinASTAndExec(
-    JS::CompileOptions& aCompileOptions, const uint8_t* aBuf, size_t aLength,
-    JS::MutableHandle<JSScript*> aScript) {
+nsresult nsJSUtils::ExecutionContext::DecodeBinAST(
+    JS::CompileOptions& aCompileOptions, const uint8_t* aBuf, size_t aLength) {
 #ifdef JS_BUILD_BINAST
   MOZ_ASSERT(mScopeChain.length() == 0,
              "BinAST decoding is not supported in non-syntactic scopes");
 
   if (mSkip) {
     return mRv;
   }
 
   MOZ_ASSERT(aBuf);
   MOZ_ASSERT(mRetValue.isUndefined());
 #ifdef DEBUG
   mWantsReturnValue = !aCompileOptions.noScriptRval;
 #endif
 
-  aScript.set(JS::DecodeBinAST(mCx, aCompileOptions, aBuf, aLength));
+  mScript.set(JS::DecodeBinAST(mCx, aCompileOptions, aBuf, aLength));
 
-  if (!aScript) {
+  if (!mScript) {
     mSkip = true;
     mRv = EvaluationExceptionToNSResult(mCx);
     return mRv;
   }
 
-  if (mEncodeBytecode && !StartIncrementalEncoding(mCx, aScript)) {
-    mSkip = true;
-    mRv = EvaluationExceptionToNSResult(mCx);
-    return mRv;
-  }
-
-  MOZ_ASSERT(!mCoerceToString || mWantsReturnValue);
-  if (!JS_ExecuteScript(mCx, mScopeChain, aScript, &mRetValue)) {
+  if (mEncodeBytecode && !StartIncrementalEncoding(mCx, mScript)) {
     mSkip = true;
     mRv = EvaluationExceptionToNSResult(mCx);
     return mRv;
   }
 
   return NS_OK;
 #else
   return NS_ERROR_NOT_IMPLEMENTED;
 #endif
 }
 
+JSScript* nsJSUtils::ExecutionContext::GetScript() {
+#ifdef DEBUG
+  MOZ_ASSERT(!mSkip);
+  MOZ_ASSERT(mScript);
+  mScriptUsed = true;
+#endif
+
+  return mScript;
+}
+
+nsresult nsJSUtils::ExecutionContext::ExecScript() {
+  if (mSkip) {
+    return mRv;
+  }
+
+  MOZ_ASSERT(mScript);
+
+  if (!JS_ExecuteScript(mCx, mScopeChain, mScript)) {
+    mSkip = true;
+    mRv = EvaluationExceptionToNSResult(mCx);
+    return mRv;
+  }
+
+  return NS_OK;
+}
+
 static bool IsPromiseValue(JSContext* aCx, JS::Handle<JS::Value> aValue) {
   if (!aValue.isObject()) {
     return false;
   }
 
   JS::Rooted<JSObject*> obj(aCx, js::CheckedUnwrap(&aValue.toObject()));
   if (!obj) {
     return false;
   }
 
   return JS::IsPromiseObject(obj);
 }
 
-nsresult nsJSUtils::ExecutionContext::ExtractReturnValue(
+nsresult nsJSUtils::ExecutionContext::ExecScript(
     JS::MutableHandle<JS::Value> aRetValue) {
-  MOZ_ASSERT(aRetValue.isUndefined());
   if (mSkip) {
-    // Repeat earlier result, as NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW are not
-    // failures cases.
-#ifdef DEBUG
-    mWantsReturnValue = false;
-#endif
+    aRetValue.setUndefined();
     return mRv;
   }
 
+  MOZ_ASSERT(mScript);
   MOZ_ASSERT(mWantsReturnValue);
+
+  if (!JS_ExecuteScript(mCx, mScopeChain, mScript, aRetValue)) {
+    mSkip = true;
+    mRv = EvaluationExceptionToNSResult(mCx);
+    return mRv;
+  }
+
 #ifdef DEBUG
   mWantsReturnValue = false;
 #endif
-  if (mCoerceToString && IsPromiseValue(mCx, mRetValue)) {
+  if (mCoerceToString && IsPromiseValue(mCx, aRetValue)) {
     // We're a javascript: url and we should treat Promise return values as
     // undefined.
     //
     // Once bug 1477821 is fixed this code might be able to go away, or will
     // become enshrined in the spec, depending.
-    mRetValue.setUndefined();
+    aRetValue.setUndefined();
   }
 
-  if (mCoerceToString && !mRetValue.isUndefined()) {
-    JSString* str = JS::ToString(mCx, mRetValue);
+  if (mCoerceToString && !aRetValue.isUndefined()) {
+    JSString* str = JS::ToString(mCx, aRetValue);
     if (!str) {
       // ToString can be a function call, so an exception can be raised while
       // executing the function.
       mSkip = true;
       return EvaluationExceptionToNSResult(mCx);
     }
-    mRetValue.set(JS::StringValue(str));
+    aRetValue.set(JS::StringValue(str));
   }
 
-  aRetValue.set(mRetValue);
   return NS_OK;
 }
 
 nsresult nsJSUtils::CompileModule(JSContext* aCx,
                                   JS::SourceText<char16_t>& aSrcBuf,
                                   JS::Handle<JSObject*> aEvaluationGlobal,
                                   JS::CompileOptions& aCompileOptions,
                                   JS::MutableHandle<JSObject*> aModule) {
   AUTO_PROFILER_LABEL("nsJSUtils::CompileModule", JS);
-
   MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext());
   MOZ_ASSERT(aSrcBuf.get());
   MOZ_ASSERT(JS_IsGlobalObject(aEvaluationGlobal));
   MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx) == aEvaluationGlobal);
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(CycleCollectedJSContext::Get() &&
              CycleCollectedJSContext::Get()->MicroTaskLevel());
 
--- a/dom/base/nsJSUtils.h
+++ b/dom/base/nsJSUtils.h
@@ -76,16 +76,19 @@ class nsJSUtils {
     JSAutoRealm mRealm;
 
     // Set to a valid handle if a return value is expected.
     JS::Rooted<JS::Value> mRetValue;
 
     // Scope chain in which the execution takes place.
     JS::AutoObjectVector mScopeChain;
 
+    // The compiled script.
+    JS::Rooted<JSScript*> mScript;
+
     // returned value forwarded when we have to interupt the execution eagerly
     // with mSkip.
     nsresult mRv;
 
     // Used to skip upcoming phases in case of a failure.  In such case the
     // result is carried by mRv.
     bool mSkip;
 
@@ -95,29 +98,35 @@ class nsJSUtils {
     // Encode the bytecode before it is being executed.
     bool mEncodeBytecode;
 
 #ifdef DEBUG
     // Should we set the return value.
     bool mWantsReturnValue;
 
     bool mExpectScopeChain;
+
+    bool mScriptUsed;
 #endif
 
    public:
     // Enter compartment in which the code would be executed.  The JSContext
     // must come from an AutoEntryScript.
     ExecutionContext(JSContext* aCx, JS::Handle<JSObject*> aGlobal);
 
     ExecutionContext(const ExecutionContext&) = delete;
     ExecutionContext(ExecutionContext&&) = delete;
 
     ~ExecutionContext() {
-      // This flag is resetted, when the returned value is extracted.
-      MOZ_ASSERT(!mWantsReturnValue);
+      // This flag is reset when the returned value is extracted.
+      MOZ_ASSERT_IF(!mSkip, !mWantsReturnValue);
+
+      // If encoding was started we expect the script to have been
+      // used when ending the encoding.
+      MOZ_ASSERT_IF(mEncodeBytecode && mScript && mRv == NS_OK, mScriptUsed);
     }
 
     // The returned value would be converted to a string if the
     // |aCoerceToString| is flag set.
     ExecutionContext& SetCoerceToString(bool aCoerceToString) {
       mCoerceToString = aCoerceToString;
       return *this;
     }
@@ -129,64 +138,63 @@ class nsJSUtils {
     ExecutionContext& SetEncodeBytecode(bool aEncodeBytecode) {
       mEncodeBytecode = aEncodeBytecode;
       return *this;
     }
 
     // Set the scope chain in which the code should be executed.
     void SetScopeChain(const JS::AutoObjectVector& aScopeChain);
 
-    // Copy the returned value in the mutable handle argument, in case of a
+    // After getting a notification that an off-thread compilation terminated,
+    // this function will take the result of the parser and move it to the main
+    // thread.
+    MOZ_MUST_USE nsresult JoinCompile(JS::OffThreadToken** aOffThreadToken);
+
+    // Compile a script contained in a SourceText.
+    nsresult Compile(JS::CompileOptions& aCompileOptions,
+                     JS::SourceText<char16_t>& aSrcBuf);
+
+    // Compile a script contained in a string.
+    nsresult Compile(JS::CompileOptions& aCompileOptions,
+                     const nsAString& aScript);
+
+    // Decode a script contained in a buffer.
+    nsresult Decode(JS::CompileOptions& aCompileOptions,
+                    mozilla::Vector<uint8_t>& aBytecodeBuf,
+                    size_t aBytecodeIndex);
+
+    // After getting a notification that an off-thread decoding terminated, this
+    // function will get the result of the decoder and move it to the main
+    // thread.
+    nsresult JoinDecode(JS::OffThreadToken** aOffThreadToken);
+
+    nsresult JoinDecodeBinAST(JS::OffThreadToken** aOffThreadToken);
+
+    // Decode a BinAST encoded script contained in a buffer.
+    nsresult DecodeBinAST(JS::CompileOptions& aCompileOptions,
+                          const uint8_t* aBuf, size_t aLength);
+
+    // Get a successfully compiled script.
+    JSScript* GetScript();
+
+    // Execute the compiled script and ignore the return value.
+    MOZ_MUST_USE nsresult ExecScript();
+
+    // Execute the compiled script a get the return value.
+    //
+    // Copy the returned value into the mutable handle argument. In case of a
     // evaluation failure either during the execution or the conversion of the
-    // result to a string, the nsresult would be set to the corresponding result
-    // code, and the mutable handle argument would remain unchanged.
+    // result to a string, the nsresult is be set to the corresponding result
+    // code and the mutable handle argument remains unchanged.
     //
     // The value returned in the mutable handle argument is part of the
     // compartment given as argument to the ExecutionContext constructor. If the
     // caller is in a different compartment, then the out-param value should be
     // wrapped by calling |JS_WrapValue|.
-    MOZ_MUST_USE nsresult
-    ExtractReturnValue(JS::MutableHandle<JS::Value> aRetValue);
-
-    // After getting a notification that an off-thread compilation terminated,
-    // this function will take the result of the parser by moving it to the main
-    // thread before starting the execution of the script.
-    //
-    // The compiled script would be returned in the |aScript| out-param.
-    MOZ_MUST_USE nsresult JoinAndExec(JS::OffThreadToken** aOffThreadToken,
-                                      JS::MutableHandle<JSScript*> aScript);
-
-    // Compile a script contained in a SourceText, and execute it.
-    nsresult CompileAndExec(JS::CompileOptions& aCompileOptions,
-                            JS::SourceText<char16_t>& aSrcBuf,
-                            JS::MutableHandle<JSScript*> aScript);
-
-    // Compile a script contained in a string, and execute it.
-    nsresult CompileAndExec(JS::CompileOptions& aCompileOptions,
-                            const nsAString& aScript);
-
-    // Decode a script contained in a buffer, and execute it.
-    MOZ_MUST_USE nsresult DecodeAndExec(JS::CompileOptions& aCompileOptions,
-                                        mozilla::Vector<uint8_t>& aBytecodeBuf,
-                                        size_t aBytecodeIndex);
-
-    // After getting a notification that an off-thread decoding terminated, this
-    // function will get the result of the decoder by moving it to the main
-    // thread before starting the execution of the script.
-    MOZ_MUST_USE nsresult
-    DecodeJoinAndExec(JS::OffThreadToken** aOffThreadToken);
-
-    MOZ_MUST_USE nsresult
-    DecodeBinASTJoinAndExec(JS::OffThreadToken** aOffThreadToken,
-                            JS::MutableHandle<JSScript*> aScript);
-
-    // Decode a BinAST encoded script contained in a buffer, and execute it.
-    nsresult DecodeBinASTAndExec(JS::CompileOptions& aCompileOptions,
-                                 const uint8_t* aBuf, size_t aLength,
-                                 JS::MutableHandle<JSScript*> aScript);
+    MOZ_MUST_USE nsresult ExecScript(JS::MutableHandle<JS::Value> aRetValue);
   };
 
   static nsresult CompileModule(JSContext* aCx,
                                 JS::SourceText<char16_t>& aSrcBuf,
                                 JS::Handle<JSObject*> aEvaluationGlobal,
                                 JS::CompileOptions& aCompileOptions,
                                 JS::MutableHandle<JSObject*> aModule);
 
--- a/dom/jsurl/nsJSProtocolHandler.cpp
+++ b/dom/jsurl/nsJSProtocolHandler.cpp
@@ -249,18 +249,18 @@ nsresult nsJSThunk::EvaluateScript(
 
   JS::Rooted<JS::Value> v(cx, JS::UndefinedValue());
   // Finally, we have everything needed to evaluate the expression.
   JS::CompileOptions options(cx);
   options.setFileAndLine(mURL.get(), 1);
   {
     nsJSUtils::ExecutionContext exec(cx, globalJSObject);
     exec.SetCoerceToString(true);
-    exec.CompileAndExec(options, NS_ConvertUTF8toUTF16(script));
-    rv = exec.ExtractReturnValue(&v);
+    exec.Compile(options, NS_ConvertUTF8toUTF16(script));
+    rv = exec.ExecScript(&v);
   }
 
   js::AssertSameCompartment(cx, v);
 
   if (NS_FAILED(rv) || !(v.isString() || v.isUndefined())) {
     return NS_ERROR_MALFORMED_URI;
   } else if (v.isUndefined()) {
     return NS_ERROR_DOM_RETVAL_UNDEFINED;
--- a/dom/plugins/base/nsNPAPIPlugin.cpp
+++ b/dom/plugins/base/nsNPAPIPlugin.cpp
@@ -993,18 +993,18 @@ bool _evaluate(NPP npp, NPObject *npobj,
       js::GetObjectCompartment(obj) == js::GetContextCompartment(cx),
       "nsNPObjWrapper::GetNewOrUsed must wrap its return value");
   obj = JS::CurrentGlobalOrNull(cx);
   MOZ_ASSERT(obj);
   nsresult rv = NS_OK;
   {
     nsJSUtils::ExecutionContext exec(cx, obj);
     exec.SetScopeChain(scopeChain);
-    exec.CompileAndExec(options, utf16script);
-    rv = exec.ExtractReturnValue(&rval);
+    exec.Compile(options, utf16script);
+    rv = exec.ExecScript(&rval);
   }
 
   if (!JS_WrapValue(cx, &rval)) {
     return false;
   }
 
   return NS_SUCCEEDED(rv) &&
          (!result || JSValToNPVariant(npp, cx, rval, result));
--- a/dom/script/ScriptLoader.cpp
+++ b/dom/script/ScriptLoader.cpp
@@ -3,18 +3,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ScriptLoader.h"
 #include "ScriptLoadHandler.h"
 #include "ScriptLoadRequest.h"
 #include "ScriptTrace.h"
+#include "LoadedScript.h"
 #include "ModuleLoadRequest.h"
-#include "LoadedScript.h"
 
 #include "prsystem.h"
 #include "jsapi.h"
 #include "jsfriendapi.h"
 #include "js/CompilationAndEvaluation.h"
 #include "js/MemoryFunctions.h"
 #include "js/OffThreadScriptCompilation.h"
 #include "js/SourceText.h"
@@ -2465,23 +2465,24 @@ nsresult ScriptLoader::EvaluateScript(Sc
 
       if (NS_SUCCEEDED(rv)) {
         if (aRequest->IsBytecode()) {
           TRACE_FOR_TEST(aRequest->Element(), "scriptloader_execute");
           nsJSUtils::ExecutionContext exec(cx, global);
           if (aRequest->mOffThreadToken) {
             LOG(("ScriptLoadRequest (%p): Decode Bytecode & Join and Execute",
                  aRequest));
-            rv = exec.DecodeJoinAndExec(&aRequest->mOffThreadToken);
+            exec.JoinDecode(&aRequest->mOffThreadToken);
           } else {
             LOG(("ScriptLoadRequest (%p): Decode Bytecode and Execute",
                  aRequest));
-            rv = exec.DecodeAndExec(options, aRequest->mScriptBytecode,
-                                    aRequest->mBytecodeOffset);
+            exec.Decode(options, aRequest->mScriptBytecode,
+                        aRequest->mBytecodeOffset);
           }
+          rv = exec.ExecScript();
           // We do not expect to be saving anything when we already have some
           // bytecode.
           MOZ_ASSERT(!aRequest->mCacheInfo);
         } else {
           MOZ_ASSERT(aRequest->IsSource());
           JS::Rooted<JSScript*> script(cx);
           bool encodeBytecode = ShouldCacheBytecode(aRequest);
 
@@ -2491,40 +2492,44 @@ nsresult ScriptLoader::EvaluateScript(Sc
             TRACE_FOR_TEST(aRequest->Element(), "scriptloader_execute");
             if (aRequest->mOffThreadToken) {
               // Off-main-thread parsing.
               LOG(
                   ("ScriptLoadRequest (%p): Join (off-thread parsing) and "
                    "Execute",
                    aRequest));
               if (aRequest->IsBinASTSource()) {
-                rv = exec.DecodeBinASTJoinAndExec(&aRequest->mOffThreadToken,
-                                                  &script);
+                rv = exec.JoinDecodeBinAST(&aRequest->mOffThreadToken);
               } else {
                 MOZ_ASSERT(aRequest->IsTextSource());
-                rv = exec.JoinAndExec(&aRequest->mOffThreadToken, &script);
+                rv = exec.JoinCompile(&aRequest->mOffThreadToken);
               }
             } else {
               // Main thread parsing (inline and small scripts)
               LOG(("ScriptLoadRequest (%p): Compile And Exec", aRequest));
               if (aRequest->IsBinASTSource()) {
-                rv = exec.DecodeBinASTAndExec(
-                    options, aRequest->ScriptBinASTData().begin(),
-                    aRequest->ScriptBinASTData().length(), &script);
+                rv = exec.DecodeBinAST(options,
+                                       aRequest->ScriptBinASTData().begin(),
+                                       aRequest->ScriptBinASTData().length());
               } else {
                 MOZ_ASSERT(aRequest->IsTextSource());
                 auto srcBuf = GetScriptSource(cx, aRequest);
 
                 if (srcBuf) {
-                  rv = exec.CompileAndExec(options, *srcBuf, &script);
+                  rv = exec.Compile(options, *srcBuf);
                 } else {
                   rv = NS_ERROR_OUT_OF_MEMORY;
                 }
               }
             }
+
+            if (rv == NS_OK) {
+              script = exec.GetScript();
+              rv = exec.ExecScript();
+            }
           }
 
           // Queue the current script load request to later save the bytecode.
           if (script && encodeBytecode) {
             aRequest->SetScript(script);
             TRACE_FOR_TEST(aRequest->Element(), "scriptloader_encode");
             MOZ_ASSERT(aRequest->mBytecodeOffset ==
                        aRequest->mScriptBytecode.length());
--- a/dom/xbl/nsXBLProtoImplField.cpp
+++ b/dom/xbl/nsXBLProtoImplField.cpp
@@ -410,19 +410,18 @@ nsresult nsXBLProtoImplField::InstallFie
   if (!nsJSUtils::GetScopeChainForXBL(cx, boundElement, aProtoBinding,
                                       scopeChain)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
   rv = NS_OK;
   {
     nsJSUtils::ExecutionContext exec(cx, scopeObject);
     exec.SetScopeChain(scopeChain);
-    exec.CompileAndExec(options,
-                        nsDependentString(mFieldText, mFieldTextLength));
-    rv = exec.ExtractReturnValue(&result);
+    exec.Compile(options, nsDependentString(mFieldText, mFieldTextLength));
+    rv = exec.ExecScript(&result);
   }
 
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   if (rv == NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW) {
     // Report the exception now, before we try using the JSContext for