/* -*- 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/. */ #include "ScriptLoadRequest.h" #include "GeckoProfiler.h" #include "mozilla/dom/Document.h" #include "mozilla/HoldDropJSObjects.h" #include "mozilla/StaticPrefs_dom.h" #include "mozilla/Unused.h" #include "mozilla/Utf8.h" // mozilla::Utf8Unit #include "js/OffThreadScriptCompilation.h" #include "js/SourceText.h" #include "ModuleLoadRequest.h" #include "nsContentUtils.h" #include "nsICacheInfoChannel.h" #include "nsIClassOfService.h" #include "nsISupportsPriority.h" #include "ScriptSettings.h" using JS::SourceText; namespace mozilla { namespace dom { ////////////////////////////////////////////////////////////// // ScriptFetchOptions ////////////////////////////////////////////////////////////// NS_IMPL_CYCLE_COLLECTION(ScriptFetchOptions, mTriggeringPrincipal) NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(ScriptFetchOptions, AddRef) NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(ScriptFetchOptions, Release) ScriptFetchOptions::ScriptFetchOptions(mozilla::CORSMode aCORSMode, ReferrerPolicy aReferrerPolicy, nsIPrincipal* aTriggeringPrincipal) : mCORSMode(aCORSMode), mReferrerPolicy(aReferrerPolicy), mTriggeringPrincipal(aTriggeringPrincipal) { MOZ_ASSERT(mTriggeringPrincipal); } ScriptFetchOptions::~ScriptFetchOptions() = default; ////////////////////////////////////////////////////////////// // ScriptLoadRequest ////////////////////////////////////////////////////////////// NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ScriptLoadRequest) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(ScriptLoadRequest) NS_IMPL_CYCLE_COLLECTING_RELEASE(ScriptLoadRequest) NS_IMPL_CYCLE_COLLECTION_CLASS(ScriptLoadRequest) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ScriptLoadRequest) NS_IMPL_CYCLE_COLLECTION_UNLINK(mFetchOptions, mCacheInfo, mLoadContext) tmp->mScript = nullptr; tmp->DropBytecodeCacheReferences(); NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ScriptLoadRequest) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFetchOptions, mCacheInfo, mLoadContext) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(ScriptLoadRequest) NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mScript) NS_IMPL_CYCLE_COLLECTION_TRACE_END ScriptLoadRequest::ScriptLoadRequest(ScriptKind aKind, nsIURI* aURI, ScriptFetchOptions* aFetchOptions, const SRIMetadata& aIntegrity, nsIURI* aReferrer) : mKind(aKind), mIsCanceled(false), mProgress(Progress::eLoading), mDataType(DataType::eUnknown), mFetchOptions(aFetchOptions), mIntegrity(aIntegrity), mReferrer(aReferrer), mScriptTextLength(0), mScriptBytecode(), mBytecodeOffset(0), mURI(aURI), mLineNo(1) { MOZ_ASSERT(mFetchOptions); } ////////////////////////////////////////////////////////////// // DOMScriptLoadContext ////////////////////////////////////////////////////////////// NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMScriptLoadContext) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMScriptLoadContext) NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMScriptLoadContext) NS_IMPL_CYCLE_COLLECTION_CLASS(DOMScriptLoadContext) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DOMScriptLoadContext) NS_IMPL_CYCLE_COLLECTION_UNLINK(mElement, mWebExtGlobal) // XXX missing mLoadBlockedDocument ? if (Runnable* runnable = tmp->mRunnable.exchange(nullptr)) { runnable->Release(); } tmp->MaybeUnblockOnload(); NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DOMScriptLoadContext) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLoadBlockedDocument, mRequest, mElement, mWebExtGlobal) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(DOMScriptLoadContext) NS_IMPL_CYCLE_COLLECTION_TRACE_END DOMScriptLoadContext::DOMScriptLoadContext(Element* aElement, ScriptLoadRequest* aRequest, nsIGlobalObject* aWebExtGlobal) : mScriptMode(ScriptMode::eBlocking), mScriptFromHead(false), mIsInline(true), mInDeferList(false), mInAsyncList(false), mIsNonAsyncScriptInserted(false), mIsXSLT(false), mInCompilingList(false), mIsTracking(false), mWasCompiledOMT(false), mOffThreadToken(nullptr), mRunnable(nullptr), mIsPreload(false), mElement(aElement), mWebExtGlobal(aWebExtGlobal), mRequest(aRequest), mUnreportedPreloadError(NS_OK) {} ScriptLoadRequest::~ScriptLoadRequest() { if (mScript) { DropBytecodeCacheReferences(); } mLoadContext = nullptr; DropJSObjects(this); } DOMScriptLoadContext::~DOMScriptLoadContext() { // When speculative parsing is enabled, it is possible to off-main-thread // compile scripts that are never executed. These should be cleaned up here // if they exist. mRequest = nullptr; MOZ_ASSERT_IF( !StaticPrefs:: dom_script_loader_external_scripts_speculative_omt_parse_enabled(), !mOffThreadToken); MaybeCancelOffThreadScript(); MaybeUnblockOnload(); } void DOMScriptLoadContext::BlockOnload(Document* aDocument) { MOZ_ASSERT(!mLoadBlockedDocument); aDocument->BlockOnload(); mLoadBlockedDocument = aDocument; } void DOMScriptLoadContext::MaybeUnblockOnload() { if (mLoadBlockedDocument) { mLoadBlockedDocument->UnblockOnload(false); mLoadBlockedDocument = nullptr; } } void ScriptLoadRequest::SetReady() { MOZ_ASSERT(mProgress != Progress::eReady); mProgress = Progress::eReady; } void ScriptLoadRequest::Cancel() { mIsCanceled = true; if (HasLoadContext()) { GetLoadContext()->MaybeCancelOffThreadScript(); } } void DOMScriptLoadContext::MaybeCancelOffThreadScript() { MOZ_ASSERT(NS_IsMainThread()); if (!mOffThreadToken) { return; } JSContext* cx = danger::GetJSContext(); // Follow the same conditions as ScriptLoader::AttemptAsyncScriptCompile if (mRequest->IsModuleRequest()) { JS::CancelCompileModuleToStencilOffThread(cx, mOffThreadToken); } else if (mRequest->IsSource()) { JS::CancelCompileToStencilOffThread(cx, mOffThreadToken); } else { MOZ_ASSERT(mRequest->IsBytecode()); JS::CancelDecodeStencilOffThread(cx, mOffThreadToken); } // Cancellation request above should guarantee removal of the parse task, so // releasing the runnable should be safe to do here. if (Runnable* runnable = mRunnable.exchange(nullptr)) { runnable->Release(); } MaybeUnblockOnload(); mOffThreadToken = nullptr; } void ScriptLoadRequest::DropBytecodeCacheReferences() { mCacheInfo = nullptr; DropJSObjects(this); } ModuleLoadRequest* ScriptLoadRequest::AsModuleRequest() { MOZ_ASSERT(IsModuleRequest()); return static_cast(this); } void DOMScriptLoadContext::SetScriptMode(bool aDeferAttr, bool aAsyncAttr, bool aLinkPreload) { if (aLinkPreload) { mScriptMode = ScriptMode::eLinkPreload; } else if (aAsyncAttr) { mScriptMode = ScriptMode::eAsync; } else if (aDeferAttr || mRequest->IsModuleRequest()) { mScriptMode = ScriptMode::eDeferred; } else { mScriptMode = ScriptMode::eBlocking; } } void ScriptLoadRequest::SetBytecode() { MOZ_ASSERT(IsUnknownDataType()); mDataType = DataType::eBytecode; } void ScriptLoadRequest::ClearScriptSource() { if (IsTextSource()) { ClearScriptText(); } } void ScriptLoadRequest::SetScript(JSScript* aScript) { MOZ_ASSERT(!mScript); mScript = aScript; HoldJSObjects(this); } // static void DOMScriptLoadContext::PrioritizeAsPreload(nsIChannel* aChannel) { if (nsCOMPtr cos = do_QueryInterface(aChannel)) { cos->AddClassFlags(nsIClassOfService::Unblocked); } if (nsCOMPtr sp = do_QueryInterface(aChannel)) { sp->AdjustPriority(nsISupportsPriority::PRIORITY_HIGHEST); } } void DOMScriptLoadContext::PrioritizeAsPreload() { if (!IsLinkPreloadScript()) { // Do the prioritization only if this request has not already been created // as a preload. PrioritizeAsPreload(Channel()); } } bool DOMScriptLoadContext::IsPreload() const { if (mRequest->IsModuleRequest() && !mRequest->IsTopLevel()) { ModuleLoadRequest* root = mRequest->AsModuleRequest()->GetRootModule(); return root->GetLoadContext()->IsPreload(); } MOZ_ASSERT_IF(mIsPreload, !GetScriptElement()); return mIsPreload; } nsIGlobalObject* DOMScriptLoadContext::GetWebExtGlobal() const { if (mRequest->IsModuleRequest() && !mRequest->IsTopLevel()) { ModuleLoadRequest* root = mRequest->AsModuleRequest()->GetRootModule(); return root->GetLoadContext()->GetWebExtGlobal(); } return mWebExtGlobal; } nsIScriptElement* DOMScriptLoadContext::GetScriptElement() const { if (mRequest->IsModuleRequest() && !mRequest->IsTopLevel()) { ModuleLoadRequest* root = mRequest->AsModuleRequest()->GetRootModule(); return root->GetLoadContext()->GetScriptElement(); } nsCOMPtr scriptElement = do_QueryInterface(mElement); return scriptElement; } void DOMScriptLoadContext::SetIsLoadRequest(nsIScriptElement* aElement) { MOZ_ASSERT(aElement); MOZ_ASSERT(!GetScriptElement()); MOZ_ASSERT(IsPreload()); // TODO: How to allow both to access fetch options mElement = do_QueryInterface(aElement); mIsPreload = false; } nsresult ScriptLoadRequest::GetScriptSource(JSContext* aCx, MaybeSourceText* aMaybeSource) { // If there's no script text, we try to get it from the element if (HasLoadContext() && GetLoadContext()->mIsInline) { nsAutoString inlineData; GetLoadContext()->GetScriptElement()->GetScriptText(inlineData); size_t nbytes = inlineData.Length() * sizeof(char16_t); JS::UniqueTwoByteChars chars( static_cast(JS_malloc(aCx, nbytes))); if (!chars) { return NS_ERROR_OUT_OF_MEMORY; } memcpy(chars.get(), inlineData.get(), nbytes); SourceText srcBuf; if (!srcBuf.init(aCx, std::move(chars), inlineData.Length())) { return NS_ERROR_OUT_OF_MEMORY; } aMaybeSource->construct>(std::move(srcBuf)); return NS_OK; } size_t length = ScriptTextLength(); if (IsUTF16Text()) { JS::UniqueTwoByteChars chars; chars.reset(ScriptText().extractOrCopyRawBuffer()); if (!chars) { JS_ReportOutOfMemory(aCx); return NS_ERROR_OUT_OF_MEMORY; } SourceText srcBuf; if (!srcBuf.init(aCx, std::move(chars), length)) { return NS_ERROR_OUT_OF_MEMORY; } aMaybeSource->construct>(std::move(srcBuf)); return NS_OK; } MOZ_ASSERT(IsUTF8Text()); UniquePtr chars; chars.reset(ScriptText().extractOrCopyRawBuffer()); if (!chars) { JS_ReportOutOfMemory(aCx); return NS_ERROR_OUT_OF_MEMORY; } SourceText srcBuf; if (!srcBuf.init(aCx, std::move(chars), length)) { return NS_ERROR_OUT_OF_MEMORY; } aMaybeSource->construct>(std::move(srcBuf)); return NS_OK; } void DOMScriptLoadContext::GetProfilerLabel(nsACString& aOutString) { if (!profiler_is_active()) { aOutString.Append("