diff --git a/src/browser/js/Caller.zig b/src/browser/js/Caller.zig index d5aeb85cf..a1844976f 100644 --- a/src/browser/js/Caller.zig +++ b/src/browser/js/Caller.zig @@ -510,31 +510,24 @@ fn errorLocal(comptime T: type, local: *const Local, info: anytype) Local { return local.*; } + const frame = switch (local.ctx.global) { + .frame => |f| f, + .worker => return local.*, + }; + const Node = @import("../webapi/Node.zig"); const Document = @import("../webapi/Document.zig"); const is_node_type = comptime blk: { - if (@typeInfo(T) != .@"struct") break :blk false; - if (T == Node) break :blk true; - break :blk @hasDecl(T, "JsApi") and @hasDecl(T, "asNode") and @TypeOf(T.asNode) == fn (*T) *Node; + if (@typeInfo(T) != .@"struct" or !@hasDecl(T, "JsApi")) break :blk false; + break :blk @import("bridge.zig").inheritsOrIs(T.JsApi, Node.JsApi); }; if (comptime !is_node_type) { return local.*; } - const node: *Node = blk: { - const instance = TaggedOpaque.fromJS(*T, info.getThis()) catch return local.*; - if (comptime T == Node) { - break :blk instance; - } else { - break :blk instance.asNode(); - } - }; - - const frame = switch (local.ctx.global) { - .frame => |f| f, - .worker => return local.*, - }; + const instance = TaggedOpaque.fromJS(*T, info.getThis()) catch return local.*; + const node = protoNode(T, instance); const doc: *Document = node.ownerDocument(frame) orelse switch (node._type) { .document => |d| d, @@ -546,7 +539,7 @@ fn errorLocal(comptime T: type, local: *const Local, info: anytype) Local { } const ctx = doc_frame.js; - const local_v8_context: *const v8.Context = @ptrCast(v8.v8__Global__Get(&ctx.handle, ctx.isolate.handle)); + const local_v8_context: *const v8.Context = @ptrCast(v8.v8__Global__Get(&ctx.handle, ctx.isolate.handle) orelse return local.*); return .{ .ctx = ctx, .handle = local_v8_context, @@ -555,6 +548,17 @@ fn errorLocal(comptime T: type, local: *const Local, info: anytype) Local { }; } +// Upcast a Node-descendant instance to *Node by walking the _proto chain. +// Not every node type defines an asNode() helper (e.g. Comment, Text), but +// inheritsOrIs guarantees Node is in the chain +fn protoNode(comptime T: type, instance: *T) *@import("../webapi/Node.zig") { + if (T == @import("../webapi/Node.zig")) { + return instance; + } + const Proto = @typeInfo(std.meta.fieldInfo(T, ._proto).type).pointer.child; + return protoNode(Proto, instance._proto); +} + fn handleError(comptime T: type, comptime F: type, local: *const Local, err: anyerror, info: anytype) void { const isolate = local.isolate; @@ -568,26 +572,32 @@ fn handleError(comptime T: type, comptime F: type, local: *const Local, err: any } } + // early exit + switch (err) { + error.TryCatchRethrow => return, + // A JS exception is already pending in the isolate (e.g. a value's + // toString threw during argument conversion); throwing anything here + // would replace the original exception the script expects to see. + error.JsException => return, + else => {}, + } + const err_local = errorLocal(T, local, info); const js_err: *const v8.Value = blk: { // Error constructors use the isolate's current context: enter the // receiver's realm so the exception gets its prototypes. - const entered = err_local.handle != local.handle; + const entered = err_local.ctx != local.ctx; if (entered) v8.v8__Context__Enter(err_local.handle); defer if (entered) v8.v8__Context__Exit(err_local.handle); break :blk switch (err) { - error.TryCatchRethrow => return, - // A JS exception is already pending in the isolate (e.g. a value's - // toString threw during argument conversion); throwing anything here - // would replace the original exception the script expects to see. - error.JsException => return, error.InvalidArgument => isolate.createTypeError("invalid argument"), error.TypeError => isolate.createTypeError(""), error.RangeError => isolate.createRangeError(""), error.OutOfMemory => isolate.createError("out of memory"), error.IllegalConstructor => isolate.createError("Illegal Constructor"), + error.TryCatchRethrow, error.JsException => unreachable, // early exited a few lines up else => domExceptionToJs(&err_local, err) orelse isolate.createError(@errorName(err)), }; }; diff --git a/src/browser/js/Snapshot.zig b/src/browser/js/Snapshot.zig index 80b660de5..7441051ea 100644 --- a/src/browser/js/Snapshot.zig +++ b/src/browser/js/Snapshot.zig @@ -737,7 +737,7 @@ fn inheritsFromHtmlElement(comptime JsApi: type) bool { if (JsApi.bridge.type == HtmlElement) { return false; } - return inheritsOrIs(JsApi, HtmlElement.JsApi); + return bridge.inheritsOrIs(JsApi, HtmlElement.JsApi); } const Unforgeable = struct { @@ -874,7 +874,7 @@ fn attachClass(comptime JsApi: type, comptime flatten: bool, isolate: *v8.Isolat if (comptime flatten == false) { inline for (unforgeables) |u| { - if (comptime inheritsOrIs(JsApi, u.Owner)) { + if (comptime bridge.inheritsOrIs(JsApi, u.Owner)) { // unforgeables attributes aren't only applied directly on the // instance, they're also applied directly on every child instance attachAccessorProperty(u.name, u.accessor, isolate, template, signature, instance); @@ -949,23 +949,6 @@ const unforgeables: []const Unforgeable = blk: { break :blk list; }; -// Whether Child is Ancestor or inherits from it, following the _proto chain. -fn inheritsOrIs(comptime Child: type, comptime Ancestor: type) bool { - comptime { - const target = Ancestor.bridge.type; - var T = Child.bridge.type; - while (true) { - if (T == target) { - return true; - } - if (!@hasField(T, "_proto")) { - return false; - } - T = @typeInfo(std.meta.fieldInfo(T, ._proto).type).pointer.child; - } - } -} - // Attach JsApi members to a template (public for reuse). This is called on all // types. But, for globals (window, WGS) it's called twice. The first time, it's // called like any other interface. The 2nd time, it's called with flatten == true diff --git a/src/browser/js/bridge.zig b/src/browser/js/bridge.zig index ff6091240..7bbd62a48 100644 --- a/src/browser/js/bridge.zig +++ b/src/browser/js/bridge.zig @@ -1269,3 +1269,20 @@ pub const JsApis = blk: { } break :blk base ++ [_]type{@import("../webapi/WebDriver.zig").JsApi}; }; + +// Whether Child is Ancestor or inherits from it, following the _proto chain. +pub fn inheritsOrIs(comptime Child: type, comptime Ancestor: type) bool { + comptime { + const target = Ancestor.bridge.type; + var T = Child.bridge.type; + while (true) { + if (T == target) { + return true; + } + if (!@hasField(T, "_proto")) { + return false; + } + T = @typeInfo(std.meta.fieldInfo(T, ._proto).type).pointer.child; + } + } +} diff --git a/src/browser/webapi/Document.zig b/src/browser/webapi/Document.zig index c4795d218..b34de50ba 100644 --- a/src/browser/webapi/Document.zig +++ b/src/browser/webapi/Document.zig @@ -108,24 +108,6 @@ pub fn setOnClick(self: *Document, setter: ?Window.FunctionSetter, frame: *Frame } } -// Generates a getter/setter pair backed by the frame's attribute-listener -// map, like onclick above, for other document event handler properties. -fn handlerAccessor(comptime handler: @import("global_event_handlers.zig").Handler) type { - return struct { - pub fn get(self: *Document, frame: *Frame) ?js.Function.Global { - return frame._event_target_attr_listeners.get(.{ .target = self.asEventTarget(), .handler = handler }); - } - - pub fn set(self: *Document, setter: ?Window.FunctionSetter, frame: *Frame) !void { - if (Window.getFunctionFromSetter(setter)) |cb| { - try frame._event_target_attr_listeners.put(frame.arena, .{ .target = self.asEventTarget(), .handler = handler }, cb); - } else { - _ = frame._event_target_attr_listeners.remove(.{ .target = self.asEventTarget(), .handler = handler }); - } - } - }; -} - pub const Type = union(enum) { generic, html: *HTMLDocument, @@ -1530,6 +1512,26 @@ pub const JsApi = struct { return doc_frame.charset; } pub const referrer = bridge.property("", .{ .template = false }); + + // Generates a getter/setter pair backed by the frame's attribute-listener + // map, like onclick above, for other document event handler properties. + fn handlerAccessor(comptime handler: @import("global_event_handlers.zig").Handler) type { + return struct { + pub fn get(self: *Document, frame: *Frame) ?js.Function.Global { + const owner = self._frame orelse frame; + return owner._event_target_attr_listeners.get(.{ .target = self.asEventTarget(), .handler = handler }); + } + + pub fn set(self: *Document, setter: ?Window.FunctionSetter, frame: *Frame) !void { + const owner = self._frame orelse frame; + if (Window.getFunctionFromSetter(setter)) |cb| { + try owner._event_target_attr_listeners.put(owner.arena, .{ .target = self.asEventTarget(), .handler = handler }, cb); + } else { + _ = owner._event_target_attr_listeners.remove(.{ .target = self.asEventTarget(), .handler = handler }); + } + } + }; + } }; const testing = @import("../../testing.zig"); diff --git a/src/browser/webapi/event/DeviceMotionEvent.zig b/src/browser/webapi/event/DeviceMotionEvent.zig index bae8d8b9d..6da445f79 100644 --- a/src/browser/webapi/event/DeviceMotionEvent.zig +++ b/src/browser/webapi/event/DeviceMotionEvent.zig @@ -89,8 +89,8 @@ pub const JsApi = struct { }; pub const constructor = bridge.constructor(DeviceMotionEvent.init, .{}); - pub const acceleration = bridge.accessor(DeviceMotionEvent.getAcceleration, null, .{ .null_as_undefined = false }); - pub const accelerationIncludingGravity = bridge.accessor(DeviceMotionEvent.getAccelerationIncludingGravity, null, .{ .null_as_undefined = false }); - pub const rotationRate = bridge.accessor(DeviceMotionEvent.getRotationRate, null, .{ .null_as_undefined = false }); + pub const acceleration = bridge.accessor(DeviceMotionEvent.getAcceleration, null, .{}); + pub const accelerationIncludingGravity = bridge.accessor(DeviceMotionEvent.getAccelerationIncludingGravity, null, .{}); + pub const rotationRate = bridge.accessor(DeviceMotionEvent.getRotationRate, null, .{}); pub const interval = bridge.accessor(DeviceMotionEvent.getInterval, null, .{}); }; diff --git a/src/browser/webapi/event/DeviceOrientationEvent.zig b/src/browser/webapi/event/DeviceOrientationEvent.zig index 84fecb265..267611c85 100644 --- a/src/browser/webapi/event/DeviceOrientationEvent.zig +++ b/src/browser/webapi/event/DeviceOrientationEvent.zig @@ -97,8 +97,8 @@ pub const JsApi = struct { }; pub const constructor = bridge.constructor(DeviceOrientationEvent.init, .{}); - pub const alpha = bridge.accessor(DeviceOrientationEvent.getAlpha, null, .{ .null_as_undefined = false }); - pub const beta = bridge.accessor(DeviceOrientationEvent.getBeta, null, .{ .null_as_undefined = false }); - pub const gamma = bridge.accessor(DeviceOrientationEvent.getGamma, null, .{ .null_as_undefined = false }); + pub const alpha = bridge.accessor(DeviceOrientationEvent.getAlpha, null, .{}); + pub const beta = bridge.accessor(DeviceOrientationEvent.getBeta, null, .{}); + pub const gamma = bridge.accessor(DeviceOrientationEvent.getGamma, null, .{}); pub const absolute = bridge.accessor(DeviceOrientationEvent.getAbsolute, null, .{}); }; diff --git a/src/browser/webapi/event/StorageEvent.zig b/src/browser/webapi/event/StorageEvent.zig index 66465029c..bb299a2b9 100644 --- a/src/browser/webapi/event/StorageEvent.zig +++ b/src/browser/webapi/event/StorageEvent.zig @@ -111,7 +111,6 @@ pub fn initStorageEvent( old_value: ?[]const u8, new_value: ?[]const u8, url: ?[]const u8, - frame: *Frame, ) !void { const event = self._proto; if (event._event_phase != .none) { @@ -127,7 +126,6 @@ pub fn initStorageEvent( self._old_value = if (old_value) |v| try arena.dupe(u8, v) else null; self._new_value = if (new_value) |v| try arena.dupe(u8, v) else null; self._url = if (url) |u| try arena.dupe(u8, u) else ""; - _ = frame; } pub const JsApi = struct { @@ -140,10 +138,10 @@ pub const JsApi = struct { }; pub const constructor = bridge.constructor(StorageEvent.init, .{}); - pub const key = bridge.accessor(StorageEvent.getKey, null, .{ .null_as_undefined = false }); - pub const oldValue = bridge.accessor(StorageEvent.getOldValue, null, .{ .null_as_undefined = false }); - pub const newValue = bridge.accessor(StorageEvent.getNewValue, null, .{ .null_as_undefined = false }); + pub const key = bridge.accessor(StorageEvent.getKey, null, .{}); + pub const oldValue = bridge.accessor(StorageEvent.getOldValue, null, .{}); + pub const newValue = bridge.accessor(StorageEvent.getNewValue, null, .{}); pub const url = bridge.accessor(StorageEvent.getUrl, null, .{}); - pub const storageArea = bridge.accessor(StorageEvent.getStorageArea, null, .{ .null_as_undefined = false }); + pub const storageArea = bridge.accessor(StorageEvent.getStorageArea, null, .{}); pub const initStorageEvent = bridge.function(StorageEvent.initStorageEvent, .{}); };