mirror of
https://github.com/Basilisk-Development-Team/UXP-mirror.git
synced 2026-11-05 09:40:21 -05:00
Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
845bff9d31 | ||
|
|
883f60cf12 | ||
|
|
eaa73cedc8 | ||
|
|
a2478fe2cb | ||
|
|
480b203dea | ||
|
|
5c7eb114dd | ||
|
|
5b3cc8dab5 | ||
|
|
b8eb00f26c | ||
|
|
97bc446a95 | ||
|
|
d415454413 | ||
|
|
5b9c897904 | ||
|
|
63181f6d11 | ||
|
|
38892f7308 |
No files matched your search
@@ -294,13 +294,26 @@ HTMLScriptElement::FreezeExecutionAttrs(nsIDocument* aOwnerDoc)
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!mIsModule && !mAsync && !mDefer && !mExternal);
|
||||
MOZ_ASSERT((mKind != ScriptKind::Module) &&
|
||||
(mKind != ScriptKind::ImportMap) &&
|
||||
!mAsync && !mDefer && !mExternal);
|
||||
|
||||
// Determine whether this is a classic script or a module script.
|
||||
// Determine whether this is a(n) classic/module/importmap script.
|
||||
nsAutoString type;
|
||||
GetScriptType(type);
|
||||
mIsModule = aOwnerDoc->ModuleScriptsEnabled() &&
|
||||
!type.IsEmpty() && type.LowerCaseEqualsASCII("module");
|
||||
if (!type.IsEmpty()) {
|
||||
if (aOwnerDoc->ModuleScriptsEnabled() &&
|
||||
type.LowerCaseEqualsASCII("module")) {
|
||||
mKind = ScriptKind::Module;
|
||||
}
|
||||
|
||||
// https://wicg.github.io/import-maps/#integration-prepare-a-script
|
||||
// If the script block's type string is an ASCII case-insensitive match
|
||||
// for the string "importmap", the script's type is "importmap".
|
||||
if (type.LowerCaseEqualsASCII("importmap")) {
|
||||
mKind = ScriptKind::ImportMap;
|
||||
}
|
||||
}
|
||||
|
||||
// variation of this code in nsSVGScriptElement - check if changes
|
||||
// need to be transfered when modifying. Note that we don't use GetSrc here
|
||||
@@ -320,7 +333,7 @@ HTMLScriptElement::FreezeExecutionAttrs(nsIDocument* aOwnerDoc)
|
||||
|
||||
bool defer, async;
|
||||
GetAsync(&async);
|
||||
mAsync = (mExternal || mIsModule) && async;
|
||||
mAsync = (mExternal || mKind == ScriptKind::Module) && async;
|
||||
GetDefer(&defer);
|
||||
mDefer = mExternal && !async && defer;
|
||||
|
||||
@@ -346,5 +359,12 @@ HTMLScriptElement::HasScriptContent()
|
||||
nsContentUtils::HasNonEmptyTextContent(this);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/scripting.html#dom-script-supports
|
||||
/* static */ bool
|
||||
HTMLScriptElement::Supports(const GlobalObject& aGlobal, const nsAString& aType)
|
||||
{
|
||||
return aType.EqualsLiteral("classic") || aType.EqualsLiteral("module") || aType.EqualsLiteral("importmap");
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
@@ -115,6 +115,9 @@ public:
|
||||
bool NoModule();
|
||||
void SetNoModule(bool aValue, ErrorResult& rv);
|
||||
|
||||
[[nodiscard]] static bool Supports(const GlobalObject& aGlobal,
|
||||
const nsAString& aType);
|
||||
|
||||
protected:
|
||||
virtual ~HTMLScriptElement();
|
||||
|
||||
|
||||
@@ -323,3 +323,17 @@ ReloadFloodingPrevented=Call to Reload ignored due to excessive calls within a s
|
||||
DOMQuadBoundsAttrWarning=DOMQuad.bounds is deprecated in favor of DOMQuad.getBounds()
|
||||
UnsupportedEntryTypesIgnored=Ignoring unsupported entryTypes: %S.
|
||||
AllEntryTypesIgnored=No valid entryTypes; aborting registration.
|
||||
ModuleResolveFailureNoWarn=Error resolving module specifier “%S”.
|
||||
ModuleResolveFailureWarnRelative=Error resolving module specifier “%S”. Relative module specifiers must start with “./”, “../” or “/”.
|
||||
ImportMapResolveInvalidBareSpecifierWarnRelative=Error resolving module specifier “%S”. Relative module specifiers must start with “./”, “../” or “/”.
|
||||
ImportMapResolutionBlockedByNullEntry=Resolution of specifier “%S” was blocked by a null entry.
|
||||
ImportMapResolutionBlockedByAfterPrefix=Resolution of specifier “%S” was blocked since the after-prefix portion could not be URL-parsed relative to the URL mapped to by the prefix.
|
||||
ImportMapResolutionBlockedByBacktrackingPrefix=Resolution of specifier “%S” was blocked due to it backtracking above its prefix.
|
||||
ImportMapInvalidTopLevelKey=An invalid top-level key “%S” was present in the import map.
|
||||
ImportMapEmptySpecifierKeys=Specifier keys cannot be the empty string.
|
||||
ImportMapAddressesNotStrings=Addresses need to be strings.
|
||||
ImportMapInvalidAddress=Address for “%S” was invalid.
|
||||
ImportMapAddressNotEndsWithSlash=An invalid address was given for the specifier key “%S”; since the specifier ended with a slash, the address needs to as well.
|
||||
ImportMapScopePrefixNotParseable=The scope prefix URL “%S” was not parseable.
|
||||
ImportMapIntegrityKeyNotParseable=Import map integrity key %S was not parseable as a URL.
|
||||
ImportMapIntegrityValueNotAString=Import map integrity value for %S needs to be a string.
|
||||
@@ -0,0 +1,803 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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 "jsapi.h"
|
||||
#include "jsfriendapi.h"
|
||||
#include "ModuleScript.h"
|
||||
#include "ScriptLoader.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsIScriptElement.h"
|
||||
#include "nsIScriptError.h"
|
||||
#include "nsJSUtils.h"
|
||||
#include "nsNetUtil.h"
|
||||
|
||||
using mozilla::Err;
|
||||
using mozilla::LazyLogModule;
|
||||
using mozilla::MakeUnique;
|
||||
using mozilla::UniquePtr;
|
||||
using mozilla::WrapNotNull;
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// Bug 1979050: reading a top-level key with JS_GetProperty walks the prototype
|
||||
// chain, so a polluted Object.prototype could inject import map entries. Only
|
||||
// own properties count, both for the presence check and for the value read.
|
||||
static bool
|
||||
GetOwnProperty(JSContext* aCx, JS::Handle<JSObject*> aObject, const char* aName,
|
||||
JS::MutableHandle<JS::Value> aResult)
|
||||
{
|
||||
bool hasOwn = false;
|
||||
if (!JS_HasOwnProperty(aCx, aObject, aName, &hasOwn)) {
|
||||
return false;
|
||||
}
|
||||
if (!hasOwn) {
|
||||
aResult.setUndefined();
|
||||
return true;
|
||||
}
|
||||
return JS_GetProperty(aCx, aObject, aName, aResult);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#resolving-a-url-like-module-specifier
|
||||
static ResolveResult
|
||||
ResolveURLLikeModuleSpecifier(const nsAString& aSpecifier, nsIURI* aBaseURL)
|
||||
{
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
nsresult rv;
|
||||
|
||||
// Step 1. If specifier starts with "/", "./", or "../", then:
|
||||
if (StringBeginsWith(aSpecifier, NS_LITERAL_STRING("/")) ||
|
||||
StringBeginsWith(aSpecifier, NS_LITERAL_STRING("./")) ||
|
||||
StringBeginsWith(aSpecifier, NS_LITERAL_STRING("../"))) {
|
||||
// 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 Err(ResolveError::Failure);
|
||||
}
|
||||
|
||||
// Step 1.3. Return url.
|
||||
return WrapNotNull(uri);
|
||||
}
|
||||
|
||||
// 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 Err(ResolveError::FailureMayBeBare);
|
||||
}
|
||||
|
||||
// Step 4. Return url.
|
||||
return WrapNotNull(uri);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#normalizing-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 resolving a URL-like module specifier,
|
||||
// given specifierKey and baseURL.
|
||||
auto parseResult = ResolveURLLikeModuleSpecifier(aSpecifierKey, aBaseURL);
|
||||
|
||||
// Step 3. If url is not null, then return the serialization of url.
|
||||
if (parseResult.isOk()) {
|
||||
nsCOMPtr<nsIURI> url = parseResult.unwrap();
|
||||
aRetVal = NS_ConvertUTF8toUTF16(url->GetSpecOrDefault());
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 4. Return specifierKey.
|
||||
aRetVal = aSpecifierKey;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#sorting-and-normalizing-a-module-specifier-map
|
||||
static UniquePtr<SpecifierMap>
|
||||
SortAndNormalizeSpecifierMap(JSContext* aCx, JS::HandleObject aOriginalMap, nsIURI* aBaseURL,
|
||||
const ReportWarningHelper& aWarning)
|
||||
{
|
||||
// Step 1. Let normalized be an empty ordered 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;
|
||||
}
|
||||
|
||||
// Impl note: JS_Enumerate only returns own properties, so reading the value
|
||||
// by id is already immune to prototype pollution (bug 1979050).
|
||||
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. The user agent may report a warning to the console
|
||||
// indicating 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 resolving a URL-like module
|
||||
// specifier given value and baseURL.
|
||||
auto parseResult = ResolveURLLikeModuleSpecifier(value, aBaseURL);
|
||||
|
||||
// Step 2.5. If addressURL is null, then:
|
||||
if (parseResult.isErr()) {
|
||||
// Step 2.5.1. The user agent may report a warning to the console
|
||||
// indicating 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;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIURI> addressURL = parseResult.unwrap();
|
||||
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, NS_LITERAL_STRING("/")) &&
|
||||
!StringEndsWith(address, NS_LITERAL_CSTRING("/"))) {
|
||||
// Step 2.6.1. The user agent may report a warning to the console
|
||||
// indicating that an invalid address was given for the specifier key
|
||||
// specifierKey; since specifierKey ends with 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 b's key is code unit less than a's 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 (!JS_IsArrayObject(aCx, aMapVal, &isArray)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*aIsMap = !isArray;
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#sorting-and-normalizing-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 an ordered 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 URL parsing scopePrefix
|
||||
// with baseURL.
|
||||
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. The user agent may 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 in descending order normalized, with
|
||||
// an entry a being less than an entry b if a's key is code unit less than b's
|
||||
// key.
|
||||
//
|
||||
// Impl note: The sorting is done when inserting the entry.
|
||||
return normalized;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#normalize-a-module-integrity-map
|
||||
static UniquePtr<IntegrityMap>
|
||||
NormalizeModuleIntegrityMap(JSContext* aCx, JS::HandleObject aOriginalMap,
|
||||
nsIURI* aBaseURL,
|
||||
const ReportWarningHelper& aWarning)
|
||||
{
|
||||
UniquePtr<IntegrityMap> normalized = MakeUnique<IntegrityMap>();
|
||||
|
||||
JS::Rooted<JS::IdVector> keys(aCx, JS::IdVector(aCx));
|
||||
if (!JS_Enumerate(aCx, aOriginalMap, &keys)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < keys.length(); i++) {
|
||||
const JS::RootedId keyId(aCx, keys[i]);
|
||||
nsAutoJSString key;
|
||||
NS_ENSURE_TRUE(key.init(aCx, keyId), nullptr);
|
||||
|
||||
// Step 2.1. Let resolvedURL be the result of resolving a URL-like module
|
||||
// specifier given key and baseURL.
|
||||
auto parseResult = ResolveURLLikeModuleSpecifier(key, aBaseURL);
|
||||
|
||||
// Step 2.2. If resolvedURL is null, warn and continue.
|
||||
if (parseResult.isErr()) {
|
||||
AutoTArray<nsString, 1> params;
|
||||
params.AppendElement(key);
|
||||
aWarning.Report("ImportMapIntegrityKeyNotParseable", params);
|
||||
continue;
|
||||
}
|
||||
|
||||
JS::RootedValue idVal(aCx);
|
||||
NS_ENSURE_TRUE(JS_GetPropertyById(aCx, aOriginalMap, keyId, &idVal), nullptr);
|
||||
|
||||
// Step 2.3. If value is not a string, warn and continue.
|
||||
if (!idVal.isString()) {
|
||||
AutoTArray<nsString, 1> params;
|
||||
params.AppendElement(key);
|
||||
aWarning.Report("ImportMapIntegrityValueNotAString", params);
|
||||
continue;
|
||||
}
|
||||
|
||||
nsAutoJSString value;
|
||||
NS_ENSURE_TRUE(value.init(aCx, idVal), nullptr);
|
||||
|
||||
// Step 2.4. Set normalized[resolvedURL] to value.
|
||||
normalized->insert_or_assign(parseResult.unwrap()->GetSpecOrDefault(),
|
||||
nsString(value));
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#parse-an-import-map-string
|
||||
// static
|
||||
UniquePtr<ImportMap>
|
||||
ImportMap::ParseString(JSContext* aCx, JS::SourceBufferHolder& 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 an ordered 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 (!GetOwnProperty(aCx, parsedObj, "imports", &importsVal)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 3. Let sortedAndNormalizedImports be an empty ordered 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 an ordered 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 module 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 (!GetOwnProperty(aCx, parsedObj, "scopes", &scopesVal)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 5. Let sortedAndNormalizedScopes be an empty ordered 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 an ordered 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;
|
||||
}
|
||||
}
|
||||
|
||||
JS::RootedValue integrityVal(aCx);
|
||||
if (!GetOwnProperty(aCx, parsedObj, "integrity", &integrityVal)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Impl note: If parsed["integrity"] doesn't exist, we will allocate
|
||||
// sortedAndNormalizedIntegrity to an empty map below.
|
||||
UniquePtr<IntegrityMap> sortedAndNormalizedIntegrity = nullptr;
|
||||
|
||||
if (!integrityVal.isUndefined()) {
|
||||
bool isMap;
|
||||
if (!IsMapObject(aCx, integrityVal, &isMap)) {
|
||||
return nullptr;
|
||||
}
|
||||
if (!isMap) {
|
||||
JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr,
|
||||
JSMSG_IMPORT_MAPS_INTEGRITY_NOT_A_MAP);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
JS::RootedObject integrityObj(aCx, &integrityVal.toObject());
|
||||
sortedAndNormalizedIntegrity = NormalizeModuleIntegrityMap(
|
||||
aCx, integrityObj, aBaseURL, aWarning);
|
||||
if (!sortedAndNormalizedIntegrity) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 7. If parsed's keys contains any items besides "imports" or
|
||||
// "scopes", then the user agent should report a warning to the console
|
||||
// indicating 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") ||
|
||||
val.EqualsLiteral("integrity")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
AutoTArray<nsString, 1> params;
|
||||
params.AppendElement(val);
|
||||
aWarning.Report("ImportMapInvalidTopLevelKey", params);
|
||||
}
|
||||
|
||||
// Impl note: Create empty maps for sortedAndNormalizedImports and
|
||||
// sortedAndNormalizedScopes if they aren't allocated.
|
||||
if (!sortedAndNormalizedImports) {
|
||||
sortedAndNormalizedImports = MakeUnique<SpecifierMap>();
|
||||
}
|
||||
if (!sortedAndNormalizedScopes) {
|
||||
sortedAndNormalizedScopes = MakeUnique<ScopeMap>();
|
||||
}
|
||||
if (!sortedAndNormalizedIntegrity) {
|
||||
sortedAndNormalizedIntegrity = MakeUnique<IntegrityMap>();
|
||||
}
|
||||
|
||||
// Step 8. Return an import map whose imports are
|
||||
// sortedAndNormalizedImports and whose scopes scopes are
|
||||
// sortedAndNormalizedScopes.
|
||||
return MakeUnique<ImportMap>(std::move(sortedAndNormalizedImports),
|
||||
std::move(sortedAndNormalizedScopes),
|
||||
std::move(sortedAndNormalizedIntegrity));
|
||||
}
|
||||
|
||||
const nsString* ImportMap::GetIntegrity(nsIURI* aURL) const {
|
||||
if (!mIntegrity || !aURL) {
|
||||
return nullptr;
|
||||
}
|
||||
auto it = mIntegrity->find(aURL->GetSpecOrDefault());
|
||||
if (it == mIntegrity->end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return &it->second;
|
||||
}
|
||||
|
||||
// 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://html.spec.whatwg.org/multipage/webappapis.html#resolving-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 (/),
|
||||
// specifierKey is a code unit prefix of normalizedSpecifier, and
|
||||
// either asURL is null, or asURL is special
|
||||
if (StringEndsWith(specifierKey, NS_LITERAL_STRING("/")) &&
|
||||
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 U+002F (/),
|
||||
// as enforced during parsing
|
||||
MOZ_ASSERT(StringEndsWith(resolutionResult->GetSpecOrDefault(),
|
||||
NS_LITERAL_CSTRING("/")));
|
||||
|
||||
// Step 1.2.5. Let url be the result of URL parsing afterPrefix with
|
||||
// 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 resolutionResult is not a code unit
|
||||
// prefix of the serialization of url, 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://html.spec.whatwg.org/multipage/webappapis.html#resolve-a-module-specifier
|
||||
// static
|
||||
ResolveResult
|
||||
ImportMap::ResolveModuleSpecifier(ImportMap* aImportMap, ScriptLoader* 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 7. Let asURL be the result of resolving a URL-like module specifier
|
||||
// given specifier and baseURL.
|
||||
//
|
||||
// Impl note: Step 6 is done below if aImportMap exists.
|
||||
auto parseResult = ResolveURLLikeModuleSpecifier(aSpecifier, baseURL);
|
||||
nsCOMPtr<nsIURI> asURL;
|
||||
if (parseResult.isOk()) {
|
||||
asURL = parseResult.unwrap();
|
||||
}
|
||||
|
||||
if (aImportMap) {
|
||||
// Step 6. Let baseURLString be baseURL, serialized.
|
||||
nsCString baseURLString = baseURL->GetSpecOrDefault();
|
||||
|
||||
// Step 8. Let normalizedSpecifier be the serialization of asURL, if asURL
|
||||
// is non-null; otherwise, specifier.
|
||||
nsAutoString normalizedSpecifier =
|
||||
asURL ? NS_ConvertUTF8toUTF16(asURL->GetSpecOrDefault())
|
||||
: nsAutoString{aSpecifier};
|
||||
|
||||
// Step 9. For each scopePrefix → scopeImports of importMap's scopes,
|
||||
for (auto&& [scopePrefix, scopeImports] : *aImportMap->mScopes) {
|
||||
// Step 9.1. If scopePrefix is baseURLString, or if scopePrefix ends with
|
||||
// U+002F (/) and scopePrefix is a code unit prefix of baseURLString,
|
||||
// then:
|
||||
if (scopePrefix.Equals(baseURLString) ||
|
||||
(StringEndsWith(scopePrefix, NS_LITERAL_CSTRING("/")) &&
|
||||
StringBeginsWith(baseURLString, scopePrefix))) {
|
||||
// Step 9.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 9.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 10. Let topLevelImportsMatch be the result of resolving an imports
|
||||
// match given normalizedSpecifier, asURL, and importMap's imports.
|
||||
auto result = ResolveImportsMatch(normalizedSpecifier, asURL,
|
||||
aImportMap->mImports.get());
|
||||
if (result.isErr()) {
|
||||
return result.propagateErr();
|
||||
}
|
||||
nsCOMPtr<nsIURI> topLevelImportsMatch = result.unwrap();
|
||||
|
||||
// Step 11. If topLevelImportsMatch is not null, then return
|
||||
// topLevelImportsMatch.
|
||||
if (topLevelImportsMatch) {
|
||||
LOG(("ImportMap::ResolveModuleSpecifier returns topLevelImportsMatch: %s",
|
||||
topLevelImportsMatch->GetSpecOrDefault().get()));
|
||||
return WrapNotNull(topLevelImportsMatch);
|
||||
}
|
||||
}
|
||||
|
||||
// Step 12. At this point, the specifier was able to be turned in to a URL,
|
||||
// but it wasn't 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 13. Throw a TypeError indicating that specifier was a bare specifier,
|
||||
// but was not remapped to anything by importMap.
|
||||
if (parseResult.unwrapErr() != ResolveError::FailureMayBeBare) {
|
||||
// We may have failed to parse a non-bare specifier for another reason.
|
||||
return Err(ResolveError::Failure);
|
||||
}
|
||||
|
||||
return Err(ResolveError::InvalidBareSpecifier);
|
||||
}
|
||||
|
||||
#undef LOG
|
||||
#undef LOG_ENABLED
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
@@ -0,0 +1,115 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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 dom_script_ImportMap_h
|
||||
#define dom_script_ImportMap_h
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
|
||||
#include "js/SourceBufferHolder.h"
|
||||
#include "mozilla/AlreadyAddRefed.h"
|
||||
#include "mozilla/Logging.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "nsStringFwd.h"
|
||||
#include "nsTArray.h"
|
||||
#include "ResolveResult.h"
|
||||
|
||||
struct JSContext;
|
||||
class nsIScriptElement;
|
||||
class nsIURI;
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
class LoadedScript;
|
||||
class ScriptLoadRequest;
|
||||
class ScriptLoader;
|
||||
|
||||
/**
|
||||
* A helper class to report warning to ScriptLoader.
|
||||
*/
|
||||
class ReportWarningHelper {
|
||||
public:
|
||||
ReportWarningHelper(ScriptLoader* aLoader, ScriptLoadRequest* aRequest)
|
||||
: mLoader(aLoader), mRequest(aRequest) {}
|
||||
|
||||
void Report(const char* aMessageName,
|
||||
const nsTArray<nsString>& aParams = nsTArray<nsString>()) const;
|
||||
|
||||
private:
|
||||
RefPtr<ScriptLoader> mLoader;
|
||||
ScriptLoadRequest* mRequest;
|
||||
};
|
||||
|
||||
// Specifier map from import maps.
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#specifier-map
|
||||
using SpecifierMap =
|
||||
std::map<nsString, nsCOMPtr<nsIURI>, std::greater<nsString>>;
|
||||
|
||||
// Scope map from import maps.
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#import-map-scopes
|
||||
using ScopeMap = std::map<nsCString, mozilla::UniquePtr<SpecifierMap>,
|
||||
std::greater<nsCString>>;
|
||||
|
||||
// Integrity map from import maps.
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#module-integrity-map
|
||||
using IntegrityMap = std::map<nsCString, nsString>;
|
||||
|
||||
/**
|
||||
* Implementation of Import maps.
|
||||
* https://html.spec.whatwg.org/multipage/webappapis.html#import-maps
|
||||
*/
|
||||
class ImportMap {
|
||||
public:
|
||||
ImportMap(mozilla::UniquePtr<SpecifierMap> aImports,
|
||||
mozilla::UniquePtr<ScopeMap> aScopes,
|
||||
mozilla::UniquePtr<IntegrityMap> aIntegrity)
|
||||
: mImports(std::move(aImports)),
|
||||
mScopes(std::move(aScopes)),
|
||||
mIntegrity(std::move(aIntegrity)) {}
|
||||
|
||||
/**
|
||||
* 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://html.spec.whatwg.org/multipage/webappapis.html#parse-an-import-map-string
|
||||
*/
|
||||
static mozilla::UniquePtr<ImportMap> ParseString(
|
||||
JSContext* aCx, JS::SourceBufferHolder& aInput, nsIURI* aBaseURL,
|
||||
const ReportWarningHelper& aWarning);
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#resolve-a-module-integrity-metadata
|
||||
const nsString* GetIntegrity(nsIURI* aURL) const;
|
||||
|
||||
// Implements
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#resolve-a-module-specifier
|
||||
static ResolveResult ResolveModuleSpecifier(ImportMap* aImportMap,
|
||||
ScriptLoader* aLoader,
|
||||
LoadedScript* aScript,
|
||||
const nsAString& aSpecifier);
|
||||
|
||||
// Logging
|
||||
static mozilla::LazyLogModule gImportMapLog;
|
||||
|
||||
private:
|
||||
/**
|
||||
* https://html.spec.whatwg.org/multipage/webappapis.html#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;
|
||||
mozilla::UniquePtr<IntegrityMap> mIntegrity;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // dom_script_ImportMap_h
|
||||
@@ -57,7 +57,8 @@ static VisitedURLSet* NewVisitedSetForTopLevelImport(nsIURI* aURI) {
|
||||
/* static */ ModuleLoadRequest* ModuleLoadRequest::CreateStaticImport(
|
||||
nsIURI* aURI, ModuleLoadRequest* aParent) {
|
||||
auto request =
|
||||
new ModuleLoadRequest(aURI, aParent->mFetchOptions, SRIMetadata(),
|
||||
new ModuleLoadRequest(aURI, aParent->mFetchOptions,
|
||||
aParent->mLoader->ComputeModuleIntegrity(aURI),
|
||||
aParent->mURI, false, /* is top level */
|
||||
false, /* is dynamic import */
|
||||
aParent->mLoader, aParent->mVisitedSet);
|
||||
@@ -77,7 +78,7 @@ static VisitedURLSet* NewVisitedSetForTopLevelImport(nsIURI* aURI) {
|
||||
MOZ_ASSERT(aPromise);
|
||||
|
||||
auto request = new ModuleLoadRequest(
|
||||
aURI, aFetchOptions, SRIMetadata(), aBaseURL,
|
||||
aURI, aFetchOptions, aLoader->ComputeModuleIntegrity(aURI), aBaseURL,
|
||||
true, /* is top level */
|
||||
true, /* is dynamic import */
|
||||
aLoader, NewVisitedSetForTopLevelImport(aURI));
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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 dom_script_ResolveResult_h
|
||||
#define dom_script_ResolveResult_h
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/NotNull.h"
|
||||
#include "mozilla/Result.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIURI.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
enum class ResolveError : uint8_t
|
||||
{
|
||||
Failure,
|
||||
FailureMayBeBare,
|
||||
BlockedByNullEntry,
|
||||
BlockedByAfterPrefix,
|
||||
BlockedByBacktrackingPrefix,
|
||||
InvalidBareSpecifier
|
||||
};
|
||||
|
||||
struct ResolveErrorInfo
|
||||
{
|
||||
static const char* GetString(ResolveError aError) {
|
||||
switch (aError) {
|
||||
case ResolveError::Failure:
|
||||
return "ModuleResolveFailureNoWarn";
|
||||
case ResolveError::FailureMayBeBare:
|
||||
return "ModuleResolveFailureWarnRelative";
|
||||
case ResolveError::BlockedByNullEntry:
|
||||
return "ImportMapResolutionBlockedByNullEntry";
|
||||
case ResolveError::BlockedByAfterPrefix:
|
||||
return "ImportMapResolutionBlockedByAfterPrefix";
|
||||
case ResolveError::BlockedByBacktrackingPrefix:
|
||||
return "ImportMapResolutionBlockedByBacktrackingPrefix";
|
||||
case ResolveError::InvalidBareSpecifier:
|
||||
return "ImportMapResolveInvalidBareSpecifierWarnRelative";
|
||||
default:
|
||||
MOZ_CRASH("Unexpected ResolveError value");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* ResolveResult is used to store the result of 'resolving a module specifier',
|
||||
* which could be an URI on success or a ResolveError on failure.
|
||||
*/
|
||||
using ResolveResult =
|
||||
mozilla::Result<mozilla::NotNull<nsCOMPtr<nsIURI>>, ResolveError>;
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // dom_script_ResolveResult_h
|
||||
@@ -0,0 +1,25 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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 mozilla_dom_ScriptKind_h
|
||||
#define mozilla_dom_ScriptKind_h
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
// Per-request data structure
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
enum class ScriptKind {
|
||||
Classic,
|
||||
Module,
|
||||
ImportMap
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
||||
+292
-68
@@ -60,6 +60,8 @@
|
||||
#include "nsIScriptError.h"
|
||||
|
||||
using JS::SourceBufferHolder;
|
||||
using mozilla::Err;
|
||||
using mozilla::WrapNotNull;
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
@@ -326,6 +328,7 @@ ScriptLoader::ScriptLoader(nsIDocument *aDocument)
|
||||
mDeferEnabled(false),
|
||||
mDocumentParsingDone(false),
|
||||
mBlockingDOMContentLoaded(false),
|
||||
mImportMapsAllowed(true),
|
||||
mReporter(new ConsoleReportCollector())
|
||||
{
|
||||
EnsureModuleHooksInitialized();
|
||||
@@ -670,15 +673,35 @@ ScriptLoader::CreateModuleScript(ModuleLoadRequest* aRequest)
|
||||
}
|
||||
|
||||
static bool
|
||||
CreateTypeError(JSContext* aCx, ModuleScript* aScript, const nsString& aMessage,
|
||||
CreateTypeError(JSContext* aCx, LoadedScript* aScript, const nsString& aMessage,
|
||||
JS::MutableHandle<JS::Value> aError)
|
||||
{
|
||||
JS::Rooted<JSObject*> module(aCx, aScript->ModuleRecord());
|
||||
JS::Rooted<JSScript*> script(aCx, JS::GetModuleScript(aCx, module));
|
||||
JS::Rooted<JSString*> filename(aCx);
|
||||
filename = JS_NewStringCopyZ(aCx, JS_GetScriptFilename(script));
|
||||
if (aScript && aScript->IsModuleScript()) {
|
||||
JS::Rooted<JSObject*> module(aCx,
|
||||
aScript->AsModuleScript()->ModuleRecord());
|
||||
if (module) {
|
||||
JS::Rooted<JSScript*> script(aCx, JS::GetModuleScript(aCx, module));
|
||||
if (script) {
|
||||
filename = JS_NewStringCopyZ(aCx, JS_GetScriptFilename(script));
|
||||
if (!filename) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!filename) {
|
||||
return false;
|
||||
// No module record (classic script, dynamic import with no referencing
|
||||
// script, or a module that never got that far). Fall back to the base URL.
|
||||
nsAutoCString url;
|
||||
if (aScript && aScript->BaseURL()) {
|
||||
aScript->BaseURL()->GetAsciiSpec(url);
|
||||
}
|
||||
filename = JS_NewStringCopyZ(aCx, url.get());
|
||||
if (!filename) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
JS::Rooted<JSString*> message(aCx, JS_NewUCStringCopyZ(aCx, aMessage.get()));
|
||||
@@ -691,63 +714,30 @@ CreateTypeError(JSContext* aCx, ModuleScript* aScript, const nsString& aMessage,
|
||||
}
|
||||
|
||||
static nsresult
|
||||
HandleResolveFailure(JSContext* aCx, ModuleScript* aScript,
|
||||
const nsAString& aSpecifier)
|
||||
HandleResolveFailure(JSContext* aCx, LoadedScript* aScript,
|
||||
const nsAString& aSpecifier,
|
||||
JS::MutableHandle<JS::Value> aErrorOut)
|
||||
{
|
||||
// TODO: How can we get the line number of the failed import?
|
||||
|
||||
nsAutoString message(NS_LITERAL_STRING("Error resolving module specifier: "));
|
||||
message.Append(aSpecifier);
|
||||
|
||||
JS::Rooted<JS::Value> error(aCx);
|
||||
if (!CreateTypeError(aCx, aScript, message, &error)) {
|
||||
if (!CreateTypeError(aCx, aScript, message, aErrorOut)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
aScript->SetParseError(error);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
static already_AddRefed<nsIURI>
|
||||
ResolveModuleSpecifier(ScriptLoader* aLoader, LoadedScript* aScript, const nsAString& aSpecifier)
|
||||
ResolveResult
|
||||
ResolveModuleSpecifier(ScriptLoader* aLoader, LoadedScript* aScript,
|
||||
const nsAString& 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.
|
||||
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
nsresult rv = NS_NewURI(getter_AddRefs(uri), aSpecifier);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
return uri.forget();
|
||||
}
|
||||
|
||||
if (rv != NS_ERROR_MALFORMED_URI) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!StringBeginsWith(aSpecifier, NS_LITERAL_STRING("/")) &&
|
||||
!StringBeginsWith(aSpecifier, NS_LITERAL_STRING("./")) &&
|
||||
!StringBeginsWith(aSpecifier, NS_LITERAL_STRING("../"))) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Get the document's base URL if we don't have a referencing script here.
|
||||
nsCOMPtr<nsIURI> baseURL;
|
||||
if (aScript) {
|
||||
baseURL = aScript->BaseURL();
|
||||
} else {
|
||||
baseURL = aLoader->GetDocument()->GetDocBaseURI();
|
||||
}
|
||||
|
||||
rv = NS_NewURI(getter_AddRefs(uri), aSpecifier, nullptr, baseURL);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
return uri.forget();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
// ImportMap::ResolveModuleSpecifier implements the whole algorithm, including
|
||||
// the aImportMap == nullptr case, so there is nothing left to do here.
|
||||
return ImportMap::ResolveModuleSpecifier(aLoader->GetImportMap(), aLoader,
|
||||
aScript, aSpecifier);
|
||||
}
|
||||
|
||||
static nsresult
|
||||
@@ -780,15 +770,18 @@ ResolveRequestedModules(ModuleLoadRequest* aRequest, nsCOMArray<nsIURI>* aUrlsOu
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// Let url be the result of resolving a module specifier given module script and requested.
|
||||
nsCOMPtr<nsIURI> uri =
|
||||
ResolveModuleSpecifier(aRequest->mLoader, ms, specifier);
|
||||
if (!uri) {
|
||||
nsresult rv = HandleResolveFailure(cx, ms, specifier);
|
||||
// Let url be the result of resolving a module specifier given module script
|
||||
// and requested.
|
||||
auto result = ResolveModuleSpecifier(aRequest->mLoader, ms, specifier);
|
||||
if (result.isErr()) {
|
||||
JS::Rooted<JS::Value> error(cx);
|
||||
nsresult rv = HandleResolveFailure(cx, ms, specifier, &error);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
ms->SetParseError(error);
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIURI> uri = result.unwrap();
|
||||
if (aUrlsOut) {
|
||||
aUrlsOut->AppendElement(uri.forget());
|
||||
}
|
||||
@@ -953,17 +946,28 @@ HostResolveImportedModule(JSContext* aCx,
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIURI> uri = ResolveModuleSpecifier(loader, script, string);
|
||||
auto result = ResolveModuleSpecifier(loader, script, string);
|
||||
|
||||
// This cannot fail because resolving a module specifier must have been
|
||||
// previously successful with these same two arguments.
|
||||
MOZ_ASSERT(uri, "Failed to resolve previously-resolved module specifier");
|
||||
if (result.isErr()) {
|
||||
JS_ReportErrorASCII(aCx, "Failed to resolve previously-resolved module specifier");
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIURI> uri = result.unwrap();
|
||||
MOZ_ASSERT(uri);
|
||||
|
||||
// Let resolved module script be moduleMap[url]. (This entry must exist for us
|
||||
// to have gotten to this point.)
|
||||
|
||||
ModuleScript* ms = loader->GetFetchedModule(uri);
|
||||
MOZ_ASSERT(ms, "Resolved module not found in module map");
|
||||
// This is now a potential result with import maps.
|
||||
// So instead of asserting report the error to JS and return.
|
||||
if (!ms) {
|
||||
JS_ReportErrorASCII(aCx, "Resolved module not found in module map");
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!ms->HasParseError());
|
||||
MOZ_ASSERT(ms->ModuleRecord());
|
||||
@@ -1016,13 +1020,22 @@ bool HostImportModuleDynamically(JSContext* aCx,
|
||||
return false;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIURI> uri = ResolveModuleSpecifier(loader, script, string);
|
||||
if (!uri) {
|
||||
JS_ReportErrorNumberUC(aCx, js::GetErrorMessage, nullptr,
|
||||
JSMSG_BAD_MODULE_SPECIFIER, string.get());
|
||||
// Import maps should be disabled after this point.
|
||||
// At least until multiple import maps support is included.
|
||||
loader->DisallowImportMaps();
|
||||
|
||||
auto result = ResolveModuleSpecifier(loader, script, string);
|
||||
if (result.isErr()) {
|
||||
JS::Rooted<JS::Value> error(aCx);
|
||||
if (NS_FAILED(HandleResolveFailure(aCx, script, string, &error))) {
|
||||
return false;
|
||||
}
|
||||
JS_SetPendingException(aCx, error);
|
||||
return false;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIURI> uri = result.unwrap();
|
||||
|
||||
// Create a new top-level load request.
|
||||
ScriptFetchOptions* options;
|
||||
nsIURI* baseURL;
|
||||
@@ -1500,11 +1513,12 @@ ScriptLoader::CreateLoadRequest(ScriptKind aKind,
|
||||
ScriptFetchOptions* fetchOptions =
|
||||
new ScriptFetchOptions(aCORSMode, aReferrerPolicy, aElement, aTriggeringPrincipal);
|
||||
|
||||
if (aKind == ScriptKind::Classic) {
|
||||
if (aKind == ScriptKind::Classic || aKind == ScriptKind::ImportMap) {
|
||||
return new ScriptLoadRequest(aKind, aURI, fetchOptions, aIntegrity, referrer);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(aKind == ScriptKind::Module);
|
||||
|
||||
return ModuleLoadRequest::CreateTopLevel(aURI, fetchOptions, aIntegrity, referrer, this);
|
||||
}
|
||||
|
||||
@@ -1526,9 +1540,14 @@ ScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
|
||||
nsAutoString type;
|
||||
bool hasType = aElement->GetScriptType(type);
|
||||
|
||||
ScriptKind scriptKind = aElement->GetScriptIsModule() ?
|
||||
ScriptKind::Module :
|
||||
ScriptKind::Classic;
|
||||
ScriptKind scriptKind;
|
||||
if (aElement->GetScriptIsModule()) {
|
||||
scriptKind = ScriptKind::Module;
|
||||
} else if (aElement->GetScriptIsImportMap()) {
|
||||
scriptKind = ScriptKind::ImportMap;
|
||||
} else {
|
||||
scriptKind = ScriptKind::Classic;
|
||||
}
|
||||
|
||||
// Step 13. Check that the script is not an eventhandler
|
||||
if (IsScriptEventHandler(scriptKind, scriptContent)) {
|
||||
@@ -1569,12 +1588,31 @@ ScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
|
||||
return false;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/scripting.html#prepare-the-script-element
|
||||
// Step 33.1: external import maps are not supported.
|
||||
if (scriptKind == ScriptKind::ImportMap && aElement->GetScriptExternal()) {
|
||||
NS_DispatchToCurrentThread(
|
||||
NewRunnableMethod(aElement, &nsIScriptElement::FireErrorEvent));
|
||||
return false;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/scripting.html#prepare-the-script-element
|
||||
// If el's type is "importmap": if el's relevant global object's import maps
|
||||
// allowed is false, queue an element task to fire `error` at el, and return.
|
||||
if (scriptKind == ScriptKind::ImportMap && !mImportMapsAllowed) {
|
||||
NS_WARNING("ScriptLoader: import maps allowed is false.");
|
||||
NS_DispatchToCurrentThread(
|
||||
NewRunnableMethod(aElement, &nsIScriptElement::FireErrorEvent));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 15. and later in the HTML5 spec
|
||||
nsresult rv = NS_OK;
|
||||
RefPtr<ScriptLoadRequest> request;
|
||||
mozilla::net::ReferrerPolicy referrerPolicy = GetReferrerPolicy(aElement);
|
||||
if (aElement->GetScriptExternal()) {
|
||||
// external script
|
||||
|
||||
nsCOMPtr<nsIURI> scriptURI = aElement->GetScriptURI();
|
||||
if (!scriptURI) {
|
||||
// Asynchronously report the failure to create a URI object
|
||||
@@ -1584,6 +1622,14 @@ ScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
|
||||
return false;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-module-script-tree
|
||||
// Step 1. Disallow further import maps given settings object.
|
||||
// Must happen for every module script element, not only when a
|
||||
// speculative preload happens to be reused.
|
||||
if (scriptKind == ScriptKind::Module) {
|
||||
DisallowImportMaps();
|
||||
}
|
||||
|
||||
// Double-check that the preload matches what we're asked to load now.
|
||||
CORSMode ourCORSMode = aElement->GetCORSMode();
|
||||
nsTArray<PreloadInfo>::index_type i =
|
||||
@@ -1611,6 +1657,31 @@ ScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
|
||||
request->Cancel();
|
||||
return false;
|
||||
}
|
||||
// A speculative preload is performed before the tree ops of its flush
|
||||
// batch run (nsHtml5TreeOpExecutor::RunFlushLoop), and PreloadURI has no
|
||||
// element, so a module preload never carries import-map integrity.
|
||||
// mIntegrity is const and the channel is already open, so the preload
|
||||
// has to be discarded rather than patched.
|
||||
if (scriptKind == ScriptKind::Module &&
|
||||
!scriptContent->HasAttr(kNameSpaceID_None, nsGkAtoms::integrity)) {
|
||||
SRIMetadata fromMap = ComputeModuleIntegrity(scriptURI);
|
||||
if (!fromMap.IsEmpty() &&
|
||||
!fromMap.GetIntegrityString().Equals(
|
||||
request->mIntegrity.GetIntegrityString())) {
|
||||
// Drop the stale preload. The module map entry has to go too,
|
||||
// otherwise the replacement request's StartLoad() will see
|
||||
// ModuleMapContainsURL() == true and just wait for this
|
||||
// unverified fetch instead of starting its own.
|
||||
request->Cancel();
|
||||
RefPtr<GenericPromise::Private> waiters;
|
||||
bool ownsFetch = mFetchingModules.Get(scriptURI, getter_AddRefs(waiters)) && !waiters;
|
||||
if (ownsFetch) {
|
||||
mFetchingModules.Remove(scriptURI);
|
||||
}
|
||||
mFetchedModules.Remove(scriptURI);
|
||||
request = nullptr;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Drop the preload
|
||||
request = nullptr;
|
||||
@@ -1628,7 +1699,9 @@ ScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
|
||||
// No usable preload found.
|
||||
|
||||
SRIMetadata sriMetadata;
|
||||
{
|
||||
if (scriptContent->HasAttr(kNameSpaceID_None, nsGkAtoms::integrity)) {
|
||||
// The element's own integrity attribute wins — even when it is present
|
||||
// but empty, in which case no metadata is used at all.
|
||||
nsAutoString integrity;
|
||||
scriptContent->GetAttr(kNameSpaceID_None, nsGkAtoms::integrity,
|
||||
integrity);
|
||||
@@ -1643,6 +1716,11 @@ ScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
|
||||
SRICheck::IntegrityMetadata(integrity, sourceUri, mReporter,
|
||||
&sriMetadata);
|
||||
}
|
||||
} else if (scriptKind == ScriptKind::Module) {
|
||||
// https://html.spec.whatwg.org/multipage/scripting.html#prepare-the-script-element
|
||||
// Step 33.1.11: no integrity attribute, so resolve the metadata from
|
||||
// the document's import map for the resolved URL.
|
||||
sriMetadata = ComputeModuleIntegrity(scriptURI);
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIPrincipal> principal = aElement->GetScriptURITriggeringPrincipal();
|
||||
@@ -1787,6 +1865,10 @@ ScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
|
||||
request->mBaseURL = mDocument->GetDocBaseURI();
|
||||
|
||||
if (request->IsModuleRequest()) {
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#fetch-an-inline-module-script-graph
|
||||
// Step 1. Disallow further import maps given settings object.
|
||||
DisallowImportMaps();
|
||||
|
||||
ModuleLoadRequest* modReq = request->AsModuleRequest();
|
||||
|
||||
if (aElement->GetScriptAsync()) {
|
||||
@@ -1802,6 +1884,39 @@ ScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (request->IsImportMapRequest()) {
|
||||
// https://html.spec.whatwg.org/multipage/scripting.html#prepare-the-script-element
|
||||
// Step 31.2 type is "importmap":
|
||||
// Impl note: Step 1 is done above before creating a ScriptLoadRequest.
|
||||
MOZ_ASSERT(mImportMapsAllowed);
|
||||
|
||||
// Step 2. Set el's relevant global object's import maps allowed to false.
|
||||
DisallowImportMaps();
|
||||
|
||||
// Step 3. Let result be the result of creating an import map parse result
|
||||
// given source text and base URL.
|
||||
UniquePtr<ImportMap> importMap = ParseImportMap(request);
|
||||
if (!importMap) {
|
||||
// If parsing import maps fails, the exception will have been reported by
|
||||
// ImportMap::ParseString, and the registration of the import map bails
|
||||
// out early. Note the script's result is an import map parse result,
|
||||
// which is never null, so no error event is fired here.
|
||||
return false;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/scripting.html#execute-the-script-element
|
||||
// The spec defines 'register an import map' should be done in
|
||||
// 'execute the script element', because inside 'execute the script element'
|
||||
// it will perform a 'preparation-time document check'.
|
||||
// However, as import maps can currently only be inline scripts, the
|
||||
// 'preparation-time document check' will never fail for import maps.
|
||||
// So we simply call 'register an import map' here.
|
||||
MOZ_ASSERT(aElement->GetScriptIsImportMap());
|
||||
RegisterImportMap(std::move(importMap));
|
||||
return false;
|
||||
}
|
||||
|
||||
request->mProgress = ScriptLoadRequest::Progress::Ready;
|
||||
if (aElement->GetParserCreated() == FROM_PARSER_XSLT &&
|
||||
(!ReadyToExecuteParserBlockingScripts() || !mXSLTRequests.isEmpty())) {
|
||||
@@ -1948,6 +2063,28 @@ OffThreadScriptLoaderCallback(void *aToken, void *aCallbackData)
|
||||
NS_DispatchToMainThread(aRunnable);
|
||||
}
|
||||
|
||||
void
|
||||
ScriptLoader::ReportWarningToConsole(ScriptLoadRequest* aRequest,
|
||||
const char* aMessageName,
|
||||
const nsTArray<nsString>& aParams) const
|
||||
{
|
||||
nsIScriptElement* element = aRequest->Element();
|
||||
uint32_t lineNo = element ? element->GetScriptLineNumber() : 0;
|
||||
uint32_t columnNo = element ? element->GetScriptColumnNumber() : 0;
|
||||
|
||||
nsTArray<const char16_t*> stringParams;
|
||||
for (const auto& param : aParams) {
|
||||
stringParams.AppendElement(param.get());
|
||||
}
|
||||
|
||||
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
|
||||
NS_LITERAL_CSTRING("Script Loader"), mDocument,
|
||||
nsContentUtils::eDOM_PROPERTIES, aMessageName,
|
||||
stringParams.Elements(),
|
||||
stringParams.Length(), aRequest->mBaseURL,
|
||||
EmptyString(), lineNo, columnNo);
|
||||
}
|
||||
|
||||
nsresult
|
||||
ScriptLoader::AttemptAsyncScriptCompile(ScriptLoadRequest* aRequest)
|
||||
{
|
||||
@@ -2740,7 +2877,15 @@ void
|
||||
ScriptLoader::HandleLoadError(ScriptLoadRequest *aRequest, nsresult aResult) {
|
||||
if (aRequest->IsModuleRequest() && !aRequest->mIsInline) {
|
||||
auto request = aRequest->AsModuleRequest();
|
||||
SetModuleFetchFinishedAndResumeWaitingRequests(request, aResult);
|
||||
|
||||
// A preload dropped in ProcessScriptElement has already had its URL
|
||||
// removed from the module map, so its late completion must not touch the
|
||||
// entry that the replacement request now owns: SetModuleFetchFinished...
|
||||
// would trip its MOZ_ALWAYS_TRUE on mFetchingModules and then park a
|
||||
// nullptr in mFetchedModules over the real module.
|
||||
if (IsFetchingModule(request)) {
|
||||
SetModuleFetchFinishedAndResumeWaitingRequests(request, aResult);
|
||||
}
|
||||
}
|
||||
|
||||
if (aRequest->mInDeferList) {
|
||||
@@ -2790,8 +2935,15 @@ ScriptLoader::HandleLoadError(ScriptLoadRequest *aRequest, nsresult aResult) {
|
||||
FinishDynamicImport(modReq, aResult);
|
||||
}
|
||||
} else {
|
||||
MOZ_ASSERT(!modReq->IsTopLevel());
|
||||
// A top-level request can reach here: it is a preload that
|
||||
// ProcessScriptElement dropped in favour of a replacement request
|
||||
// because the import map supplied integrity metadata. Dropping it
|
||||
// cleared mIsPreload and took it out of mPreloads, but it was never in
|
||||
// any of the request lists either, so it is indistinguishable from a
|
||||
// child import except by IsTopLevel(). Its error is reported through
|
||||
// the replacement request, so just cancel and forget it.
|
||||
MOZ_ASSERT(!modReq->isInList());
|
||||
MOZ_ASSERT_IF(modReq->IsTopLevel(), modReq->IsCanceled());
|
||||
modReq->Cancel();
|
||||
// The error is handled for the top level module.
|
||||
}
|
||||
@@ -3160,5 +3312,77 @@ ScriptLoader::MaybeRemovedDeferRequests()
|
||||
return false;
|
||||
}
|
||||
|
||||
UniquePtr<ImportMap>
|
||||
ScriptLoader::ParseImportMap(ScriptLoadRequest* aRequest)
|
||||
{
|
||||
nsCOMPtr<nsIScriptGlobalObject> globalObject = GetScriptGlobalObject();
|
||||
if (!globalObject) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AutoJSAPI jsapi;
|
||||
if (!jsapi.Init(globalObject)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Import map scripts are always inline, so the source text is on the element.
|
||||
nsAutoString inlineData;
|
||||
SourceBufferHolder srcBuf = GetScriptSource(aRequest, inlineData);
|
||||
ReportWarningHelper warning{this, 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(), srcBuf, aRequest->mBaseURL, warning);
|
||||
}
|
||||
|
||||
void
|
||||
ScriptLoader::RegisterImportMap(UniquePtr<ImportMap> aImportMap)
|
||||
{
|
||||
// Check for aImportMap is done in ScriptLoader.
|
||||
MOZ_ASSERT(aImportMap);
|
||||
|
||||
// Step 8. Set element's node document's import map to import map parse
|
||||
// result's import map.
|
||||
mImportMap = std::move(aImportMap);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#resolve-a-module-integrity-metadata
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#get-the-descendant-script-fetch-options
|
||||
SRIMetadata
|
||||
ScriptLoader::ComputeModuleIntegrity(nsIURI* aURI)
|
||||
{
|
||||
SRIMetadata integrity;
|
||||
ImportMap* importMap = GetImportMap();
|
||||
if (!importMap || !aURI) {
|
||||
return integrity;
|
||||
}
|
||||
|
||||
// Step 2. If map's integrity[url] does not exist, return the empty string.
|
||||
const nsString* metadata = importMap->GetIntegrity(aURI);
|
||||
if (!metadata || metadata->IsEmpty()) {
|
||||
return integrity;
|
||||
}
|
||||
|
||||
nsAutoCString sourceUri;
|
||||
if (mDocument && mDocument->GetDocumentURI()) {
|
||||
mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
|
||||
}
|
||||
SRICheck::IntegrityMetadata(*metadata, sourceUri, mReporter, &integrity);
|
||||
return integrity;
|
||||
}
|
||||
|
||||
} // dom namespace
|
||||
} // mozilla namespace
|
||||
@@ -6,6 +6,7 @@
|
||||
#ifndef mozilla_dom_ScriptLoader_h
|
||||
#define mozilla_dom_ScriptLoader_h
|
||||
|
||||
#include "ImportMap.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsRefPtrHashtable.h"
|
||||
#include "nsIUnicodeDecoder.h"
|
||||
@@ -18,11 +19,14 @@
|
||||
#include "nsIIncrementalStreamLoader.h"
|
||||
#include "nsURIHashKey.h"
|
||||
#include "mozilla/CORSMode.h"
|
||||
#include "mozilla/dom/ScriptKind.h"
|
||||
#include "mozilla/dom/SRIMetadata.h"
|
||||
#include "mozilla/dom/SRICheck.h"
|
||||
#include "mozilla/MozPromise.h"
|
||||
#include "mozilla/net/ReferrerPolicy.h"
|
||||
#include "mozilla/Vector.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "ResolveResult.h"
|
||||
|
||||
class nsIURI;
|
||||
|
||||
@@ -39,15 +43,6 @@ class ModuleLoadRequest;
|
||||
class ModuleScript;
|
||||
class ScriptLoadRequestList;
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
// Per-request data structure
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
enum class ScriptKind {
|
||||
Classic,
|
||||
Module
|
||||
};
|
||||
|
||||
/*
|
||||
* Some options used when fetching script resources. This only loosely
|
||||
* corresponds to HTML's "script fetch options".
|
||||
@@ -103,6 +98,11 @@ public:
|
||||
return mKind == ScriptKind::Module;
|
||||
}
|
||||
|
||||
bool IsImportMapRequest() const
|
||||
{
|
||||
return mKind == ScriptKind::ImportMap;
|
||||
}
|
||||
|
||||
ModuleLoadRequest* AsModuleRequest();
|
||||
|
||||
void FireScriptAvailable(nsresult aResult)
|
||||
@@ -562,6 +562,11 @@ public:
|
||||
return mPendingChildLoaders.AppendElement(aChild) != nullptr;
|
||||
}
|
||||
|
||||
nsIURI* GetBaseURI() const {
|
||||
MOZ_ASSERT(mDocument);
|
||||
return mDocument->GetBaseURI().take();
|
||||
}
|
||||
|
||||
/*
|
||||
* Clear the map of loaded modules. Called when a Document object is reused
|
||||
* for a different global.
|
||||
@@ -598,6 +603,33 @@ public:
|
||||
|
||||
nsIDocument* GetDocument() const { return mDocument; }
|
||||
|
||||
// Process <script type="importmap">
|
||||
mozilla::UniquePtr<ImportMap> ParseImportMap(ScriptLoadRequest* aRequest);
|
||||
|
||||
// Implements
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#register-an-import-map
|
||||
void RegisterImportMap(mozilla::UniquePtr<ImportMap> aImportMap);
|
||||
|
||||
ImportMap* GetImportMap() { return mImportMap.get(); }
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#resolve-a-module-integrity-metadata
|
||||
SRIMetadata ComputeModuleIntegrity(nsIURI* aURI);
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#concept-import-maps-allowed
|
||||
bool IsImportMapAllowed() const { return mImportMapsAllowed; }
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#disallow-further-import-maps
|
||||
void DisallowImportMaps() { mImportMapsAllowed = false; }
|
||||
|
||||
void ReportWarningToConsole(ScriptLoadRequest* aRequest, const char* aMessageName,
|
||||
const nsTArray<nsString>& aParams = nsTArray<nsString>()) const;
|
||||
|
||||
|
||||
protected:
|
||||
|
||||
mozilla::UniquePtr<ImportMap> mImportMap;
|
||||
bool mImportMapsAllowed;
|
||||
|
||||
private:
|
||||
virtual ~ScriptLoader();
|
||||
|
||||
|
||||
@@ -12,13 +12,17 @@ XPIDL_MODULE = 'dom'
|
||||
EXPORTS += ['nsIScriptElement.h']
|
||||
|
||||
EXPORTS.mozilla.dom += [
|
||||
"ImportMap.h",
|
||||
'ModuleScript.h',
|
||||
'ResolveResult.h',
|
||||
'ScriptElement.h',
|
||||
'ScriptKind.h',
|
||||
'ScriptLoader.h',
|
||||
'ScriptSettings.h',
|
||||
]
|
||||
|
||||
SOURCES += [
|
||||
"ImportMap.cpp",
|
||||
'ModuleLoadRequest.cpp',
|
||||
'ModuleScript.cpp',
|
||||
'ScriptElement.cpp',
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "nsIDOMHTMLScriptElement.h"
|
||||
#include "mozilla/CORSMode.h"
|
||||
#include "mozilla/net/ReferrerPolicy.h"
|
||||
#include "mozilla/dom/ScriptKind.h"
|
||||
|
||||
#define NS_ISCRIPTELEMENT_IID \
|
||||
{ 0xe60fca9b, 0x1b96, 0x4e4e, \
|
||||
@@ -39,10 +40,10 @@ public:
|
||||
mForceAsync(aFromParser == mozilla::dom::NOT_FROM_PARSER ||
|
||||
aFromParser == mozilla::dom::FROM_PARSER_FRAGMENT),
|
||||
mFrozen(false),
|
||||
mIsModule(false),
|
||||
mDefer(false),
|
||||
mAsync(false),
|
||||
mExternal(false),
|
||||
mKind(mozilla::dom::ScriptKind::Classic),
|
||||
mParserCreated(aFromParser == mozilla::dom::FROM_PARSER_FRAGMENT ?
|
||||
mozilla::dom::NOT_FROM_PARSER : aFromParser),
|
||||
// Fragment parser-created scripts (if executable)
|
||||
@@ -85,6 +86,7 @@ public:
|
||||
* Freezes the return values of the following methods so that subsequent
|
||||
* modifications to the attributes don't change execution behavior:
|
||||
* - GetScriptIsModule()
|
||||
* - GetScriptIsImportMap()
|
||||
* - GetScriptDeferred()
|
||||
* - GetScriptAsync()
|
||||
* - GetScriptURI()
|
||||
@@ -99,7 +101,16 @@ public:
|
||||
bool GetScriptIsModule()
|
||||
{
|
||||
NS_PRECONDITION(mFrozen, "Execution attributes not yet frozen: Not ready for this call!");
|
||||
return mIsModule;
|
||||
return mKind == mozilla::dom::ScriptKind::Module;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the script an import map. Currently only supported by HTML scripts.
|
||||
*/
|
||||
bool GetScriptIsImportMap()
|
||||
{
|
||||
NS_PRECONDITION(mFrozen, "Not ready for this call yet!");
|
||||
return mKind == mozilla::dom::ScriptKind::ImportMap;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -338,11 +349,6 @@ protected:
|
||||
*/
|
||||
bool mFrozen;
|
||||
|
||||
/**
|
||||
* The effective moduleness.
|
||||
*/
|
||||
bool mIsModule;
|
||||
|
||||
/**
|
||||
* The effective deferredness.
|
||||
*/
|
||||
@@ -359,6 +365,11 @@ protected:
|
||||
*/
|
||||
bool mExternal;
|
||||
|
||||
/**
|
||||
* The effective script kind.
|
||||
*/
|
||||
mozilla::dom::ScriptKind mKind;
|
||||
|
||||
/**
|
||||
* Whether this element was parser-created.
|
||||
*/
|
||||
|
||||
@@ -32,6 +32,8 @@ interface HTMLScriptElement : HTMLElement {
|
||||
attribute DOMString nonce;
|
||||
[CEReactions, SetterThrows, Pure]
|
||||
attribute DOMString integrity;
|
||||
|
||||
static boolean supports(DOMString type);
|
||||
};
|
||||
|
||||
// http://www.whatwg.org/specs/web-apps/current-work/#other-elements,-attributes-and-apis
|
||||
|
||||
@@ -1155,8 +1155,6 @@ ModuleObject::execute(JSContext* cx, HandleModuleObject self, MutableHandleValue
|
||||
evalPromise = &promiseObj->as<PromiseObject>();
|
||||
self->setEvaluationPromise(evalPromise);
|
||||
}
|
||||
} else {
|
||||
self->setReservedSlot(ScriptSlot, UndefinedValue());
|
||||
}
|
||||
|
||||
RootedModuleEnvironmentObject scope(cx, self->environment());
|
||||
@@ -1184,9 +1182,10 @@ ModuleObject::execute(JSContext* cx, HandleModuleObject self, MutableHandleValue
|
||||
RootedValue undefinedValue(cx, UndefinedValue());
|
||||
if (!PromiseObject::resolve(cx, evalPromise, undefinedValue))
|
||||
return false;
|
||||
self->setReservedSlot(ScriptSlot, UndefinedValue());
|
||||
rval.setObject(*evalPromise);
|
||||
}
|
||||
// Module finished and will not be resumed; release the script.
|
||||
self->setReservedSlot(ScriptSlot, UndefinedValue());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -612,6 +612,13 @@ MSG_DEF(JSMSG_NO_DYNAMIC_IMPORT, 0, JSEXN_SYNTAXERR, "dynamic module imp
|
||||
MSG_DEF(JSMSG_DYNAMIC_IMPORT_FAILED, 0, JSEXN_TYPEERR, "error loading dynamically imported module")
|
||||
MSG_DEF(JSMSG_BAD_MODULE_SPECIFIER, 1, JSEXN_TYPEERR, "error resolving module specifier '{0}'")
|
||||
|
||||
// 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")
|
||||
MSG_DEF(JSMSG_IMPORT_MAPS_INTEGRITY_NOT_A_MAP, 0, JSEXN_TYPEERR, "The value of the \"integrity\" top-level key in an import map 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.")
|
||||
|
||||
+1
-1
@@ -4821,7 +4821,7 @@ JS::GetModuleScript(JSContext* cx, JS::HandleObject moduleArg)
|
||||
AssertHeapIsIdle(cx);
|
||||
CHECK_REQUEST(cx);
|
||||
assertSameCompartment(cx, moduleArg);
|
||||
return moduleArg->as<ModuleObject>().script();
|
||||
return moduleArg->as<ModuleObject>().maybeScript();
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(JSObject*)
|
||||
|
||||
@@ -220,6 +220,12 @@ public:
|
||||
return mImpl.unwrapErr();
|
||||
}
|
||||
|
||||
|
||||
GenericErrorResult<E> propagateErr() const {
|
||||
MOZ_ASSERT(isErr());
|
||||
return GenericErrorResult<E>(mImpl.unwrapErr());
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a function V -> W over this result's success variant. If this result is
|
||||
* an error, do not invoke the function and return a copy of the error.
|
||||
@@ -276,6 +282,13 @@ MakeGenericErrorResult(E&& aErrorValue)
|
||||
return GenericErrorResult<E>(aErrorValue);
|
||||
}
|
||||
|
||||
|
||||
template <typename E>
|
||||
inline GenericErrorResult<E> Err(E&& aErrorValue)
|
||||
{
|
||||
return GenericErrorResult<E>(aErrorValue);
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user