Bug 1688879 - Part 5: ResolveModuleSpecifier for import maps. r=jonco,yulia,flod

Implement https://wicg.github.io/import-maps/#resolve-a-module-specifier

Differential Revision: https://phabricator.services.mozilla.com/D142072
This commit is contained in:
Yoshi Cheng-Hao Huang 2022-05-05 21:19:02 +00:00
Родитель 7ba3206f1c
Коммит 8a939e5954
5 изменённых файлов: 264 добавлений и 3 удалений

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

@ -330,6 +330,10 @@ 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.
ImportMapResolutionBlockedByNullEntry=Resolution of specifier “%S” was blocked by a null entry.
ImportMapResolutionBlockedByAfterPrefix=Resolution of specifier “%S” was blocked since the substring after prefix could not be parsed as a URL relative to the address in the import map.
ImportMapResolutionBlockedByBacktrackingPrefix=Resolution of specifier “%S” was blocked since the parsed URL does not start with the address in the import map.
ImportMapResolveInvalidBareSpecifier=The specifier “%S” was a bare specifier, but was not remapped to anything.
# 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".

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

@ -9,7 +9,8 @@
#include "js/Array.h" // IsArrayObject
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
#include "js/JSON.h" // JS_ParseJSON
#include "ModuleLoaderBase.h" // ScriptLoaderInterface
#include "LoadedScript.h"
#include "ModuleLoaderBase.h" // ScriptLoaderInterface
#include "nsContentUtils.h"
#include "nsIScriptElement.h"
#include "nsIScriptError.h"
@ -18,9 +19,11 @@
#include "ScriptLoadRequest.h"
using JS::SourceText;
using mozilla::Err;
using mozilla::LazyLogModule;
using mozilla::MakeUnique;
using mozilla::UniquePtr;
using mozilla::WrapNotNull;
namespace JS::loader {
@ -436,6 +439,225 @@ UniquePtr<ImportMap> ImportMap::ParseString(
std::move(sortedAndNormalizedScopes));
}
// https://url.spec.whatwg.org/#is-special
static bool IsSpecialScheme(nsIURI* aURI) {
nsAutoCString scheme;
aURI->GetScheme(scheme);
return scheme.EqualsLiteral("ftp") || scheme.EqualsLiteral("file") ||
scheme.EqualsLiteral("http") || scheme.EqualsLiteral("https") ||
scheme.EqualsLiteral("ws") || scheme.EqualsLiteral("wss");
}
// https://wicg.github.io/import-maps/#resolve-an-imports-match
static mozilla::Result<nsCOMPtr<nsIURI>, ResolveError> ResolveImportsMatch(
nsString& aNormalizedSpecifier, nsIURI* aAsURL,
const SpecifierMap* aSpecifierMap) {
// Step 1. For each specifierKey → resolutionResult of specifierMap,
for (auto&& [specifierKey, resolutionResult] : *aSpecifierMap) {
nsAutoString specifier{aNormalizedSpecifier};
nsCString asURL = aAsURL ? aAsURL->GetSpecOrDefault() : EmptyCString();
// Step 1.1. If specifierKey is normalizedSpecifier, then:
if (specifierKey.Equals(aNormalizedSpecifier)) {
// Step 1.1.1. If resolutionResult is null, then throw a TypeError
// indicating that resolution of specifierKey was blocked by a null entry.
// This will terminate the entire resolve a module specifier algorithm,
// without any further fallbacks.
if (!resolutionResult) {
LOG(
("ImportMap::ResolveImportsMatch normalizedSpecifier: %s, "
"specifierKey: %s, but resolution is null.",
NS_ConvertUTF16toUTF8(aNormalizedSpecifier).get(),
NS_ConvertUTF16toUTF8(specifierKey).get()));
return Err(ResolveError::BlockedByNullEntry);
}
// Step 1.1.2. Assert: resolutionResult is a URL.
MOZ_ASSERT(resolutionResult);
// Step 1.1.3. Return resolutionResult.
return resolutionResult;
}
// Step 1.2. If all of the following are true:
// specifierKey ends with U+002F (/),
// normalizedSpecifier starts with specifierKey, and
// either asURL is null, or asURL is special
if (StringEndsWith(specifierKey, u"/"_ns) &&
StringBeginsWith(aNormalizedSpecifier, specifierKey) &&
(!aAsURL || IsSpecialScheme(aAsURL))) {
// Step 1.2.1. If resolutionResult is null, then throw a TypeError
// indicating that resolution of specifierKey was blocked by a null entry.
// This will terminate the entire resolve a module specifier algorithm,
// without any further fallbacks.
if (!resolutionResult) {
LOG(
("ImportMap::ResolveImportsMatch normalizedSpecifier: %s, "
"specifierKey: %s, but resolution is null.",
NS_ConvertUTF16toUTF8(aNormalizedSpecifier).get(),
NS_ConvertUTF16toUTF8(specifierKey).get()));
return Err(ResolveError::BlockedByNullEntry);
}
// Step 1.2.2. Assert: resolutionResult is a URL.
MOZ_ASSERT(resolutionResult);
// Step 1.2.3. Let afterPrefix be the portion of normalizedSpecifier after
// the initial specifierKey prefix.
nsAutoString afterPrefix(
Substring(aNormalizedSpecifier, specifierKey.Length()));
// Step 1.2.4. Assert: resolutionResult, serialized, ends with "/", as
// enforced during parsing.
MOZ_ASSERT(StringEndsWith(resolutionResult->GetSpecOrDefault(), "/"_ns));
// Step 1.2.5. Let url be the result of parsing afterPrefix relative to
// the base URL resolutionResult.
nsCOMPtr<nsIURI> url;
nsresult rv = NS_NewURI(getter_AddRefs(url), afterPrefix, nullptr,
resolutionResult);
// Step 1.2.6. If url is failure, then throw a TypeError indicating that
// resolution of normalizedSpecifier was blocked since the afterPrefix
// portion could not be URL-parsed relative to the resolutionResult mapped
// to by the specifierKey prefix.
//
// This will terminate the entire resolve a module specifier algorithm,
// without any further fallbacks.
if (NS_FAILED(rv)) {
LOG(
("ImportMap::ResolveImportsMatch normalizedSpecifier: %s, "
"specifierKey: %s, resolutionResult: %s, afterPrefix: %s, "
"but URL is not parsable.",
NS_ConvertUTF16toUTF8(aNormalizedSpecifier).get(),
NS_ConvertUTF16toUTF8(specifierKey).get(),
resolutionResult->GetSpecOrDefault().get(),
NS_ConvertUTF16toUTF8(afterPrefix).get()));
return Err(ResolveError::BlockedByAfterPrefix);
}
// Step 1.2.7. Assert: url is a URL.
MOZ_ASSERT(url);
// Step 1.2.8. If the serialization of url does not start with the
// serialization of resolutionResult, then throw a TypeError indicating
// that resolution of normalizedSpecifier was blocked due to it
// backtracking above its prefix specifierKey.
//
// This will terminate the entire resolve a module specifier algorithm,
// without any further fallbacks.
if (!StringBeginsWith(url->GetSpecOrDefault(),
resolutionResult->GetSpecOrDefault())) {
LOG(
("ImportMap::ResolveImportsMatch normalizedSpecifier: %s, "
"specifierKey: %s, "
"url %s does not start with resolutionResult %s.",
NS_ConvertUTF16toUTF8(aNormalizedSpecifier).get(),
NS_ConvertUTF16toUTF8(specifierKey).get(),
url->GetSpecOrDefault().get(),
resolutionResult->GetSpecOrDefault().get()));
return Err(ResolveError::BlockedByBacktrackingPrefix);
}
// Step 1.2.9. Return url.
return std::move(url);
}
}
// Step 2. Return null.
return nsCOMPtr<nsIURI>(nullptr);
}
// https://wicg.github.io/import-maps/#resolve-a-module-specifier
// static
ResolveResult ImportMap::ResolveModuleSpecifier(ImportMap* aImportMap,
ScriptLoaderInterface* aLoader,
LoadedScript* aScript,
const nsAString& aSpecifier) {
LOG(("ImportMap::ResolveModuleSpecifier specifier: %s",
NS_ConvertUTF16toUTF8(aSpecifier).get()));
nsCOMPtr<nsIURI> baseURL;
if (aScript) {
baseURL = aScript->BaseURL();
} else {
baseURL = aLoader->GetBaseURI();
}
// Step 6. Let asURL be the result of parsing a URL-like import specifier
// given specifier and baseURL.
//
// Impl note: Step 5 is done below if aImportMap exists.
nsCOMPtr<nsIURI> asURL = ParseURLLikeImportSpecifier(aSpecifier, baseURL);
if (aImportMap) {
// Step 5. Let baseURLString be baseURL, serialized.
nsCString baseURLString = baseURL->GetSpecOrDefault();
// Step 7. Let normalizedSpecifier be the serialization of asURL, if asURL
// is non-null; otherwise, specifier.
nsAutoString normalizedSpecifier =
asURL ? NS_ConvertUTF8toUTF16(asURL->GetSpecOrDefault())
: nsAutoString{aSpecifier};
// Step 8. For each scopePrefix → scopeImports of importMaps scopes,
for (auto&& [scopePrefix, scopeImports] : *aImportMap->mScopes) {
// Step 8.1. If scopePrefix is baseURLString, or if scopePrefix ends with
// U+002F (/) and baseURLString starts with scopePrefix, then:
if (scopePrefix.Equals(baseURLString) ||
(StringEndsWith(scopePrefix, "/"_ns) &&
StringBeginsWith(baseURLString, scopePrefix))) {
// Step 8.1.1. Let scopeImportsMatch be the result of resolving an
// imports match given normalizedSpecifier, asURL, and scopeImports.
auto result =
ResolveImportsMatch(normalizedSpecifier, asURL, scopeImports.get());
if (result.isErr()) {
return result.propagateErr();
}
nsCOMPtr<nsIURI> scopeImportsMatch = result.unwrap();
// Step 8.1.2. If scopeImportsMatch is not null, then return
// scopeImportsMatch.
if (scopeImportsMatch) {
LOG((
"ImportMap::ResolveModuleSpecifier returns scopeImportsMatch: %s",
scopeImportsMatch->GetSpecOrDefault().get()));
return WrapNotNull(scopeImportsMatch);
}
}
}
// Step 9. Let topLevelImportsMatch be the result of resolving an imports
// match given normalizedSpecifier, asURL, and importMaps imports.
auto result = ResolveImportsMatch(normalizedSpecifier, asURL,
aImportMap->mImports.get());
if (result.isErr()) {
return result.propagateErr();
}
nsCOMPtr<nsIURI> topLevelImportsMatch = result.unwrap();
// Step 10. If topLevelImportsMatch is not null, then return
// topLevelImportsMatch.
if (topLevelImportsMatch) {
LOG(("ImportMap::ResolveModuleSpecifier returns topLevelImportsMatch: %s",
topLevelImportsMatch->GetSpecOrDefault().get()));
return WrapNotNull(topLevelImportsMatch);
}
}
// Step 11. At this point, the specifier was able to be turned in to a URL,
// but it wasnt remapped to anything by importMap. If asURL is not null, then
// return asURL.
if (asURL) {
LOG(("ImportMap::ResolveModuleSpecifier returns asURL: %s",
asURL->GetSpecOrDefault().get()));
return WrapNotNull(asURL);
}
// Step 12. Throw a TypeError indicating that specifier was a bare specifier,
// but was not remapped to anything by importMap.
return Err(ResolveError::InvalidBareSpecifier);
}
#undef LOG
#undef LOG_ENABLED
} // namespace JS::loader

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

