diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index 744b3ab8d3..78d014a7bd 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -224,6 +224,7 @@ #include "mozilla/dom/PrimitiveConversions.h" #include "mozilla/dom/WindowBinding.h" #include "nsITabChild.h" +#include "mozilla/dom/ModuleScript.h" #include "mozilla/dom/MediaQueryList.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/NavigatorBinding.h" @@ -12949,9 +12950,6 @@ nsGlobalWindow::RunTimeoutHandler(Timeout* aTimeout, RefPtr callback = handler->GetCallback(); if (!callback) { - // Evaluate the timeout expression. - const nsAString& script = handler->GetHandlerText(); - const char* filename = nullptr; uint32_t lineNo = 0, dummyColumn = 0; handler->GetLocation(&filename, &lineNo, &dummyColumn); @@ -12962,9 +12960,22 @@ nsGlobalWindow::RunTimeoutHandler(Timeout* aTimeout, AutoEntryScript aes(this, reason, true); JS::CompileOptions options(aes.cx()); options.setFileAndLine(filename, lineNo).setVersion(JSVERSION_DEFAULT); + options.setNoScriptRval(true); JS::Rooted global(aes.cx(), FastGetGlobalJSObject()); - nsresult rv = - nsJSUtils::EvaluateString(aes.cx(), script, global, options); + nsresult rv; + { + nsJSUtils::ExecutionContext exec(aes.cx(), global); + rv = exec.Compile(options, handler->GetHandlerText()); + + if (rv == NS_OK) { + LoadedScript* initiatingScript = handler->GetInitiatingScript(); + if (initiatingScript) { + initiatingScript->AssociateWithScript(exec.GetScript()); + } + + rv = exec.ExecScript(); + } + } if (rv == NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW_UNCATCHABLE) { abortIntervalHandler = true; } diff --git a/dom/base/nsIScriptTimeoutHandler.h b/dom/base/nsIScriptTimeoutHandler.h index 9679f70478..b1472ad748 100644 --- a/dom/base/nsIScriptTimeoutHandler.h +++ b/dom/base/nsIScriptTimeoutHandler.h @@ -14,6 +14,7 @@ namespace mozilla { namespace dom { class Function; +class LoadedScript; } // namespace dom } // namespace mozilla @@ -44,6 +45,9 @@ public: // If we have a Function, get the arguments for passing to it. virtual const nsTArray& GetArgs() = 0; + + // If we have an expression, get the initiating script. + virtual mozilla::dom::LoadedScript* GetInitiatingScript() = 0; }; NS_DEFINE_STATIC_IID_ACCESSOR(nsIScriptTimeoutHandler, diff --git a/dom/base/nsJSTimeoutHandler.cpp b/dom/base/nsJSTimeoutHandler.cpp index a370fe03f7..5c836694ae 100644 --- a/dom/base/nsJSTimeoutHandler.cpp +++ b/dom/base/nsJSTimeoutHandler.cpp @@ -10,6 +10,7 @@ #include "mozilla/Likely.h" #include "mozilla/Maybe.h" #include "mozilla/dom/FunctionBinding.h" +#include "mozilla/dom/ModuleScript.h" #include "nsAXPCNativeCallContext.h" #include "nsCOMPtr.h" #include "nsContentUtils.h" @@ -44,6 +45,7 @@ public: nsTArray>&& aArguments, ErrorResult& aError); nsJSScriptTimeoutHandler(JSContext* aCx, nsGlobalWindow* aWindow, + LoadedScript* aInitiatingScript, const nsAString& aExpression, bool* aAllowEval, ErrorResult& aError); nsJSScriptTimeoutHandler(JSContext* aCx, WorkerPrivate* aWorkerPrivate, @@ -77,6 +79,10 @@ public: *aColumn = mColumn; } + virtual LoadedScript* GetInitiatingScript() override { + return mInitiatingScript; + } + virtual void MarkForCC() override { if (mFunction) { @@ -104,6 +110,9 @@ private: // it should be used, else use mExpr. nsString mExpr; RefPtr mFunction; + + // Initiating script for use when evaluating mExpr on the main thread. + RefPtr mInitiatingScript; }; @@ -112,6 +121,8 @@ private: NS_IMPL_CYCLE_COLLECTION_CLASS(nsJSScriptTimeoutHandler) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsJSScriptTimeoutHandler) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mFunction) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mInitiatingScript) tmp->ReleaseJSObjects(); NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsJSScriptTimeoutHandler) @@ -151,6 +162,9 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsJSScriptTimeoutHandler) if (tmp->mFunction) { NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFunction) } + if (tmp->mInitiatingScript) { + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInitiatingScript) + } NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsJSScriptTimeoutHandler) @@ -243,12 +257,14 @@ nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler(JSContext* aCx, nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler(JSContext* aCx, nsGlobalWindow *aWindow, + LoadedScript* aInitiatingScript, const nsAString& aExpression, bool* aAllowEval, ErrorResult& aError) : mLineNo(0) , mColumn(0) , mExpr(aExpression) + , mInitiatingScript(aInitiatingScript) { if (!aWindow->GetContextInternal() || !aWindow->FastGetGlobalJSObject()) { // This window was already closed, or never properly initialized, @@ -352,9 +368,11 @@ already_AddRefed NS_CreateJSTimeoutHandler(JSContext* aCx, nsGlobalWindow *aWindow, const nsAString& aExpression, ErrorResult& aError) { + LoadedScript* script = ScriptLoader::GetActiveScript(aCx); + bool allowEval = false; RefPtr handler = - new nsJSScriptTimeoutHandler(aCx, aWindow, aExpression, &allowEval, aError); + new nsJSScriptTimeoutHandler(aCx, aWindow, script, aExpression, &allowEval, aError); if (aError.Failed() || !allowEval) { return nullptr; } diff --git a/dom/base/nsJSUtils.cpp b/dom/base/nsJSUtils.cpp index 1b667326b0..43efe6f4e3 100644 --- a/dom/base/nsJSUtils.cpp +++ b/dom/base/nsJSUtils.cpp @@ -123,157 +123,276 @@ nsJSUtils::CompileFunction(AutoJSAPI& jsapi, return NS_OK; } -nsresult -nsJSUtils::EvaluateString(JSContext* aCx, - const nsAString& aScript, - JS::Handle aEvaluationGlobal, - JS::CompileOptions& aCompileOptions, - const EvaluateOptions& aEvaluateOptions, - JS::MutableHandle aRetValue) +static nsresult +EvaluationExceptionToNSResult(JSContext* aCx) { + if (JS_IsExceptionPending(aCx)) { + return NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW; + } + return NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW_UNCATCHABLE; +} + +nsJSUtils::ExecutionContext::ExecutionContext(JSContext* aCx, + JS::Handle aGlobal) + : mCx(aCx) + , mCompartment(aCx, aGlobal) + , mRetValue(aCx) + , mScopeChain(aCx) + , mScript(aCx) + , mRv(NS_OK) + , mSkip(false) + , mCoerceToString(false) + , mEncodeBytecode(false) +#ifdef DEBUG + , mWantsReturnValue(false) + , mExpectScopeChain(false) + , mScriptUsed(false) +#endif +{ + MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext()); + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mRetValue.isUndefined()); + + MOZ_ASSERT(js::GetGlobalForObjectCrossCompartment(aGlobal) == aGlobal); + if (MOZ_UNLIKELY(!xpc::Scriptability::Get(aGlobal).Allowed())) { + mSkip = true; + mRv = NS_OK; + } +} + +void +nsJSUtils::ExecutionContext::SetScopeChain( + const JS::AutoObjectVector& aScopeChain) +{ + if (mSkip) { + return; + } + +#ifdef DEBUG + mExpectScopeChain = true; +#endif + // Now make sure to wrap the scope chain into the right compartment. + if (!mScopeChain.reserve(aScopeChain.length())) { + mSkip = true; + mRv = NS_ERROR_OUT_OF_MEMORY; + return; + } + + for (size_t i = 0; i < aScopeChain.length(); ++i) { + JS::ExposeObjectToActiveJS(aScopeChain[i]); + mScopeChain.infallibleAppend(aScopeChain[i]); + if (!JS_WrapObject(mCx, mScopeChain[i])) { + mSkip = true; + mRv = NS_ERROR_OUT_OF_MEMORY; + return; + } + } +} + +nsresult +nsJSUtils::ExecutionContext::JoinCompile(void** aOffThreadToken) +{ + if (mSkip) { + return mRv; + } + + MOZ_ASSERT(!mWantsReturnValue); + MOZ_ASSERT(!mExpectScopeChain); + MOZ_ASSERT(!mScript); + mScript.set(JS::FinishOffThreadScript(mCx, *aOffThreadToken)); + *aOffThreadToken = nullptr; // Mark the token as having been finished. + if (!mScript) { + mSkip = true; + mRv = EvaluationExceptionToNSResult(mCx); + return mRv; + } + + if (mEncodeBytecode && !StartIncrementalEncoding(mCx, mScript)) { + mSkip = true; + mRv = EvaluationExceptionToNSResult(mCx); + return mRv; + } + + return NS_OK; +} + +nsresult +nsJSUtils::ExecutionContext::Compile(JS::CompileOptions& aCompileOptions, + JS::SourceBufferHolder& 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, &mScript); + } else { + compiled = JS::CompileForNonSyntacticScope(mCx, aCompileOptions, aSrcBuf, + &mScript); + } + + MOZ_ASSERT_IF(compiled, mScript); + if (!compiled) { + mSkip = true; + mRv = EvaluationExceptionToNSResult(mCx); + return mRv; + } + + if (mEncodeBytecode && !StartIncrementalEncoding(mCx, mScript)) { + mSkip = true; + mRv = EvaluationExceptionToNSResult(mCx); + return mRv; + } + + return NS_OK; +} + +nsresult +nsJSUtils::ExecutionContext::Compile(JS::CompileOptions& aCompileOptions, + const nsAString& aScript) +{ + if (mSkip) { + return mRv; + } + const nsPromiseFlatString& flatScript = PromiseFlatString(aScript); JS::SourceBufferHolder srcBuf(flatScript.get(), aScript.Length(), JS::SourceBufferHolder::NoOwnership); - return EvaluateString(aCx, srcBuf, aEvaluationGlobal, aCompileOptions, - aEvaluateOptions, aRetValue, nullptr); + return Compile(aCompileOptions, srcBuf); } nsresult -nsJSUtils::EvaluateString(JSContext* aCx, - JS::SourceBufferHolder& aSrcBuf, - JS::Handle aEvaluationGlobal, - JS::CompileOptions& aCompileOptions, - const EvaluateOptions& aEvaluateOptions, - JS::MutableHandle aRetValue, - void **aOffThreadToken) +nsJSUtils::ExecutionContext::Decode(JS::CompileOptions& aCompileOptions, + mozilla::Vector& aBytecodeBuf, + size_t aBytecodeIndex) { - PROFILER_LABEL("nsJSUtils", "EvaluateString", - js::ProfileEntry::Category::JS); - - MOZ_ASSERT_IF(aCompileOptions.versionSet, - aCompileOptions.version != JSVERSION_UNKNOWN); - MOZ_ASSERT_IF(aEvaluateOptions.coerceToString, !aCompileOptions.noScriptRval); - MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext()); - MOZ_ASSERT(aSrcBuf.get()); - MOZ_ASSERT(js::GetGlobalForObjectCrossCompartment(aEvaluationGlobal) == - aEvaluationGlobal); - MOZ_ASSERT_IF(aOffThreadToken, aCompileOptions.noScriptRval); - MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(CycleCollectedJSContext::Get() && - CycleCollectedJSContext::Get()->MicroTaskLevel()); - - // Unfortunately, the JS engine actually compiles scripts with a return value - // in a different, less efficient way. Furthermore, it can't JIT them in many - // cases. So we need to be explicitly told whether the caller cares about the - // return value. Callers can do this by calling the other overload of - // EvaluateString() which calls this function with - // aCompileOptions.noScriptRval set to true. - aRetValue.setUndefined(); - - nsresult rv = NS_OK; - - NS_ENSURE_TRUE(xpc::Scriptability::Get(aEvaluationGlobal).Allowed(), NS_OK); - - bool ok = true; - // Scope the JSAutoCompartment so that we can later wrap the return value - // into the caller's cx. - { - JSAutoCompartment ac(aCx, aEvaluationGlobal); - - // Now make sure to wrap the scope chain into the right compartment. - JS::AutoObjectVector scopeChain(aCx); - if (!scopeChain.reserve(aEvaluateOptions.scopeChain.length())) { - return NS_ERROR_OUT_OF_MEMORY; - } - - for (size_t i = 0; i < aEvaluateOptions.scopeChain.length(); ++i) { - JS::ExposeObjectToActiveJS(aEvaluateOptions.scopeChain[i]); - scopeChain.infallibleAppend(aEvaluateOptions.scopeChain[i]); - if (!JS_WrapObject(aCx, scopeChain[i])) { - ok = false; - break; - } - } - - if (ok && aOffThreadToken) { - JS::Rooted - script(aCx, JS::FinishOffThreadScript(aCx, *aOffThreadToken)); - *aOffThreadToken = nullptr; // Mark the token as having been finished. - if (script) { - ok = JS_ExecuteScript(aCx, scopeChain, script); - } else { - ok = false; - } - } else if (ok) { - ok = JS::Evaluate(aCx, scopeChain, aCompileOptions, aSrcBuf, aRetValue); - } - - if (ok && aEvaluateOptions.coerceToString && !aRetValue.isUndefined()) { - JS::Rooted value(aCx, aRetValue); - JSString* str = JS::ToString(aCx, value); - ok = !!str; - aRetValue.set(ok ? JS::StringValue(str) : JS::UndefinedValue()); - } + if (mSkip) { + return mRv; } - if (!ok) { - if (JS_IsExceptionPending(aCx)) { - rv = NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW; - } else { - rv = NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW_UNCATCHABLE; - } - - if (!aCompileOptions.noScriptRval) { - aRetValue.setUndefined(); - } + MOZ_ASSERT(!mWantsReturnValue); + JS::TranscodeResult tr = 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; } - // Wrap the return value into whatever compartment aCx was in. - if (ok && !aCompileOptions.noScriptRval) { - if (!JS_WrapValue(aCx, aRetValue)) { - return NS_ERROR_OUT_OF_MEMORY; - } + return mRv; +} + +nsresult +nsJSUtils::ExecutionContext::JoinDecode(void **aOffThreadToken) +{ + if (mSkip) { + return mRv; } - return rv; + + MOZ_ASSERT(!mWantsReturnValue); + MOZ_ASSERT(!mExpectScopeChain); + mScript.set(JS::FinishOffThreadScriptDecoder(mCx, *aOffThreadToken)); + *aOffThreadToken = nullptr; // Mark the token as having been finished. + if (!mScript) { + mSkip = true; + mRv = EvaluationExceptionToNSResult(mCx); + return mRv; + } + + return NS_OK; +} + +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 aValue) { + if (!aValue.isObject()) { + return false; + } + + JS::Rooted obj(aCx, js::CheckedUnwrap(&aValue.toObject())); + if (!obj) { + return false; + } + + return JS::IsPromiseObject(obj); } nsresult -nsJSUtils::EvaluateString(JSContext* aCx, - JS::SourceBufferHolder& aSrcBuf, - JS::Handle aEvaluationGlobal, - JS::CompileOptions& aCompileOptions, - const EvaluateOptions& aEvaluateOptions, - JS::MutableHandle aRetValue) +nsJSUtils::ExecutionContext::ExecScript(JS::MutableHandle aRetValue) { - return EvaluateString(aCx, aSrcBuf, aEvaluationGlobal, aCompileOptions, - aEvaluateOptions, aRetValue, nullptr); -} + if (mSkip) { + return mRv; + } -nsresult -nsJSUtils::EvaluateString(JSContext* aCx, - const nsAString& aScript, - JS::Handle aEvaluationGlobal, - JS::CompileOptions& aCompileOptions) -{ - EvaluateOptions options(aCx); - aCompileOptions.setNoScriptRval(true); - JS::RootedValue unused(aCx); - return EvaluateString(aCx, aScript, aEvaluationGlobal, aCompileOptions, - options, &unused); -} + MOZ_ASSERT(mScript); + MOZ_ASSERT(mWantsReturnValue); -nsresult -nsJSUtils::EvaluateString(JSContext* aCx, - JS::SourceBufferHolder& aSrcBuf, - JS::Handle aEvaluationGlobal, - JS::CompileOptions& aCompileOptions, - void **aOffThreadToken) -{ - EvaluateOptions options(aCx); - aCompileOptions.setNoScriptRval(true); - JS::RootedValue unused(aCx); - return EvaluateString(aCx, aSrcBuf, aEvaluationGlobal, aCompileOptions, - options, &unused, aOffThreadToken); + if (!JS_ExecuteScript(mCx, mScopeChain, mScript, aRetValue)) { + mSkip = true; + mRv = EvaluationExceptionToNSResult(mCx); + return mRv; + } + +#ifdef DEBUG + mWantsReturnValue = false; +#endif + 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. + aRetValue.setUndefined(); + } + + 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); + } + aRetValue.set(JS::StringValue(str)); + } + + return NS_OK; } nsresult diff --git a/dom/base/nsJSUtils.h b/dom/base/nsJSUtils.h index cb80ef4526..db70cd924d 100644 --- a/dom/base/nsJSUtils.h +++ b/dom/base/nsJSUtils.h @@ -63,52 +63,129 @@ public: const nsAString& aBody, JSObject** aFunctionObject); - struct MOZ_STACK_CLASS EvaluateOptions { - bool coerceToString; - JS::AutoObjectVector scopeChain; + // ExecutionContext is used to switch compartment. + class MOZ_STACK_CLASS ExecutionContext { + JSContext* mCx; - explicit EvaluateOptions(JSContext* cx) - : coerceToString(false) - , scopeChain(cx) - {} + // Handles switching to our global's compartment. + JSAutoCompartment mCompartment; - EvaluateOptions& setCoerceToString(bool aCoerce) { - coerceToString = aCoerce; + // Set to a valid handle if a return value is expected. + JS::Rooted mRetValue; + + // Scope chain in which the execution takes place. + JS::AutoObjectVector mScopeChain; + + // The compiled script. + JS::Rooted 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; + + // Should the result be serialized before being returned. + bool mCoerceToString; + + // 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 that has had + // TakeOwnershipOfErrorReporting() called on it. + ExecutionContext(JSContext* aCx, JS::Handle aGlobal); + + ExecutionContext(const ExecutionContext&) = delete; + ExecutionContext(ExecutionContext&&) = delete; + + ~ExecutionContext() { + // 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; } + + // When set, this flag records and encodes the bytecode as soon as it is + // being compiled, and before it is being executed. The bytecode can then be + // requested by using |JS::FinishIncrementalEncoding| with the mutable + // handle |aScript| argument of |CompileAndExec| or |JoinAndExec|. + 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); + + // 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(void** aOffThreadToken); + + // Compile a script contained in a SourceText. + nsresult Compile(JS::CompileOptions& aCompileOptions, + JS::SourceBufferHolder& 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& 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(void** aOffThreadToken); + + // 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 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 aRetValue); + + MOZ_MUST_USE nsresult ExecScript(JS::MutableHandle aRetValue); }; - // aEvaluationGlobal is the global to evaluate in. The return value - // will then be wrapped back into the compartment aCx is in when - // this function is called. For all the EvaluateString overloads, - // the JSContext must come from an AutoJSAPI that has had - // TakeOwnershipOfErrorReporting() called on it. - static nsresult EvaluateString(JSContext* aCx, - const nsAString& aScript, - JS::Handle aEvaluationGlobal, - JS::CompileOptions &aCompileOptions, - const EvaluateOptions& aEvaluateOptions, - JS::MutableHandle aRetValue); - - static nsresult EvaluateString(JSContext* aCx, - JS::SourceBufferHolder& aSrcBuf, - JS::Handle aEvaluationGlobal, - JS::CompileOptions &aCompileOptions, - const EvaluateOptions& aEvaluateOptions, - JS::MutableHandle aRetValue); - - - static nsresult EvaluateString(JSContext* aCx, - const nsAString& aScript, - JS::Handle aEvaluationGlobal, - JS::CompileOptions &aCompileOptions); - - static nsresult EvaluateString(JSContext* aCx, - JS::SourceBufferHolder& aSrcBuf, - JS::Handle aEvaluationGlobal, - JS::CompileOptions &aCompileOptions, - void **aOffThreadToken); - static nsresult CompileModule(JSContext* aCx, JS::SourceBufferHolder& aSrcBuf, JS::Handle aEvaluationGlobal, @@ -128,16 +205,6 @@ public: JS::AutoObjectVector& aScopeChain); static void ResetTimeZone(); - -private: - // Implementation for our EvaluateString bits - static nsresult EvaluateString(JSContext* aCx, - JS::SourceBufferHolder& aSrcBuf, - JS::Handle aEvaluationGlobal, - JS::CompileOptions& aCompileOptions, - const EvaluateOptions& aEvaluateOptions, - JS::MutableHandle aRetValue, - void **aOffThreadToken); }; template diff --git a/dom/events/EventListenerManager.cpp b/dom/events/EventListenerManager.cpp index 4ba5ed3386..b6fbf6f6ad 100644 --- a/dom/events/EventListenerManager.cpp +++ b/dom/events/EventListenerManager.cpp @@ -23,6 +23,8 @@ #include "mozilla/dom/Event.h" #include "mozilla/dom/EventTargetBinding.h" #include "mozilla/ScopeExit.h" +#include "mozilla/dom/ModuleScript.h" +#include "mozilla/dom/ScriptLoader.h" #include "mozilla/dom/TouchEvent.h" #include "mozilla/TimelineConsumers.h" #include "mozilla/EventTimelineMarker.h" @@ -1019,6 +1021,15 @@ EventListenerManager::CompileEventHandlerInternal(Listener* aListener, NS_ENSURE_SUCCESS(result, result); NS_ENSURE_TRUE(handler, NS_ERROR_FAILURE); + JS::Rooted func(cx, JS_GetObjectFunction(handler)); + MOZ_ASSERT(func); + JS::Rooted jsScript(cx, JS_GetFunctionScript(cx, func)); + MOZ_ASSERT(jsScript); + RefPtr loaderScript = ScriptLoader::GetActiveScript(cx); + if (loaderScript) { + loaderScript->AssociateWithScript(jsScript); + } + if (jsEventHandler->EventName() == nsGkAtoms::onerror && win) { RefPtr handlerCallback = new OnErrorEventHandlerNonNull(nullptr, handler, /* aIncumbentGlobal = */ nullptr); diff --git a/dom/fetch/Request.cpp b/dom/fetch/Request.cpp index 7a93a81799..ab87a3215a 100644 --- a/dom/fetch/Request.cpp +++ b/dom/fetch/Request.cpp @@ -313,7 +313,8 @@ Request::Constructor(const GlobalObject& aGlobal, nsAutoString requestURL; nsCString fragment; if (NS_IsMainThread()) { - nsIDocument* doc = GetEntryDocument(); + nsCOMPtr inner(do_QueryInterface(global)); + nsIDocument* doc = inner ? inner->GetExtantDoc() : nullptr; if (doc) { GetRequestURLFromDocument(doc, input, requestURL, fragment, aRv); } else { @@ -362,7 +363,8 @@ Request::Constructor(const GlobalObject& aGlobal, } else { nsAutoString referrerURL; if (NS_IsMainThread()) { - nsIDocument* doc = GetEntryDocument(); + nsCOMPtr inner(do_QueryInterface(global)); + nsIDocument* doc = inner ? inner->GetExtantDoc() : nullptr; nsCOMPtr uri; if (doc) { uri = ParseURLFromDocument(doc, referrer, aRv); diff --git a/dom/fetch/Response.cpp b/dom/fetch/Response.cpp index 100a0204c3..42b25ae1d9 100644 --- a/dom/fetch/Response.cpp +++ b/dom/fetch/Response.cpp @@ -65,7 +65,8 @@ Response::Redirect(const GlobalObject& aGlobal, const nsAString& aUrl, if (NS_IsMainThread()) { nsCOMPtr baseURI; - nsIDocument* doc = GetEntryDocument(); + nsCOMPtr inner(do_QueryInterface(aGlobal.GetAsSupports())); + nsIDocument* doc = inner ? inner->GetExtantDoc() : nullptr; if (doc) { baseURI = doc->GetBaseURI(); } diff --git a/dom/ipc/StructuredCloneData.h b/dom/ipc/StructuredCloneData.h index 9bf29337d0..528e276c78 100644 --- a/dom/ipc/StructuredCloneData.h +++ b/dom/ipc/StructuredCloneData.h @@ -79,9 +79,6 @@ public: StructuredCloneData& operator=(const StructuredCloneData& aOther) = delete; - StructuredCloneData& - operator=(StructuredCloneData&& aOther) = default; - const nsTArray>& BlobImpls() const { return mBlobImplArray; diff --git a/dom/jsurl/nsJSProtocolHandler.cpp b/dom/jsurl/nsJSProtocolHandler.cpp index 5db4d69e0d..fe3c12b76a 100644 --- a/dom/jsurl/nsJSProtocolHandler.cpp +++ b/dom/jsurl/nsJSProtocolHandler.cpp @@ -275,10 +275,14 @@ nsresult nsJSThunk::EvaluateScript(nsIChannel *aChannel, JS::CompileOptions options(cx); options.setFileAndLine(mURL.get(), 1) .setVersion(JSVERSION_DEFAULT); - nsJSUtils::EvaluateOptions evalOptions(cx); - evalOptions.setCoerceToString(true); - rv = nsJSUtils::EvaluateString(cx, NS_ConvertUTF8toUTF16(script), - globalJSObject, options, evalOptions, &v); + { + nsJSUtils::ExecutionContext exec(cx, globalJSObject); + exec.SetCoerceToString(true); + 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; diff --git a/dom/plugins/base/nsNPAPIPlugin.cpp b/dom/plugins/base/nsNPAPIPlugin.cpp index da4f09914f..4b02440e20 100644 --- a/dom/plugins/base/nsNPAPIPlugin.cpp +++ b/dom/plugins/base/nsNPAPIPlugin.cpp @@ -1388,14 +1388,23 @@ _evaluate(NPP npp, NPObject* npobj, NPString *script, NPVariant *result) options.setFileAndLine(spec, 0) .setVersion(JSVERSION_DEFAULT); JS::Rooted rval(cx); - nsJSUtils::EvaluateOptions evalOptions(cx); + JS::AutoObjectVector scopeChain(cx); if (obj != js::GetGlobalForObjectCrossCompartment(obj) && - !evalOptions.scopeChain.append(obj)) { + !scopeChain.append(obj)) { return false; } obj = js::GetGlobalForObjectCrossCompartment(obj); - nsresult rv = nsJSUtils::EvaluateString(cx, utf16script, obj, options, - evalOptions, &rval); + nsresult rv = NS_OK; + { + nsJSUtils::ExecutionContext exec(cx, obj); + exec.SetScopeChain(scopeChain); + 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)); diff --git a/dom/promise/Promise.cpp b/dom/promise/Promise.cpp index 38db74b020..0e1349d087 100644 --- a/dom/promise/Promise.cpp +++ b/dom/promise/Promise.cpp @@ -227,15 +227,6 @@ Promise::Then(JSContext* aCx, aRetval.setObject(*retval); } -// We need a dummy function to pass to JS::NewPromiseObject. -static bool -DoNothingPromiseExecutor(JSContext*, unsigned aArgc, JS::Value* aVp) -{ - JS::CallArgs args = CallArgsFromVp(aArgc, aVp); - args.rval().setUndefined(); - return true; -} - void Promise::CreateWrapper(JS::Handle aDesiredProto, ErrorResult& aRv) { @@ -246,17 +237,7 @@ Promise::CreateWrapper(JS::Handle aDesiredProto, ErrorResult& aRv) } JSContext* cx = jsapi.cx(); - JSFunction* doNothingFunc = - JS_NewFunction(cx, DoNothingPromiseExecutor, /* nargs = */ 2, - /* flags = */ 0, nullptr); - if (!doNothingFunc) { - JS_ClearPendingException(cx); - aRv.Throw(NS_ERROR_OUT_OF_MEMORY); - return; - } - - JS::Rooted doNothingObj(cx, JS_GetFunctionObject(doNothingFunc)); - mPromiseObj = JS::NewPromiseObject(cx, doNothingObj, aDesiredProto); + mPromiseObj = JS::NewPromiseObject(cx, nullptr, aDesiredProto); if (!mPromiseObj) { JS_ClearPendingException(cx); aRv.Throw(NS_ERROR_OUT_OF_MEMORY); diff --git a/dom/script/ModuleLoadRequest.cpp b/dom/script/ModuleLoadRequest.cpp index 2abc6236e8..9055e2a57d 100644 --- a/dom/script/ModuleLoadRequest.cpp +++ b/dom/script/ModuleLoadRequest.cpp @@ -4,6 +4,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "ModuleLoadRequest.h" + +#include "mozilla/HoldDropJSObjects.h" + #include "ModuleScript.h" #include "ScriptLoader.h" @@ -13,58 +16,94 @@ namespace dom { NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ModuleLoadRequest) NS_INTERFACE_MAP_END_INHERITING(ScriptLoadRequest) -NS_IMPL_CYCLE_COLLECTION_INHERITED(ModuleLoadRequest, ScriptLoadRequest, - mBaseURL, - mLoader, - mModuleScript, - mImports) +NS_IMPL_CYCLE_COLLECTION_CLASS(ModuleLoadRequest) +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(ModuleLoadRequest, + ScriptLoadRequest) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mLoader, mModuleScript, mImports) + tmp->ClearDynamicImport(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ModuleLoadRequest, + ScriptLoadRequest) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLoader, mModuleScript, mImports) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(ModuleLoadRequest, + ScriptLoadRequest) + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mDynamicReferencingPrivate) + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mDynamicSpecifier) + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mDynamicPromise) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + NS_IMPL_ADDREF_INHERITED(ModuleLoadRequest, ScriptLoadRequest) NS_IMPL_RELEASE_INHERITED(ModuleLoadRequest, ScriptLoadRequest) -ModuleLoadRequest::ModuleLoadRequest(nsIURI* aURI, - nsIScriptElement* aElement, - uint32_t aVersion, - CORSMode aCORSMode, - const SRIMetadata &aIntegrity, - nsIURI* aReferrer, - mozilla::net::ReferrerPolicy aReferrerPolicy, - ScriptLoader* aLoader) - : ScriptLoadRequest(ScriptKind::Module, - aURI, - aElement, - aVersion, - aCORSMode, - aIntegrity, - aReferrer, - aReferrerPolicy), - mIsTopLevel(true), - mLoader(aLoader), - mVisitedSet(new VisitedURLSet()) -{ - mVisitedSet->PutEntry(aURI); +static VisitedURLSet* NewVisitedSetForTopLevelImport(nsIURI* aURI) { + auto set = new VisitedURLSet(); + set->PutEntry(aURI); + return set; } -ModuleLoadRequest::ModuleLoadRequest(nsIURI* aURI, - ModuleLoadRequest* aParent) - : ScriptLoadRequest(ScriptKind::Module, - aURI, - aParent->mElement, - aParent->mJSVersion, - aParent->mCORSMode, - SRIMetadata(), - aParent->mURI, - aParent->mReferrerPolicy), - mIsTopLevel(false), - mLoader(aParent->mLoader), - mVisitedSet(aParent->mVisitedSet) -{ - MOZ_ASSERT(mVisitedSet->Contains(aURI)); - - mIsInline = false; - mScriptMode = aParent->mScriptMode; +/* static */ ModuleLoadRequest* ModuleLoadRequest::CreateTopLevel( + nsIURI* aURI, ScriptFetchOptions* aFetchOptions, + const SRIMetadata& aIntegrity, nsIURI* aReferrer, ScriptLoader* aLoader) { + return new ModuleLoadRequest(aURI, aFetchOptions, aIntegrity, aReferrer, + true, /* is top level */ + false, /* is dynamic import */ + aLoader, NewVisitedSetForTopLevelImport(aURI)); } +/* static */ ModuleLoadRequest* ModuleLoadRequest::CreateStaticImport( + nsIURI* aURI, ModuleLoadRequest* aParent) { + auto request = + new ModuleLoadRequest(aURI, aParent->mFetchOptions, SRIMetadata(), + aParent->mURI, false, /* is top level */ + false, /* is dynamic import */ + aParent->mLoader, aParent->mVisitedSet); + + request->mIsInline = false; + request->mScriptMode = aParent->mScriptMode; + + return request; +} + +/* static */ ModuleLoadRequest* ModuleLoadRequest::CreateDynamicImport( + nsIURI* aURI, ScriptFetchOptions* aFetchOptions, nsIURI* aBaseURL, + ScriptLoader* aLoader, JS::Handle aReferencingPrivate, + JS::Handle aSpecifier, JS::Handle aPromise) +{ + MOZ_ASSERT(aSpecifier); + MOZ_ASSERT(aPromise); + + auto request = new ModuleLoadRequest( + aURI, aFetchOptions, SRIMetadata(), aBaseURL, + true, /* is top level */ + true, /* is dynamic import */ + aLoader, NewVisitedSetForTopLevelImport(aURI)); + + request->mIsInline = false; + request->mScriptMode = ScriptMode::eAsync; + request->mDynamicReferencingPrivate = aReferencingPrivate; + request->mDynamicSpecifier = aSpecifier; + request->mDynamicPromise = aPromise; + + HoldJSObjects(request); + + return request; +} + +ModuleLoadRequest::ModuleLoadRequest( + nsIURI* aURI, ScriptFetchOptions* aFetchOptions, + const SRIMetadata& aIntegrity, nsIURI* aReferrer, bool aIsTopLevel, + bool aIsDynamicImport, ScriptLoader* aLoader, VisitedURLSet* aVisitedSet) + : ScriptLoadRequest(ScriptKind::Module, aURI, aFetchOptions, aIntegrity, + aReferrer), + mIsTopLevel(aIsTopLevel), + mIsDynamicImport(aIsDynamicImport), + mLoader(aLoader), + mVisitedSet(aVisitedSet) {} + void ModuleLoadRequest::Cancel() { ScriptLoadRequest::Cancel(); @@ -166,5 +205,11 @@ ModuleLoadRequest::LoadFinished() mLoader = nullptr; } +void ModuleLoadRequest::ClearDynamicImport() { + mDynamicReferencingPrivate = JS::UndefinedValue(); + mDynamicSpecifier = nullptr; + mDynamicPromise = nullptr; +} + } // dom namespace } // mozilla namespace diff --git a/dom/script/ModuleLoadRequest.h b/dom/script/ModuleLoadRequest.h index ad6573cf3b..0b5f331bff 100644 --- a/dom/script/ModuleLoadRequest.h +++ b/dom/script/ModuleLoadRequest.h @@ -37,30 +37,42 @@ class ModuleLoadRequest final : public ScriptLoadRequest ModuleLoadRequest(const ModuleLoadRequest& aOther) = delete; ModuleLoadRequest(ModuleLoadRequest&& aOther) = delete; + ModuleLoadRequest(nsIURI* aURI, ScriptFetchOptions* aFetchOptions, + const SRIMetadata& aIntegrity, nsIURI* aReferrer, + bool aIsTopLevel, bool aIsDynamicImport, + ScriptLoader* aLoader, VisitedURLSet* aVisitedSet); + public: NS_DECL_ISUPPORTS_INHERITED - NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ModuleLoadRequest, ScriptLoadRequest) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(ModuleLoadRequest, + ScriptLoadRequest) // Create a top-level module load request. - ModuleLoadRequest(nsIURI* aURI, - nsIScriptElement* aElement, - uint32_t aVersion, - CORSMode aCORSMode, - const SRIMetadata& aIntegrity, - nsIURI* aReferrer, - mozilla::net::ReferrerPolicy, - ScriptLoader* aLoader); + static ModuleLoadRequest* CreateTopLevel(nsIURI* aURI, + ScriptFetchOptions* aFetchOptions, + const SRIMetadata& aIntegrity, + nsIURI* aReferrer, + ScriptLoader* aLoader); - // Create a module load request for an imported module. - ModuleLoadRequest(nsIURI* aURI, - ModuleLoadRequest* aParent); + // Create a module load request for a static module import. + static ModuleLoadRequest* CreateStaticImport(nsIURI* aURI, + ModuleLoadRequest* aParent); + + // Create a module load request for dynamic module import. + static ModuleLoadRequest* CreateDynamicImport( + nsIURI* aURI, ScriptFetchOptions* aFetchOptions, nsIURI* aBaseURL, + ScriptLoader* aLoader, JS::Handle aReferencingPrivate, + JS::Handle aSpecifier, JS::Handle aPromise); bool IsTopLevel() const override { return mIsTopLevel; } + bool IsDynamicImport() const { return mIsDynamicImport; } + void SetReady() override; void Cancel() override; + void ClearDynamicImport(); void ModuleLoaded(); void ModuleErrored(); @@ -74,9 +86,9 @@ private: public: // Is this a request for a top level module script or an import? const bool mIsTopLevel; - - // The base URL used for resolving relative module imports. - nsCOMPtr mBaseURL; + + // Is this the top level request for a dynamic module import? + const bool mIsDynamicImport; // Pointer to the script loader, used to trigger actions when the module load // finishes. @@ -97,6 +109,11 @@ public: // Set of module URLs visited while fetching the module graph this request is // part of. RefPtr mVisitedSet; + + // For dynamic imports, the details to pass to FinishDynamicImport. + JS::Heap mDynamicReferencingPrivate; + JS::Heap mDynamicSpecifier; + JS::Heap mDynamicPromise; }; } // dom namespace diff --git a/dom/script/ModuleScript.cpp b/dom/script/ModuleScript.cpp index 9f7fe9ab92..425da8207a 100644 --- a/dom/script/ModuleScript.cpp +++ b/dom/script/ModuleScript.cpp @@ -7,48 +7,116 @@ * A class that handles loading and evaluation of