Bug 1606652 - Speculatively off thread parse external scripts as soon as they are fetched. r=smaug

The changes proposed here will speculatively parse all external scripts off thread as soon as they are fetched, except for async, link preload, and non parser inserted scripts.  This should save us some time since currently all scripts are parsed right before execution or while they are blocking the dom parser.

Differential Revision: https://phabricator.services.mozilla.com/D76644
This commit is contained in:
Denis Palmeiro 2020-06-10 00:45:26 +00:00
Родитель 205b2ad0b6
Коммит 522b733636
5 изменённых файлов: 151 добавлений и 35 удалений

Просмотреть файл

@ -59,10 +59,12 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ScriptLoadRequest)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mFetchOptions, mCacheInfo)
tmp->mScript = nullptr;
tmp->DropBytecodeCacheReferences();
tmp->MaybeUnblockOnload();
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ScriptLoadRequest)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFetchOptions, mCacheInfo)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFetchOptions, mCacheInfo,
mLoadBlockedDocument)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(ScriptLoadRequest)
@ -101,17 +103,21 @@ ScriptLoadRequest::ScriptLoadRequest(ScriptKind aKind, nsIURI* aURI,
}
ScriptLoadRequest::~ScriptLoadRequest() {
// We should always clean up any off-thread script parsing resources.
MOZ_ASSERT(!mOffThreadToken);
// 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.
MOZ_ASSERT_IF(
!StaticPrefs::
dom_script_loader_external_scripts_speculative_omt_parse_enabled(),
!mOffThreadToken);
// But play it safe in release builds and try to clean them up here
// as a fail safe.
MaybeCancelOffThreadScript();
if (mScript) {
DropBytecodeCacheReferences();
}
MaybeUnblockOnload();
DropJSObjects(this);
}

Просмотреть файл

