tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit fb321bdf9c63c3b21e158571d2f0349132155212
parent 9f310f512f1c0298b0464ed4bb10cfa06e472c05
Author: Tooru Fujisawa <arai_a@mac.com>
Date:   Wed, 17 Dec 2025 18:38:05 +0000

Bug 2004064 - Yield to other tasks before processing long defer scripts if no FCP have happened. r=nbp

Differential Revision: https://phabricator.services.mozilla.com/D275741

Diffstat:
Mdom/script/ScriptLoader.cpp | 47+++++++++++++++++++++++++++++++++++++++++++++++
Mdom/script/ScriptLoader.h | 3+++
Mjs/loader/LoadedScript.cpp | 2++
Mjs/loader/LoadedScript.h | 18++++++++++++++++++
Mjs/loader/ScriptLoadRequest.cpp | 1+
Mjs/loader/ScriptLoadRequest.h | 6++++++
6 files changed, 77 insertions(+), 0 deletions(-)

diff --git a/dom/script/ScriptLoader.cpp b/dom/script/ScriptLoader.cpp @@ -49,6 +49,7 @@ #include "mozilla/Utf8.h" // mozilla::Utf8Unit #include "mozilla/dom/AutoEntryScript.h" #include "mozilla/dom/DocGroup.h" +#include "mozilla/dom/DocumentInlines.h" // Document::GetPresContext #include "mozilla/dom/Element.h" #include "mozilla/dom/FetchPriority.h" #include "mozilla/dom/JSExecutionUtils.h" // mozilla::dom::Compile, mozilla::dom::InstantiateStencil, mozilla::dom::EvaluationExceptionToNSResult @@ -93,6 +94,7 @@ #include "nsJSPrincipals.h" #include "nsJSUtils.h" #include "nsNetUtil.h" +#include "nsPresContext.h" // nsPresContext #include "nsProxyRelease.h" #include "nsQueryObject.h" #include "nsThreadUtils.h" @@ -207,6 +209,7 @@ ScriptLoader::ScriptLoader(Document* aDocument) mLoadEventFired(false), mGiveUpDiskCaching(false), mContinueParsingDocumentAfterCurrentScript(false), + mHadFCPDoNotUseDirectly(false), mReporter(new ConsoleReportCollector()) { LOG(("ScriptLoader::ScriptLoader %p", this)); @@ -3514,7 +3517,18 @@ nsresult ScriptLoader::EvaluateScript(nsIGlobalObject* aGlobalObject, MOZ_ASSERT(options.noScriptRval); TRACE_FOR_TEST(aRequest, "evaluate:classic"); + + auto start = TimeStamp::Now(); + ExecuteCompiledScript(cx, classicScript, script, erv); + + auto end = TimeStamp::Now(); + auto duration = (end - start).ToMilliseconds(); + + static constexpr double LongScriptThresholdInMilliseconds = 1.0; + if (duration > LongScriptThresholdInMilliseconds) { + aRequest->SetTookLongInPreviousRuns(); + } } rv = EvaluationExceptionToNSResult(erv); @@ -3907,6 +3921,13 @@ void ScriptLoader::ProcessPendingRequests(bool aAllowBypassingParserBlocking) { if (mDeferCheckpointReached && mXSLTRequests.isEmpty()) { while (ReadyToExecuteScripts() && !mDeferRequests.isEmpty() && mDeferRequests.getFirst()->IsFinished()) { + if (mDeferRequests.getFirst()->TookLongInPreviousRuns() && + !mDeferRequests.getFirst()->HadPostponed() && IsBeforeFCP()) { + mDeferRequests.getFirst()->SetHadPostponed(); + ProcessPendingRequestsAsync(); + return; + } + request = mDeferRequests.StealFirst(); ProcessRequest(request); } @@ -3938,6 +3959,32 @@ void ScriptLoader::ProcessPendingRequests(bool aAllowBypassingParserBlocking) { } } +bool ScriptLoader::IsBeforeFCP() { + if (mHadFCPDoNotUseDirectly) { + return false; + } + + if (mLoadEventFired) { + return false; + } + + if (!mDocument) { + return false; + } + + nsPresContext* context = mDocument->GetPresContext(); + if (!context) { + return false; + } + + if (context->HadFirstContentfulPaint()) { + mHadFCPDoNotUseDirectly = true; + return false; + } + + return true; +} + bool ScriptLoader::ReadyToExecuteParserBlockingScripts() { // Make sure the SelfReadyToExecuteParserBlockingScripts check is first, so // that we don't block twice on an ancestor. diff --git a/dom/script/ScriptLoader.h b/dom/script/ScriptLoader.h @@ -809,6 +809,8 @@ class ScriptLoader final : public JS::loader::ScriptLoaderInterface { void MaybeMoveToLoadedList(ScriptLoadRequest* aRequest); + bool IsBeforeFCP(); + public: struct DiskCacheStrategy { bool mIsDisabled = false; @@ -924,6 +926,7 @@ class ScriptLoader final : public JS::loader::ScriptLoaderInterface { bool mLoadEventFired; bool mGiveUpDiskCaching; bool mContinueParsingDocumentAfterCurrentScript; + bool mHadFCPDoNotUseDirectly; TimeDuration mMainThreadParseTime; diff --git a/js/loader/LoadedScript.cpp b/js/loader/LoadedScript.cpp @@ -56,6 +56,7 @@ LoadedScript::LoadedScript(ScriptKind aKind, mSerializedStencilOffset(0), mCacheEntryId(InvalidCacheEntryId), mIsDirty(false), + mTookLongInPreviousRuns(false), mFetchOptions(aFetchOptions), mURI(aURI), mReceivedScriptTextLength(0) { @@ -70,6 +71,7 @@ LoadedScript::LoadedScript(const LoadedScript& aOther) mSerializedStencilOffset(0), mCacheEntryId(aOther.mCacheEntryId), mIsDirty(aOther.mIsDirty), + mTookLongInPreviousRuns(aOther.mTookLongInPreviousRuns), mFetchOptions(aOther.mFetchOptions), mURI(aOther.mURI), mBaseURL(aOther.mBaseURL), diff --git a/js/loader/LoadedScript.h b/js/loader/LoadedScript.h @@ -315,6 +315,9 @@ class LoadedScript : public nsIMemoryReporter { // ==== Other methods ==== + void SetTookLongInPreviousRuns() { mTookLongInPreviousRuns = true; } + bool TookLongInPreviousRuns() const { return mTookLongInPreviousRuns; } + /* * Set the mBaseURL, based on aChannel. * aOriginalURI is the result of aChannel->GetOriginalURI. @@ -405,6 +408,14 @@ class LoadedScript : public nsIMemoryReporter { // this must be uint64_t. uint64_t mIsDirty : 1; + // Set to true if executing the top-level script takes long. + // This can be used for scheduling the script execution in subsequent loads. + // The threshold of "takes long" is user-defined. + // See dom::ScriptLoader::EvaluateScript for the example case + // + // TODO: Move this into JS::Stencil, and save to the disk cache (bug 2005128) + uint64_t mTookLongInPreviousRuns : 1; + RefPtr<ScriptFetchOptions> mFetchOptions; nsCOMPtr<nsIURI> mURI; @@ -562,6 +573,13 @@ class LoadedScriptDelegate { GetLoadedScript()->SetStencil(aStencil); } void ClearStencil() { GetLoadedScript()->ClearStencil(); } + + void SetTookLongInPreviousRuns() { + GetLoadedScript()->SetTookLongInPreviousRuns(); + } + bool TookLongInPreviousRuns() const { + return GetLoadedScript()->TookLongInPreviousRuns(); + } }; class ClassicScript final : public LoadedScript { diff --git a/js/loader/ScriptLoadRequest.cpp b/js/loader/ScriptLoadRequest.cpp @@ -94,6 +94,7 @@ ScriptLoadRequest::ScriptLoadRequest(ScriptKind aKind, mFetchSourceOnly(false), mHasSourceMapURL_(false), mHasDirtyCache_(false), + mHadPostponed_(false), mDiskCachingPlan(CachingPlan::Uninitialized), mMemoryCachingPlan(CachingPlan::Uninitialized), mIntegrity(aIntegrity), diff --git a/js/loader/ScriptLoadRequest.h b/js/loader/ScriptLoadRequest.h @@ -268,6 +268,9 @@ class ScriptLoadRequest : public nsISupports, bool HasDirtyCache() const { return mHasDirtyCache_; } void SetHasDirtyCache() { mHasDirtyCache_ = true; } + bool HadPostponed() const { return mHadPostponed_; } + void SetHadPostponed() { mHadPostponed_ = true; } + public: // Fields. @@ -293,6 +296,9 @@ class ScriptLoadRequest : public nsISupports, // the cache should be either revived or evicted. bool mHasDirtyCache_ : 1; + // Set to true if this script had already been postponed in the scheduling. + bool mHadPostponed_ : 1; + enum class CachingPlan : uint8_t { // This is not yet considered for caching. Uninitialized,