From 25dbac994553c92d08944d2fe2d2382aaffff1f4 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Fri, 26 Dec 2025 08:45:20 +0800 Subject: [PATCH] event isTrusted support and better composedPath for shadowroots --- src/browser/EventManager.zig | 16 ++- src/browser/Page.zig | 9 +- src/browser/ScriptManager.zig | 2 +- src/browser/tests/event/keyboard.html | 15 +++ src/browser/tests/event/mouse.html | 15 +++ src/browser/tests/events.html | 18 +++ src/browser/tests/legacy/events/event.html | 2 +- src/browser/tests/shadowroot/events.html | 97 ++++++++++++++++ src/browser/webapi/AbortSignal.zig | 2 +- src/browser/webapi/Document.zig | 4 +- src/browser/webapi/Element.zig | 6 +- src/browser/webapi/Event.zig | 107 ++++++++++++++++-- src/browser/webapi/History.zig | 2 +- src/browser/webapi/MessagePort.zig | 2 +- src/browser/webapi/Range.zig | 4 +- src/browser/webapi/Window.zig | 8 +- src/browser/webapi/element/Html.zig | 1 + src/browser/webapi/event/CompositionEvent.zig | 2 +- src/browser/webapi/event/CustomEvent.zig | 12 +- src/browser/webapi/event/ErrorEvent.zig | 10 +- src/browser/webapi/event/KeyboardEvent.zig | 10 +- src/browser/webapi/event/MessageEvent.zig | 10 +- src/browser/webapi/event/MouseEvent.zig | 2 +- .../NavigationCurrentEntryChangeEvent.zig | 13 ++- .../webapi/event/PageTransitionEvent.zig | 10 +- src/browser/webapi/event/PopStateEvent.zig | 10 +- src/browser/webapi/event/ProgressEvent.zig | 10 +- src/browser/webapi/event/UIEvent.zig | 2 +- src/browser/webapi/navigation/Navigation.zig | 10 +- src/browser/webapi/net/XMLHttpRequest.zig | 2 +- .../webapi/net/XMLHttpRequestEventTarget.zig | 2 +- src/cdp/domains/input.zig | 2 +- 32 files changed, 357 insertions(+), 60 deletions(-) diff --git a/src/browser/EventManager.zig b/src/browser/EventManager.zig index 5ed22c45..0307662c 100644 --- a/src/browser/EventManager.zig +++ b/src/browser/EventManager.zig @@ -83,12 +83,14 @@ pub fn register(self: *EventManager, target: *EventTarget, typ: []const u8, call var node = gop.value_ptr.*.first; while (node) |n| { const listener: *Listener = @alignCast(@fieldParentPtr("node", n)); - const is_duplicate = switch (callback) { - .object => |obj| listener.function.eqlObject(obj), - .function => |func| listener.function.eqlFunction(func), - }; - if (is_duplicate and listener.capture == opts.capture) { - return; + if (listener.typ.eqlSlice(typ)) { + const is_duplicate = switch (callback) { + .object => |obj| listener.function.eqlObject(obj), + .function => |func| listener.function.eqlFunction(func), + }; + if (is_duplicate and listener.capture == opts.capture) { + return; + } } node = n.next; } @@ -132,6 +134,7 @@ pub fn dispatch(self: *EventManager, target: *EventTarget, event: *Event) !void } event._target = target; + event._dispatch_target = target; // Store original target for composedPath() var was_handled = false; defer if (was_handled) { @@ -173,6 +176,7 @@ pub fn dispatchWithFunction(self: *EventManager, target: *EventTarget, event: *E if (comptime opts.inject_target) { event._target = target; + event._dispatch_target = target; // Store original target for composedPath() } var was_dispatched = false; diff --git a/src/browser/Page.zig b/src/browser/Page.zig index 58b63dfb..48efcb44 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -503,7 +503,7 @@ pub fn documentIsLoaded(self: *Page) void { } pub fn _documentIsLoaded(self: *Page) !void { - const event = try Event.init("DOMContentLoaded", .{ .bubbles = true }, self); + const event = try Event.initTrusted("DOMContentLoaded", .{ .bubbles = true }, self); try self._event_manager.dispatch( self.document.asEventTarget(), event, @@ -549,7 +549,7 @@ fn _documentIsComplete(self: *Page) !void { self.document._ready_state = .complete; // dispatch window.load event - const event = try Event.init("load", .{}, self); + const event = try Event.initTrusted("load", .{}, self); // this event is weird, it's dispatched directly on the window, but // with the document as the target event._target = self.document.asEventTarget(); @@ -560,7 +560,7 @@ fn _documentIsComplete(self: *Page) !void { .{ .inject_target = false, .context = "page load" }, ); - const pageshow_event = try PageTransitionEvent.init("pageshow", .{}, self); + const pageshow_event = try PageTransitionEvent.initTrusted("pageshow", .{}, self); try self._event_manager.dispatchWithFunction( self.window.asEventTarget(), pageshow_event.asEvent(), @@ -1174,7 +1174,7 @@ pub fn deliverSlotchangeEvents(self: *Page) void { self._slots_pending_slotchange.clearRetainingCapacity(); for (slots) |slot| { - const event = Event.init("slotchange", .{ .bubbles = true }, self) catch |err| { + const event = Event.initTrusted("slotchange", .{ .bubbles = true }, self) catch |err| { log.err(.page, "deliverSlotchange.init", .{ .err = err }); continue; }; @@ -2419,6 +2419,7 @@ pub fn triggerMouseClick(self: *Page, x: f64, y: f64) !void { const event = try @import("webapi/event/MouseEvent.zig").init("click", .{ .bubbles = true, .cancelable = true, + .composed = true, .clientX = x, .clientY = y, }, self); diff --git a/src/browser/ScriptManager.zig b/src/browser/ScriptManager.zig index 363e1fa1..1936f390 100644 --- a/src/browser/ScriptManager.zig +++ b/src/browser/ScriptManager.zig @@ -833,7 +833,7 @@ pub const Script = struct { const cb = cb_ orelse return; const Event = @import("webapi/Event.zig"); - const event = Event.init(typ, .{}, page) catch |err| { + const event = Event.initTrusted(typ, .{}, page) catch |err| { log.warn(.js, "script internal callback", .{ .url = self.url, .type = typ, diff --git a/src/browser/tests/event/keyboard.html b/src/browser/tests/event/keyboard.html index 54419903..b6c3ddd8 100644 --- a/src/browser/tests/event/keyboard.html +++ b/src/browser/tests/event/keyboard.html @@ -88,3 +88,18 @@ testing.expectEqual(true, isKeyPress); + + diff --git a/src/browser/tests/event/mouse.html b/src/browser/tests/event/mouse.html index 4f8dd17f..896ba62b 100644 --- a/src/browser/tests/event/mouse.html +++ b/src/browser/tests/event/mouse.html @@ -35,3 +35,18 @@ testing.expectEqual('click', evt.type); testing.expectEqual(true, evt instanceof MouseEvent); + + diff --git a/src/browser/tests/events.html b/src/browser/tests/events.html index 84300217..a6706847 100644 --- a/src/browser/tests/events.html +++ b/src/browser/tests/events.html @@ -612,3 +612,21 @@ content.dispatchEvent(new Event('he2')); } + + diff --git a/src/browser/tests/legacy/events/event.html b/src/browser/tests/legacy/events/event.html index 752d64ba..a24cfbdb 100644 --- a/src/browser/tests/legacy/events/event.html +++ b/src/browser/tests/legacy/events/event.html @@ -27,7 +27,7 @@ testing.expectEqual(true, evt.bubbles); testing.expectEqual(true, evt.cancelable); testing.expectEqual(true, evt.defaultPrevented); - testing.expectEqual(true, evt.isTrusted); + testing.expectEqual(false, evt.isTrusted); testing.expectEqual(true, evt.timeStamp >= Math.floor(startTime)); diff --git a/src/browser/tests/shadowroot/events.html b/src/browser/tests/shadowroot/events.html index de6f7cdc..c0832f21 100644 --- a/src/browser/tests/shadowroot/events.html +++ b/src/browser/tests/shadowroot/events.html @@ -184,3 +184,100 @@ host.remove(); } + + + + + + diff --git a/src/browser/webapi/AbortSignal.zig b/src/browser/webapi/AbortSignal.zig index 9d01ef64..c4b0f7ed 100644 --- a/src/browser/webapi/AbortSignal.zig +++ b/src/browser/webapi/AbortSignal.zig @@ -80,7 +80,7 @@ pub fn abort(self: *AbortSignal, reason_: ?Reason, page: *Page) !void { } // Dispatch abort event - const event = try Event.init("abort", .{}, page); + const event = try Event.initTrusted("abort", .{}, page); try page._event_manager.dispatchWithFunction( self.asEventTarget(), event, diff --git a/src/browser/webapi/Document.zig b/src/browser/webapi/Document.zig index a4d20ed1..70289a37 100644 --- a/src/browser/webapi/Document.zig +++ b/src/browser/webapi/Document.zig @@ -251,7 +251,7 @@ pub fn createTextNode(_: *const Document, data: []const u8, page: *Page) !*Node pub fn createCDATASection(self: *const Document, data: []const u8, page: *Page) !*Node { switch (self._type) { - .html => return error.NotSupported, // cannot create a CDataSection in an HTMLDocument + .html => return error.NotSupported, // cannot create a CDataSection in an HTMLDocument .xml => return page.createCDATASection(data), .generic => return page.createCDATASection(data), } @@ -570,7 +570,7 @@ pub fn getChildElementCount(self: *Document) u32 { return i; } - pub fn getAdoptedStyleSheets(self: *Document, page: *Page) !js.Object { +pub fn getAdoptedStyleSheets(self: *Document, page: *Page) !js.Object { if (self._adopted_style_sheets) |ass| { return ass; } diff --git a/src/browser/webapi/Element.zig b/src/browser/webapi/Element.zig index 0fbf547f..a3c8ed0e 100644 --- a/src/browser/webapi/Element.zig +++ b/src/browser/webapi/Element.zig @@ -596,7 +596,7 @@ pub fn focus(self: *Element, page: *Page) !void { return; } - const blur_event = try Event.init("blur", null, page); + const blur_event = try Event.initTrusted("blur", null, page); try page._event_manager.dispatch(old.asEventTarget(), blur_event); } @@ -604,7 +604,7 @@ pub fn focus(self: *Element, page: *Page) !void { page.document._active_element = self; } - const focus_event = try Event.init("focus", null, page); + const focus_event = try Event.initTrusted("focus", null, page); try page._event_manager.dispatch(self.asEventTarget(), focus_event); } @@ -614,7 +614,7 @@ pub fn blur(self: *Element, page: *Page) !void { page.document._active_element = null; const Event = @import("Event.zig"); - const blur_event = try Event.init("blur", null, page); + const blur_event = try Event.initTrusted("blur", null, page); try page._event_manager.dispatch(self.asEventTarget(), blur_event); } diff --git a/src/browser/webapi/Event.zig b/src/browser/webapi/Event.zig index 7aa14cff..f32f21ab 100644 --- a/src/browser/webapi/Event.zig +++ b/src/browser/webapi/Event.zig @@ -35,12 +35,14 @@ _composed: bool = false, _type_string: String, _target: ?*EventTarget = null, _current_target: ?*EventTarget = null, +_dispatch_target: ?*EventTarget = null, // Original target for composedPath() _prevent_default: bool = false, _stop_propagation: bool = false, _stop_immediate_propagation: bool = false, _event_phase: EventPhase = .none, _time_stamp: u64 = 0, _needs_retargeting: bool = false, +_isTrusted: bool = false, pub const EventPhase = enum(u8) { none = 0, @@ -68,14 +70,22 @@ pub const Options = struct { composed: bool = false, }; +pub fn initTrusted(typ: []const u8, opts_: ?Options, page: *Page) !*Event { + return initWithTrusted(typ, opts_, true, page); +} + pub fn init(typ: []const u8, opts_: ?Options, page: *Page) !*Event { + return initWithTrusted(typ, opts_, false, page); +} + +fn initWithTrusted(typ: []const u8, opts_: ?Options, trusted: bool, page: *Page) !*Event { const opts = opts_ orelse Options{}; // Round to 2ms for privacy (browsers do this) const raw_timestamp = @import("../../datetime.zig").milliTimestamp(.monotonic); const time_stamp = (raw_timestamp / 2) * 2; - return page._factory.create(Event{ + const event = try page._factory.create(Event{ ._type = .generic, ._bubbles = opts.bubbles, ._time_stamp = time_stamp, @@ -83,6 +93,21 @@ pub fn init(typ: []const u8, opts_: ?Options, page: *Page) !*Event { ._composed = opts.composed, ._type_string = try String.init(page.arena, typ, .{}), }); + + event._isTrusted = trusted; + return event; +} + +pub fn initEvent( + self: *Event, + event_string: []const u8, + bubbles: ?bool, + cancelable: ?bool, + page: *Page, +) !void { + self._type_string = try String.init(page.arena, event_string, .{}); + self._bubbles = bubbles orelse false; + self._cancelable = cancelable orelse false; } pub fn as(self: *Event, comptime T: type) *T { @@ -159,14 +184,27 @@ pub fn getTimeStamp(self: *const Event) u64 { return self._time_stamp; } +pub fn setTrusted(self: *Event) void { + self._isTrusted = true; +} + +pub fn setUntrusted(self: *Event) void { + self._isTrusted = false; +} + +pub fn getIsTrusted(self: *const Event) bool { + return self._isTrusted; +} + pub fn composedPath(self: *Event, page: *Page) ![]const *EventTarget { // Return empty array if event is not being dispatched if (self._event_phase == .none) { return &.{}; } - // If there's no target, return empty array - const target = self._target orelse return &.{}; + // Use dispatch_target (original target) if available, otherwise fall back to target + // This is important because _target gets retargeted during event dispatch + const target = self._dispatch_target orelse self._target orelse return &.{}; // Only nodes have a propagation path const target_node = switch (target._type) { @@ -179,6 +217,9 @@ pub fn composedPath(self: *Event, page: *Page) ![]const *EventTarget { var path_buffer: [128]*EventTarget = undefined; var stopped_at_shadow_boundary = false; + // Track closed shadow boundaries (position in path and host position) + var closed_shadow_boundary: ?struct { shadow_end: usize, host_start: usize } = null; + var node: ?*Node = target_node; while (node) |n| { if (path_len >= path_buffer.len) { @@ -198,7 +239,17 @@ pub fn composedPath(self: *Event, page: *Page) ![]const *EventTarget { break; } - // Otherwise, jump to the shadow host and continue + // Track the first closed shadow boundary we encounter + if (shadow._mode == .closed and closed_shadow_boundary == null) { + // Mark where the shadow root is in the path + // The next element will be the host + closed_shadow_boundary = .{ + .shadow_end = path_len - 1, // index of shadow root + .host_start = path_len, // index where host will be + }; + } + + // Jump to the shadow host and continue node = shadow._host.asNode(); continue; } @@ -215,9 +266,40 @@ pub fn composedPath(self: *Event, page: *Page) ![]const *EventTarget { } } - // Allocate and return the path using call_arena (short-lived) - const path = try page.call_arena.alloc(*EventTarget, path_len); - @memcpy(path, path_buffer[0..path_len]); + // Determine visible path based on current_target and closed shadow boundaries + var visible_start_index: usize = 0; + + if (closed_shadow_boundary) |boundary| { + // Check if current_target is outside the closed shadow + // If current_target is null or is at/after the host position, hide shadow internals + const current_target = self._current_target; + + if (current_target) |ct| { + // Find current_target in the path + var ct_index: ?usize = null; + for (path_buffer[0..path_len], 0..) |elem, i| { + if (elem == ct) { + ct_index = i; + break; + } + } + + // If current_target is at or after the host (outside the closed shadow), + // hide everything from target up to the host + if (ct_index) |idx| { + if (idx >= boundary.host_start) { + visible_start_index = boundary.host_start; + } + } + } + } + + // Calculate the visible portion of the path + const visible_path_len = if (path_len > visible_start_index) path_len - visible_start_index else 0; + + // Allocate and return the visible path using call_arena (short-lived) + const path = try page.call_arena.alloc(*EventTarget, visible_path_len); + @memcpy(path, path_buffer[visible_start_index..path_len]); return path; } @@ -257,16 +339,21 @@ pub fn inheritOptions(comptime T: type, comptime additions: anytype) type { }); } -pub fn populatePrototypes(self: anytype, opts: anytype) void { +pub fn populatePrototypes(self: anytype, opts: anytype, trusted: bool) void { const T = @TypeOf(self.*); if (@hasField(T, "_proto")) { - populatePrototypes(self._proto, opts); + populatePrototypes(self._proto, opts, trusted); } if (@hasDecl(T, "populateFromOptions")) { T.populateFromOptions(self, opts); } + + // Set isTrusted at the Event level (base of prototype chain) + if (T == Event or @hasField(T, "_isTrusted")) { + self._isTrusted = trusted; + } } pub const JsApi = struct { @@ -289,10 +376,12 @@ pub const JsApi = struct { pub const eventPhase = bridge.accessor(Event.getEventPhase, null, .{}); pub const defaultPrevented = bridge.accessor(Event.getDefaultPrevented, null, .{}); pub const timeStamp = bridge.accessor(Event.getTimeStamp, null, .{}); + pub const isTrusted = bridge.accessor(Event.getIsTrusted, null, .{}); pub const preventDefault = bridge.function(Event.preventDefault, .{}); pub const stopPropagation = bridge.function(Event.stopPropagation, .{}); pub const stopImmediatePropagation = bridge.function(Event.stopImmediatePropagation, .{}); pub const composedPath = bridge.function(Event.composedPath, .{}); + pub const initEvent = bridge.function(Event.initEvent, .{}); // Event phase constants pub const NONE = bridge.property(@intFromEnum(EventPhase.none)); diff --git a/src/browser/webapi/History.zig b/src/browser/webapi/History.zig index 9511e4d4..5ff5c860 100644 --- a/src/browser/webapi/History.zig +++ b/src/browser/webapi/History.zig @@ -79,7 +79,7 @@ fn goInner(delta: i32, page: *Page) !void { if (entry._url) |url| { if (try page.isSameOrigin(url)) { - const event = try PopStateEvent.init("popstate", .{ .state = entry._state.value }, page); + const event = try PopStateEvent.initTrusted("popstate", .{ .state = entry._state.value }, page); try page._event_manager.dispatchWithFunction( page.window.asEventTarget(), diff --git a/src/browser/webapi/MessagePort.zig b/src/browser/webapi/MessagePort.zig index 4d72a934..ea6e79ca 100644 --- a/src/browser/webapi/MessagePort.zig +++ b/src/browser/webapi/MessagePort.zig @@ -129,7 +129,7 @@ const PostMessageCallback = struct { return null; } - const event = MessageEvent.init("message", .{ + const event = MessageEvent.initTrusted("message", .{ .data = self.message, .origin = "", .source = null, diff --git a/src/browser/webapi/Range.zig b/src/browser/webapi/Range.zig index 8f220e67..feca23c4 100644 --- a/src/browser/webapi/Range.zig +++ b/src/browser/webapi/Range.zig @@ -33,7 +33,7 @@ pub fn asAbstractRange(self: *Range) *AbstractRange { } pub fn init(page: *Page) !*Range { - return page._factory.abstractRange(Range{._proto = undefined}, page); + return page._factory.abstractRange(Range{ ._proto = undefined }, page); } pub fn setStart(self: *Range, node: *Node, offset: u32) !void { @@ -106,7 +106,7 @@ pub fn collapse(self: *Range, to_start: ?bool) void { } pub fn cloneRange(self: *const Range, page: *Page) !*Range { - const clone = try page._factory.abstractRange(Range{._proto = undefined}, page); + const clone = try page._factory.abstractRange(Range{ ._proto = undefined }, page); clone._proto._end_offset = self._proto._end_offset; clone._proto._start_offset = self._proto._start_offset; clone._proto._end_container = self._proto._end_container; diff --git a/src/browser/webapi/Window.zig b/src/browser/webapi/Window.zig index 1fcfec80..2aaff9f7 100644 --- a/src/browser/webapi/Window.zig +++ b/src/browser/webapi/Window.zig @@ -270,7 +270,7 @@ pub fn cancelIdleCallback(self: *Window, id: u32) void { } pub fn reportError(self: *Window, err: js.Object, page: *Page) !void { - const error_event = try ErrorEvent.init("error", .{ + const error_event = try ErrorEvent.initTrusted("error", .{ .@"error" = err, .message = err.toString() catch "Unknown error", .bubbles = false, @@ -410,7 +410,7 @@ pub fn scrollTo(self: *Window, opts: ScrollToOpts, y: ?i32, page: *Page) !void { return null; } - const event = try Event.init("scroll", .{ .bubbles = true }, p); + const event = try Event.initTrusted("scroll", .{ .bubbles = true }, p); try p._event_manager.dispatch(p.document.asEventTarget(), event); pos.state = .end; @@ -437,7 +437,7 @@ pub fn scrollTo(self: *Window, opts: ScrollToOpts, y: ?i32, page: *Page) !void { .end => {}, .done => return null, } - const event = try Event.init("scrollend", .{ .bubbles = true }, p); + const event = try Event.initTrusted("scrollend", .{ .bubbles = true }, p); try p._event_manager.dispatch(p.document.asEventTarget(), event); pos.state = .done; @@ -586,7 +586,7 @@ const PostMessageCallback = struct { const self: *PostMessageCallback = @ptrCast(@alignCast(ctx)); defer self.deinit(); - const message_event = try MessageEvent.init("message", .{ + const message_event = try MessageEvent.initTrusted("message", .{ .data = self.message, .origin = self.origin, .source = self.window, diff --git a/src/browser/webapi/element/Html.zig b/src/browser/webapi/element/Html.zig index 8de457b6..fc033441 100644 --- a/src/browser/webapi/element/Html.zig +++ b/src/browser/webapi/element/Html.zig @@ -306,6 +306,7 @@ pub fn click(self: *HtmlElement, page: *Page) !void { const event = try @import("../event/MouseEvent.zig").init("click", .{ .bubbles = true, .cancelable = true, + .composed = true, .clientX = 0, .clientY = 0, }, page); diff --git a/src/browser/webapi/event/CompositionEvent.zig b/src/browser/webapi/event/CompositionEvent.zig index a758c621..f77489dc 100644 --- a/src/browser/webapi/event/CompositionEvent.zig +++ b/src/browser/webapi/event/CompositionEvent.zig @@ -40,7 +40,7 @@ pub fn init(typ: []const u8, opts_: ?Options, page: *Page) !*CompositionEvent { ._data = if (opts.data) |str| try page.dupeString(str) else "", }); - Event.populatePrototypes(event, opts); + Event.populatePrototypes(event, opts, false); return event; } diff --git a/src/browser/webapi/event/CustomEvent.zig b/src/browser/webapi/event/CustomEvent.zig index 6456b6cf..55f6b87c 100644 --- a/src/browser/webapi/event/CustomEvent.zig +++ b/src/browser/webapi/event/CustomEvent.zig @@ -49,23 +49,23 @@ pub fn init(typ: []const u8, opts_: ?Options, page: *Page) !*CustomEvent { }, ); - Event.populatePrototypes(event, opts); + Event.populatePrototypes(event, opts, false); return event; } - pub fn initCustomEvent( +pub fn initCustomEvent( self: *CustomEvent, event_string: []const u8, - bubbles: bool, - cancelable: bool, + bubbles: ?bool, + cancelable: ?bool, detail_: ?js.Object, page: *Page, ) !void { // This function can only be called after the constructor has called. // So we assume proto is initialized already by constructor. self._proto._type_string = try String.init(page.arena, event_string, .{}); - self._proto._bubbles = bubbles; - self._proto._cancelable = cancelable; + self._proto._bubbles = bubbles orelse false; + self._proto._cancelable = cancelable orelse false; // Detail is stored separately. if (detail_) |detail| { self._detail = try detail.persist(); diff --git a/src/browser/webapi/event/ErrorEvent.zig b/src/browser/webapi/event/ErrorEvent.zig index 08124d7f..25e2e787 100644 --- a/src/browser/webapi/event/ErrorEvent.zig +++ b/src/browser/webapi/event/ErrorEvent.zig @@ -44,6 +44,14 @@ pub const ErrorEventOptions = struct { const Options = Event.inheritOptions(ErrorEvent, ErrorEventOptions); pub fn init(typ: []const u8, opts_: ?Options, page: *Page) !*ErrorEvent { + return initWithTrusted(typ, opts_, false, page); +} + +pub fn initTrusted(typ: []const u8, opts_: ?Options, page: *Page) !*ErrorEvent { + return initWithTrusted(typ, opts_, true, page); +} + +fn initWithTrusted(typ: []const u8, opts_: ?Options, trusted: bool, page: *Page) !*ErrorEvent { const arena = page.arena; const opts = opts_ orelse Options{}; @@ -60,7 +68,7 @@ pub fn init(typ: []const u8, opts_: ?Options, page: *Page) !*ErrorEvent { }, ); - Event.populatePrototypes(event, opts); + Event.populatePrototypes(event, opts, trusted); return event; } diff --git a/src/browser/webapi/event/KeyboardEvent.zig b/src/browser/webapi/event/KeyboardEvent.zig index eaa04e64..d0f47d23 100644 --- a/src/browser/webapi/event/KeyboardEvent.zig +++ b/src/browser/webapi/event/KeyboardEvent.zig @@ -182,7 +182,15 @@ const Options = Event.inheritOptions( KeyboardEventOptions, ); +pub fn initTrusted(typ: []const u8, _opts: ?Options, page: *Page) !*KeyboardEvent { + return initWithTrusted(typ, _opts, true, page); +} + pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*KeyboardEvent { + return initWithTrusted(typ, _opts, false, page); +} + +fn initWithTrusted(typ: []const u8, _opts: ?Options, trusted: bool, page: *Page) !*KeyboardEvent { const opts = _opts orelse Options{}; const event = try page._factory.uiEvent( @@ -201,7 +209,7 @@ pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*KeyboardEvent { }, ); - Event.populatePrototypes(event, opts); + Event.populatePrototypes(event, opts, trusted); return event; } diff --git a/src/browser/webapi/event/MessageEvent.zig b/src/browser/webapi/event/MessageEvent.zig index 9f24c517..086bb0e5 100644 --- a/src/browser/webapi/event/MessageEvent.zig +++ b/src/browser/webapi/event/MessageEvent.zig @@ -38,6 +38,14 @@ const MessageEventOptions = struct { const Options = Event.inheritOptions(MessageEvent, MessageEventOptions); pub fn init(typ: []const u8, opts_: ?Options, page: *Page) !*MessageEvent { + return initWithTrusted(typ, opts_, false, page); +} + +pub fn initTrusted(typ: []const u8, opts_: ?Options, page: *Page) !*MessageEvent { + return initWithTrusted(typ, opts_, true, page); +} + +fn initWithTrusted(typ: []const u8, opts_: ?Options, trusted: bool, page: *Page) !*MessageEvent { const opts = opts_ orelse Options{}; const event = try page._factory.event( @@ -50,7 +58,7 @@ pub fn init(typ: []const u8, opts_: ?Options, page: *Page) !*MessageEvent { }, ); - Event.populatePrototypes(event, opts); + Event.populatePrototypes(event, opts, trusted); return event; } diff --git a/src/browser/webapi/event/MouseEvent.zig b/src/browser/webapi/event/MouseEvent.zig index 50d5f879..977034cf 100644 --- a/src/browser/webapi/event/MouseEvent.zig +++ b/src/browser/webapi/event/MouseEvent.zig @@ -88,7 +88,7 @@ pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*MouseEvent { }, ); - Event.populatePrototypes(event, opts); + Event.populatePrototypes(event, opts, false); return event; } diff --git a/src/browser/webapi/event/NavigationCurrentEntryChangeEvent.zig b/src/browser/webapi/event/NavigationCurrentEntryChangeEvent.zig index 40c8122a..b22e32f0 100644 --- a/src/browser/webapi/event/NavigationCurrentEntryChangeEvent.zig +++ b/src/browser/webapi/event/NavigationCurrentEntryChangeEvent.zig @@ -40,9 +40,18 @@ const Options = Event.inheritOptions( NavigationCurrentEntryChangeEventOptions, ); -pub fn init( +pub fn init(typ: []const u8, opts: Options, page: *Page) !*NavigationCurrentEntryChangeEvent { + return initWithTrusted(typ, opts, false, page); +} + +pub fn initTrusted(typ: []const u8, opts: Options, page: *Page) !*NavigationCurrentEntryChangeEvent { + return initWithTrusted(typ, opts, true, page); +} + +fn initWithTrusted( typ: []const u8, opts: Options, + trusted: bool, page: *Page, ) !*NavigationCurrentEntryChangeEvent { const navigation_type = if (opts.navigationType) |nav_type_str| @@ -59,7 +68,7 @@ pub fn init( }, ); - Event.populatePrototypes(event, opts); + Event.populatePrototypes(event, opts, trusted); return event; } diff --git a/src/browser/webapi/event/PageTransitionEvent.zig b/src/browser/webapi/event/PageTransitionEvent.zig index 2b7d063f..37b7edf1 100644 --- a/src/browser/webapi/event/PageTransitionEvent.zig +++ b/src/browser/webapi/event/PageTransitionEvent.zig @@ -35,6 +35,14 @@ const PageTransitionEventOptions = struct { const Options = Event.inheritOptions(PageTransitionEvent, PageTransitionEventOptions); pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*PageTransitionEvent { + return initWithTrusted(typ, _opts, false, page); +} + +pub fn initTrusted(typ: []const u8, _opts: ?Options, page: *Page) !*PageTransitionEvent { + return initWithTrusted(typ, _opts, true, page); +} + +fn initWithTrusted(typ: []const u8, _opts: ?Options, trusted: bool, page: *Page) !*PageTransitionEvent { const opts = _opts orelse Options{}; const event = try page._factory.event( @@ -45,7 +53,7 @@ pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*PageTransitionEvent }, ); - Event.populatePrototypes(event, opts); + Event.populatePrototypes(event, opts, trusted); return event; } diff --git a/src/browser/webapi/event/PopStateEvent.zig b/src/browser/webapi/event/PopStateEvent.zig index 45a088c5..305ef5cc 100644 --- a/src/browser/webapi/event/PopStateEvent.zig +++ b/src/browser/webapi/event/PopStateEvent.zig @@ -35,6 +35,14 @@ const PopStateEventOptions = struct { const Options = Event.inheritOptions(PopStateEvent, PopStateEventOptions); pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*PopStateEvent { + return initWithTrusted(typ, _opts, false, page); +} + +pub fn initTrusted(typ: []const u8, _opts: ?Options, page: *Page) !*PopStateEvent { + return initWithTrusted(typ, _opts, true, page); +} + +fn initWithTrusted(typ: []const u8, _opts: ?Options, trusted: bool, page: *Page) !*PopStateEvent { const opts = _opts orelse Options{}; const event = try page._factory.event( @@ -45,7 +53,7 @@ pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*PopStateEvent { }, ); - Event.populatePrototypes(event, opts); + Event.populatePrototypes(event, opts, trusted); return event; } diff --git a/src/browser/webapi/event/ProgressEvent.zig b/src/browser/webapi/event/ProgressEvent.zig index 8fbfbb87..61fe49c9 100644 --- a/src/browser/webapi/event/ProgressEvent.zig +++ b/src/browser/webapi/event/ProgressEvent.zig @@ -34,6 +34,14 @@ const ProgressEventOptions = struct { const Options = Event.inheritOptions(ProgressEvent, ProgressEventOptions); pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*ProgressEvent { + return initWithTrusted(typ, _opts, false, page); +} + +pub fn initTrusted(typ: []const u8, _opts: ?Options, page: *Page) !*ProgressEvent { + return initWithTrusted(typ, _opts, true, page); +} + +fn initWithTrusted(typ: []const u8, _opts: ?Options, trusted: bool, page: *Page) !*ProgressEvent { const opts = _opts orelse Options{}; const event = try page._factory.event( @@ -45,7 +53,7 @@ pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*ProgressEvent { }, ); - Event.populatePrototypes(event, opts); + Event.populatePrototypes(event, opts, trusted); return event; } diff --git a/src/browser/webapi/event/UIEvent.zig b/src/browser/webapi/event/UIEvent.zig index c30e8743..2ea51f7a 100644 --- a/src/browser/webapi/event/UIEvent.zig +++ b/src/browser/webapi/event/UIEvent.zig @@ -57,7 +57,7 @@ pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*UIEvent { }, ); - Event.populatePrototypes(event, opts); + Event.populatePrototypes(event, opts, false); return event; } diff --git a/src/browser/webapi/navigation/Navigation.zig b/src/browser/webapi/navigation/Navigation.zig index b1fcb225..e3e38a7b 100644 --- a/src/browser/webapi/navigation/Navigation.zig +++ b/src/browser/webapi/navigation/Navigation.zig @@ -201,7 +201,7 @@ pub fn pushEntry( if (previous) |prev| { if (dispatch) { - const event = try NavigationCurrentEntryChangeEvent.init( + const event = try NavigationCurrentEntryChangeEvent.initTrusted( "currententrychange", .{ .from = prev, .navigationType = @tagName(.push) }, page, @@ -240,7 +240,7 @@ pub fn replaceEntry( self._entries.items[self._index] = entry; if (dispatch) { - const event = try NavigationCurrentEntryChangeEvent.init( + const event = try NavigationCurrentEntryChangeEvent.initTrusted( "currententrychange", .{ .from = previous, .navigationType = @tagName(.replace) }, page, @@ -324,7 +324,7 @@ pub fn navigateInner( } // If we haven't navigated off, let us fire off an a currententrychange. - const event = try NavigationCurrentEntryChangeEvent.init( + const event = try NavigationCurrentEntryChangeEvent.initTrusted( "currententrychange", .{ .from = previous, .navigationType = @tagName(kind) }, page, @@ -363,7 +363,7 @@ pub fn reload(self: *Navigation, _opts: ?ReloadOptions, page: *Page) !Navigation const previous = entry; entry._state = .{ .source = .navigation, .value = state.toJson(arena) catch return error.DataClone }; - const event = try NavigationCurrentEntryChangeEvent.init( + const event = try NavigationCurrentEntryChangeEvent.initTrusted( "currententrychange", .{ .from = previous, .navigationType = @tagName(.reload) }, page, @@ -405,7 +405,7 @@ pub fn updateCurrentEntry(self: *Navigation, options: UpdateCurrentEntryOptions, .value = options.state.toJson(arena) catch return error.DataClone, }; - const event = try NavigationCurrentEntryChangeEvent.init( + const event = try NavigationCurrentEntryChangeEvent.initTrusted( "currententrychange", .{ .from = previous, .navigationType = null }, page, diff --git a/src/browser/webapi/net/XMLHttpRequest.zig b/src/browser/webapi/net/XMLHttpRequest.zig index 0d903249..c3aef7f6 100644 --- a/src/browser/webapi/net/XMLHttpRequest.zig +++ b/src/browser/webapi/net/XMLHttpRequest.zig @@ -416,7 +416,7 @@ fn stateChanged(self: *XMLHttpRequest, state: ReadyState, page: *Page) !void { self._ready_state = state; - const event = try Event.init("readystatechange", .{}, page); + const event = try Event.initTrusted("readystatechange", .{}, page); try page._event_manager.dispatchWithFunction( self.asEventTarget(), event, diff --git a/src/browser/webapi/net/XMLHttpRequestEventTarget.zig b/src/browser/webapi/net/XMLHttpRequestEventTarget.zig index af861c74..b8efe47d 100644 --- a/src/browser/webapi/net/XMLHttpRequestEventTarget.zig +++ b/src/browser/webapi/net/XMLHttpRequestEventTarget.zig @@ -57,7 +57,7 @@ pub fn dispatch(self: *XMLHttpRequestEventTarget, comptime event_type: DispatchT }; const progress = progress_ orelse Progress{}; - const event = try ProgressEvent.init( + const event = try ProgressEvent.initTrusted( typ, .{ .total = progress.total, .loaded = progress.loaded }, page, diff --git a/src/cdp/domains/input.zig b/src/cdp/domains/input.zig index 388d986b..492e9b9e 100644 --- a/src/cdp/domains/input.zig +++ b/src/cdp/domains/input.zig @@ -60,7 +60,7 @@ fn dispatchKeyEvent(cmd: anytype) !void { const page = bc.session.currentPage() orelse return; const KeyboardEvent = @import("../../browser/webapi/event/KeyboardEvent.zig"); - const keyboard_event = try KeyboardEvent.init("keydown", .{ + const keyboard_event = try KeyboardEvent.initTrusted("keydown", .{ .key = params.key, .code = params.code, .altKey = params.modifiers & 1 == 1,