event isTrusted support and better composedPath for shadowroots

This commit is contained in:
Karl Seguin
2025-12-26 08:45:20 +08:00
parent b379b775f9
commit 25dbac9945
32 changed files with 357 additions and 60 deletions

View File

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

View File

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

View File

@@ -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,

View File

@@ -88,3 +88,18 @@
testing.expectEqual(true, isKeyPress);
</script>
<script id=isTrusted>
// Test isTrusted on KeyboardEvent
let keyEvent = new KeyboardEvent('keydown', {key: 'a'});
testing.expectEqual(false, keyEvent.isTrusted);
// Test isTrusted on dispatched KeyboardEvent
let keyIsTrusted = null;
document.addEventListener('keytest', (e) => {
keyIsTrusted = e.isTrusted;
testing.expectEqual(true, e instanceof KeyboardEvent);
});
document.dispatchEvent(new KeyboardEvent('keytest', {key: 'b'}));
testing.expectEqual(false, keyIsTrusted);
</script>

View File

@@ -35,3 +35,18 @@
testing.expectEqual('click', evt.type);
testing.expectEqual(true, evt instanceof MouseEvent);
</script>
<script id=isTrusted>
// Test isTrusted on MouseEvent
let mouseEvent = new MouseEvent('click');
testing.expectEqual(false, mouseEvent.isTrusted);
// Test isTrusted on dispatched MouseEvent
let mouseIsTrusted = null;
document.addEventListener('mousetest', (e) => {
mouseIsTrusted = e.isTrusted;
testing.expectEqual(true, e instanceof MouseEvent);
});
document.dispatchEvent(new MouseEvent('mousetest'));
testing.expectEqual(false, mouseIsTrusted);
</script>

View File

@@ -612,3 +612,21 @@
content.dispatchEvent(new Event('he2'));
}
</script>
<script id=isTrusted>
// Test isTrusted property on generic Event
let untrustedEvent = new Event('test');
testing.expectEqual(false, untrustedEvent.isTrusted);
// Test isTrusted on dispatched events
let isTrustedValue = null;
document.addEventListener('trusttest', (e) => {
isTrustedValue = e.isTrusted;
});
document.dispatchEvent(new Event('trusttest'));
testing.expectEqual(false, isTrustedValue);
// Test isTrusted with bubbling events
let bubbledEvent = new Event('bubble', {bubbles: true});
testing.expectEqual(false, bubbledEvent.isTrusted);
</script>

View File

@@ -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));
</script>

View File

@@ -184,3 +184,100 @@
host.remove();
}
</script>
<script id="composedPath_open_shadow_from_host">
{
// Test that open shadow root exposes internals in composedPath
const host = document.createElement('div');
const shadow = host.attachShadow({ mode: 'open' });
const button = document.createElement('button');
shadow.appendChild(button);
document.body.appendChild(host);
let capturedPath = null;
host.addEventListener('click', (e) => {
capturedPath = e.composedPath();
});
const event = new Event('click', { bubbles: true, composed: true });
button.dispatchEvent(event);
// Open shadow: external listener should see shadow internals
testing.expectEqual(7, capturedPath.length);
testing.expectEqual(button, capturedPath[0]);
testing.expectEqual(shadow, capturedPath[1]);
testing.expectEqual(host, capturedPath[2]);
testing.expectEqual(document.body, capturedPath[3]);
testing.expectEqual(document.documentElement, capturedPath[4]);
testing.expectEqual(document, capturedPath[5]);
testing.expectEqual(window, capturedPath[6]);
host.remove();
}
</script>
<script id="composedPath_closed_shadow_from_host">
{
// Test that closed shadow root hides internals in composedPath
const host = document.createElement('div');
const shadow = host.attachShadow({ mode: 'closed' });
const button = document.createElement('button');
shadow.appendChild(button);
document.body.appendChild(host);
let capturedPath = null;
host.addEventListener('click', (e) => {
capturedPath = e.composedPath();
});
const event = new Event('click', { bubbles: true, composed: true });
button.dispatchEvent(event);
// Closed shadow: external listener should NOT see shadow internals
testing.expectEqual(5, capturedPath.length);
testing.expectEqual(host, capturedPath[0]);
testing.expectEqual(document.body, capturedPath[1]);
testing.expectEqual(document.documentElement, capturedPath[2]);
testing.expectEqual(document, capturedPath[3]);
testing.expectEqual(window, capturedPath[4]);
host.remove();
}
</script>
<script id="composedPath_closed_shadow_from_inside">
{
// Test that closed shadow root still exposes internals to internal listeners
const host = document.createElement('div');
const shadow = host.attachShadow({ mode: 'closed' });
const button = document.createElement('button');
shadow.appendChild(button);
document.body.appendChild(host);
let capturedPath = null;
button.addEventListener('click', (e) => {
capturedPath = e.composedPath();
});
const event = new Event('click', { bubbles: true, composed: true });
button.dispatchEvent(event);
// Inside the shadow: should see full path including shadow internals
testing.expectEqual(7, capturedPath.length);
testing.expectEqual(button, capturedPath[0]);
testing.expectEqual(shadow, capturedPath[1]);
testing.expectEqual(host, capturedPath[2]);
testing.expectEqual(document.body, capturedPath[3]);
testing.expectEqual(document.documentElement, capturedPath[4]);
testing.expectEqual(document, capturedPath[5]);
testing.expectEqual(window, capturedPath[6]);
host.remove();
}
</script>

View File

@@ -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,

View File

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

View File

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

View File

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

View File

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

View File

@@ -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,

View File

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

View File

@@ -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,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,