add CompilationCallback

and load polyfill depending the source content
This commit is contained in:
Pierre Tachoire
2025-07-11 17:20:52 -07:00
parent 818f4540fd
commit 4a9a4cbc01
6 changed files with 91 additions and 9 deletions

View File

@@ -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) {

View File

@@ -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;
}
};

View File

@@ -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();

View File

@@ -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),
},
);
}
};

View File

@@ -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);
}
};
};
}

View File

@@ -53,7 +53,7 @@ pub fn Runner(comptime State: type, comptime Global: type, comptime types: anyty
state,
{},
true,
null,
.{},
);
return self;
}