/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* 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/. */ /** * This is not a generated file. It contains common utility functions * invoked from the JavaScript code generated from IDL interfaces. * The goal of the utility functions is to cut down on the size of * the generated code itself. */ #include "mozilla/dom/JSExecutionContext.h" #include #include "ErrorList.h" #include "MainThreadUtils.h" #include "js/CompilationAndEvaluation.h" #include "js/CompileOptions.h" #include "js/Conversions.h" #include "js/experimental/JSStencil.h" #include "js/HeapAPI.h" #include "js/OffThreadScriptCompilation.h" #include "js/ProfilingCategory.h" #include "js/Promise.h" #include "js/SourceText.h" #include "js/Transcoding.h" #include "js/Value.h" #include "js/Wrapper.h" #include "jsapi.h" #include "mozilla/CycleCollectedJSContext.h" #include "mozilla/Likely.h" #include "nsContentUtils.h" #include "nsTPromiseFlatString.h" #include "xpcpublic.h" #if !defined(DEBUG) && !defined(MOZ_ENABLE_JS_DUMP) # include "mozilla/StaticPrefs_browser.h" #endif using namespace mozilla; using namespace mozilla::dom; static nsresult EvaluationExceptionToNSResult(JSContext* aCx) { if (JS_IsExceptionPending(aCx)) { return NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW; } return NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW_UNCATCHABLE; } JSExecutionContext::JSExecutionContext( JSContext* aCx, JS::Handle aGlobal, JS::CompileOptions& aCompileOptions, JS::Handle aDebuggerPrivateValue, JS::Handle aDebuggerIntroductionScript) : mAutoProfilerLabel("JSExecutionContext", /* dynamicStr */ nullptr, JS::ProfilingCategoryPair::JS), mCx(aCx), mRealm(aCx, aGlobal), mRetValue(aCx), mScript(aCx), mCompileOptions(aCompileOptions), mDebuggerPrivateValue(aCx, aDebuggerPrivateValue), mDebuggerIntroductionScript(aCx, aDebuggerIntroductionScript), mRv(NS_OK), mSkip(false), mCoerceToString(false), mEncodeBytecode(false) #ifdef DEBUG , mWantsReturnValue(false), mScriptUsed(false) #endif { MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext()); MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(CycleCollectedJSContext::Get() && CycleCollectedJSContext::Get()->MicroTaskLevel()); MOZ_ASSERT(mRetValue.isUndefined()); MOZ_ASSERT(JS_IsGlobalObject(aGlobal)); if (MOZ_UNLIKELY(!xpc::Scriptability::Get(aGlobal).Allowed())) { mSkip = true; mRv = NS_OK; } } nsresult JSExecutionContext::JoinOffThread( JS::OffThreadToken** aOffThreadToken) { if (mSkip) { return mRv; } MOZ_ASSERT(!mWantsReturnValue); JS::Rooted storage(mCx); RefPtr stencil = JS::FinishOffThreadStencil(mCx, *aOffThreadToken, storage.address()); *aOffThreadToken = nullptr; // Mark the token as having been finished. if (!stencil) { mSkip = true; mRv = EvaluationExceptionToNSResult(mCx); return mRv; } return InstantiateStencil(std::move(stencil), storage.address()); } template nsresult JSExecutionContext::InternalCompile(JS::SourceText& aSrcBuf) { if (mSkip) { return mRv; } MOZ_ASSERT(aSrcBuf.get()); MOZ_ASSERT(mRetValue.isUndefined()); #ifdef DEBUG mWantsReturnValue = !mCompileOptions.noScriptRval; #endif RefPtr stencil = CompileGlobalScriptToStencil(mCx, mCompileOptions, aSrcBuf); if (!stencil) { mSkip = true; mRv = EvaluationExceptionToNSResult(mCx); return mRv; } return InstantiateStencil(std::move(stencil)); } nsresult JSExecutionContext::Compile(JS::SourceText& aSrcBuf) { return InternalCompile(aSrcBuf); } nsresult JSExecutionContext::Compile(JS::SourceText& aSrcBuf) { return InternalCompile(aSrcBuf); } nsresult JSExecutionContext::Compile(const nsAString& aScript) { if (mSkip) { return mRv; } const nsPromiseFlatString& flatScript = PromiseFlatString(aScript); JS::SourceText srcBuf; if (!srcBuf.init(mCx, flatScript.get(), flatScript.Length(), JS::SourceOwnership::Borrowed)) { mSkip = true; mRv = EvaluationExceptionToNSResult(mCx); return mRv; } return Compile(srcBuf); } nsresult JSExecutionContext::Decode(mozilla::Vector& aBytecodeBuf, size_t aBytecodeIndex) { if (mSkip) { return mRv; } JS::DecodeOptions decodeOptions(mCompileOptions); decodeOptions.borrowBuffer = true; JS::TranscodeRange range(aBytecodeBuf.begin() + aBytecodeIndex, aBytecodeBuf.length() - aBytecodeIndex); MOZ_ASSERT(!mWantsReturnValue); RefPtr stencil; JS::TranscodeResult tr = JS::DecodeStencil(mCx, decodeOptions, range, getter_AddRefs(stencil)); // 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); if (tr != JS::TranscodeResult::Ok) { mSkip = true; mRv = NS_ERROR_DOM_JS_DECODING_ERROR; return mRv; } return InstantiateStencil(std::move(stencil)); } nsresult JSExecutionContext::InstantiateStencil( RefPtr&& aStencil, JS::InstantiationStorage* aStorage) { JS::InstantiateOptions instantiateOptions(mCompileOptions); JS::Rooted script( mCx, JS::InstantiateGlobalStencil(mCx, instantiateOptions, aStencil, aStorage)); if (!script) { mSkip = true; mRv = EvaluationExceptionToNSResult(mCx); return mRv; } if (mEncodeBytecode) { if (!JS::StartIncrementalEncoding(mCx, std::move(aStencil))) { mSkip = true; mRv = EvaluationExceptionToNSResult(mCx); return mRv; } } MOZ_ASSERT(!mScript); mScript.set(script); if (instantiateOptions.deferDebugMetadata) { if (!JS::UpdateDebugMetadata(mCx, mScript, instantiateOptions, mDebuggerPrivateValue, nullptr, mDebuggerIntroductionScript, nullptr)) { return NS_ERROR_OUT_OF_MEMORY; } } return NS_OK; } JSScript* JSExecutionContext::GetScript() { #ifdef DEBUG MOZ_ASSERT(!mSkip); MOZ_ASSERT(mScript); mScriptUsed = true; #endif return MaybeGetScript(); } JSScript* JSExecutionContext::MaybeGetScript() { return mScript; } nsresult JSExecutionContext::ExecScript() { if (mSkip) { return mRv; } MOZ_ASSERT(mScript); if (!JS_ExecuteScript(mCx, mScript)) { mSkip = true; mRv = EvaluationExceptionToNSResult(mCx); return mRv; } return NS_OK; } static bool IsPromiseValue(JSContext* aCx, JS::Handle aValue) { if (!aValue.isObject()) { return false; } // We only care about Promise here, so CheckedUnwrapStatic is fine. JS::Rooted obj(aCx, js::CheckedUnwrapStatic(&aValue.toObject())); if (!obj) { return false; } return JS::IsPromiseObject(obj); } nsresult JSExecutionContext::ExecScript( JS::MutableHandle aRetValue) { if (mSkip) { aRetValue.setUndefined(); return mRv; } MOZ_ASSERT(mScript); MOZ_ASSERT(mWantsReturnValue); if (!JS_ExecuteScript(mCx, 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; }