enable conditionnal loading for polyfill

This commit is contained in:
Pierre Tachoire
2025-07-05 16:45:01 -07:00
parent 300428ddfb
commit 941dace7f9
9 changed files with 93 additions and 29 deletions

View File

@@ -95,6 +95,8 @@ pub const Page = struct {
state_pool: *std.heap.MemoryPool(State), state_pool: *std.heap.MemoryPool(State),
polyfill_loader: polyfill.Loader = .{},
pub fn init(self: *Page, arena: Allocator, session: *Session) !void { pub fn init(self: *Page, arena: Allocator, session: *Session) !void {
const browser = session.browser; const browser = session.browser;
self.* = .{ self.* = .{
@@ -117,10 +119,7 @@ pub const Page = struct {
}), }),
.main_context = undefined, .main_context = undefined,
}; };
self.main_context = try session.executor.createJsContext(&self.window, self, self, true); self.main_context = try session.executor.createJsContext(&self.window, self, self, true, Env.GlobalMissingCallback.init(&self.polyfill_loader));
// load polyfills
try polyfill.load(self.arena, self.main_context);
_ = try session.browser.app.loop.timeout(1 * std.time.ns_per_ms, &self.microtask_node); _ = try session.browser.app.loop.timeout(1 * std.time.ns_per_ms, &self.microtask_node);
// message loop must run only non-test env // message loop must run only non-test env

View File

