Bug 1688879 - Part 3: Parse and register an import map. r=jonco,yulia

Implement
https://wicg.github.io/import-maps/#parse-an-import-map-string,
and
https://wicg.github.io/import-maps/#register-an-import-map

Differential Revision: https://phabricator.services.mozilla.com/D142071
This commit is contained in:
Yoshi Cheng-Hao Huang
2022-05-05 21:19:01 +00:00
parent 31656e9e1f
commit d5ac332d96
10 changed files with 654 additions and 1 deletions

View File

@@ -323,6 +323,13 @@ ScriptSourceNotAllowed=<script> source URI is not allowed in this document: “%
ModuleSourceNotAllowed=Module source URI is not allowed in this document: “%S”.
WebExtContentScriptModuleSourceNotAllowed=WebExtension content scripts may only load modules with moz-extension URLs and not: “%S”.
ModuleResolveFailure=Error resolving module specifier “%S”. Relative module specifiers must start with “./”, “../” or “/”.
ImportMapInvalidTopLevelKey=An invalid top-level key “%S” was present in the import map.
ImportMapEmptySpecifierKeys=Specifier keys cannot be empty strings.
ImportMapAddressesNotStrings=Addresses need to be strings.
ImportMapInvalidAddress=Address “%S” was invalid.
# %1$S is the specifier key, %2$S is the URL.
ImportMapAddressNotEndsWithSlash=An invalid address was given for the specifier key “%1$S”; since “%1$S” ended in a slash, the address “%2$S” needs to as well.
ImportMapScopePrefixNotParseable=The scope prefix URL “%S” was not parseable.
# LOCALIZATION NOTE: %1$S is the invalid property value and %2$S is the property name.
InvalidKeyframePropertyValue=Keyframe property value “%1$S” is invalid according to the syntax for “%2$S”.
# LOCALIZATION NOTE: Do not translate "ReadableStream".

View File

