add named access on the Window object

This commit is contained in:
Muki Kiboigo
2026-01-02 21:02:51 -08:00
parent dcde19de3c
commit a67f46b550
4 changed files with 69 additions and 30 deletions

View File

@@ -74,26 +74,23 @@ pub fn createContext(self: *ExecutionWorld, page: *Page, enter: bool) !*Context
const env = self.env;
const isolate = env.isolate;
const arena = self.context_arena.allocator();
var v8_context: v8.Context = blk: {
var temp_scope: v8.HandleScope = undefined;
v8.HandleScope.init(&temp_scope, isolate);
defer temp_scope.deinit();
if (comptime IS_DEBUG) {
// Getting this into the snapshot is tricky (anything involving the
// global is tricky). Easier to do here, and in debug mode, we're
// fine with paying the small perf hit.
const js_global = v8.FunctionTemplate.initDefault(isolate);
const global_template = js_global.getInstanceTemplate();
// Creates a global template that inherits from Window.
const global_template = @import("Snapshot.zig").createGlobalTemplate(isolate, env.templates);
global_template.setNamedProperty(v8.NamedPropertyHandlerConfiguration{
.getter = unknownPropertyCallback,
.flags = v8.PropertyHandlerFlags.NonMasking | v8.PropertyHandlerFlags.OnlyInterceptStrings,
}, null);
}
// Add the named property handler
global_template.setNamedProperty(v8.NamedPropertyHandlerConfiguration{
.getter = unknownPropertyCallback,
.flags = v8.PropertyHandlerFlags.NonMasking | v8.PropertyHandlerFlags.OnlyInterceptStrings,
}, v8.External.init(isolate, page));
const context_local = v8.Context.init(isolate, null, null);
const context_local = v8.Context.init(isolate, global_template, null);
const v8_context = v8.Persistent(v8.Context).init(isolate, context_local).castToContext();
break :blk v8_context;
};
@@ -124,7 +121,7 @@ pub fn createContext(self: *ExecutionWorld, page: *Page, enter: bool) !*Context
.handle_scope = handle_scope,
.script_manager = &page._script_manager,
.call_arena = page.call_arena,
.arena = self.context_arena.allocator(),
.arena = arena,
};
var context = &self.context.?;
@@ -159,9 +156,13 @@ pub fn resumeExecution(self: *const ExecutionWorld) void {
pub fn unknownPropertyCallback(c_name: ?*const v8.C_Name, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) u8 {
const info = v8.PropertyCallbackInfo.initFromV8(raw_info);
const context = Context.fromIsolate(info.getIsolate());
const property = context.valueToString(.{ .handle = c_name.? }, .{}) catch "???";
const data_value = info.getData();
const external = data_value.castTo(v8.External);
const page: *Page = @ptrCast(@alignCast(external.get()));
const context = Context.fromIsolate(info.getIsolate());
const maybe_property: ?[]u8 = context.valueToString(.{ .handle = c_name.? }, .{}) catch null;
const ignored = std.StaticStringMap(void).initComptime(.{
.{ "process", {} },
@@ -185,12 +186,25 @@ pub fn unknownPropertyCallback(c_name: ?*const v8.C_Name, raw_info: ?*const v8.C
.{ "CLOSURE_FLAGS", {} },
});
if (!ignored.has(property)) {
log.debug(.unknown_prop, "unkown global property", .{
.info = "but the property can exist in pure JS",
.stack = context.stackTrace() catch "???",
.property = property,
});
if (maybe_property) |prop| {
if (!ignored.has(prop)) {
const document = page.document;
if (document.getElementById(prop)) |el| {
const js_value = context.zigValueToJs(el, .{}) catch {
return v8.Intercepted.No;
};
info.getReturnValue().set(js_value);
return v8.Intercepted.Yes;
}
log.debug(.unknown_prop, "unknown global property", .{
.info = "but the property can exist in pure JS",
.stack = context.stackTrace() catch "???",
.property = prop,
});
}
}
return v8.Intercepted.No;

View File

@@ -54,7 +54,7 @@ pub fn set(self: Object, key: []const u8, value: anytype, opts: SetOpts) error{
const context = self.context;
const js_key = v8.String.initUtf8(context.isolate, key);
const js_value = try context.zigValueToJs(value);
const js_value = try context.zigValueToJs(value, .{});
const res = self.js_obj.defineOwnProperty(context.v8_context, js_key.toName(), js_value, @bitCast(opts)) orelse false;
if (!res) {

View File

@@ -113,6 +113,17 @@ fn isValid(self: Snapshot) bool {
return v8.SnapshotCreator.startupDataIsValid(self.startup_data);
}
pub fn createGlobalTemplate(isolate: v8.Isolate, templates: []const v8.FunctionTemplate) v8.ObjectTemplate {
// Set up the global template to inherit from Window's template
// This way the global object gets all Window properties through inheritance
const js_global = v8.FunctionTemplate.initDefault(isolate);
js_global.setClassName(v8.String.initUtf8(isolate, "Window"));
// Find Window in JsApis by name (avoids circular import)
const window_index = comptime bridge.JsApiLookup.getId(Window.JsApi);
js_global.inherit(templates[window_index]);
return js_global.getInstanceTemplate();
}
pub fn create(allocator: Allocator) !Snapshot {
var external_references = collectExternalReferences();
@@ -154,14 +165,7 @@ pub fn create(allocator: Allocator) !Snapshot {
// Set up the global template to inherit from Window's template
// This way the global object gets all Window properties through inheritance
const js_global = v8.FunctionTemplate.initDefault(isolate);
js_global.setClassName(v8.String.initUtf8(isolate, "Window"));
// Find Window in JsApis by name (avoids circular import)
const window_index = comptime bridge.JsApiLookup.getId(Window.JsApi);
js_global.inherit(templates[window_index]);
const global_template = js_global.getInstanceTemplate();
const global_template = createGlobalTemplate(isolate, templates[0..]);
const context = v8.Context.init(isolate, global_template, null);
context.enter();

View File

@@ -0,0 +1,21 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<div id=i></div>
<div id=testDiv></div>
<span id=mySpan></span>
<p id=paragraph></p>
<script id=named_access_global>
testing.expectEqual('i', i.id);
testing.expectEqual('testDiv',testDiv.id);
testing.expectEqual('mySpan', mySpan.id);
testing.expectEqual('paragraph', paragraph.id);
</script>
<script id=named_access_window>
testing.expectEqual('i', window.i.id);
testing.expectEqual('testDiv', window.testDiv.id);
testing.expectEqual('mySpan', window.mySpan.id);
testing.expectEqual('paragraph', window.paragraph.id);
</script>