@ -123,6 +123,19 @@ class ScriptLoadRequest
mIsTracking = true;
}
void BlockOnload(Document* aDocument) {
MOZ_ASSERT(!mLoadBlockedDocument);
aDocument->BlockOnload();
mLoadBlockedDocument = aDocument;
}
void MaybeUnblockOnload() {
if (mLoadBlockedDocument) {
mLoadBlockedDocument->UnblockOnload(false);
mLoadBlockedDocument = nullptr;
}
}
enum class Progress : uint8_t {
eLoading, // Request either source or bytecode
eLoading_Source, // Explicitly Request source stream
@ -344,6 +357,9 @@ class ScriptLoadRequest
const SRIMetadata mIntegrity;
const nsCOMPtr<nsIURI> mReferrer;
// Non-null if there is a document that this request is blocking from loading.
RefPtr<Document> mLoadBlockedDocument;
// Holds the Cache information, which is used to register the bytecode
// on the cache entry, such that we can load it the next time.
nsCOMPtr<nsICacheInfoChannel> mCacheInfo;

Просмотреть файл

@ -170,6 +170,7 @@ ScriptLoader::ScriptLoader(Document* aDocument)
mNumberOfProcessors(0),
mEnabled(true),
mDeferEnabled(false),
mSpeculativeOMTParsingEnabled(false),
mDeferCheckpointReached(false),
mBlockingDOMContentLoaded(false),
mLoadEventFired(false),
@ -177,6 +178,9 @@ ScriptLoader::ScriptLoader(Document* aDocument)
mReporter(new ConsoleReportCollector()) {
LOG(("ScriptLoader::ScriptLoader %p", this));
EnsureModuleHooksInitialized();
mSpeculativeOMTParsingEnabled = StaticPrefs::
dom_script_loader_external_scripts_speculative_omt_parse_enabled();
}
ScriptLoader::~ScriptLoader() {
@ -1151,9 +1155,7 @@ void ScriptLoader::ProcessLoadedModuleTree(ModuleLoadRequest* aRequest) {
}
}
if (aRequest->mWasCompiledOMT) {
mDocument->UnblockOnload(false);
}
aRequest->MaybeUnblockOnload();
}
JS::Value ScriptLoader::FindFirstParseError(ModuleLoadRequest* aRequest) {
@ -1741,8 +1743,9 @@ bool ScriptLoader::ProcessExternalScript(nsIScriptElement* aElement,
}
// We should still be in loading stage of script unless we're loading a
// module.
NS_ASSERTION(!request->InCompilingStage() || request->IsModuleRequest(),
// module or speculatively off-main-thread parsing a script.
NS_ASSERTION(SpeculativeOMTParsingEnabled() || !request->InCompilingStage() ||
request->IsModuleRequest(),
"Request should not yet be in compiling stage.");
if (request->IsAsyncScript()) {
@ -1943,6 +1946,11 @@ ScriptLoadRequest* ScriptLoader::LookupPreloadRequest(
// preload!
RefPtr<ScriptLoadRequest> request = mPreloads[i].mRequest;
request->SetIsLoadRequest(aElement);
if (request->mWasCompiledOMT && !request->IsModuleRequest()) {
request->SetReady();
}
nsString preloadCharset(mPreloads[i].mCharset);
mPreloads.RemoveElementAt(i);
@ -2066,6 +2074,15 @@ nsresult ScriptLoader::ProcessOffThreadRequest(ScriptLoadRequest* aRequest) {
return ProcessFetchedModuleSource(request);
}
// Element may not be ready yet if speculatively compiling, so process the
// request in ProcessPendingRequests when it is available.
MOZ_ASSERT_IF(!SpeculativeOMTParsingEnabled(), aRequest->GetScriptElement());
if (!aRequest->GetScriptElement()) {
// Unblock onload here in case this request never gets executed.
aRequest->MaybeUnblockOnload();
return NS_OK;
}
aRequest->SetReady();
if (aRequest == mParserBlockingRequest) {
@ -2080,14 +2097,19 @@ nsresult ScriptLoader::ProcessOffThreadRequest(ScriptLoadRequest* aRequest) {
mParserBlockingRequest = nullptr;
UnblockParser(aRequest);
ProcessRequest(aRequest);
mDocument->UnblockOnload(false);
ContinueParserAsync(aRequest);
return NS_OK;
}
nsresult rv = ProcessRequest(aRequest);
mDocument->UnblockOnload(false);
return rv;
// Async scripts and blocking scripts can be executed right away.
if (aRequest->IsAsyncScript() || aRequest->IsBlockingScript()) {
nsresult rv = ProcessRequest(aRequest);
return rv;
}
// Process other scripts in the proper order.
ProcessPendingRequests();
return NS_OK;
}
NotifyOffThreadScriptLoadCompletedRunnable::
@ -2198,7 +2220,10 @@ static void OffThreadScriptLoaderCallback(JS::OffThreadToken* aToken,
nsresult ScriptLoader::AttemptAsyncScriptCompile(ScriptLoadRequest* aRequest,
bool* aCouldCompileOut) {
MOZ_ASSERT_IF(!aRequest->IsModuleRequest(), aRequest->IsReadyToRun());
// If speculative parsing is enabled, the request may not be ready to run if
// the element is not yet available.
MOZ_ASSERT_IF(!SpeculativeOMTParsingEnabled() && !aRequest->IsModuleRequest(),
aRequest->IsReadyToRun());
MOZ_ASSERT(!aRequest->mWasCompiledOMT);
MOZ_ASSERT(aCouldCompileOut && !*aCouldCompileOut);
@ -2303,7 +2328,7 @@ nsresult ScriptLoader::AttemptAsyncScriptCompile(ScriptLoadRequest* aRequest,
}
}
mDocument->BlockOnload();
aRequest->BlockOnload(mDocument);
// Once the compilation is finished, an event would be added to the event loop
// to call ScriptLoader::ProcessOffThreadRequest with the same request.
@ -2318,21 +2343,18 @@ nsresult ScriptLoader::CompileOffThreadOrProcessRequest(
ScriptLoadRequest* aRequest) {
NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
"Processing requests when running scripts is unsafe.");
NS_ASSERTION(!aRequest->mOffThreadToken,
"Candidate for off-thread compile is already parsed off-thread");
NS_ASSERTION(
!aRequest->InCompilingStage(),
"Candidate for off-thread compile is already in compiling stage.");
bool couldCompile = false;
nsresult rv = AttemptAsyncScriptCompile(aRequest, &couldCompile);
if (NS_FAILED(rv)) {
HandleLoadError(aRequest, rv);
return rv;
}
if (!aRequest->mOffThreadToken && !aRequest->mWasCompiledOMT) {
bool couldCompile = false;
nsresult rv = AttemptAsyncScriptCompile(aRequest, &couldCompile);
if (NS_FAILED(rv)) {
HandleLoadError(aRequest, rv);
return rv;
}
if (couldCompile) {
return NS_OK;
if (couldCompile) {
return NS_OK;
}
}
return ProcessRequest(aRequest);
@ -2409,6 +2431,8 @@ nsresult ScriptLoader::ProcessRequest(ScriptLoadRequest* aRequest) {
NS_ENSURE_ARG(aRequest);
auto unblockOnload = MakeScopeExit([&] { aRequest->MaybeUnblockOnload(); });
if (aRequest->IsModuleRequest()) {
ModuleLoadRequest* request = aRequest->AsModuleRequest();
if (request->mModuleScript) {
@ -3211,9 +3235,6 @@ void ScriptLoader::ProcessPendingRequests() {
request.swap(mParserBlockingRequest);
UnblockParser(request);
ProcessRequest(request);
if (request->mWasCompiledOMT) {
mDocument->UnblockOnload(false);
}
ContinueParserAsync(request);
}
@ -3683,6 +3704,40 @@ static bool IsInternalURIScheme(nsIURI* uri) {
uri->SchemeIs("chrome");
}
bool ScriptLoader::ShouldCompileOffThread(ScriptLoadRequest* aRequest) {
if (NumberOfProcessors() <= 1) {
return false;
}
if (aRequest == mParserBlockingRequest) {
return true;
}
if (SpeculativeOMTParsingEnabled()) {
// Processing non async inserted scripts too early can potentially delay the
// load event from firing so focus on other scripts instead.
if (aRequest->mIsNonAsyncScriptInserted &&
!StaticPrefs::
dom_script_loader_external_scripts_speculate_non_parser_inserted_enabled()) {
return false;
}
// Async and link preload scripts do not need to be parsed right away.
if (aRequest->IsAsyncScript() &&
!StaticPrefs::
dom_script_loader_external_scripts_speculate_async_enabled()) {
return false;
}
if (aRequest->IsLinkPreloadScript() &&
!StaticPrefs::
dom_script_loader_external_scripts_speculate_link_preload_enabled()) {
return false;
}
return true;
}
return false;
}
nsresult ScriptLoader::PrepareLoadedRequest(ScriptLoadRequest* aRequest,
nsIIncrementalStreamLoader* aLoader,
nsresult aStatus) {
@ -3796,9 +3851,10 @@ nsresult ScriptLoader::PrepareLoadedRequest(ScriptLoadRequest* aRequest,
// The script is now loaded and ready to run.
aRequest->SetReady();
// If this is currently blocking the parser, attempt to compile it
// off-main-thread.
if (aRequest == mParserBlockingRequest && NumberOfProcessors() > 1) {
// If speculative parsing is enabled attempt to compile all
// external scripts off-main-thread. Otherwise, only omt compile scripts
// blocking the parser.
if (ShouldCompileOffThread(aRequest)) {
MOZ_ASSERT(!aRequest->IsModuleRequest());
bool couldCompile = false;
nsresult rv = AttemptAsyncScriptCompile(aRequest, &couldCompile);

Просмотреть файл

@ -150,6 +150,16 @@ class ScriptLoader final : public nsISupports {
mEnabled = aEnabled;
}
/**
* Check whether to speculatively OMT parse scripts as soon as
* they are fetched, even if not a parser blocking request.
* Controlled by
* dom.script_loader.external_scripts.speculative_omt_parse_enabled
*/
bool SpeculativeOMTParsingEnabled() const {
return mSpeculativeOMTParsingEnabled;
}
/**
* Add/remove a blocker for parser-blocking scripts (and XSLT
* scripts). Blockers will stop such scripts from executing, but not from
@ -555,6 +565,8 @@ class ScriptLoader final : public nsISupports {
void AddAsyncRequest(ScriptLoadRequest* aRequest);
bool MaybeRemovedDeferRequests();
bool ShouldCompileOffThread(ScriptLoadRequest* aRequest);
void MaybeMoveToLoadedList(ScriptLoadRequest* aRequest);
using MaybeSourceText =
@ -648,6 +660,7 @@ class ScriptLoader final : public nsISupports {
uint32_t mNumberOfProcessors;
bool mEnabled;
bool mDeferEnabled;
bool mSpeculativeOMTParsingEnabled;
bool mDeferCheckpointReached;
bool mBlockingDOMContentLoaded;
bool mLoadEventFired;

Просмотреть файл

@ -2482,6 +2482,31 @@
value: true
mirror: always
# Enable speculative off main thread parsing of external scripts as
# soon as they are fetched.
- name: dom.script_loader.external_scripts.speculative_omt_parse.enabled
type: bool
value: @IS_NIGHTLY_BUILD@
mirror: always
# Speculatively compile non parser inserted scripts
- name: dom.script_loader.external_scripts.speculate_non_parser_inserted.enabled
type: bool
value: false
mirror: always
# Speculatively compile async scripts
- name: dom.script_loader.external_scripts.speculate_async.enabled
type: bool
value: false
mirror: always
# Speculatively compile link preload scripts
- name: dom.script_loader.external_scripts.speculate_link_preload.enabled
type: bool
value: false
mirror: always
- name: dom.securecontext.whitelist_onions
type: bool
value: false