diff --git a/src/browser/js/Context.zig b/src/browser/js/Context.zig index fc7c3347..c4e0c456 100644 --- a/src/browser/js/Context.zig +++ b/src/browser/js/Context.zig @@ -610,7 +610,6 @@ pub fn dynamicModuleCallback( } pub fn metaObjectCallback(c_context: ?*v8.Context, c_module: ?*v8.Module, c_meta: ?*v8.Value) callconv(.c) void { - // @HandleScope implement this without a fat context/local.. const self = fromC(c_context.?).?; var local = js.Local{ .ctx = self, @@ -636,6 +635,66 @@ pub fn metaObjectCallback(c_context: ?*v8.Context, c_module: ?*v8.Module, c_meta if (!res) { log.err(.js, "import meta", .{ .err = error.FailedToSet }); } + + // import.meta.resolve(specifier) resolves against this module's URL, + // applying the document's importmap. The base is bound per-module so the + // function keeps working even when detached from import.meta. + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import.meta/resolve + const resolve_data = self.arena.create(ImportMetaResolveData) catch { + log.err(.js, "import meta", .{ .err = error.OutOfMemory }); + return; + }; + resolve_data.* = .{ .context = self, .base = url }; + + const resolve_fn = newFunctionWithData(&local, importMetaResolveCallback, @ptrCast(resolve_data)); + const resolve_value = js.Value{ .local = &local, .handle = @ptrCast(resolve_fn.handle) }; + const resolve_res = meta.defineOwnProperty("resolve", resolve_value, 0) orelse false; + if (!resolve_res) { + log.err(.js, "import meta", .{ .err = error.FailedToSet }); + } +} + +const ImportMetaResolveData = struct { + context: *Context, + base: [:0]const u8, +}; + +// Implements import.meta.resolve(specifier): resolves the specifier against the +// module's base URL (applying the document's importmap) and returns the +// absolute URL. +fn importMetaResolveCallback(callback_handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void { + var c: Caller = undefined; + if (!c.initFromHandle(callback_handle)) { + return; + } + defer c.deinit(); + + const l = &c.local; + const info = Caller.FunctionCallbackInfo{ .handle = callback_handle.? }; + const data: *ImportMetaResolveData = @ptrCast(@alignCast(info.getData() orelse return)); + const ctx = data.context; + const isolate = ctx.isolate; + + if (info.length() == 0) { + _ = isolate.throwException(isolate.createTypeError("import.meta.resolve requires a specifier")); + return; + } + + const specifier = info.getArg(0, l).toStringSliceZ() catch { + _ = isolate.throwException(isolate.createTypeError("invalid specifier")); + return; + }; + + const resolved = ctx.script_manager.resolveSpecifier(ctx.call_arena, data.base, specifier) catch { + _ = isolate.throwException(isolate.createTypeError("failed to resolve module specifier")); + return; + }; + + const result = l.zigValueToJs(resolved, .{}) catch { + _ = isolate.throwException(isolate.createTypeError("failed to resolve module specifier")); + return; + }; + info.getReturnValue().set(result); } fn _resolveModuleCallback(self: *Context, referrer: js.Module, specifier: [:0]const u8, local: *const js.Local) !?*const v8.Module { diff --git a/src/browser/tests/page/module.html b/src/browser/tests/page/module.html index ed3869a9..d01f0e02 100644 --- a/src/browser/tests/page/module.html +++ b/src/browser/tests/page/module.html @@ -5,6 +5,20 @@ testing.expectEqual('/src/browser/tests/page/module.html', new URL(import.meta.url).pathname) + + + +