From 4a9a4cbc0117b57cb969c921d3300815a990cd9d Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Fri, 11 Jul 2025 17:20:52 -0700 Subject: [PATCH] add CompilationCallback and load polyfill depending the source content --- src/browser/page.zig | 5 +- src/browser/polyfill/polyfill.zig | 21 +++++++++ src/browser/polyfill/webcomponents.zig | 2 - src/cdp/cdp.zig | 5 +- src/runtime/js.zig | 65 ++++++++++++++++++++++++-- src/runtime/testing.zig | 2 +- 6 files changed, 91 insertions(+), 9 deletions(-) diff --git a/src/browser/page.zig b/src/browser/page.zig index 84b08b59..50da86e9 100644 --- a/src/browser/page.zig +++ b/src/browser/page.zig @@ -119,7 +119,10 @@ pub const Page = struct { }), .main_context = undefined, }; - self.main_context = try session.executor.createJsContext(&self.window, self, self, true, Env.GlobalMissingCallback.init(&self.polyfill_loader)); + self.main_context = try session.executor.createJsContext(&self.window, self, self, true, .{ + .global_callback = Env.GlobalMissingCallback.init(&self.polyfill_loader), + .compilation_callback = Env.CompilationCallback.init(&self.polyfill_loader), + }); // message loop must run only non-test env if (comptime !builtin.is_test) { diff --git a/src/browser/polyfill/polyfill.zig b/src/browser/polyfill/polyfill.zig index 899eaa89..51140e9d 100644 --- a/src/browser/polyfill/polyfill.zig +++ b/src/browser/polyfill/polyfill.zig @@ -50,6 +50,23 @@ pub const Loader = struct { @field(self.done, name) = true; } + // CompilationCallback implementation + pub fn script(self: *Loader, src: []const u8, _: ?[]const u8, js_context: *Env.JsContext) void { + if (!self.done.webcomponents and containsWebcomponents(src)) { + const source = @import("webcomponents.zig").source; + self.load("webcomponents", source, js_context); + } + } + + // CompilationCallback implementation + pub fn module(self: *Loader, src: []const u8, _: []const u8, js_context: *Env.JsContext) void { + if (!self.done.webcomponents and containsWebcomponents(src)) { + const source = @import("webcomponents.zig").source; + self.load("webcomponents", source, js_context); + } + } + + // GlobalMissingCallback implementation pub fn missing(self: *Loader, name: []const u8, js_context: *Env.JsContext) bool { // Avoid recursive calls during polyfill loading. if (self.state == .loading) { @@ -102,4 +119,8 @@ pub const Loader = struct { if (std.mem.eql(u8, name, "customElements")) return true; return false; } + + fn containsWebcomponents(src: []const u8) bool { + return std.mem.indexOf(u8, src, " extends ") != null; + } }; diff --git a/src/browser/polyfill/webcomponents.zig b/src/browser/polyfill/webcomponents.zig index 8e76dff6..4dc38484 100644 --- a/src/browser/polyfill/webcomponents.zig +++ b/src/browser/polyfill/webcomponents.zig @@ -13,8 +13,6 @@ test "Browser.webcomponents" { try runner.testCases(&.{ .{ - \\ window.customElements; // temporarily needed, lazy loading doesn't work! - \\ \\ class LightPanda extends HTMLElement { \\ constructor() { \\ super(); diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index 2472481a..b90deb01 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -580,7 +580,10 @@ const IsolatedWorld = struct { page, {}, false, - Env.GlobalMissingCallback.init(&self.polyfill_loader), + .{ + .global_callback = Env.GlobalMissingCallback.init(&self.polyfill_loader), + .compilation_callback = Env.CompilationCallback.init(&self.polyfill_loader), + }, ); } }; diff --git a/src/runtime/js.zig b/src/runtime/js.zig index c99fd129..6dbd32f2 100644 --- a/src/runtime/js.zig +++ b/src/runtime/js.zig @@ -364,13 +364,25 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { self.context_arena.deinit(); } + pub const CreateJsContextOpt = struct { + global_callback: ?GlobalMissingCallback = null, + compilation_callback: ?CompilationCallback = null, + }; + // Only the top JsContext in the Main ExecutionWorld should hold a handle_scope. // A v8.HandleScope is like an arena. Once created, any "Local" that // v8 creates will be released (or at least, releasable by the v8 GC) // when the handle_scope is freed. // We also maintain our own "context_arena" which allows us to have // all page related memory easily managed. - pub fn createJsContext(self: *ExecutionWorld, global: anytype, state: State, module_loader: anytype, enter: bool, global_callback: ?GlobalMissingCallback) !*JsContext { + pub fn createJsContext( + self: *ExecutionWorld, + global: anytype, + state: State, + module_loader: anytype, + enter: bool, + opt: CreateJsContextOpt, + ) !*JsContext { std.debug.assert(self.js_context == null); const ModuleLoader = switch (@typeInfo(@TypeOf(module_loader))) { @@ -401,7 +413,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { // Configure the missing property callback on the global // object. - if (global_callback != null) { + if (opt.global_callback != null) { const configuration = v8.NamedPropertyHandlerConfiguration{ .getter = struct { fn callback(c_name: ?*const v8.C_Name, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) u8 { @@ -505,7 +517,8 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { .ptr = safe_module_loader, .func = ModuleLoader.fetchModuleSource, }, - .global_callback = global_callback, + .global_callback = opt.global_callback, + .compilation_callback = opt.compilation_callback, }; var js_context = &self.js_context.?; @@ -658,9 +671,12 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { // necessary to lookup/store the dependent module in the module_cache. module_identifier: std.AutoHashMapUnmanaged(u32, []const u8) = .empty, - // Global callback is called on missing property. + // Global callback is called when a property is missing on the + // global object. global_callback: ?GlobalMissingCallback = null, + compilation_callback: ?CompilationCallback = null, + const ModuleLoader = struct { ptr: *anyopaque, func: *const fn (ptr: *anyopaque, specifier: []const u8) anyerror!?[]const u8, @@ -747,6 +763,8 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { } pub fn exec(self: *JsContext, src: []const u8, name: ?[]const u8) !Value { + if (self.compilation_callback) |cbk| cbk.script(src, name, self); + const isolate = self.isolate; const v8_context = self.v8_context; @@ -770,6 +788,8 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { // compile and eval a JS module // It doesn't wait for callbacks execution pub fn module(self: *JsContext, src: []const u8, url: []const u8, cacheable: bool) !void { + if (self.compilation_callback) |cbk| cbk.module(src, url, self); + if (!cacheable) { return self.moduleNoCache(src, url); } @@ -2604,6 +2624,43 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { return self.missingFn(self.ptr, name, ctx); } }; + + // CompilationCallback called before script and module compilation. + pub const CompilationCallback = struct { + ptr: *anyopaque, + scriptFn: *const fn (ptr: *anyopaque, source: []const u8, name: ?[]const u8, ctx: *JsContext) void, + moduleFn: *const fn (ptr: *anyopaque, source: []const u8, url: []const u8, ctx: *JsContext) void, + + pub fn init(ptr: anytype) CompilationCallback { + const T = @TypeOf(ptr); + const ptr_info = @typeInfo(T); + + const gen = struct { + pub fn script(pointer: *anyopaque, source: []const u8, name: ?[]const u8, ctx: *JsContext) void { + const self: T = @ptrCast(@alignCast(pointer)); + return ptr_info.pointer.child.script(self, source, name, ctx); + } + pub fn module(pointer: *anyopaque, source: []const u8, url: []const u8, ctx: *JsContext) void { + const self: T = @ptrCast(@alignCast(pointer)); + return ptr_info.pointer.child.module(self, source, url, ctx); + } + }; + + return .{ + .ptr = ptr, + .scriptFn = gen.script, + .moduleFn = gen.module, + }; + } + + pub fn script(self: CompilationCallback, source: []const u8, name: ?[]const u8, ctx: *JsContext) void { + return self.scriptFn(self.ptr, source, name, ctx); + } + + pub fn module(self: CompilationCallback, source: []const u8, url: []const u8, ctx: *JsContext) void { + return self.moduleFn(self.ptr, source, url, ctx); + } + }; }; } diff --git a/src/runtime/testing.zig b/src/runtime/testing.zig index 87246d8e..20f3d367 100644 --- a/src/runtime/testing.zig +++ b/src/runtime/testing.zig @@ -53,7 +53,7 @@ pub fn Runner(comptime State: type, comptime Global: type, comptime types: anyty state, {}, true, - null, + .{}, ); return self; }