nsresult nsJSUtils::EvaluateString(JSContext* aCx, JS::SourceBufferHolder& aSrcBuf, JS::Handle<JSObject*> aEvaluationGlobal, JS::CompileOptions& aCompileOptions, const EvaluateOptions& aEvaluateOptions, JS::MutableHandle<JS::Value> aRetValue, void **aOffThreadToken) { PROFILER_LABEL("nsJSUtils", "EvaluateString", js::ProfileEntry::Category::JS); MOZ_ASSERT(JS::ContextOptionsRef(aCx).autoJSAPIOwnsErrorReporting(), "Caller must own error reporting"); MOZ_ASSERT_IF(aCompileOptions.versionSet, aCompileOptions.version != JSVERSION_UNKNOWN); 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(nsContentUtils::IsInMicroTask()); // 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; nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); NS_ENSURE_TRUE(ssm->ScriptAllowed(aEvaluationGlobal), 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<JSScript*> script(aCx, JS::FinishOffThreadScript(aCx, JS_GetRuntime(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) { 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(); } } // 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 rv; }
nsresult nsJSUtils::EvaluateString(JSContext* aCx, JS::SourceBufferHolder& aSrcBuf, JS::Handle<JSObject*> aScopeObject, JS::CompileOptions& aCompileOptions, const EvaluateOptions& aEvaluateOptions, JS::MutableHandle<JS::Value> aRetValue, void **aOffThreadToken) { PROFILER_LABEL("nsJSUtils", "EvaluateString", js::ProfileEntry::Category::JS); MOZ_ASSERT_IF(aCompileOptions.versionSet, aCompileOptions.version != JSVERSION_UNKNOWN); MOZ_ASSERT_IF(aEvaluateOptions.coerceToString, aEvaluateOptions.needResult); MOZ_ASSERT_IF(!aEvaluateOptions.reportUncaught, aEvaluateOptions.needResult); MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext()); MOZ_ASSERT(aSrcBuf.get()); // 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 aEvaluateOptions.needResult // set to false. aRetValue.setUndefined(); JS::ExposeObjectToActiveJS(aScopeObject); nsAutoMicroTask mt; nsresult rv = NS_OK; bool ok = false; nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); NS_ENSURE_TRUE(ssm->ScriptAllowed(js::GetGlobalForObjectCrossCompartment(aScopeObject)), NS_OK); mozilla::Maybe<AutoDontReportUncaught> dontReport; if (!aEvaluateOptions.reportUncaught) { // We need to prevent AutoLastFrameCheck from reporting and clearing // any pending exceptions. dontReport.emplace(aCx); } // Scope the JSAutoCompartment so that we can later wrap the return value // into the caller's cx. { JSAutoCompartment ac(aCx, aScopeObject); JS::Rooted<JSObject*> rootedScope(aCx, aScopeObject); if (aOffThreadToken) { JS::Rooted<JSScript*> script(aCx, JS::FinishOffThreadScript(aCx, JS_GetRuntime(aCx), *aOffThreadToken)); *aOffThreadToken = nullptr; // Mark the token as having been finished. if (script) { if (aEvaluateOptions.needResult) { ok = JS_ExecuteScript(aCx, rootedScope, script, aRetValue); } else { ok = JS_ExecuteScript(aCx, rootedScope, script); } } else { ok = false; } } else { if (aEvaluateOptions.needResult) { ok = JS::Evaluate(aCx, rootedScope, aCompileOptions, aSrcBuf, aRetValue); } else { ok = JS::Evaluate(aCx, rootedScope, aCompileOptions, aSrcBuf); } } if (ok && aEvaluateOptions.coerceToString && !aRetValue.isUndefined()) { JS::Rooted<JS::Value> value(aCx, aRetValue); JSString* str = JS::ToString(aCx, value); ok = !!str; aRetValue.set(ok ? JS::StringValue(str) : JS::UndefinedValue()); } } if (!ok) { if (aEvaluateOptions.reportUncaught) { ReportPendingException(aCx); if (aEvaluateOptions.needResult) { aRetValue.setUndefined(); } } else { rv = JS_IsExceptionPending(aCx) ? NS_ERROR_FAILURE : NS_ERROR_OUT_OF_MEMORY; JS::Rooted<JS::Value> exn(aCx); JS_GetPendingException(aCx, &exn); if (aEvaluateOptions.needResult) { aRetValue.set(exn); } JS_ClearPendingException(aCx); } } // Wrap the return value into whatever compartment aCx was in. if (aEvaluateOptions.needResult) { JS::Rooted<JS::Value> v(aCx, aRetValue); if (!JS_WrapValue(aCx, &v)) { return NS_ERROR_OUT_OF_MEMORY; } aRetValue.set(v); } return rv; }