@ -17,6 +17,7 @@
#include "mozilla/UniquePtr.h"
#include "nsStringFwd.h"
#include "nsTArray.h"
#include "ResolveResult.h"
struct JSContext;
class nsIScriptElement;
@ -75,6 +76,17 @@ class ImportMap {
JSContext* aCx, JS::SourceText<char16_t>& aInput, nsIURI* aBaseURL,
const ReportWarningHelper& aWarning);
/**
* This implements "Resolve a module specifier" algorithm defined in the
* Import maps spec.
*
* See https://wicg.github.io/import-maps/#resolve-a-module-specifier
*/
static ResolveResult ResolveModuleSpecifier(ImportMap* aImportMap,
ScriptLoaderInterface* aLoader,
LoadedScript* aScript,
const nsAString& aSpecifier);
// Logging
static mozilla::LazyLogModule gImportMapLog;

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

@ -579,12 +579,23 @@ nsresult ModuleLoaderBase::HandleResolveFailure(
ResolveResult ModuleLoaderBase::ResolveModuleSpecifier(
LoadedScript* aScript, const nsAString& aSpecifier) {
bool importMapsEnabled = Preferences::GetBool("dom.importMaps.enabled");
// If import map is enabled, forward to the updated 'Resolve a module
// specifier' algorithm defined in Import maps spec.
//
// Once import map is enabled by default,
// ModuleLoaderBase::ResolveModuleSpecifier should be replaced by
// ImportMap::ResolveModuleSpecifier.
if (importMapsEnabled) {
return ImportMap::ResolveModuleSpecifier(mImportMap.get(), mLoader, aScript,
aSpecifier);
}
// The following module specifiers are allowed by the spec:
// - a valid absolute URL
// - a valid relative URL that starts with "/", "./" or "../"
//
// Bareword module specifiers are currently disallowed as these may be given
// special meanings in the future.
// Bareword module specifiers are handled in Import maps.
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri), aSpecifier);

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

@ -15,6 +15,10 @@ namespace JS::loader {
enum class ResolveError : uint8_t {
ModuleResolveFailure,
BlockedByNullEntry,
BlockedByAfterPrefix,
BlockedByBacktrackingPrefix,
InvalidBareSpecifier
};
struct ResolveErrorInfo {
@ -22,6 +26,14 @@ struct ResolveErrorInfo {
switch (aError) {
case ResolveError::ModuleResolveFailure:
return "ModuleResolveFailure";
case ResolveError::BlockedByNullEntry:
return "ImportMapResolutionBlockedByNullEntry";
case ResolveError::BlockedByAfterPrefix:
return "ImportMapResolutionBlockedByAfterPrefix";
case ResolveError::BlockedByBacktrackingPrefix:
return "ImportMapResolutionBlockedByBacktrackingPrefix";
case ResolveError::InvalidBareSpecifier:
return "ImportMapResolveInvalidBareSpecifier";
default:
MOZ_CRASH("Unexpected ResolveError value");
}