Compare commits

...
13 Commits
Author SHA1 Message Date
Moonchild 845bff9d31 Merge pull request 'Implement Import Maps support and HTMScriptElement.supports()' (#3209) from dbsoft/UXP:importmap-work-3 into master
Reviewed-on: https://repo.palemoon.org/MoonchildProductions/UXP/pulls/3209
2026-11-05 06:59:40 +00:00
Brian Smith 883f60cf12 Issue #2464 - Part 12: Fix a debug assert caused by the preload canceling in part 10.
In debug builds canceling the preload causes an assert when checking top-level.
2026-11-02 10:26:57 -06:00
Brian Smith eaa73cedc8 Issue #2464 - Part 11: Fix a regression caused by that last commit.
I had some code I was testing with in there that didn't work and caused a
formerly passing test to fail This removes this erroneous change.
Also cleanly handle a failure instead of just asserting in debug mode.
2026-11-02 00:33:34 -06:00
Brian Smith a2478fe2cb Issue #2464 - Part 10:Fix the remaining non-expected FAIL WPT tests.
Also fixes a crash in dynamic imports caused by the import maps change.
2026-11-02 00:33:34 -06:00
Brian Smith 480b203dea Issue #2464 - Part 9: Implement importmap integrity verification.
https://bugzilla.mozilla.org/show_bug.cgi?id=1945540
2026-11-02 00:33:34 -06:00
Brian Smith 5c7eb114dd Issue #2464 - Part 8: Fix a release assert when a dynamic module fails to load.
Reorganize the failure handling in execute() to keep the failed script alive longer.
Please note JS::GetModuleScript() can now return NULL.
2026-11-02 00:33:34 -06:00
Brian Smith 5b3cc8dab5 Issue #2464 - Part 7: Fix some incorrect import map error bailouts.
Should always error bailout for external scripts.
Error bailout and warn when import maps are no longer allowed.
2026-11-02 00:33:34 -06:00
Brian Smith b8eb00f26c Issue #2464 - Part 6: Implement HTMLScriptElement.supports(type) method.
https://bugzilla.mozilla.org/show_bug.cgi?id=1729239
2026-11-02 00:33:34 -06:00
Brian Smith 97bc446a95 Issue #2464 - Part 5: Update Import-Maps implementation to meet the latest whatwg PR 8075.
https://bugzilla.mozilla.org/show_bug.cgi?id=1778289
2026-11-02 00:33:34 -06:00
Brian Smith d415454413 Issue #2464 - Part 4: Import map JSON parsing code can be tricked by overwriting Object.prototype properties.
Also fix an erroneous comment.
2026-11-02 00:33:34 -06:00
Brian Smith 5b9c897904 Issue #2464 - Part 3: ResolveModuleSpecifier for import maps.
https://bugzilla.mozilla.org/show_bug.cgi?id=1688879
Acquiring import maps.
External import maps aren't supported.
2026-11-02 00:33:34 -06:00
Brian Smith 63181f6d11 Issue #2464 - Part 2: Parse and register an import map.
https://bugzilla.mozilla.org/show_bug.cgi?id=1688879
Refactor ResolveModuleSpecifier.
2026-11-02 00:33:34 -06:00
Brian Smith 38892f7308 Issue #2464 - Part 1: Add support for import maps in ScriptElement/ScriptLoader/ScriptKind.
https://bugzilla.mozilla.org/show_bug.cgi?id=1688879
2026-11-02 00:33:34 -06:00
17 changed files with 1428 additions and 95 deletions

No files matched your search

+25 -5
View File
@@ -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
+3
View File
@@ -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.
+803
View File
@@ -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
+115
View File
@@ -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
+3 -2
View File
@@ -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));
+60
View File
@@ -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
+25
View File
@@ -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
View File
@@ -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
+41 -9
View File
@@ -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();
+4
View File
@@ -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',
+18 -7
View File
@@ -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.
*/
+2
View File
@@ -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
+2 -3
View File
@@ -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;
}
+7
View File
@@ -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
View File
@@ -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*)
+13
View File
@@ -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
/**