diff --git a/dom/script/ScriptLoadContext.cpp b/dom/script/ScriptLoadContext.cpp index b3c0c185ebcb..b2574d4faeaf 100644 --- a/dom/script/ScriptLoadContext.cpp +++ b/dom/script/ScriptLoadContext.cpp @@ -37,9 +37,8 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(ScriptLoadContext) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(ScriptLoadContext, JS::loader::LoadContextBase) - if (Runnable* runnable = tmp->mRunnable.exchange(nullptr)) { - runnable->Release(); - } + MOZ_ASSERT(!tmp->mOffThreadToken); + MOZ_ASSERT(!tmp->mRunnable); tmp->MaybeUnblockOnload(); NS_IMPL_CYCLE_COLLECTION_UNLINK_END @@ -70,16 +69,12 @@ ScriptLoadContext::ScriptLoadContext() mUnreportedPreloadError(NS_OK) {} ScriptLoadContext::~ScriptLoadContext() { - // 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); + MOZ_ASSERT(NS_IsMainThread()); - MaybeCancelOffThreadScript(); + // Off-thread parsing must have completed by this point. + MOZ_DIAGNOSTIC_ASSERT(!mOffThreadToken && !mRunnable); + + mRequest = nullptr; MaybeUnblockOnload(); } @@ -104,17 +99,18 @@ void ScriptLoadContext::MaybeCancelOffThreadScript() { return; } + // Cancel parse if it hasn't been started yet or wait for it to finish and + // clean up finished parse data. JSContext* cx = danger::GetJSContext(); JS::CancelOffThreadToken(cx, mOffThreadToken); + mOffThreadToken = nullptr; - // 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(); - } + // Clear the pointer to the runnable. It may still run later if we didn't + // cancel in time. In this case the runnable is held live by the reference + // passed to Dispatch, which is dropped after it runs. + mRunnable = nullptr; MaybeUnblockOnload(); - mOffThreadToken = nullptr; } void ScriptLoadContext::SetScriptMode(bool aDeferAttr, bool aAsyncAttr, diff --git a/dom/script/ScriptLoadContext.h b/dom/script/ScriptLoadContext.h index 108295d9671d..284422373cf1 100644 --- a/dom/script/ScriptLoadContext.h +++ b/dom/script/ScriptLoadContext.h @@ -153,9 +153,6 @@ class ScriptLoadContext : public JS::loader::LoadContextBase, void MaybeCancelOffThreadScript(); - TimeStamp mOffThreadParseStartTime; - TimeStamp mOffThreadParseStopTime; - ScriptMode mScriptMode; // Whether this is a blocking, defer or async script. bool mScriptFromHead; // Synchronous head script block loading of other non // js/css content. @@ -172,11 +169,13 @@ class ScriptLoadContext : public JS::loader::LoadContextBase, bool mWasCompiledOMT; // True if the script has been compiled off main // thread. - JS::OffThreadToken* mOffThreadToken; // Off-thread parsing token. + // Off-thread parsing token. Set at the start of off-thread parsing and + // cleared when the result of the parse is used. + JS::OffThreadToken* mOffThreadToken; - Atomic mRunnable; // Runnable created when dispatching off thread - // compile. Tracked here so that it can be - // properly released during cancellation. + // Runnable that is dispatched to the main thread when off-thread compilation + // completes. + RefPtr mRunnable; int32_t mLineNo; diff --git a/dom/script/ScriptLoader.cpp b/dom/script/ScriptLoader.cpp index b742dddb4769..6629fe21c46c 100644 --- a/dom/script/ScriptLoader.cpp +++ b/dom/script/ScriptLoader.cpp @@ -1387,19 +1387,19 @@ ReferrerPolicy ScriptLoader::GetReferrerPolicy(nsIScriptElement* aElement) { namespace { class NotifyOffThreadScriptLoadCompletedRunnable : public Runnable { - RefPtr mRequest; - RefPtr mLoader; + nsMainThreadPtrHandle mRequest; + nsMainThreadPtrHandle mLoader; nsCOMPtr mEventTarget; JS::OffThreadToken* mToken; + TimeStamp mStartTime; + TimeStamp mStopTime; public: - ScriptLoadRequest* GetScriptLoadRequest() { return mRequest; } - NotifyOffThreadScriptLoadCompletedRunnable(ScriptLoadRequest* aRequest, ScriptLoader* aLoader) : Runnable("dom::NotifyOffThreadScriptLoadCompletedRunnable"), - mRequest(aRequest), - mLoader(aLoader), + mRequest(new nsMainThreadPtrHolder("mRequest", aRequest)), + mLoader(new nsMainThreadPtrHolder("mLoader", aLoader)), mToken(nullptr) { MOZ_ASSERT(NS_IsMainThread()); if (DocGroup* docGroup = aLoader->GetDocGroup()) { @@ -1407,7 +1407,8 @@ class NotifyOffThreadScriptLoadCompletedRunnable : public Runnable { } } - virtual ~NotifyOffThreadScriptLoadCompletedRunnable(); + void RecordStartTime() { mStartTime = TimeStamp::Now(); } + void RecordStopTime() { mStopTime = TimeStamp::Now(); } void SetToken(JS::OffThreadToken* aToken) { MOZ_ASSERT(aToken && !mToken); @@ -1466,7 +1467,7 @@ void ScriptLoader::CancelScriptLoadRequests() { } nsresult ScriptLoader::ProcessOffThreadRequest(ScriptLoadRequest* aRequest) { - MOZ_ASSERT(aRequest->mState == ScriptLoadRequest::State::Compiling); + MOZ_ASSERT(aRequest->IsCompiling()); MOZ_ASSERT(!aRequest->GetScriptLoadContext()->mWasCompiledOMT); if (aRequest->IsCanceled()) { @@ -1526,81 +1527,56 @@ nsresult ScriptLoader::ProcessOffThreadRequest(ScriptLoadRequest* aRequest) { return NS_OK; } -NotifyOffThreadScriptLoadCompletedRunnable:: - ~NotifyOffThreadScriptLoadCompletedRunnable() { - if (MOZ_UNLIKELY(mRequest || mLoader) && !NS_IsMainThread()) { - NS_ReleaseOnMainThread( - "NotifyOffThreadScriptLoadCompletedRunnable::mRequest", - mRequest.forget()); - NS_ReleaseOnMainThread( - "NotifyOffThreadScriptLoadCompletedRunnable::mLoader", - mLoader.forget()); - } -} - NS_IMETHODIMP NotifyOffThreadScriptLoadCompletedRunnable::Run() { MOZ_ASSERT(NS_IsMainThread()); - // We want these to be dropped on the main thread, once we return from this - // function. - RefPtr request = std::move(mRequest); + RefPtr context = mRequest->GetScriptLoadContext(); + MOZ_ASSERT_IF(context->mRunnable, context->mRunnable == this); + MOZ_ASSERT_IF(context->mOffThreadToken, context->mOffThreadToken == mToken); - // Runnable pointer should have been cleared in the offthread callback. - MOZ_ASSERT(!request->GetScriptLoadContext()->mRunnable); + // Clear the pointer to the runnable. The final reference will be released + // when this method returns. + context->mRunnable = nullptr; + + if (!context->mOffThreadToken) { + // Request has been cancelled by MaybeCancelOffThreadScript. + return NS_OK; + } if (profiler_is_active()) { ProfilerString8View scriptSourceString; - if (request->IsTextSource()) { + if (mRequest->IsTextSource()) { scriptSourceString = "ScriptCompileOffThread"; } else { - MOZ_ASSERT(request->IsBytecode()); + MOZ_ASSERT(mRequest->IsBytecode()); scriptSourceString = "BytecodeDecodeOffThread"; } nsAutoCString profilerLabelString; - request->GetScriptLoadContext()->GetProfilerLabel(profilerLabelString); - PROFILER_MARKER_TEXT( - scriptSourceString, JS, - MarkerTiming::Interval( - request->GetScriptLoadContext()->mOffThreadParseStartTime, - request->GetScriptLoadContext()->mOffThreadParseStopTime), - profilerLabelString); + mRequest->GetScriptLoadContext()->GetProfilerLabel(profilerLabelString); + PROFILER_MARKER_TEXT(scriptSourceString, JS, + MarkerTiming::Interval(mStartTime, mStopTime), + profilerLabelString); } - RefPtr loader = std::move(mLoader); + nsresult rv = mLoader->ProcessOffThreadRequest(mRequest); - // Request was already cancelled at some earlier point. - if (!request->GetScriptLoadContext()->mOffThreadToken) { - return NS_OK; - } - - return loader->ProcessOffThreadRequest(request); + mRequest = nullptr; + mLoader = nullptr; + return rv; } static void OffThreadScriptLoaderCallback(JS::OffThreadToken* aToken, void* aCallbackData) { - RefPtr aRunnable = dont_AddRef( - static_cast(aCallbackData)); - MOZ_ASSERT( - aRunnable.get() == - aRunnable->GetScriptLoadRequest()->GetScriptLoadContext()->mRunnable); - - aRunnable->GetScriptLoadRequest() - ->GetScriptLoadContext() - ->mOffThreadParseStopTime = TimeStamp::Now(); + RefPtr aRunnable = + static_cast(aCallbackData); LogRunnable::Run run(aRunnable); + aRunnable->RecordStopTime(); aRunnable->SetToken(aToken); - // If mRunnable was cleared then request was canceled so do nothing. - if (!aRunnable->GetScriptLoadRequest() - ->GetScriptLoadContext() - ->mRunnable.exchange(nullptr)) { - return; - } - NotifyOffThreadScriptLoadCompletedRunnable::Dispatch(aRunnable.forget()); } @@ -1660,108 +1636,25 @@ nsresult ScriptLoader::AttemptAsyncScriptCompile(ScriptLoadRequest* aRequest, RefPtr runnable = new NotifyOffThreadScriptLoadCompletedRunnable(aRequest, this); - // Emulate dispatch. CompileOffThreadModule will call + // Emulate dispatch. CompileOffThreadModule will call // OffThreadScriptLoaderCallback were we will emulate run. LogRunnable::LogDispatch(runnable); - aRequest->GetScriptLoadContext()->mOffThreadParseStartTime = TimeStamp::Now(); + runnable->RecordStartTime(); - // Save the runnable so it can be properly cleared during cancellation. - aRequest->GetScriptLoadContext()->mRunnable = runnable.get(); - auto signalOOM = mozilla::MakeScopeExit( - [&aRequest]() { aRequest->GetScriptLoadContext()->mRunnable = nullptr; }); + JS::OffThreadToken* token = nullptr; + rv = StartOffThreadCompilation(cx, aRequest, options, runnable, &token); + NS_ENSURE_SUCCESS(rv, rv); + MOZ_ASSERT(token); - // The conditions should match ScriptLoadContext::MaybeCancelOffThreadScript. - if (aRequest->IsBytecode()) { - JS::DecodeOptions decodeOptions(options); - aRequest->GetScriptLoadContext()->mOffThreadToken = - JS::DecodeStencilOffThread(cx, decodeOptions, aRequest->mScriptBytecode, - aRequest->mBytecodeOffset, - OffThreadScriptLoaderCallback, - static_cast(runnable)); - if (!aRequest->GetScriptLoadContext()->mOffThreadToken) { - return NS_ERROR_OUT_OF_MEMORY; - } - } else if (aRequest->IsModuleRequest()) { - MOZ_ASSERT(aRequest->IsTextSource()); - MaybeSourceText maybeSource; - nsresult rv = aRequest->GetScriptSource(cx, &maybeSource); - NS_ENSURE_SUCCESS(rv, rv); - - auto compile = [&](auto& source) { - return JS::CompileModuleToStencilOffThread( - cx, options, source, OffThreadScriptLoaderCallback, runnable.get()); - }; - - MOZ_ASSERT(!maybeSource.empty()); - JS::OffThreadToken* token = maybeSource.mapNonEmpty(compile); - if (!token) { - return NS_ERROR_OUT_OF_MEMORY; - } - - aRequest->GetScriptLoadContext()->mOffThreadToken = token; - } else { - MOZ_ASSERT(aRequest->IsTextSource()); - - if (ShouldApplyDelazifyStrategy(aRequest)) { - ApplyDelazifyStrategy(&options); - mTotalFullParseSize += - aRequest->ScriptTextLength() > 0 - ? static_cast(aRequest->ScriptTextLength()) - : 0; - - LOG( - ("ScriptLoadRequest (%p): non-on-demand-only Parsing Enabled for " - "url=%s mTotalFullParseSize=%u", - aRequest, aRequest->mURI->GetSpecOrDefault().get(), - mTotalFullParseSize)); - } - - MaybeSourceText maybeSource; - nsresult rv = aRequest->GetScriptSource(cx, &maybeSource); - NS_ENSURE_SUCCESS(rv, rv); - - if (StaticPrefs::dom_expose_test_interfaces()) { - switch (options.eagerDelazificationStrategy()) { - case JS::DelazificationOption::OnDemandOnly: - TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(), - "delazification_on_demand_only"); - break; - case JS::DelazificationOption::CheckConcurrentWithOnDemand: - case JS::DelazificationOption::ConcurrentDepthFirst: - TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(), - "delazification_concurrent_depth_first"); - break; - case JS::DelazificationOption::ConcurrentLargeFirst: - TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(), - "delazification_concurrent_large_first"); - break; - case JS::DelazificationOption::ParseEverythingEagerly: - TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(), - "delazification_parse_everything_eagerly"); - break; - } - } - - auto compile = [&](auto& source) { - return JS::CompileToStencilOffThread( - cx, options, source, OffThreadScriptLoaderCallback, runnable.get()); - }; - - MOZ_ASSERT(!maybeSource.empty()); - JS::OffThreadToken* token = maybeSource.mapNonEmpty(compile); - if (!token) { - return NS_ERROR_OUT_OF_MEMORY; - } - - aRequest->GetScriptLoadContext()->mOffThreadToken = token; - } - signalOOM.release(); + aRequest->GetScriptLoadContext()->mOffThreadToken = token; + aRequest->GetScriptLoadContext()->mRunnable = runnable; aRequest->GetScriptLoadContext()->BlockOnload(mDocument); - // Once the compilation is finished, an event would be added to the event loop - // to call ScriptLoader::ProcessOffThreadRequest with the same request. + // Once the compilation is finished, a callback will dispatch the runnable to + // the main thread to call ScriptLoader::ProcessOffThreadRequest for the + // request. aRequest->mState = ScriptLoadRequest::State::Compiling; // Requests that are not tracked elsewhere are added to a list while they are @@ -1776,10 +1669,88 @@ nsresult ScriptLoader::AttemptAsyncScriptCompile(ScriptLoadRequest* aRequest, } *aCouldCompileOut = true; - Unused << runnable.forget(); + return NS_OK; } +static inline nsresult CompileResultForToken(void* aToken) { + return aToken ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +nsresult ScriptLoader::StartOffThreadCompilation( + JSContext* aCx, ScriptLoadRequest* aRequest, JS::CompileOptions& aOptions, + Runnable* aRunnable, JS::OffThreadToken** aTokenOut) { + const JS::OffThreadCompileCallback callback = OffThreadScriptLoaderCallback; + + if (aRequest->IsBytecode()) { + JS::DecodeOptions decodeOptions(aOptions); + *aTokenOut = JS::DecodeStencilOffThread( + aCx, decodeOptions, aRequest->mScriptBytecode, + aRequest->mBytecodeOffset, callback, aRunnable); + return CompileResultForToken(*aTokenOut); + } + + MaybeSourceText maybeSource; + nsresult rv = aRequest->GetScriptSource(aCx, &maybeSource); + NS_ENSURE_SUCCESS(rv, rv); + + if (aRequest->IsModuleRequest()) { + auto compile = [&](auto& source) { + return JS::CompileModuleToStencilOffThread(aCx, aOptions, source, + callback, aRunnable); + }; + + MOZ_ASSERT(!maybeSource.empty()); + *aTokenOut = maybeSource.mapNonEmpty(compile); + return CompileResultForToken(*aTokenOut); + } + + if (ShouldApplyDelazifyStrategy(aRequest)) { + ApplyDelazifyStrategy(&aOptions); + mTotalFullParseSize += + aRequest->ScriptTextLength() > 0 + ? static_cast(aRequest->ScriptTextLength()) + : 0; + + LOG( + ("ScriptLoadRequest (%p): non-on-demand-only Parsing Enabled for " + "url=%s mTotalFullParseSize=%u", + aRequest, aRequest->mURI->GetSpecOrDefault().get(), + mTotalFullParseSize)); + } + + if (StaticPrefs::dom_expose_test_interfaces()) { + switch (aOptions.eagerDelazificationStrategy()) { + case JS::DelazificationOption::OnDemandOnly: + TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(), + "delazification_on_demand_only"); + break; + case JS::DelazificationOption::CheckConcurrentWithOnDemand: + case JS::DelazificationOption::ConcurrentDepthFirst: + TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(), + "delazification_concurrent_depth_first"); + break; + case JS::DelazificationOption::ConcurrentLargeFirst: + TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(), + "delazification_concurrent_large_first"); + break; + case JS::DelazificationOption::ParseEverythingEagerly: + TRACE_FOR_TEST(aRequest->GetScriptLoadContext()->GetScriptElement(), + "delazification_parse_everything_eagerly"); + break; + } + } + + auto compile = [&](auto& source) { + return JS::CompileToStencilOffThread(aCx, aOptions, source, callback, + aRunnable); + }; + + MOZ_ASSERT(!maybeSource.empty()); + *aTokenOut = maybeSource.mapNonEmpty(compile); + return CompileResultForToken(*aTokenOut); +} + nsresult ScriptLoader::CompileOffThreadOrProcessRequest( ScriptLoadRequest* aRequest) { NS_ASSERTION(nsContentUtils::IsSafeToRunScript(), diff --git a/dom/script/ScriptLoader.h b/dom/script/ScriptLoader.h index 0626ed90ab8d..051d4cdb7b48 100644 --- a/dom/script/ScriptLoader.h +++ b/dom/script/ScriptLoader.h @@ -578,6 +578,13 @@ class ScriptLoader final : public JS::loader::ScriptLoaderInterface { nsresult AttemptAsyncScriptCompile(ScriptLoadRequest* aRequest, bool* aCouldCompileOut); + + nsresult StartOffThreadCompilation(JSContext* aCx, + ScriptLoadRequest* aRequest, + JS::CompileOptions& aOptions, + Runnable* aRunnable, + JS::OffThreadToken** aTokenOut); + nsresult ProcessRequest(ScriptLoadRequest* aRequest); nsresult CompileOffThreadOrProcessRequest(ScriptLoadRequest* aRequest); void FireScriptAvailable(nsresult aResult, ScriptLoadRequest* aRequest);