Skip to content

Commit

Permalink
Bug 1775424 - Part 1: Impl import.meta.resolve() on browser. r=jonco,…
Browse files Browse the repository at this point in the history
…yulia

Define 'resolve()' function on import.meta object on browser.

Add mochitests for import.meta.resolve, as some of the wpt tests for
import.meta.resolve cannot be run due to some bugs in wpt testing framework.
See https://bugzilla.mozilla.org/show_bug.cgi?id=1785806

Differential Revision: https://phabricator.services.mozilla.com/D154105
  • Loading branch information
allstarschh committed Aug 30, 2022
1 parent b4a2cfe commit d17eb87
Show file tree
Hide file tree
Showing 8 changed files with 220 additions and 22 deletions.
1 change: 1 addition & 0 deletions dom/base/test/jsmodules/chrome.ini
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ support-files =
[test_scriptNotParsedAsModule.html]
[test_typeAttrCaseInsensitive.html]
[test_moduleNotFound.html]
[test_import_meta_resolve.html]
[test_importNotFound.html]
[test_syntaxError.html]
[test_syntaxErrorAsync.html]
Expand Down
1 change: 1 addition & 0 deletions dom/base/test/jsmodules/importmaps/chrome.ini
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ prefs =

[test_dynamic_import_reject_importMap.html]
[test_externalImportMap.html]
[test_import_meta_resolve_importMap.html]
[test_inline_module_reject_importMap.html]
[test_load_importMap_with_base.html]
[test_load_importMap_with_base2.html]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<!DOCTYPE html>
<head>
<meta charset=utf-8>
<title>Test import.meta.resolve with import maps</title>
</head>
<body onload='testLoaded()'>

<script type="importmap">
{
"imports": {
"simple": "./module_simpleExport.js"
}
}
</script>

<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script>
var wasRun = false;
var hasThrown = false;
window.onerror = handleError;

function handleError(msg, url, line, col, error) {
ok(error instanceof TypeError, "Thrown error should be TypeError.");
hasThrown = true;
}
</script>

<script type="module">
ok(import.meta.resolve("simple") ==
"chrome://mochitests/content/chrome/dom/base/test/jsmodules/importmaps/module_simpleExport.js",
"calling import.meta.resolve with a specifier from import map.");
wasRun = true;
</script>

<script type="module">
// should throw a TypeError
import.meta.resolve("fail");
</script>

<script>
SimpleTest.waitForExplicitFinish();

function testLoaded() {
ok(wasRun, "Check inline module has run.");
ok(hasThrown, "Check inline module has thrown.");
SimpleTest.finish();
}
</script>
</body>
65 changes: 65 additions & 0 deletions dom/base/test/jsmodules/test_import_meta_resolve.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<!DOCTYPE html>
<head>
<meta charset=utf-8>
<title>Test import.meta.resolve</title>
</head>
<body onload='testLoaded()'>

<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script>
SimpleTest.waitForExplicitFinish();

function assertThrowsTypeError(fn, msg) {
let hasThrown = false;
try {
fn();
} catch (error) {
hasThrown = true;
ok(error instanceof TypeError, "Thrown error should be TypeError.");
}
ok(hasThrown, msg);
}

function testLoaded() {
SimpleTest.finish();
}
</script>

<script type="module">
is(typeof import.meta.resolve, "function", "resolve should be a function.");
is(import.meta.resolve.name, "resolve", "resolve.name should be 'resolve'.");
is(import.meta.resolve.length, 1, "resolve.length should be 1.");
is(Object.getPrototypeOf(import.meta.resolve), Function.prototype,
"prototype of resolve should be Function.prototype.");
</script>

<script type="module">
is(import.meta.resolve("http://example.com/"), "http://example.com/",
"resolve specifiers with absolute path.");
</script>

<script type="module">
is(import.meta.resolve("./x"), (new URL("./x", import.meta.url)).href,
"resolve specifiers with relative path.");
</script>

<script type="module">
assertThrowsTypeError(() => new import.meta.resolve("./x"),
"import.meta.resolve is not a constructor.");
</script>

<script type="module">
// Fails to resolve the specifier should throw a TypeError.
assertThrowsTypeError(() => import.meta.resolve("failed"),
"import.meta.resolve should throw if fails to resolve");
</script>

<script type="module">
for (const name of Reflect.ownKeys(import.meta)) {
const desc = Object.getOwnPropertyDescriptor(import.meta, name);
is(desc.writable, true, name + ".writable should be true.");
is(desc.enumerable, true, name + ".enumerable should be true.");
is(desc.configurable, true, name + ".configurable should be true.");
}
</script>
</body>
93 changes: 91 additions & 2 deletions js/loader/ModuleLoaderBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,75 @@ JSObject* ModuleLoaderBase::HostResolveImportedModule(
return module;
}

