Bug 1342012 - Initial browser support for dynamic import from module scripts r=smaug

This commit is contained in:
Jon Coppeard
2018-12-06 16:52:15 -05:00
parent 26f32028e8
commit 6e8ed561b5
23 changed files with 450 additions and 166 deletions

View File

@@ -113,9 +113,9 @@ NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTION(ScriptLoader, mNonAsyncExternalScriptInsertedRequests,
mLoadingAsyncRequests, mLoadedAsyncRequests,
mDeferRequests, mXSLTRequests, mParserBlockingRequest,
mBytecodeEncodingQueue, mPreloads,
mPendingChildLoaders, mFetchedModules)
mDeferRequests, mXSLTRequests, mDynamicImportRequests,
mParserBlockingRequest, mBytecodeEncodingQueue,
mPreloads, mPendingChildLoaders, mFetchedModules)
NS_IMPL_CYCLE_COLLECTING_ADDREF(ScriptLoader)
NS_IMPL_CYCLE_COLLECTING_RELEASE(ScriptLoader)
@@ -133,6 +133,7 @@ ScriptLoader::ScriptLoader(nsIDocument* aDocument)
mGiveUpEncoding(false),
mReporter(new ConsoleReportCollector()) {
LOG(("ScriptLoader::ScriptLoader %p", this));
EnsureModuleHooksInitialized();
}
ScriptLoader::~ScriptLoader() {
@@ -164,6 +165,11 @@ ScriptLoader::~ScriptLoader() {
req->FireScriptAvailable(NS_ERROR_ABORT);
}
for (ScriptLoadRequest* req = mDynamicImportRequests.getFirst(); req;
req = req->getNext()) {
FinishDynamicImport(req->AsModuleRequest(), NS_ERROR_ABORT);
}
for (ScriptLoadRequest* req =
mNonAsyncExternalScriptInsertedRequests.getFirst();
req; req = req->getNext()) {
@@ -471,7 +477,7 @@ nsresult ScriptLoader::CreateModuleScript(ModuleLoadRequest* aRequest) {
MOZ_ASSERT(NS_SUCCEEDED(rv) == (module != nullptr));
RefPtr<ModuleScript> moduleScript =
new ModuleScript(this, aRequest->mBaseURL);
new ModuleScript(this, aRequest->mFetchOptions, aRequest->mBaseURL);
aRequest->mModuleScript = moduleScript;
if (!module) {
@@ -689,7 +695,8 @@ RefPtr<GenericPromise> ScriptLoader::StartFetchingModuleAndDependencies(
ModuleLoadRequest* aParent, nsIURI* aURI) {
MOZ_ASSERT(aURI);
RefPtr<ModuleLoadRequest> childRequest = new ModuleLoadRequest(aURI, aParent);
RefPtr<ModuleLoadRequest> childRequest =
ModuleLoadRequest::CreateStaticImport(aURI, aParent);
aParent->mImports.AppendElement(childRequest);
@@ -725,6 +732,12 @@ JSObject* HostResolveImportedModule(JSContext* aCx,
JS::Handle<JS::Value> aReferencingPrivate,
JS::Handle<JSString*> aSpecifier) {
// Let referencing module script be referencingModule.[[HostDefined]].
if (aReferencingPrivate.isUndefined()) {
JS_ReportErrorNumberUC(aCx, js::GetErrorMessage, nullptr,
JSMSG_IMPORT_SCRIPT_NOT_FOUND);
return nullptr;
}
auto script = static_cast<ModuleScript*>(aReferencingPrivate.toPrivate());
MOZ_ASSERT(JS::GetModulePrivate(script->ModuleRecord()) ==
aReferencingPrivate);
@@ -774,14 +787,114 @@ bool HostPopulateImportMeta(JSContext* aCx,
JSPROP_ENUMERATE);
}
static void EnsureModuleResolveHook(JSContext* aCx) {
JSRuntime* rt = JS_GetRuntime(aCx);
bool HostImportModuleDynamically(JSContext* aCx,
JS::Handle<JS::Value> aReferencingPrivate,
JS::Handle<JSString*> aSpecifier,
JS::Handle<JSObject*> aPromise) {
if (aReferencingPrivate.isUndefined()) {
JS_ReportErrorNumberUC(aCx, js::GetErrorMessage, nullptr,
JSMSG_IMPORT_SCRIPT_NOT_FOUND);
return false;
}
auto script = static_cast<ModuleScript*>(aReferencingPrivate.toPrivate());
MOZ_ASSERT(JS::GetModulePrivate(script->ModuleRecord()) ==
aReferencingPrivate);
// Attempt to resolve the module specifier.
nsAutoJSString string;
if (!string.init(aCx, aSpecifier)) {
return false;
}
nsCOMPtr<nsIURI> uri = ResolveModuleSpecifier(script, string);
if (!uri) {
JS_ReportErrorNumberUC(aCx, js::GetErrorMessage, nullptr,
JSMSG_BAD_MODULE_SPECIFIER, string.get());
return false;
}
// Create a new top-level load request.
RefPtr<ModuleLoadRequest> request = ModuleLoadRequest::CreateDynamicImport(
uri, script, aReferencingPrivate, aSpecifier, aPromise);
script->Loader()->StartDynamicImport(request);
return true;
}
void ScriptLoader::StartDynamicImport(ModuleLoadRequest* aRequest) {
LOG(("ScriptLoadRequest (%p): Start dynamic import", aRequest));
mDynamicImportRequests.AppendElement(aRequest);
nsresult rv = StartLoad(aRequest);
if (NS_FAILED(rv)) {
FinishDynamicImport(aRequest, rv);
}
}
void ScriptLoader::FinishDynamicImport(ModuleLoadRequest* aRequest,
nsresult aResult) {
AutoJSAPI jsapi;
MOZ_ALWAYS_TRUE(jsapi.Init(aRequest->mDynamicPromise));
FinishDynamicImport(jsapi.cx(), aRequest, aResult);
}
void ScriptLoader::FinishDynamicImport(JSContext* aCx,
ModuleLoadRequest* aRequest,
nsresult aResult) {
LOG(("ScriptLoadRequest (%p): Finish dynamic import %d %d", aRequest,
unsigned(aResult), JS_IsExceptionPending(aCx)));
// Complete the dynamic import, report failures indicated by aResult or as a
// pending exception on the context.
if (NS_FAILED(aResult)) {
MOZ_ASSERT(!JS_IsExceptionPending(aCx));
JS_ReportErrorNumberUC(aCx, js::GetErrorMessage, nullptr,
JSMSG_IMPORT_SCRIPT_NOT_FOUND);
}
JS::Rooted<JS::Value> referencingScript(aCx,
aRequest->mDynamicReferencingPrivate);
JS::Rooted<JSString*> specifier(aCx, aRequest->mDynamicSpecifier);
JS::Rooted<JSObject*> promise(aCx, aRequest->mDynamicPromise);
JS::FinishDynamicModuleImport(aCx, referencingScript, specifier, promise);
// FinishDynamicModuleImport clears any pending exception.
MOZ_ASSERT(!JS_IsExceptionPending(aCx));
aRequest->ClearDynamicImport();
}
static void DynamicImportPrefChangedCallback(const char* aPrefName,
void* aClosure) {
bool enabled = Preferences::GetBool(aPrefName);
JS::ModuleDynamicImportHook hook =
enabled ? HostImportModuleDynamically : nullptr;
AutoJSAPI jsapi;
jsapi.Init();
JSRuntime* rt = JS_GetRuntime(jsapi.cx());
JS::SetModuleDynamicImportHook(rt, hook);
}
void ScriptLoader::EnsureModuleHooksInitialized() {
AutoJSAPI jsapi;
jsapi.Init();
JSRuntime* rt = JS_GetRuntime(jsapi.cx());
if (JS::GetModuleResolveHook(rt)) {
return;
}
JS::SetModuleResolveHook(rt, HostResolveImportedModule);
JS::SetModuleMetadataHook(rt, HostPopulateImportMeta);
JS::SetScriptPrivateFinalizeHook(rt, HostFinalizeTopLevelScript);
Preferences::RegisterCallbackAndCall(DynamicImportPrefChangedCallback,
"javascript.options.dynamicImport",
(void*)nullptr);
}
void ScriptLoader::CheckModuleDependenciesLoaded(ModuleLoadRequest* aRequest) {
@@ -815,18 +928,34 @@ class ScriptRequestProcessor : public Runnable {
: Runnable("dom::ScriptRequestProcessor"),
mLoader(aLoader),
mRequest(aRequest) {}
NS_IMETHOD Run() override { return mLoader->ProcessRequest(mRequest); }
NS_IMETHOD Run() override {
if (mRequest->IsModuleRequest() &&
mRequest->AsModuleRequest()->IsDynamicImport()) {
mLoader->ProcessDynamicImport(mRequest->AsModuleRequest());
return NS_OK;
}
return mLoader->ProcessRequest(mRequest);
}
};
void ScriptLoader::RunScriptWhenSafe(ScriptLoadRequest* aRequest) {
auto runnable = new ScriptRequestProcessor(this, aRequest);
nsContentUtils::AddScriptRunner(runnable);
}
void ScriptLoader::ProcessLoadedModuleTree(ModuleLoadRequest* aRequest) {
MOZ_ASSERT(aRequest->IsReadyToRun());
if (aRequest->IsTopLevel()) {
if (aRequest->mIsInline &&
aRequest->Element()->GetParserCreated() == NOT_FROM_PARSER) {
if (aRequest->IsDynamicImport()) {
MOZ_ASSERT(aRequest->isInList());
RefPtr<ScriptLoadRequest> req = mDynamicImportRequests.Steal(aRequest);
RunScriptWhenSafe(req);
} else if (aRequest->mIsInline &&
aRequest->Element()->GetParserCreated() == NOT_FROM_PARSER) {
MOZ_ASSERT(!aRequest->isInList());
nsContentUtils::AddScriptRunner(
new ScriptRequestProcessor(this, aRequest));
RunScriptWhenSafe(aRequest);
} else {
MaybeMoveToLoadedList(aRequest);
ProcessPendingRequests();
@@ -884,8 +1013,6 @@ bool ScriptLoader::InstantiateModuleTree(ModuleLoadRequest* aRequest) {
return false;
}
EnsureModuleResolveHook(jsapi.cx());
JS::Rooted<JSObject*> module(jsapi.cx(), moduleScript->ModuleRecord());
bool ok = NS_SUCCEEDED(nsJSUtils::ModuleInstantiate(jsapi.cx(), module));
@@ -1210,7 +1337,8 @@ ScriptLoadRequest* ScriptLoader::CreateLoadRequest(
}
MOZ_ASSERT(aKind == ScriptKind::eModule);
return new ModuleLoadRequest(aURI, fetchOptions, aIntegrity, referrer, this);
return ModuleLoadRequest::CreateTopLevel(aURI, fetchOptions, aIntegrity,
referrer, this);
}
bool ScriptLoader::ProcessScriptElement(nsIScriptElement* aElement) {
@@ -1538,7 +1666,7 @@ bool ScriptLoader::ProcessInlineScript(nsIScriptElement* aElement,
NS_ASSERTION(
!nsContentUtils::IsSafeToRunScript(),
"A script-inserted script is inserted without an update batch?");
nsContentUtils::AddScriptRunner(new ScriptRequestProcessor(this, request));
RunScriptWhenSafe(request);
return false;
}
if (aElement->GetParserCreated() == FROM_PARSER_NETWORK &&
@@ -1915,8 +2043,7 @@ nsresult ScriptLoader::ProcessRequest(ScriptLoadRequest* aRequest) {
if (aRequest->IsModuleRequest()) {
ModuleLoadRequest* request = aRequest->AsModuleRequest();
if (request->mModuleScript &&
!request->mModuleScript->HasErrorToRethrow()) {
if (request->mModuleScript) {
if (!InstantiateModuleTree(request)) {
request->mModuleScript = nullptr;
}
@@ -2018,6 +2145,25 @@ nsresult ScriptLoader::ProcessRequest(ScriptLoadRequest* aRequest) {
return rv;
}
void ScriptLoader::ProcessDynamicImport(ModuleLoadRequest* aRequest) {
if (aRequest->mModuleScript) {
if (!InstantiateModuleTree(aRequest)) {
aRequest->mModuleScript = nullptr;
}
}
nsresult rv = NS_ERROR_FAILURE;
if (aRequest->mModuleScript) {
rv = EvaluateScript(aRequest);
}
if (NS_FAILED(rv)) {
FinishDynamicImport(aRequest, rv);
}
return;
}
void ScriptLoader::FireScriptAvailable(nsresult aResult,
ScriptLoadRequest* aRequest) {
for (int32_t i = 0; i < mObservers.Count(); i++) {
@@ -2062,7 +2208,7 @@ already_AddRefed<nsIScriptGlobalObject> ScriptLoader::GetScriptGlobalObject() {
}
nsresult ScriptLoader::FillCompileOptionsForRequest(
const AutoJSAPI& jsapi, ScriptLoadRequest* aRequest,
const mozilla::dom::AutoJSAPI& jsapi, ScriptLoadRequest* aRequest,
JS::Handle<JSObject*> aScopeChain, JS::CompileOptions* aOptions) {
// It's very important to use aRequest->mURI, not the final URI of the channel
// aRequest ended up getting script data from, as the script filename.
@@ -2203,6 +2349,19 @@ nsresult ScriptLoader::FillCompileOptionsForRequest(
return true;
}
class MOZ_RAII AutoSetProcessingScriptTag {
nsCOMPtr<nsIScriptContext> mContext;
bool mOldTag;
public:
explicit AutoSetProcessingScriptTag(nsIScriptContext* aContext)
: mContext(aContext), mOldTag(mContext->GetProcessingScriptTag()) {
mContext->SetProcessingScriptTag(true);
}
~AutoSetProcessingScriptTag() { mContext->SetProcessingScriptTag(mOldTag); }
};
nsresult ScriptLoader::EvaluateScript(ScriptLoadRequest* aRequest) {
using namespace mozilla::Telemetry;
MOZ_ASSERT(aRequest->IsReadyToRun());
@@ -2242,8 +2401,8 @@ nsresult ScriptLoader::EvaluateScript(ScriptLoadRequest* aRequest) {
JSContext* cx = aes.cx();
JS::Rooted<JSObject*> global(cx, globalObject->GetGlobalJSObject());
bool oldProcessingScriptTag = context->GetProcessingScriptTag();
context->SetProcessingScriptTag(true);
AutoSetProcessingScriptTag setProcessingScriptTag(context);
nsresult rv;
{
if (aRequest->IsModuleRequest()) {
@@ -2255,8 +2414,6 @@ nsresult ScriptLoader::EvaluateScript(ScriptLoadRequest* aRequest) {
// currentScript is set to null for modules.
AutoCurrentScriptUpdater scriptUpdater(this, nullptr);
EnsureModuleResolveHook(cx);
ModuleLoadRequest* request = aRequest->AsModuleRequest();
MOZ_ASSERT(request->mModuleScript);
MOZ_ASSERT(!request->mOffThreadToken);
@@ -2267,7 +2424,13 @@ nsresult ScriptLoader::EvaluateScript(ScriptLoadRequest* aRequest) {
aRequest));
JS::Rooted<JS::Value> error(cx, moduleScript->ErrorToRethrow());
JS_SetPendingException(cx, error);
return NS_OK; // An error is reported by AutoEntryScript.
// For a dynamic import, the promise is rejected. Otherwise an error is
// either reported by AutoEntryScript.
if (request->IsDynamicImport()) {
FinishDynamicImport(cx, request, NS_OK);
}
return NS_OK;
}
JS::Rooted<JSObject*> module(cx, moduleScript->ModuleRecord());
@@ -2280,9 +2443,16 @@ nsresult ScriptLoader::EvaluateScript(ScriptLoadRequest* aRequest) {
rv = nsJSUtils::ModuleEvaluate(cx, module);
MOZ_ASSERT(NS_FAILED(rv) == aes.HasException());
if (NS_FAILED(rv)) {
LOG(("ScriptLoadRequest (%p): evaluation failed", aRequest));
rv = NS_OK; // An error is reported by AutoEntryScript.
// For a dynamic import, the promise is rejected. Otherwise an error is
// either reported by AutoEntryScript.
rv = NS_OK;
}
if (request->IsDynamicImport()) {
FinishDynamicImport(cx, request, rv);
}
aRequest->mCacheInfo = nullptr;
@@ -2354,8 +2524,7 @@ nsresult ScriptLoader::EvaluateScript(ScriptLoadRequest* aRequest) {
// Queue the current script load request to later save the bytecode.
if (script && encodeBytecode) {
aRequest->mScript = script;
HoldJSObjects(aRequest);
aRequest->SetScript(script);
TRACE_FOR_TEST(aRequest->Element(), "scriptloader_encode");
MOZ_ASSERT(aRequest->mBytecodeOffset ==
aRequest->mScriptBytecode.length());
@@ -2379,7 +2548,6 @@ nsresult ScriptLoader::EvaluateScript(ScriptLoadRequest* aRequest) {
MaybeTriggerBytecodeEncoding();
}
context->SetProcessingScriptTag(oldProcessingScriptTag);
return rv;
}
@@ -2567,7 +2735,8 @@ bool ScriptLoader::HasPendingRequests() {
return mParserBlockingRequest || !mXSLTRequests.isEmpty() ||
!mLoadedAsyncRequests.isEmpty() ||
!mNonAsyncExternalScriptInsertedRequests.isEmpty() ||
!mDeferRequests.isEmpty() || !mPendingChildLoaders.IsEmpty();
!mDeferRequests.isEmpty() || !mDynamicImportRequests.isEmpty() ||
!mPendingChildLoaders.IsEmpty();
}
void ScriptLoader::ProcessPendingRequestsAsync() {
@@ -2980,12 +3149,31 @@ void ScriptLoader::HandleLoadError(ScriptLoadRequest* aRequest,
RefPtr<ScriptLoadRequest> req = mXSLTRequests.Steal(aRequest);
FireScriptAvailable(aResult, req);
}
} else if (aRequest->IsModuleRequest() && !aRequest->IsPreload()) {
} else if (aRequest->IsPreload()) {
if (aRequest->IsModuleRequest()) {
aRequest->Cancel();
}
if (aRequest->IsTopLevel()) {
MOZ_ALWAYS_TRUE(
mPreloads.RemoveElement(aRequest, PreloadRequestComparator()));
}
MOZ_ASSERT(!aRequest->isInList());
AccumulateCategorical(LABELS_DOM_SCRIPT_PRELOAD_RESULT::LoadError);
} else if (aRequest->IsModuleRequest()) {
ModuleLoadRequest* modReq = aRequest->AsModuleRequest();
MOZ_ASSERT(!modReq->IsTopLevel());
MOZ_ASSERT(!modReq->isInList());
modReq->Cancel();
// A single error is fired for the top level module.
if (modReq->IsDynamicImport()) {
MOZ_ASSERT(modReq->IsTopLevel());
if (aRequest->isInList()) {
RefPtr<ScriptLoadRequest> req = mDynamicImportRequests.Steal(aRequest);
modReq->Cancel();
FinishDynamicImport(modReq, aResult);
}
} else {
MOZ_ASSERT(!modReq->IsTopLevel());
MOZ_ASSERT(!modReq->isInList());
modReq->Cancel();
// The error is handled for the top level module.
}
} else if (mParserBlockingRequest == aRequest) {
MOZ_ASSERT(!aRequest->isInList());
mParserBlockingRequest = nullptr;
@@ -3000,16 +3188,6 @@ void ScriptLoader::HandleLoadError(ScriptLoadRequest* aRequest,
FireScriptAvailable(aResult, aRequest);
ContinueParserAsync(aRequest);
mCurrentParserInsertedScript = oldParserInsertedScript;
} else if (aRequest->IsPreload()) {
if (aRequest->IsModuleRequest()) {
aRequest->Cancel();
}
if (aRequest->IsTopLevel()) {
MOZ_ALWAYS_TRUE(
mPreloads.RemoveElement(aRequest, PreloadRequestComparator()));
}
MOZ_ASSERT(!aRequest->isInList());
AccumulateCategorical(LABELS_DOM_SCRIPT_PRELOAD_RESULT::LoadError);
} else {
// This happens for blocking requests cancelled by ParsingComplete().
MOZ_ASSERT(aRequest->IsCanceled());
@@ -3110,11 +3288,12 @@ nsresult ScriptLoader::PrepareLoadedRequest(ScriptLoadRequest* aRequest,
mLoadingAsyncRequests.Contains(aRequest) ||
mNonAsyncExternalScriptInsertedRequests.Contains(aRequest) ||
mXSLTRequests.Contains(aRequest) ||
mDynamicImportRequests.Contains(aRequest) ||
(aRequest->IsModuleRequest() &&
!aRequest->AsModuleRequest()->IsTopLevel() &&
!aRequest->isInList()) ||
mPreloads.Contains(aRequest, PreloadRequestComparator()) ||
mParserBlockingRequest,
mParserBlockingRequest == aRequest,
"aRequest should be pending!");
if (aRequest->IsModuleRequest()) {
@@ -3191,6 +3370,13 @@ void ScriptLoader::ParsingComplete(bool aTerminated) {
mLoadedAsyncRequests.Clear();
mNonAsyncExternalScriptInsertedRequests.Clear();
mXSLTRequests.Clear();
for (ScriptLoadRequest* req = mDynamicImportRequests.getFirst(); req;
req = req->getNext()) {
req->Cancel();
FinishDynamicImport(req->AsModuleRequest(), NS_ERROR_ABORT);
}
if (mParserBlockingRequest) {
mParserBlockingRequest->Cancel();
mParserBlockingRequest = nullptr;