mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-29 07:03:29 +00:00
add importmap support
This commit is contained in:
@@ -70,6 +70,10 @@ async_module_pool: std.heap.MemoryPool(AsyncModule),
|
||||
// and can be requested as needed.
|
||||
sync_modules: std.StringHashMapUnmanaged(*SyncModule),
|
||||
|
||||
// Mapping between module specifier and resolution.
|
||||
// see https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/script/type/importmap
|
||||
importmap: std.StringHashMapUnmanaged([]const u8),
|
||||
|
||||
const OrderList = std.DoublyLinkedList;
|
||||
|
||||
pub fn init(browser: *Browser, page: *Page) ScriptManager {
|
||||
@@ -80,6 +84,7 @@ pub fn init(browser: *Browser, page: *Page) ScriptManager {
|
||||
.asyncs = .{},
|
||||
.scripts = .{},
|
||||
.deferreds = .{},
|
||||
.importmap = .empty,
|
||||
.sync_modules = .empty,
|
||||
.is_evaluating = false,
|
||||
.allocator = allocator,
|
||||
@@ -106,6 +111,8 @@ pub fn deinit(self: *ScriptManager) void {
|
||||
self.async_module_pool.deinit();
|
||||
|
||||
self.sync_modules.deinit(self.allocator);
|
||||
// we don't deinit self.importmap b/c we use the page's arena for its
|
||||
// allocations.
|
||||
}
|
||||
|
||||
pub fn reset(self: *ScriptManager) void {
|
||||
@@ -115,6 +122,7 @@ pub fn reset(self: *ScriptManager) void {
|
||||
self.sync_module_pool.destroy(value_ptr.*);
|
||||
}
|
||||
self.sync_modules.clearRetainingCapacity();
|
||||
self.importmap.clearRetainingCapacity();
|
||||
|
||||
self.clearList(&self.asyncs);
|
||||
self.clearList(&self.scripts);
|
||||
@@ -164,6 +172,9 @@ pub fn addFromElement(self: *ScriptManager, element: *parser.Element, comptime c
|
||||
if (std.ascii.eqlIgnoreCase(script_type, "module")) {
|
||||
break :blk .module;
|
||||
}
|
||||
if (std.ascii.eqlIgnoreCase(script_type, "importmap")) {
|
||||
break :blk .importmap;
|
||||
}
|
||||
|
||||
// "type" could be anything, but only the above are ones we need to process.
|
||||
// Common other ones are application/json, application/ld+json, text/template
|
||||
@@ -249,7 +260,13 @@ pub fn addFromElement(self: *ScriptManager, element: *parser.Element, comptime c
|
||||
}
|
||||
|
||||
// Resolve a module specifier to an valid URL.
|
||||
pub fn resolveSpecifier(_: *ScriptManager, arena: Allocator, specifier: []const u8, base: []const u8) ![:0]const u8 {
|
||||
pub fn resolveSpecifier(self: *ScriptManager, arena: Allocator, _specifier: []const u8, base: []const u8) ![:0]const u8 {
|
||||
var specifier = _specifier;
|
||||
// If the specifier is mapped in the importmap, use it to resolve the path.
|
||||
if (self.importmap.get(specifier)) |s| {
|
||||
specifier = s;
|
||||
}
|
||||
|
||||
return URL.stitch(
|
||||
arena,
|
||||
specifier,
|
||||
@@ -462,6 +479,28 @@ fn errorCallback(ctx: *anyopaque, err: anyerror) void {
|
||||
script.errorCallback(err);
|
||||
}
|
||||
|
||||
fn parseImportmap(self: *ScriptManager, script: *const Script) !void {
|
||||
const content = script.source.content();
|
||||
|
||||
const Imports = struct {
|
||||
imports: std.json.ArrayHashMap([]const u8),
|
||||
};
|
||||
|
||||
const imports = try std.json.parseFromSliceLeaky(
|
||||
Imports,
|
||||
self.page.arena,
|
||||
content,
|
||||
.{ .allocate = .alloc_always },
|
||||
);
|
||||
|
||||
var iter = imports.imports.map.iterator();
|
||||
while (iter.next()) |entry| {
|
||||
try self.importmap.put(self.page.arena, entry.key_ptr.*, entry.value_ptr.*);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// A script which is pending execution.
|
||||
// It could be pending because:
|
||||
// (a) we're still downloading its content or
|
||||
@@ -591,6 +630,7 @@ const Script = struct {
|
||||
const Kind = enum {
|
||||
module,
|
||||
javascript,
|
||||
importmap,
|
||||
};
|
||||
|
||||
const Callback = union(enum) {
|
||||
@@ -631,6 +671,23 @@ const Script = struct {
|
||||
.cacheable = cacheable,
|
||||
});
|
||||
|
||||
// Handle importmap special case here: the content is a JSON containing
|
||||
// imports.
|
||||
if (self.kind == .importmap) {
|
||||
page.script_manager.parseImportmap(self) catch |err| {
|
||||
log.err(.browser, "parse importmap script", .{
|
||||
.err = err,
|
||||
.src = url,
|
||||
.kind = self.kind,
|
||||
.cacheable = cacheable,
|
||||
});
|
||||
self.executeCallback("onerror", page);
|
||||
return;
|
||||
};
|
||||
self.executeCallback("onload", page);
|
||||
return;
|
||||
}
|
||||
|
||||
const js_context = page.js;
|
||||
var try_catch: js.TryCatch = undefined;
|
||||
try_catch.init(js_context);
|
||||
@@ -644,6 +701,7 @@ const Script = struct {
|
||||
// We don't care about waiting for the evaluation here.
|
||||
js_context.module(false, content, url, cacheable) catch break :blk false;
|
||||
},
|
||||
.importmap => unreachable, // handled before the try/catch.
|
||||
}
|
||||
break :blk true;
|
||||
};
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
<script src="../../testing.js"></script>
|
||||
|
||||
<script id=importmap type=importmap>
|
||||
<script type=importmap>
|
||||
{
|
||||
"imports": {
|
||||
"core": "./import.js",
|
||||
"core": "./import.js"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user