// static
bool ModuleLoaderBase::ImportMetaResolve(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
RootedValue modulePrivate(
cx, js::GetFunctionNativeReserved(&args.callee(), ModulePrivateSlot));

// https://html.spec.whatwg.org/#hostgetimportmetaproperties
// Step 4.1. Set specifier to ? ToString(specifier).
//
// https://tc39.es/ecma262/#sec-tostring
RootedValue v(cx, args.get(ImportMetaResolveSpecifierArg));
RootedString specifier(cx, JS::ToString(cx, v));
if (!specifier) {
return false;
}

// Step 4.2, 4.3 are implemented in ImportMetaResolveImpl.
RootedString url(cx, ImportMetaResolveImpl(cx, modulePrivate, specifier));
if (!url) {
return false;
}

// Step 4.4. Return the serialization of url.
args.rval().setString(url);
return true;
}

// static
JSString* ModuleLoaderBase::ImportMetaResolveImpl(
JSContext* aCx, JS::Handle<JS::Value> aReferencingPrivate,
JS::Handle<JSString*> aSpecifier) {
RefPtr<ModuleScript> script =
static_cast<ModuleScript*>(aReferencingPrivate.toPrivate());
MOZ_ASSERT(script->IsModuleScript());
MOZ_ASSERT(JS::GetModulePrivate(script->ModuleRecord()) ==
aReferencingPrivate);

RefPtr<ModuleLoaderBase> loader = GetCurrentModuleLoader(aCx);
if (!loader) {
return nullptr;
}

nsAutoJSString specifier;
if (!specifier.init(aCx, aSpecifier)) {
return nullptr;
}

auto result = loader->ResolveModuleSpecifier(script, specifier);
if (result.isErr()) {
JS::Rooted<JS::Value> error(aCx);
nsresult rv = HandleResolveFailure(aCx, script, specifier,
result.unwrapErr(), 0, 0, &error);
if (NS_FAILED(rv)) {
JS_ReportOutOfMemory(aCx);
return nullptr;
}

JS_SetPendingException(aCx, error);

return nullptr;
}

nsCOMPtr<nsIURI> uri = result.unwrap();
nsAutoCString url;
MOZ_ALWAYS_SUCCEEDS(uri->GetAsciiSpec(url));
return JS_NewStringCopyZ(aCx, url.get());
}

// static
bool ModuleLoaderBase::HostPopulateImportMeta(
JSContext* aCx, JS::Handle<JS::Value> aReferencingPrivate,
Expand All @@ -161,8 +230,28 @@ bool ModuleLoaderBase::HostPopulateImportMeta(
return false;
}

return JS_DefineProperty(aCx, aMetaObject, "url", urlString,
JSPROP_ENUMERATE);
// https://html.spec.whatwg.org/#import-meta-url
if (!JS_DefineProperty(aCx, aMetaObject, "url", urlString,
JSPROP_ENUMERATE)) {
return false;
}

// https://html.spec.whatwg.org/#import-meta-resolve
// Define 'resolve' function on the import.meta object.
JSFunction* resolveFunc = js::DefineFunctionWithReserved(
aCx, aMetaObject, "resolve", ImportMetaResolve, ImportMetaResolveNumArgs,
JSPROP_ENUMERATE);
if (!resolveFunc) {
return false;
}

// Store the 'active script' of the meta object into the function slot.
// https://html.spec.whatwg.org/#active-script
RootedObject resolveFuncObj(aCx, JS_GetFunctionObject(resolveFunc));
js::SetFunctionNativeReserved(resolveFuncObj, ModulePrivateSlot,
aReferencingPrivate);

return true;
}

// static
Expand Down
12 changes: 12 additions & 0 deletions js/loader/ModuleLoaderBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,10 @@ class ModuleLoaderBase : public nsISupports {
static bool HostPopulateImportMeta(JSContext* aCx,
JS::Handle<JS::Value> aReferencingPrivate,
JS::Handle<JSObject*> aMetaObject);
static bool ImportMetaResolve(JSContext* cx, unsigned argc, Value* vp);
static JSString* ImportMetaResolveImpl(
JSContext* aCx, JS::Handle<JS::Value> aReferencingPrivate,
JS::Handle<JSString*> aSpecifier);
static bool HostImportModuleDynamically(
JSContext* aCx, JS::Handle<JS::Value> aReferencingPrivate,
JS::Handle<JSObject*> aModuleRequest, JS::Handle<JSObject*> aPromise);
Expand Down Expand Up @@ -378,6 +382,14 @@ class ModuleLoaderBase : public nsISupports {

nsresult CreateModuleScript(ModuleLoadRequest* aRequest);

// The slot stored in ImportMetaResolve function.
enum { ModulePrivateSlot = 0, SlotCount };

// The number of args in ImportMetaResolve.
static const uint32_t ImportMetaResolveNumArgs = 1;
// The index of the 'specifier' argument in ImportMetaResolve.
static const uint32_t ImportMetaResolveSpecifierArg = 0;

public:
static mozilla::LazyLogModule gCspPRLog;
static mozilla::LazyLogModule gModuleLoaderBaseLog;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,2 @@
[import-meta-resolve-importmap.html]
[import.meta.resolve() given an import mapped bare specifier]
expected: FAIL

[import.meta.resolve() given an import mapped URL-like specifier]
expected: FAIL

[Testing the ToString() step of import.meta.resolve() via import maps]
expected: FAIL

[import(import.meta.resolve(x)) can be different from import(x)]
expected: FAIL
prefs: [dom.importMaps.enabled:true]

This file was deleted.

0 comments on commit d17eb87

Please sign in to comment.