@@ -822,7 +822,7 @@ already_AddRefed<ScriptLoadRequest> ScriptLoader::CreateLoadRequest(
aCORSMode, aReferrerPolicy, aTriggeringPrincipal, domElement);
RefPtr<ScriptLoadContext> context = new ScriptLoadContext();
if (aKind == ScriptKind::eClassic) {
if (aKind == ScriptKind::eClassic || aKind == ScriptKind::eImportMap) {
RefPtr<ScriptLoadRequest> aRequest = new ScriptLoadRequest(
aKind, aURI, fetchOptions, aIntegrity, referrer, context);
@@ -1166,6 +1166,29 @@ bool ScriptLoader::ProcessInlineScript(nsIScriptElement* aElement,
return false;
}
if (request->IsImportMapRequest()) {
UniquePtr<ImportMap> importMap = mModuleLoader->ParseImportMap(request);
// https://wicg.github.io/import-maps/#register-an-import-map
//
// Step 1. If elements the scripts result is null, then fire an event
// named error at element, and return.
if (!importMap) {
NS_DispatchToCurrentThread(
NewRunnableMethod("nsIScriptElement::FireErrorEvent", aElement,
&nsIScriptElement::FireErrorEvent));
return false;
}
// Step 3. Assert: elements the scripts type is "importmap".
MOZ_ASSERT(aElement->GetScriptIsImportMap());
// Step 4 to step 9 is done in RegisterImportMap.
mModuleLoader->RegisterImportMap(std::move(importMap));
return false;
}
request->mState = ScriptLoadRequest::State::Ready;
if (aElement->GetParserCreated() == FROM_PARSER_XSLT &&
(!ReadyToExecuteParserBlockingScripts() || !mXSLTRequests.isEmpty())) {
@@ -2994,6 +3017,19 @@ void ScriptLoader::ReportErrorToConsole(ScriptLoadRequest* aRequest,
params, nullptr, u""_ns, lineNo, columnNo);
}
void ScriptLoader::ReportWarningToConsole(
ScriptLoadRequest* aRequest, const char* aMessageName,
const nsTArray<nsString>& aParams) const {
nsIScriptElement* element =
aRequest->GetScriptLoadContext()->GetScriptElement();
uint32_t lineNo = element ? element->GetScriptLineNumber() : 0;
uint32_t columnNo = element ? element->GetScriptColumnNumber() : 0;
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
"Script Loader"_ns, mDocument,
nsContentUtils::eDOM_PROPERTIES, aMessageName,
aParams, nullptr, u""_ns, lineNo, columnNo);
}
void ScriptLoader::ReportPreloadErrorsToConsole(ScriptLoadRequest* aRequest) {
if (NS_FAILED(aRequest->GetScriptLoadContext()->mUnreportedPreloadError)) {
ReportErrorToConsole(

View File

@@ -551,6 +551,11 @@ class ScriptLoader final : public JS::loader::ScriptLoaderInterface {
void ReportErrorToConsole(ScriptLoadRequest* aRequest,
nsresult aResult) const override;
void ReportWarningToConsole(
ScriptLoadRequest* aRequest, const char* aMessageName,
const nsTArray<nsString>& aParams = nsTArray<nsString>()) const override;
void ReportPreloadErrorsToConsole(ScriptLoadRequest* aRequest);
nsresult AttemptAsyncScriptCompile(ScriptLoadRequest* aRequest,

441
js/loader/ImportMap.cpp Normal file
View File

@@ -0,0 +1,441 @@
/* -*- 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 "ImportMap.h"
#include "js/Array.h" // IsArrayObject
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
#include "js/JSON.h" // JS_ParseJSON
#include "ModuleLoaderBase.h" // ScriptLoaderInterface
#include "nsContentUtils.h"
#include "nsIScriptElement.h"
#include "nsIScriptError.h"
#include "nsJSUtils.h" // nsAutoJSString
#include "nsNetUtil.h" // NS_NewURI
#include "ScriptLoadRequest.h"
using JS::SourceText;
using mozilla::LazyLogModule;
using mozilla::MakeUnique;
using mozilla::UniquePtr;
namespace JS::loader {
LazyLogModule ImportMap::gImportMapLog("ImportMap");
#undef LOG
#define LOG(args) \
MOZ_LOG(ImportMap::gImportMapLog, mozilla::LogLevel::Debug, args)
#define LOG_ENABLED() \
MOZ_LOG_TEST(ImportMap::gImportMapLog, mozilla::LogLevel::Debug)
void ReportWarningHelper::Report(const char* aMessageName,
const nsTArray<nsString>& aParams) const {
mLoader->ReportWarningToConsole(mRequest, aMessageName, aParams);
}
// https://wicg.github.io/import-maps/#parse-a-url-like-import-specifier
static already_AddRefed<nsIURI> ParseURLLikeImportSpecifier(
const nsAString& aSpecifier, nsIURI* aBaseURL) {
nsCOMPtr<nsIURI> uri;
nsresult rv;
// Step 1. If specifier starts with "/", "./", or "../", then:
if (StringBeginsWith(aSpecifier, u"/"_ns) ||
StringBeginsWith(aSpecifier, u"./"_ns) ||
StringBeginsWith(aSpecifier, u"../"_ns)) {
// Step 1.1. Let url be the result of parsing specifier with baseURL as the
// base URL.
rv = NS_NewURI(getter_AddRefs(uri), aSpecifier, nullptr, aBaseURL);
// Step 1.2. If url is failure, then return null.
if (NS_FAILED(rv)) {
return nullptr;
}
// Step 1.3. Return url.
return uri.forget();
}
// Step 2. Let url be the result of parsing specifier (with no base URL).
rv = NS_NewURI(getter_AddRefs(uri), aSpecifier);
// Step 3. If url is failure, then return null.
if (NS_FAILED(rv)) {
return nullptr;
}
// Step 4. Return url.
return uri.forget();
}
// https://wicg.github.io/import-maps/#normalize-a-specifier-key
static void NormalizeSpecifierKey(const nsAString& aSpecifierKey,
nsIURI* aBaseURL,
const ReportWarningHelper& aWarning,
nsAString& aRetVal) {
// Step 1. If specifierKey is the empty string, then:
if (aSpecifierKey.IsEmpty()) {
// Step 1.1. Report a warning to the console that specifier keys cannot be
// the empty string.
aWarning.Report("ImportMapEmptySpecifierKeys");
// Step 1.2. Return null.
aRetVal = EmptyString();
return;
}
// Step 2. Let url be the result of parsing a URL-like import specifier, given
// specifierKey and baseURL.
nsCOMPtr<nsIURI> url = ParseURLLikeImportSpecifier(aSpecifierKey, aBaseURL);
// Step 3. If url is not null, then return the serialization of url.
if (url) {
aRetVal = NS_ConvertUTF8toUTF16(url->GetSpecOrDefault());
return;
}
// Step 4. Return specifierKey.
aRetVal = aSpecifierKey;
}
// https://wicg.github.io/import-maps/#sort-and-normalize-a-specifier-map
static UniquePtr<SpecifierMap> SortAndNormalizeSpecifierMap(
JSContext* aCx, JS::HandleObject aOriginalMap, nsIURI* aBaseURL,
const ReportWarningHelper& aWarning) {
// Step 1. Let normalized be an empty map.
UniquePtr<SpecifierMap> normalized = MakeUnique<SpecifierMap>();
JS::Rooted<JS::IdVector> specifierKeys(aCx, JS::IdVector(aCx));
if (!JS_Enumerate(aCx, aOriginalMap, &specifierKeys)) {
return nullptr;
}
// Step 2. For each specifierKey → value of originalMap,
for (size_t i = 0; i < specifierKeys.length(); i++) {
const JS::RootedId specifierId(aCx, specifierKeys[i]);
nsAutoJSString specifierKey;
NS_ENSURE_TRUE(specifierKey.init(aCx, specifierId), nullptr);
// Step 2.1. Let normalizedSpecifierKey be the result of normalizing a
// specifier key given specifierKey and baseURL.
nsString normalizedSpecifierKey;
NormalizeSpecifierKey(specifierKey, aBaseURL, aWarning,
normalizedSpecifierKey);
// Step 2.2. If normalizedSpecifierKey is null, then continue.
if (normalizedSpecifierKey.IsEmpty()) {
continue;
}
JS::RootedValue idVal(aCx);
NS_ENSURE_TRUE(JS_GetPropertyById(aCx, aOriginalMap, specifierId, &idVal),
nullptr);
// Step 2.3. If value is not a string, then:
if (!idVal.isString()) {
// Step 2.3.1. Report a warning to the console that addresses need to
// be strings.
aWarning.Report("ImportMapAddressesNotStrings");
// Step 2.3.2. Set normalized[normalizedSpecifierKey] to null.
normalized->insert_or_assign(normalizedSpecifierKey, nullptr);
// Step 2.3.3. Continue.
continue;
}
nsAutoJSString value;
NS_ENSURE_TRUE(value.init(aCx, idVal), nullptr);
// Step 2.4. Let addressURL be the result of parsing a URL-like import
// specifier given value and baseURL.
nsCOMPtr<nsIURI> addressURL = ParseURLLikeImportSpecifier(value, aBaseURL);
// Step 2.5. If addressURL is null, then:
if (!addressURL) {
// Step 2.5.1. Report a warning to the console that the address was
// invalid.
AutoTArray<nsString, 1> params;
params.AppendElement(value);
aWarning.Report("ImportMapInvalidAddress", params);
// Step 2.5.2. Set normalized[normalizedSpecifierKey] to null.
normalized->insert_or_assign(normalizedSpecifierKey, nullptr);
// Step 2.5.3. Continue.
continue;
}
nsCString address = addressURL->GetSpecOrDefault();
// Step 2.6. If specifierKey ends with U+002F (/), and the serialization
// of addressURL does not end with U+002F (/), then:
if (StringEndsWith(specifierKey, u"/"_ns) &&
!StringEndsWith(address, "/"_ns)) {
// Step 2.6.1. Report a warning to the console that an invalid address
// was given for the specifier key specifierKey; since specifierKey
// ended in a slash, the address needs to as well.
AutoTArray<nsString, 2> params;
params.AppendElement(specifierKey);
params.AppendElement(NS_ConvertUTF8toUTF16(address));
aWarning.Report("ImportMapAddressNotEndsWithSlash", params);
// Step 2.6.2. Set normalized[normalizedSpecifierKey] to null.
normalized->insert_or_assign(normalizedSpecifierKey, nullptr);
// Step 2.6.3. Continue.
continue;
}
LOG(("ImportMap::SortAndNormalizeSpecifierMap {%s, %s}",
NS_ConvertUTF16toUTF8(normalizedSpecifierKey).get(),
addressURL->GetSpecOrDefault().get()));
// Step 2.7. Set normalized[normalizedSpecifierKey] to addressURL.
normalized->insert_or_assign(normalizedSpecifierKey, addressURL);
}
// Step 3: Return the result of sorting normalized, with an entry a being
// less than an entry b if bs key is code unit less than as key.
//
// Impl note: The sorting is done when inserting the entry.
return normalized;
}
// Check if it's a map defined in
// https://infra.spec.whatwg.org/#ordered-map
//
// If it is, *aIsMap will be set to true.
static bool IsMapObject(JSContext* aCx, JS::HandleValue aMapVal, bool* aIsMap) {
MOZ_ASSERT(aIsMap);
*aIsMap = false;
if (!aMapVal.isObject()) {
return true;
}
bool isArray;
if (!IsArrayObject(aCx, aMapVal, &isArray)) {
return false;
}
*aIsMap = !isArray;
return true;
}
// https://wicg.github.io/import-maps/#sort-and-normalize-scopes
static UniquePtr<ScopeMap> SortAndNormalizeScopes(
JSContext* aCx, JS::HandleObject aOriginalMap, nsIURI* aBaseURL,
const ReportWarningHelper& aWarning) {
JS::Rooted<JS::IdVector> scopeKeys(aCx, JS::IdVector(aCx));
if (!JS_Enumerate(aCx, aOriginalMap, &scopeKeys)) {
return nullptr;
}
// Step 1. Let normalized be an empty map.
UniquePtr<ScopeMap> normalized = MakeUnique<ScopeMap>();
// Step 2. For each scopePrefix → potentialSpecifierMap of originalMap,
for (size_t i = 0; i < scopeKeys.length(); i++) {
const JS::RootedId scopeKey(aCx, scopeKeys[i]);
nsAutoJSString scopePrefix;
NS_ENSURE_TRUE(scopePrefix.init(aCx, scopeKey), nullptr);
// Step 2.1. If potentialSpecifierMap is not a map, then throw a TypeError
// indicating that the value of the scope with prefix scopePrefix needs to
// be a JSON object.
JS::RootedValue mapVal(aCx);
NS_ENSURE_TRUE(JS_GetPropertyById(aCx, aOriginalMap, scopeKey, &mapVal),
nullptr);
bool isMap;
if (!IsMapObject(aCx, mapVal, &isMap)) {
return nullptr;
}
if (!isMap) {
const char16_t* scope = scopePrefix.get();
JS_ReportErrorNumberUC(aCx, js::GetErrorMessage, nullptr,
JSMSG_IMPORT_MAPS_SCOPE_VALUE_NOT_A_MAP, scope);
return nullptr;
}
// Step 2.2. Let scopePrefixURL be the result of parsing scopePrefix with
// baseURL as the base URL.
nsCOMPtr<nsIURI> scopePrefixURL;
nsresult rv = NS_NewURI(getter_AddRefs(scopePrefixURL), scopePrefix,
nullptr, aBaseURL);
// Step 2.3. If scopePrefixURL is failure, then:
if (NS_FAILED(rv)) {
// Step 2.3.1. Report a warning to the console that the scope prefix URL
// was not parseable.
AutoTArray<nsString, 1> params;
params.AppendElement(scopePrefix);
aWarning.Report("ImportMapScopePrefixNotParseable", params);
// Step 2.3.2. Continue.
continue;
}
// Step 2.4. Let normalizedScopePrefix be the serialization of
// scopePrefixURL.
nsCString normalizedScopePrefix = scopePrefixURL->GetSpecOrDefault();
// Step 2.5. Set normalized[normalizedScopePrefix] to the result of sorting
// and normalizing a specifier map given potentialSpecifierMap and baseURL.
JS::RootedObject potentialSpecifierMap(aCx, &mapVal.toObject());
UniquePtr<SpecifierMap> specifierMap = SortAndNormalizeSpecifierMap(
aCx, potentialSpecifierMap, aBaseURL, aWarning);
if (!specifierMap) {
return nullptr;
}
normalized->insert_or_assign(normalizedScopePrefix,
std::move(specifierMap));
}
// Step 3. Return the result of sorting normalized, with an entry a being less
// than an entry b if bs key is code unit less than as key.
//
// Impl note: The sorting is done when inserting the entry.
return normalized;
}
// https://wicg.github.io/import-maps/#parse-an-import-map-string
// static
UniquePtr<ImportMap> ImportMap::ParseString(
JSContext* aCx, SourceText<char16_t>& aInput, nsIURI* aBaseURL,
const ReportWarningHelper& aWarning) {
// Step 1. Let parsed be the result of parsing JSON into Infra values given
// input.
JS::Rooted<JS::Value> parsedVal(aCx);
if (!JS_ParseJSON(aCx, aInput.get(), aInput.length(), &parsedVal)) {
// If JS_ParseJSON fail it will throw SyntaxError.
NS_WARNING("Parsing Import map string failed");
return nullptr;
}
// Step 2. If parsed is not a map, then throw a TypeError indicating that
// the top-level value needs to be a JSON object.
bool isMap;
if (!IsMapObject(aCx, parsedVal, &isMap)) {
return nullptr;
}
if (!isMap) {
JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr,
JSMSG_IMPORT_MAPS_NOT_A_MAP);
return nullptr;
}
JS::RootedObject parsedObj(aCx, &parsedVal.toObject());
JS::RootedValue importsVal(aCx);
if (!JS_GetProperty(aCx, parsedObj, "imports", &importsVal)) {
return nullptr;
}
// Step 3. Let sortedAndNormalizedImports be an empty map.
//
// Impl note: If parsed["imports"] doesn't exist, we will allocate
// sortedAndNormalizedImports to an empty map in Step 8 below.
UniquePtr<SpecifierMap> sortedAndNormalizedImports = nullptr;
// Step 4. If parsed["imports"] exists, then:
if (!importsVal.isUndefined()) {
// Step 4.1. If parsed["imports"] is not a map, then throw a TypeError
// indicating that the "imports" top-level key needs to be a JSON object.
bool isMap;
if (!IsMapObject(aCx, importsVal, &isMap)) {
return nullptr;
}
if (!isMap) {
JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr,
JSMSG_IMPORT_MAPS_IMPORTS_NOT_A_MAP);
return nullptr;
}
// Step 4.2. Set sortedAndNormalizedImports to the result of sorting and
// normalizing a specifier map given parsed["imports"] and baseURL.
JS::RootedObject importsObj(aCx, &importsVal.toObject());
sortedAndNormalizedImports =
SortAndNormalizeSpecifierMap(aCx, importsObj, aBaseURL, aWarning);
if (!sortedAndNormalizedImports) {
return nullptr;
}
}
JS::RootedValue scopesVal(aCx);
if (!JS_GetProperty(aCx, parsedObj, "scopes", &scopesVal)) {
return nullptr;
}
// Step 5. Let sortedAndNormalizedScopes be an empty map.
//
// Impl note: If parsed["scopes"] doesn't exist, we will allocate
// sortedAndNormalizedScopes to an empty map in Step 8 below.
UniquePtr<ScopeMap> sortedAndNormalizedScopes = nullptr;
// Step 6. If parsed["scopes"] exists, then:
if (!scopesVal.isUndefined()) {
// Step 6.1. If parsed["scopes"] is not a map, then throw a TypeError
// indicating that the "scopes" top-level key needs to be a JSON object.
bool isMap;
if (!IsMapObject(aCx, scopesVal, &isMap)) {
return nullptr;
}
if (!isMap) {
JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr,
JSMSG_IMPORT_MAPS_SCOPES_NOT_A_MAP);
return nullptr;
}
// Step 6.2. Set sortedAndNormalizedScopes to the result of sorting and
// normalizing scopes given parsed["scopes"] and baseURL.
JS::RootedObject scopesObj(aCx, &scopesVal.toObject());
sortedAndNormalizedScopes =
SortAndNormalizeScopes(aCx, scopesObj, aBaseURL, aWarning);
if (!sortedAndNormalizedScopes) {
return nullptr;
}
}
// Step 7. If parseds keys contains any items besides "imports" or
// "scopes", report a warning to the console that an invalid top-level key
// was present in the import map.
JS::Rooted<JS::IdVector> keys(aCx, JS::IdVector(aCx));
if (!JS_Enumerate(aCx, parsedObj, &keys)) {
return nullptr;
}
for (size_t i = 0; i < keys.length(); i++) {
const JS::RootedId key(aCx, keys[i]);
nsAutoJSString val;
NS_ENSURE_TRUE(val.init(aCx, key), nullptr);
if (val.EqualsLiteral("imports") || val.EqualsLiteral("scopes")) {
continue;
}
AutoTArray<nsString, 1> params;
params.AppendElement(val);
aWarning.Report("ImportMapInvalidTopLevelKey", params);
}
// Impl note: Create empty maps for sortedAndNormalizedImports and
// sortedAndNormalizedImports if they aren't allocated.
if (!sortedAndNormalizedImports) {
sortedAndNormalizedImports = MakeUnique<SpecifierMap>();
}
if (!sortedAndNormalizedScopes) {
sortedAndNormalizedScopes = MakeUnique<ScopeMap>();
}
// Step 8. Return the import map whose imports are
// sortedAndNormalizedImports and whose scopes scopes are
// sortedAndNormalizedScopes.
return MakeUnique<ImportMap>(std::move(sortedAndNormalizedImports),
std::move(sortedAndNormalizedScopes));
}
#undef LOG
#undef LOG_ENABLED
} // namespace JS::loader

95
js/loader/ImportMap.h Normal file
View File

@@ -0,0 +1,95 @@
/* -*- 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/. */
#ifndef js_loader_ImportMap_h
#define js_loader_ImportMap_h
#include <functional>
#include <map>
#include "js/SourceText.h"
#include "mozilla/AlreadyAddRefed.h"
#include "mozilla/Logging.h"
#include "mozilla/RefPtr.h"
#include "mozilla/UniquePtr.h"
#include "nsStringFwd.h"
#include "nsTArray.h"
struct JSContext;
class nsIScriptElement;
class nsIURI;
namespace JS::loader {
class LoadedScript;
class ScriptLoaderInterface;
class ScriptLoadRequest;
/**
* A helper class to report warning to ScriptLoaderInterface.
*/
class ReportWarningHelper {
public:
ReportWarningHelper(ScriptLoaderInterface* aLoader,
ScriptLoadRequest* aRequest)
: mLoader(aLoader), mRequest(aRequest) {}
void Report(const char* aMessageName,
const nsTArray<nsString>& aParams = nsTArray<nsString>()) const;
private:
RefPtr<ScriptLoaderInterface> mLoader;
ScriptLoadRequest* mRequest;
};
// Specifier map from import maps.
// https://wicg.github.io/import-maps/#specifier-map
using SpecifierMap =
std::map<nsString, nsCOMPtr<nsIURI>, std::greater<nsString>>;
// Scope map from import maps.
// https://wicg.github.io/import-maps/#import-map-scopes
using ScopeMap = std::map<nsCString, mozilla::UniquePtr<SpecifierMap>,
std::greater<nsCString>>;
/**
* Implementation of Import maps.
* https://wicg.github.io/import-maps
*/
class ImportMap {
public:
ImportMap(mozilla::UniquePtr<SpecifierMap> aImports,
mozilla::UniquePtr<ScopeMap> aScopes)
: mImports(std::move(aImports)), mScopes(std::move(aScopes)) {}
/**
* Parse the JSON string from the Import map script.
* This function will throw TypeError if there's any invalid key or value in
* the JSON text according to the spec.
*
* See https://wicg.github.io/import-maps/#parse-an-import-map-string
*/
static mozilla::UniquePtr<ImportMap> ParseString(
JSContext* aCx, JS::SourceText<char16_t>& aInput, nsIURI* aBaseURL,
const ReportWarningHelper& aWarning);
// Logging
static mozilla::LazyLogModule gImportMapLog;
private:
/**
* https://wicg.github.io/import-maps/#import-map
*
* A import map is a struct with two items:
* 1. imports, a specifier map, and
* 2. scopes, an ordered map of URLs to specifier maps.
*/
mozilla::UniquePtr<SpecifierMap> mImports;
mozilla::UniquePtr<ScopeMap> mScopes;
};
} // namespace JS::loader
#endif // js_loader_ImportMap_h

View File

@@ -8,6 +8,7 @@
#include "LoadedScript.h"
#include "ModuleLoadRequest.h"
#include "ScriptLoadRequest.h"
#include "mozilla/dom/ScriptSettings.h" // AutoJSAPI
#include "mozilla/dom/ScriptTrace.h"
#include "js/Array.h" // JS::GetArrayLength
@@ -31,6 +32,7 @@
using mozilla::GetMainThreadSerialEventTarget;
using mozilla::Preferences;
using mozilla::UniquePtr;
using mozilla::dom::AutoJSAPI;
namespace JS::loader {
@@ -1099,6 +1101,50 @@ void ModuleLoaderBase::CancelAndClearDynamicImports() {
mDynamicImportRequests.CancelRequestsAndClear();
}
UniquePtr<ImportMap> ModuleLoaderBase::ParseImportMap(
ScriptLoadRequest* aRequest) {
AutoJSAPI jsapi;
if (!jsapi.Init(GetGlobalObject())) {
return nullptr;
}
MOZ_ASSERT(aRequest->IsTextSource());
MaybeSourceText maybeSource;
nsresult rv = aRequest->GetScriptSource(jsapi.cx(), &maybeSource);
if (NS_FAILED(rv)) {
return nullptr;
}
JS::SourceText<char16_t>& text = maybeSource.ref<SourceText<char16_t>>();
ReportWarningHelper warning{mLoader, aRequest};
// https://html.spec.whatwg.org/multipage/scripting.html#prepare-a-script
// https://wicg.github.io/import-maps/#integration-prepare-a-script
// Insert the following case to prepare a script step 25.2:
// (Impl Note: the latest html spec is step 27.2)
// Switch on the script's type:
// "importmap"
// Step 1. Let import map parse result be the result of create an import map
// parse result, given source text, base URL and settings object.
//
// Impl note: According to the spec, ImportMap::ParseString will throw a
// TypeError if there's any invalid key/value in the text. After the parsing
// is done, we should report the error if there's any, this is done in
// ~AutoJSAPI.
//
// See https://wicg.github.io/import-maps/#register-an-import-map, step 7.
return ImportMap::ParseString(jsapi.cx(), text, aRequest->mBaseURL, warning);
}
void ModuleLoaderBase::RegisterImportMap(UniquePtr<ImportMap> aImportMap) {
// Check for aImportMap is done in ScriptLoader.
MOZ_ASSERT(aImportMap);
// Step 8. Set elements node document's import map to import map parse
// results import map.
mImportMap = std::move(aImportMap);
}
#undef LOG
#undef LOG_ENABLED

View File

@@ -10,6 +10,7 @@
#include "LoadedScript.h"
#include "ScriptLoadRequest.h"
#include "ImportMap.h"
#include "js/TypeDecls.h" // JS::MutableHandle, JS::Handle, JS::Root
#include "js/Modules.h"
#include "nsRefPtrHashtable.h"
@@ -22,6 +23,7 @@
#include "mozilla/dom/JSExecutionContext.h"
#include "mozilla/MaybeOneOf.h"
#include "mozilla/MozPromise.h"
#include "mozilla/UniquePtr.h"
class nsIURI;
@@ -60,6 +62,10 @@ class ScriptLoaderInterface : public nsISupports {
virtual void ReportErrorToConsole(ScriptLoadRequest* aRequest,
nsresult aResult) const = 0;
virtual void ReportWarningToConsole(
ScriptLoadRequest* aRequest, const char* aMessageName,
const nsTArray<nsString>& aParams = nsTArray<nsString>()) const = 0;
// Fill in CompileOptions, as well as produce the introducer script for
// subsequent calls to UpdateDebuggerMetadata
virtual nsresult FillCompileOptionsForRequest(
@@ -147,6 +153,8 @@ class ModuleLoaderBase : public nsISupports {
protected:
RefPtr<ScriptLoaderInterface> mLoader;
mozilla::UniquePtr<ImportMap> mImportMap;
virtual ~ModuleLoaderBase();
public:
@@ -227,6 +235,12 @@ class ModuleLoaderBase : public nsISupports {
void ProcessDynamicImport(ModuleLoadRequest* aRequest);
void CancelAndClearDynamicImports();
// Process <script type="importmap">
mozilla::UniquePtr<ImportMap> ParseImportMap(ScriptLoadRequest* aRequest);
// Implements https://wicg.github.io/import-maps/#register-an-import-map
void RegisterImportMap(mozilla::UniquePtr<ImportMap> aImportMap);
// Internal methods.
private:

View File

@@ -181,6 +181,7 @@ class ScriptLoadRequest
mozilla::MaybeOneOf<JS::SourceText<char16_t>, JS::SourceText<Utf8Unit>>;
bool IsModuleRequest() const { return mKind == ScriptKind::eModule; }
bool IsImportMapRequest() const { return mKind == ScriptKind::eImportMap; }
ModuleLoadRequest* AsModuleRequest();
const ModuleLoadRequest* AsModuleRequest() const;

View File

@@ -5,6 +5,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
EXPORTS.js.loader += [
"ImportMap.h",
"LoadContextBase.h",
"LoadedScript.h",
"ModuleLoaderBase.h",
@@ -14,6 +15,7 @@ EXPORTS.js.loader += [
]
UNIFIED_SOURCES += [
"ImportMap.cpp",
"LoadContextBase.cpp",
"LoadedScript.cpp",
"ModuleLoaderBase.cpp",

View File

@@ -727,6 +727,12 @@ MSG_DEF(JSMSG_MISSING_EXPORT, 1, JSEXN_SYNTAXERR, "local binding for
MSG_DEF(JSMSG_BAD_MODULE_STATUS, 0, JSEXN_INTERNALERR, "module record has unexpected status")
MSG_DEF(JSMSG_DYNAMIC_IMPORT_FAILED, 0, JSEXN_TYPEERR, "error loading dynamically imported module")
// Import maps
MSG_DEF(JSMSG_IMPORT_MAPS_NOT_A_MAP, 0, JSEXN_TYPEERR, "the top-level value needs to be a JSON object")
MSG_DEF(JSMSG_IMPORT_MAPS_IMPORTS_NOT_A_MAP, 0, JSEXN_TYPEERR, "the imports top-level key needs to be a JSON object")
MSG_DEF(JSMSG_IMPORT_MAPS_SCOPES_NOT_A_MAP, 0, JSEXN_TYPEERR, "the scopes top-level key needs to be a JSON object")
MSG_DEF(JSMSG_IMPORT_MAPS_SCOPE_VALUE_NOT_A_MAP, 1, JSEXN_TYPEERR, "the value of the scope with prefix '{0}' needs to be a JSON object")
// Promise
MSG_DEF(JSMSG_CANNOT_RESOLVE_PROMISE_WITH_ITSELF, 0, JSEXN_TYPEERR, "A promise cannot be resolved with itself.")
MSG_DEF(JSMSG_PROMISE_CAPABILITY_HAS_SOMETHING_ALREADY, 0, JSEXN_TYPEERR, "GetCapabilitiesExecutor function already invoked with non-undefined values.")