@@ -16,7 +16,7 @@ test "Browser.fetch" {
var runner = try testing.jsRunner(testing.tracking_allocator, .{}); var runner = try testing.jsRunner(testing.tracking_allocator, .{});
defer runner.deinit(); defer runner.deinit();
try @import("polyfill.zig").load(testing.allocator, runner.page.main_context); try @import("polyfill.zig").Loader.load("fetch", source, runner.page.main_context);
try runner.testCases(&.{ try runner.testCases(&.{
.{ .{

View File

@@ -23,25 +23,36 @@ const log = @import("../../log.zig");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const Env = @import("../env.zig").Env; const Env = @import("../env.zig").Env;
const modules = [_]struct { pub const Loader = struct {
name: []const u8, done: struct {
source: []const u8, fetch: bool = false,
}{ } = .{},
.{ .name = "polyfill-fetch", .source = @import("fetch.zig").source },
};
pub fn load(allocator: Allocator, js_context: *Env.JsContext) !void { pub fn load(name: []const u8, source: []const u8, js_context: *Env.JsContext) !void {
var try_catch: Env.TryCatch = undefined; var try_catch: Env.TryCatch = undefined;
try_catch.init(js_context); try_catch.init(js_context);
defer try_catch.deinit(); defer try_catch.deinit();
for (modules) |m| { _ = js_context.exec(source, name) catch |err| {
_ = js_context.exec(m.source, m.name) catch |err| { if (try try_catch.err(js_context.call_arena)) |msg| {
if (try try_catch.err(allocator)) |msg| { log.fatal(.app, "polyfill error", .{ .name = name, .err = msg });
defer allocator.free(msg);
log.fatal(.app, "polyfill error", .{ .name = m.name, .err = msg });
} }
return err; return err;
}; };
} }
}
pub fn missing(self: *Loader, name: []const u8, js_context: *Env.JsContext) bool {
if (!self.done.fetch and std.mem.eql(u8, name, "fetch")) {
// load the polyfill once.
self.done.fetch = true;
const _name = "fetch";
const source = @import("fetch.zig").source;
log.debug(.polyfill, "dynamic load", .{ .property = name });
load(_name, source, js_context) catch |err| {
log.fatal(.app, "polyfill load", .{ .name = name, .err = err });
};
}
return false;
}
};

View File

@@ -569,7 +569,7 @@ const IsolatedWorld = struct {
// Currently we have only 1 page/frame and thus also only 1 state in the isolate world. // Currently we have only 1 page/frame and thus also only 1 state in the isolate world.
pub fn createContext(self: *IsolatedWorld, page: *Page) !void { pub fn createContext(self: *IsolatedWorld, page: *Page) !void {
if (self.executor.js_context != null) return error.Only1IsolatedContextSupported; if (self.executor.js_context != null) return error.Only1IsolatedContextSupported;
_ = try self.executor.createJsContext(&page.window, page, {}, false); _ = try self.executor.createJsContext(&page.window, page, {}, false, null);
} }
}; };

View File

@@ -284,9 +284,6 @@ pub fn pageCreated(bc: anytype, page: *Page) !void {
if (bc.isolated_world) |*isolated_world| { if (bc.isolated_world) |*isolated_world| {
// We need to recreate the isolated world context // We need to recreate the isolated world context
try isolated_world.createContext(page); try isolated_world.createContext(page);
const polyfill = @import("../../browser/polyfill/polyfill.zig");
try polyfill.load(bc.arena, &isolated_world.executor.js_context.?);
} }
} }

View File

@@ -39,6 +39,7 @@ pub const Scope = enum {
unknown_prop, unknown_prop,
web_api, web_api,
xhr, xhr,
polyfill,
}; };
const Opts = struct { const Opts = struct {

View File

@@ -126,8 +126,6 @@ fn run(
}); });
defer runner.deinit(); defer runner.deinit();
try polyfill.load(arena, runner.page.main_context);
// loop over the scripts. // loop over the scripts.
const doc = parser.documentHTMLToDocument(runner.page.window.document); const doc = parser.documentHTMLToDocument(runner.page.window.document);
const scripts = try parser.documentGetElementsByTagName(doc, "script"); const scripts = try parser.documentGetElementsByTagName(doc, "script");

View File

@@ -346,7 +346,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
// when the handle_scope is freed. // when the handle_scope is freed.
// We also maintain our own "context_arena" which allows us to have // We also maintain our own "context_arena" which allows us to have
// all page related memory easily managed. // all page related memory easily managed.
pub fn createJsContext(self: *ExecutionWorld, global: anytype, state: State, module_loader: anytype, enter: bool) !*JsContext { pub fn createJsContext(self: *ExecutionWorld, global: anytype, state: State, module_loader: anytype, enter: bool, global_callback: ?GlobalMissingCallback) !*JsContext {
std.debug.assert(self.js_context == null); std.debug.assert(self.js_context == null);
const ModuleLoader = switch (@typeInfo(@TypeOf(module_loader))) { const ModuleLoader = switch (@typeInfo(@TypeOf(module_loader))) {
@@ -375,6 +375,30 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
const global_template = js_global.getInstanceTemplate(); const global_template = js_global.getInstanceTemplate();
global_template.setInternalFieldCount(1); global_template.setInternalFieldCount(1);
// Configure the missing property callback on the global
// object.
if (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 {
const info = v8.PropertyCallbackInfo.initFromV8(raw_info);
const _isolate = info.getIsolate();
const v8_context = _isolate.getCurrentContext();
const js_context: *JsContext = @ptrFromInt(v8_context.getEmbedderData(1).castTo(v8.BigInt).getUint64());
const property = valueToString(js_context.call_arena, .{ .handle = c_name.? }, _isolate, v8_context) catch "???";
if (js_context.global_callback.?.missing(property, js_context)) {
return v8.Intercepted.Yes;
}
return v8.Intercepted.No;
}
}.callback,
.flags = v8.PropertyHandlerFlags.NonMasking | v8.PropertyHandlerFlags.OnlyInterceptStrings,
};
global_template.setNamedProperty(configuration, null);
}
// All the FunctionTemplates that we created and setup in Env.init // All the FunctionTemplates that we created and setup in Env.init
// are now going to get associated with our global instance. // are now going to get associated with our global instance.
inline for (Types, 0..) |s, i| { inline for (Types, 0..) |s, i| {
@@ -456,6 +480,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
.ptr = safe_module_loader, .ptr = safe_module_loader,
.func = ModuleLoader.fetchModuleSource, .func = ModuleLoader.fetchModuleSource,
}, },
.global_callback = global_callback,
}; };
var js_context = &self.js_context.?; var js_context = &self.js_context.?;
@@ -608,6 +633,9 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
// necessary to lookup/store the dependent module in the module_cache. // necessary to lookup/store the dependent module in the module_cache.
module_identifier: std.AutoHashMapUnmanaged(u32, []const u8) = .empty, module_identifier: std.AutoHashMapUnmanaged(u32, []const u8) = .empty,
// Global callback is called on missing property.
global_callback: ?GlobalMissingCallback = null,
const ModuleLoader = struct { const ModuleLoader = struct {
ptr: *anyopaque, ptr: *anyopaque,
func: *const fn (ptr: *anyopaque, specifier: []const u8) anyerror!?[]const u8, func: *const fn (ptr: *anyopaque, specifier: []const u8) anyerror!?[]const u8,
@@ -2546,6 +2574,35 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
self.callScopeEndFn(self.ptr); self.callScopeEndFn(self.ptr);
} }
}; };
// Callback called on global's property mssing.
// Return true to intercept the exectution or false to let the call
// continue the chain.
pub const GlobalMissingCallback = struct {
ptr: *anyopaque,
missingFn: *const fn (ptr: *anyopaque, name: []const u8, ctx: *JsContext) bool,
pub fn init(ptr: anytype) GlobalMissingCallback {
const T = @TypeOf(ptr);
const ptr_info = @typeInfo(T);
const gen = struct {
pub fn missing(pointer: *anyopaque, name: []const u8, ctx: *JsContext) bool {
const self: T = @ptrCast(@alignCast(pointer));
return ptr_info.pointer.child.missing(self, name, ctx);
}
};
return .{
.ptr = ptr,
.missingFn = gen.missing,
};
}
pub fn missing(self: GlobalMissingCallback, name: []const u8, ctx: *JsContext) bool {
return self.missingFn(self.ptr, name, ctx);
}
};
}; };
} }
@@ -3431,7 +3488,7 @@ fn valueToDetailString(arena: Allocator, value: v8.Value, isolate: v8.Isolate, v
if (debugValueToString(arena, value.castTo(v8.Object), isolate, v8_context)) |ds| { if (debugValueToString(arena, value.castTo(v8.Object), isolate, v8_context)) |ds| {
return ds; return ds;
} else |err| { } else |err| {
log.err(.js, "debug serialize value", .{.err = err}); log.err(.js, "debug serialize value", .{ .err = err });
} }
} }
} }

View File

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