diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index 849081693ded..8a192e97c2df 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -10429,6 +10429,11 @@ nsresult nsDocShell::OpenInitializedChannel(nsIChannel* aChannel, rv = aURILoader->OpenURI(aChannel, aOpenFlags, this); NS_ENSURE_SUCCESS(rv, rv); + // We're about to load a new page and it may take time before necko + // gives back any data, so main thread might have a chance to process a + // collector slice + nsJSContext::MaybeRunNextCollectorSlice(this, JS::GCReason::DOCSHELL); + // Success. Keep the initial ClientSource if it exists. cleanupInitialClient.release(); diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp index 7ab520b6eaf2..20c1093d38a3 100644 --- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -1738,6 +1738,64 @@ void nsJSContext::RunNextCollectorTimer(JS::GCReason aReason, } } +// static +void nsJSContext::MaybeRunNextCollectorSlice(nsIDocShell* aDocShell, + JS::GCReason aReason) { + if (!aDocShell || !XRE_IsContentProcess()) { + return; + } + + BrowsingContext* bc = aDocShell->GetBrowsingContext(); + if (!bc) { + return; + } + + BrowsingContext* root = bc->Top(); + if (bc == root) { + // We don't want to run collectors when loading the top level page. + return; + } + + nsIDocShell* rootDocShell = root->GetDocShell(); + if (!rootDocShell) { + return; + } + + Document* rootDocument = rootDocShell->GetDocument(); + if (!rootDocument || + rootDocument->GetReadyStateEnum() != Document::READYSTATE_COMPLETE || + rootDocument->IsInBackgroundWindow()) { + return; + } + + PresShell* presShell = rootDocument->GetPresShell(); + if (!presShell) { + return; + } + + nsViewManager* vm = presShell->GetViewManager(); + if (!vm) { + return; + } + + // GetLastUserEventTime returns microseconds. + uint32_t lastEventTime = 0; + vm->GetLastUserEventTime(lastEventTime); + uint32_t currentTime = PR_IntervalToMicroseconds(PR_IntervalNow()); + // Only try to trigger collectors more often if user hasn't interacted with + // the page for awhile. + if ((currentTime - lastEventTime) > + (StaticPrefs::dom_events_user_interaction_interval() * + PR_USEC_PER_MSEC)) { + Maybe next = nsRefreshDriver::GetNextTickHint(); + // Try to not delay the next RefreshDriver tick, so give a reasonable + // deadline for collectors. + if (next.isSome()) { + nsJSContext::RunNextCollectorTimer(aReason, next.value()); + } + } +} + // static void nsJSContext::PokeGC(JS::GCReason aReason, JSObject* aObj, uint32_t aDelay) { diff --git a/dom/base/nsJSEnvironment.h b/dom/base/nsJSEnvironment.h index 584eea93bd41..bb60b0e2b63b 100644 --- a/dom/base/nsJSEnvironment.h +++ b/dom/base/nsJSEnvironment.h @@ -94,6 +94,11 @@ class nsJSContext : public nsIScriptContext { static void RunNextCollectorTimer( JS::GCReason aReason, mozilla::TimeStamp aDeadline = mozilla::TimeStamp()); + // If user has been idle and aDocShell is for an iframe being loaded in an + // already loaded top level docshell, this will run a CC or GC + // timer/runner if there is such pending. + static void MaybeRunNextCollectorSlice(nsIDocShell* aDocShell, + JS::GCReason aReason); // The GC should probably run soon, in the zone of object aObj (if given). static void PokeGC(JS::GCReason aReason, JSObject* aObj, uint32_t aDelay = 0); diff --git a/parser/html/nsHtml5StreamParser.cpp b/parser/html/nsHtml5StreamParser.cpp index 1bf036cb342b..76509f1ead58 100644 --- a/parser/html/nsHtml5StreamParser.cpp +++ b/parser/html/nsHtml5StreamParser.cpp @@ -999,6 +999,20 @@ void nsHtml5StreamParser::CommitLocalFileToEncoding() { } } +class MaybeRunCollector : public Runnable { + public: + explicit MaybeRunCollector(nsIDocShell* aDocShell) + : Runnable("MaybeRunCollector"), mDocShell(aDocShell) {} + + NS_IMETHOD Run() override { + nsJSContext::MaybeRunNextCollectorSlice(mDocShell, + JS::GCReason::HTML_PARSER); + return NS_OK; + } + + nsCOMPtr mDocShell; +}; + nsresult nsHtml5StreamParser::OnStartRequest(nsIRequest* aRequest) { MOZ_RELEASE_ASSERT(STREAM_NOT_STARTED == mStreamState, "Got OnStartRequest when the stream had already started."); @@ -1150,6 +1164,16 @@ nsresult nsHtml5StreamParser::OnStartRequest(nsIRequest* aRequest) { do_QueryInterface(mRequest, &rv); if (threadRetargetableRequest) { rv = threadRetargetableRequest->RetargetDeliveryTo(mEventTarget); + if (NS_SUCCEEDED(rv)) { + // Parser thread should be now ready to get data from necko and parse it + // and main thread might have a chance to process a collector slice. + // We need to do this asynchronously so that necko may continue processing + // the request. + nsCOMPtr runnable = + new MaybeRunCollector(mExecutor->GetDocument()->GetDocShell()); + mozilla::SchedulerGroup::Dispatch( + mozilla::TaskCategory::GarbageCollection, runnable.forget()); + } } if (NS_FAILED(rv)) {