Files
tubestation/dom/script/ModuleLoader.cpp
Jon Coppeard aa49ff1988 Bug 1847813 - Process loaded modules asynchronously in DOM module loader r=smaug
Based on the explanation on the bug, the solution would seem to be to call the
ModuleLoaderBase::OnModuleLoadComplete hook from a microtask. This causes a ton
of test failures.

Instead this patch changes the DOM script loader to implement this hook with a
runnable. This fixes the problem (verified manually) and all tests pass.

(Replacing the runnable with a microtask just in the DOM script loader also
does not work and results in many test failures. I'm still trying to understand
why.)

The changes to ContinueDynamicImport are removed because they are also on this
path and we don't need a microtask as well.

This takes us closer to the state before bug 1842798 landed when all the module
loader used MozPromise internally and completions like this happened via
runnables.

I was not able to immediately come up with a test for this.

Differential Revision: https://phabricator.services.mozilla.com/D185830
2023-08-09 22:49:46 +00:00

332 lines
12 KiB
C++

/* -*- 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 "ScriptLoader.h"
#include "ModuleLoader.h"
#include "jsapi.h"
#include "js/CompileOptions.h" // JS::CompileOptions, JS::InstantiateOptions
#include "js/ContextOptions.h" // JS::ContextOptionsRef
#include "js/experimental/JSStencil.h" // JS::Stencil, JS::CompileModuleScriptToStencil, JS::InstantiateModuleStencil
#include "js/MemoryFunctions.h"
#include "js/Modules.h" // JS::FinishDynamicModuleImport, JS::{G,S}etModuleResolveHook, JS::Get{ModulePrivate,ModuleScript,RequestedModule{s,Specifier,SourcePos}}, JS::SetModule{DynamicImport,Metadata}Hook
#include "js/OffThreadScriptCompilation.h"
#include "js/PropertyAndElement.h" // JS_DefineProperty
#include "js/Realm.h"
#include "js/SourceText.h"
#include "js/loader/LoadedScript.h"
#include "js/loader/ScriptLoadRequest.h"
#include "js/loader/ModuleLoaderBase.h"
#include "js/loader/ModuleLoadRequest.h"
#include "xpcpublic.h"
#include "GeckoProfiler.h"
#include "nsContentSecurityManager.h"
#include "nsIContent.h"
#include "nsJSUtils.h"
#include "mozilla/dom/AutoEntryScript.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/Element.h"
#include "nsIPrincipal.h"
#include "mozilla/LoadInfo.h"
#include "mozilla/Maybe.h"
using JS::SourceText;
using namespace JS::loader;
namespace mozilla::dom {
#undef LOG
#define LOG(args) \
MOZ_LOG(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug, args)
#define LOG_ENABLED() \
MOZ_LOG_TEST(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug)
//////////////////////////////////////////////////////////////
// DOM module loader
//////////////////////////////////////////////////////////////
ModuleLoader::ModuleLoader(ScriptLoader* aLoader,
nsIGlobalObject* aGlobalObject, Kind aKind)
: ModuleLoaderBase(aLoader, aGlobalObject), mKind(aKind) {}
ScriptLoader* ModuleLoader::GetScriptLoader() {
return static_cast<ScriptLoader*>(mLoader.get());
}
bool ModuleLoader::CanStartLoad(ModuleLoadRequest* aRequest, nsresult* aRvOut) {
if (!GetScriptLoader()->GetDocument()) {
*aRvOut = NS_ERROR_NULL_POINTER;
return false;
}
// If this document is sandboxed without 'allow-scripts', abort.
if (GetScriptLoader()->GetDocument()->HasScriptsBlockedBySandbox()) {
*aRvOut = NS_OK;
return false;
}
// To prevent dynamic code execution, content scripts can only
// load moz-extension URLs.
nsCOMPtr<nsIPrincipal> principal = aRequest->TriggeringPrincipal();
if (BasePrincipal::Cast(principal)->ContentScriptAddonPolicy() &&
!aRequest->mURI->SchemeIs("moz-extension")) {
*aRvOut = NS_ERROR_DOM_WEBEXT_CONTENT_SCRIPT_URI;
return false;
}
if (LOG_ENABLED()) {
nsAutoCString url;
aRequest->mURI->GetAsciiSpec(url);
LOG(("ScriptLoadRequest (%p): Start Module Load (url = %s)", aRequest,
url.get()));
}
return true;
}
nsresult ModuleLoader::StartFetch(ModuleLoadRequest* aRequest) {
// According to the spec, module scripts have different behaviour to classic
// scripts and always use CORS. Only exception: Non linkable about: pages
// which load local module scripts.
bool isAboutPageLoadingChromeURI = ScriptLoader::IsAboutPageLoadingChromeURI(
aRequest, GetScriptLoader()->GetDocument());
nsContentSecurityManager::CORSSecurityMapping corsMapping =
isAboutPageLoadingChromeURI
? nsContentSecurityManager::CORSSecurityMapping::DISABLE_CORS_CHECKS
: nsContentSecurityManager::CORSSecurityMapping::REQUIRE_CORS_CHECKS;
nsSecurityFlags securityFlags =
nsContentSecurityManager::ComputeSecurityFlags(aRequest->CORSMode(),
corsMapping);
securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
// Delegate Shared Behavior to base ScriptLoader
//
// aCharsetForPreload is passed as Nothing() because this is not a preload
// and `StartLoadInternal` is able to find the charset by using `aRequest`
// for this case.
nsresult rv = GetScriptLoader()->StartLoadInternal(
aRequest, securityFlags, Nothing() /* aCharsetForPreload */);
NS_ENSURE_SUCCESS(rv, rv);
// https://html.spec.whatwg.org/multipage/webappapis.html#fetch-an-import()-module-script-graph
// Step 1. Disallow further import maps given settings object.
if (!aRequest->GetScriptLoadContext()->IsPreload()) {
LOG(("ScriptLoadRequest (%p): Disallow further import maps.", aRequest));
DisallowImportMaps();
}
LOG(("ScriptLoadRequest (%p): Start fetching module", aRequest));
return NS_OK;
}
void ModuleLoader::OnModuleLoadComplete(ModuleLoadRequest* aRequest) {
MOZ_ASSERT(aRequest->IsFinished());
if (aRequest->IsTopLevel()) {
if (aRequest->GetScriptLoadContext()->mIsInline &&
aRequest->GetScriptLoadContext()->GetParserCreated() ==
NOT_FROM_PARSER) {
GetScriptLoader()->RunScriptWhenSafe(aRequest);
} else {
GetScriptLoader()->MaybeMoveToLoadedList(aRequest);
GetScriptLoader()->ProcessPendingRequestsAsync();
}
}
aRequest->GetScriptLoadContext()->MaybeUnblockOnload();
}
nsresult ModuleLoader::CompileFetchedModule(
JSContext* aCx, JS::Handle<JSObject*> aGlobal, JS::CompileOptions& aOptions,
ModuleLoadRequest* aRequest, JS::MutableHandle<JSObject*> aModuleOut) {
if (aRequest->GetScriptLoadContext()->mWasCompiledOMT) {
JS::InstantiationStorage storage;
RefPtr<JS::Stencil> stencil = JS::FinishOffThreadStencil(
aCx, aRequest->GetScriptLoadContext()->mOffThreadToken, &storage);
aRequest->GetScriptLoadContext()->mOffThreadToken = nullptr;
if (!stencil) {
return NS_ERROR_FAILURE;
}
JS::InstantiateOptions instantiateOptions(aOptions);
aModuleOut.set(JS::InstantiateModuleStencil(aCx, instantiateOptions,
stencil, &storage));
if (!aModuleOut) {
return NS_ERROR_FAILURE;
}
if (aRequest->IsTextSource() &&
ScriptLoader::ShouldCacheBytecode(aRequest)) {
if (!JS::StartIncrementalEncoding(aCx, std::move(stencil))) {
return NS_ERROR_FAILURE;
}
}
return NS_OK;
}
if (!nsJSUtils::IsScriptable(aGlobal)) {
return NS_ERROR_FAILURE;
}
RefPtr<JS::Stencil> stencil;
if (aRequest->IsTextSource()) {
MaybeSourceText maybeSource;
nsresult rv = aRequest->GetScriptSource(aCx, &maybeSource);
NS_ENSURE_SUCCESS(rv, rv);
auto compile = [&](auto& source) {
return JS::CompileModuleScriptToStencil(aCx, aOptions, source);
};
stencil = maybeSource.mapNonEmpty(compile);
} else {
MOZ_ASSERT(aRequest->IsBytecode());
JS::DecodeOptions decodeOptions(aOptions);
decodeOptions.borrowBuffer = true;
auto& bytecode = aRequest->mScriptBytecode;
auto& offset = aRequest->mBytecodeOffset;
JS::TranscodeRange range(bytecode.begin() + offset,
bytecode.length() - offset);
JS::TranscodeResult tr =
JS::DecodeStencil(aCx, decodeOptions, range, getter_AddRefs(stencil));
if (tr != JS::TranscodeResult::Ok) {
return NS_ERROR_DOM_JS_DECODING_ERROR;
}
}
if (!stencil) {
return NS_ERROR_FAILURE;
}
JS::InstantiateOptions instantiateOptions(aOptions);
aModuleOut.set(
JS::InstantiateModuleStencil(aCx, instantiateOptions, stencil));
if (!aModuleOut) {
return NS_ERROR_FAILURE;
}
if (aRequest->IsTextSource() && ScriptLoader::ShouldCacheBytecode(aRequest)) {
if (!JS::StartIncrementalEncoding(aCx, std::move(stencil))) {
return NS_ERROR_FAILURE;
}
}
return NS_OK;
}
/* static */
already_AddRefed<ModuleLoadRequest> ModuleLoader::CreateTopLevel(
nsIURI* aURI, ScriptFetchOptions* aFetchOptions,
const SRIMetadata& aIntegrity, nsIURI* aReferrer, ScriptLoader* aLoader,
ScriptLoadContext* aContext) {
RefPtr<ModuleLoadRequest> request = new ModuleLoadRequest(
aURI, aFetchOptions, aIntegrity, aReferrer, aContext, true,
/* is top level */ false, /* is dynamic import */
aLoader->GetModuleLoader(),
ModuleLoadRequest::NewVisitedSetForTopLevelImport(aURI), nullptr);
return request.forget();
}
already_AddRefed<ModuleLoadRequest> ModuleLoader::CreateStaticImport(
nsIURI* aURI, ModuleLoadRequest* aParent) {
RefPtr<ScriptLoadContext> newContext = new ScriptLoadContext();
newContext->mIsInline = false;
// Propagated Parent values. TODO: allow child modules to use root module's
// script mode.
newContext->mScriptMode = aParent->GetScriptLoadContext()->mScriptMode;
RefPtr<ModuleLoadRequest> request = new ModuleLoadRequest(
aURI, aParent->mFetchOptions, SRIMetadata(), aParent->mURI, newContext,
false, /* is top level */
false, /* is dynamic import */
aParent->mLoader, aParent->mVisitedSet, aParent->GetRootModule());
return request.forget();
}
already_AddRefed<ModuleLoadRequest> ModuleLoader::CreateDynamicImport(
JSContext* aCx, nsIURI* aURI, LoadedScript* aMaybeActiveScript,
JS::Handle<JS::Value> aReferencingPrivate, JS::Handle<JSString*> aSpecifier,
JS::Handle<JSObject*> aPromise) {
MOZ_ASSERT(aSpecifier);
MOZ_ASSERT(aPromise);
RefPtr<ScriptFetchOptions> options = nullptr;
nsIURI* baseURL = nullptr;
RefPtr<ScriptLoadContext> context = new ScriptLoadContext();
if (aMaybeActiveScript) {
// https://html.spec.whatwg.org/multipage/webappapis.html#hostloadimportedmodule
// Step 6.3. Set fetchOptions to the new descendant script fetch options for
// referencingScript's fetch options.
options = aMaybeActiveScript->GetFetchOptions();
baseURL = aMaybeActiveScript->BaseURL();
} else {
// We don't have a referencing script so fall back on using
// options from the document. This can happen when the user
// triggers an inline event handler, as there is no active script
// there.
Document* document = GetScriptLoader()->GetDocument();
nsCOMPtr<nsIPrincipal> principal = GetGlobalObject()->PrincipalOrNull();
MOZ_ASSERT_IF(GetKind() == WebExtension,
BasePrincipal::Cast(principal)->ContentScriptAddonPolicy());
MOZ_ASSERT_IF(GetKind() == Normal, principal == document->NodePrincipal());
// https://html.spec.whatwg.org/multipage/webappapis.html#hostloadimportedmodule
// Step 4. Let fetchOptions be the default classic script fetch options.
//
// https://html.spec.whatwg.org/multipage/webappapis.html#default-classic-script-fetch-options
// The default classic script fetch options are a script fetch options whose
// cryptographic nonce is the empty string, integrity metadata is the empty
// string, parser metadata is "not-parser-inserted", credentials mode is
// "same-origin", referrer policy is the empty string, and fetch priority is
// "auto".
options = new ScriptFetchOptions(
mozilla::CORS_NONE, document->GetReferrerPolicy(),
/* aNonce = */ u""_ns, ParserMetadata::NotParserInserted, principal,
nullptr);
baseURL = document->GetDocBaseURI();
}
context->mIsInline = false;
context->mScriptMode = ScriptLoadContext::ScriptMode::eAsync;
RefPtr<ModuleLoadRequest> request = new ModuleLoadRequest(
aURI, options, SRIMetadata(), baseURL, context, true,
/* is top level */ true, /* is dynamic import */
this, ModuleLoadRequest::NewVisitedSetForTopLevelImport(aURI), nullptr);
request->mDynamicReferencingPrivate = aReferencingPrivate;
request->mDynamicSpecifier = aSpecifier;
request->mDynamicPromise = aPromise;
HoldJSObjects(request.get());
return request.forget();
}
ModuleLoader::~ModuleLoader() {
LOG(("ModuleLoader::~ModuleLoader %p", this));
mLoader = nullptr;
}
#undef LOG
#undef LOG_ENABLED
} // namespace mozilla::dom