mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-07-30 17:25:58 -04:00
Merge pull request #2947 from lightpanda-io/dom-pr-06-window-event-createevent
webapi: window.event rules, Window/Document handlers, createEvent, timeStamp
This commit is contained in:
@@ -28,6 +28,7 @@ const Node = @import("webapi/Node.zig");
|
||||
const Event = @import("webapi/Event.zig");
|
||||
const EventTarget = @import("webapi/EventTarget.zig");
|
||||
const Element = @import("webapi/Element.zig");
|
||||
const ShadowRoot = @import("webapi/ShadowRoot.zig");
|
||||
|
||||
const log = lp.log;
|
||||
const Allocator = std.mem.Allocator;
|
||||
@@ -111,10 +112,27 @@ pub fn dispatchOpts(self: *EventManager, target: *EventTarget, event: *Event, co
|
||||
|
||||
switch (target._type) {
|
||||
.node => |node| try self.dispatchNode(node, event, opts),
|
||||
.xhr => |xhr| try self.dispatchDirect(target, event, xhr.inlineHandler(event._type_string), .{ .context = "dispatch" }),
|
||||
.window => |w| try self.dispatchDirect(target, event, windowInlineHandler(w, event._type_string), .{ .context = "dispatch" }),
|
||||
else => try self.dispatchDirect(target, event, null, .{ .context = "dispatch" }),
|
||||
}
|
||||
}
|
||||
|
||||
// Resolves the Window's property event handler for the given event type.
|
||||
fn windowInlineHandler(window: *@import("webapi/Window.zig"), typ: lp.String) ?js.Function.Global {
|
||||
const global_event_handlers = @import("webapi/global_event_handlers.zig");
|
||||
const handler_type = global_event_handlers.fromEventType(typ.str()) orelse return null;
|
||||
return switch (handler_type) {
|
||||
.onerror => window._on_error,
|
||||
.onload => window._on_load,
|
||||
.onblur => window._on_blur,
|
||||
.onfocus => window._on_focus,
|
||||
.onresize => window._on_resize,
|
||||
.onscroll => window._on_scroll,
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
// There are a lot of events that can be attached via addEventListener or as
|
||||
// a property, like the XHR events, or window.onload. You might think that the
|
||||
// property is just a shortcut for calling addEventListener, but they are distinct.
|
||||
@@ -144,8 +162,6 @@ pub fn hasDirectListeners(self: *EventManager, target: *EventTarget, typ: []cons
|
||||
}
|
||||
|
||||
fn dispatchNode(self: *EventManager, target: *Node, event: *Event, comptime opts: DispatchOpts) !void {
|
||||
const ShadowRoot = @import("webapi/ShadowRoot.zig");
|
||||
|
||||
{
|
||||
const et = target.asEventTarget();
|
||||
event._target = et;
|
||||
@@ -284,6 +300,10 @@ fn dispatchNode(self: *EventManager, target: *Node, event: *Event, comptime opts
|
||||
was_handled = true;
|
||||
event._current_target = target_et;
|
||||
|
||||
const prev_current_event = window._current_event;
|
||||
window._current_event = currentEventForTarget(target_et, event);
|
||||
defer window._current_event = prev_current_event;
|
||||
|
||||
// Inline handlers (e.g. onclick property) follow the same "report,
|
||||
// don't propagate" rule as addEventListener listeners — see Listener.run.
|
||||
var caught: js.TryCatch.Caught = undefined;
|
||||
@@ -331,6 +351,10 @@ fn dispatchNode(self: *EventManager, target: *Node, event: *Event, comptime opts
|
||||
was_handled = true;
|
||||
event._current_target = current_target;
|
||||
|
||||
const prev_current_event = window._current_event;
|
||||
window._current_event = currentEventForTarget(current_target, event);
|
||||
defer window._current_event = prev_current_event;
|
||||
|
||||
const original_target = event._target;
|
||||
if (event._needs_retargeting) {
|
||||
event._target = getAdjustedTarget(original_target, current_target);
|
||||
@@ -360,6 +384,10 @@ fn dispatchNode(self: *EventManager, target: *Node, event: *Event, comptime opts
|
||||
}
|
||||
}
|
||||
|
||||
fn currentEventForTarget(target: *EventTarget, event: *Event) ?*Event {
|
||||
return if (rootIsShadowRoot(target)) null else event;
|
||||
}
|
||||
|
||||
const DispatchPhaseOpts = struct {
|
||||
capture_only: ?bool = null,
|
||||
apply_ignore: bool = false,
|
||||
@@ -376,6 +404,11 @@ fn dispatchPhase(self: *EventManager, list: *std.DoublyLinkedList, current_targe
|
||||
const frame = self.frame;
|
||||
const base = &self.base;
|
||||
|
||||
const window = frame.window;
|
||||
const prev_current_event = window._current_event;
|
||||
window._current_event = currentEventForTarget(current_target, event);
|
||||
defer window._current_event = prev_current_event;
|
||||
|
||||
// Track dispatch depth for deferred removal
|
||||
base.dispatch_depth += 1;
|
||||
defer {
|
||||
@@ -477,6 +510,17 @@ fn getInlineHandler(self: *EventManager, target: *EventTarget, event: *Event) ?j
|
||||
// Look up the inline handler for this target
|
||||
const html_element = switch (target._type) {
|
||||
.node => |n| n.is(Element.Html) orelse return null,
|
||||
// The Window stores its event handlers in dedicated fields; an event
|
||||
// propagating to the window must fire them too.
|
||||
.window => |w| return switch (handler_type) {
|
||||
.onerror => w._on_error,
|
||||
.onload => w._on_load,
|
||||
.onblur => w._on_blur,
|
||||
.onfocus => w._on_focus,
|
||||
.onresize => w._on_resize,
|
||||
.onscroll => w._on_scroll,
|
||||
else => null,
|
||||
},
|
||||
else => return null,
|
||||
};
|
||||
|
||||
@@ -489,8 +533,6 @@ fn getInlineHandler(self: *EventManager, target: *EventTarget, event: *Event) ?j
|
||||
// DOM spec "retarget": walk original_target out of shadow trees until the
|
||||
// node is visible from current_target's tree.
|
||||
fn getAdjustedTarget(original_target: ?*EventTarget, current_target: *EventTarget) ?*EventTarget {
|
||||
const ShadowRoot = @import("webapi/ShadowRoot.zig");
|
||||
|
||||
const orig_node = switch ((original_target orelse return null)._type) {
|
||||
.node => |n| n,
|
||||
else => return original_target,
|
||||
@@ -512,8 +554,6 @@ fn getAdjustedTarget(original_target: ?*EventTarget, current_target: *EventTarge
|
||||
}
|
||||
|
||||
fn isShadowIncludingInclusiveAncestor(ancestor: *Node, node: *Node) bool {
|
||||
const ShadowRoot = @import("webapi/ShadowRoot.zig");
|
||||
|
||||
var n: ?*Node = node;
|
||||
while (n) |cur| {
|
||||
if (cur == ancestor) {
|
||||
@@ -531,8 +571,6 @@ fn isShadowIncludingInclusiveAncestor(ancestor: *Node, node: *Node) bool {
|
||||
// Whether the target's tree root (without crossing shadow boundaries) is a
|
||||
// shadow root. Used for the spec's post-dispatch "clear targets" step.
|
||||
fn rootIsShadowRoot(target_: ?*EventTarget) bool {
|
||||
const ShadowRoot = @import("webapi/ShadowRoot.zig");
|
||||
|
||||
const target = target_ orelse return false;
|
||||
var current: *Node = switch (target._type) {
|
||||
.node => |n| n,
|
||||
|
||||
@@ -237,8 +237,9 @@ fn AutoPrototypeChain(comptime types: []const type) type {
|
||||
|
||||
fn eventInit(arena: Allocator, typ: String, value: anytype) !Event {
|
||||
// Round to 2ms for privacy (browsers do this)
|
||||
const raw_timestamp = @import("../datetime.zig").milliTimestamp(.monotonic);
|
||||
const time_stamp = (raw_timestamp / 2) * 2;
|
||||
// Same (already coarsened) clock as the performance time origin, so the
|
||||
// timeStamp getter can report it relative to that origin.
|
||||
const time_stamp = @import("webapi/Performance.zig").highResTimestamp();
|
||||
|
||||
return .{
|
||||
._rc = .{},
|
||||
|
||||
@@ -21,6 +21,7 @@ const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../js/js.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
const Window = @import("Window.zig");
|
||||
const URL = @import("../URL.zig");
|
||||
const idna = @import("../../sys/idna.zig");
|
||||
const public_suffix_list = @import("../../data/public_suffix_list.zig");
|
||||
@@ -89,6 +90,22 @@ pub fn setOnSelectionChange(self: *Document, listener: ?js.Function) !void {
|
||||
}
|
||||
}
|
||||
|
||||
// Stored in the frame's attribute-listener map (like element and ShadowRoot
|
||||
// property handlers), which the dispatch propagation path consults for any
|
||||
// event target.
|
||||
pub fn getOnClick(self: *Document, frame: *Frame) ?js.Function.Global {
|
||||
return (self._frame orelse frame)._event_target_attr_listeners.get(.{ .target = self.asEventTarget(), .handler = .onclick });
|
||||
}
|
||||
|
||||
pub fn setOnClick(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 = .onclick }, cb);
|
||||
} else {
|
||||
_ = owner._event_target_attr_listeners.remove(.{ .target = self.asEventTarget(), .handler = .onclick });
|
||||
}
|
||||
}
|
||||
|
||||
pub const Type = union(enum) {
|
||||
generic,
|
||||
html: *HTMLDocument,
|
||||
@@ -468,56 +485,85 @@ pub fn createEvent(_: *const Document, event_type: []const u8, frame: *Frame) !*
|
||||
}
|
||||
const normalized = std.ascii.lowerString(&frame.buf, event_type);
|
||||
|
||||
if (std.mem.eql(u8, normalized, "event") or std.mem.eql(u8, normalized, "events") or std.mem.eql(u8, normalized, "htmlevents")) {
|
||||
return Event.init("", null, frame._page);
|
||||
}
|
||||
const event: *Event = blk: {
|
||||
if (std.mem.eql(u8, normalized, "event") or std.mem.eql(u8, normalized, "events") or std.mem.eql(u8, normalized, "htmlevents") or std.mem.eql(u8, normalized, "svgevents")) {
|
||||
break :blk try Event.init("", null, frame._page);
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, normalized, "customevent") or std.mem.eql(u8, normalized, "customevents")) {
|
||||
const CustomEvent = @import("event/CustomEvent.zig");
|
||||
return (try CustomEvent.init("", null, frame._page)).asEvent();
|
||||
}
|
||||
if (std.mem.eql(u8, normalized, "customevent") or std.mem.eql(u8, normalized, "customevents")) {
|
||||
const CustomEvent = @import("event/CustomEvent.zig");
|
||||
break :blk (try CustomEvent.init("", null, frame._page)).asEvent();
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, normalized, "keyboardevent")) {
|
||||
const KeyboardEvent = @import("event/KeyboardEvent.zig");
|
||||
return (try KeyboardEvent.init("", null, frame)).asEvent();
|
||||
}
|
||||
if (std.mem.eql(u8, normalized, "keyboardevent")) {
|
||||
const KeyboardEvent = @import("event/KeyboardEvent.zig");
|
||||
break :blk (try KeyboardEvent.init("", null, frame)).asEvent();
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, normalized, "inputevent")) {
|
||||
const InputEvent = @import("event/InputEvent.zig");
|
||||
return (try InputEvent.init("", null, frame)).asEvent();
|
||||
}
|
||||
if (std.mem.eql(u8, normalized, "inputevent")) {
|
||||
const InputEvent = @import("event/InputEvent.zig");
|
||||
break :blk (try InputEvent.init("", null, frame)).asEvent();
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, normalized, "mouseevent") or std.mem.eql(u8, normalized, "mouseevents")) {
|
||||
const MouseEvent = @import("event/MouseEvent.zig");
|
||||
return (try MouseEvent.init("", null, frame)).asEvent();
|
||||
}
|
||||
if (std.mem.eql(u8, normalized, "mouseevent") or std.mem.eql(u8, normalized, "mouseevents")) {
|
||||
const MouseEvent = @import("event/MouseEvent.zig");
|
||||
break :blk (try MouseEvent.init("", null, frame)).asEvent();
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, normalized, "messageevent")) {
|
||||
const MessageEvent = @import("event/MessageEvent.zig");
|
||||
return (try MessageEvent.init("", null, frame._page)).asEvent();
|
||||
}
|
||||
if (std.mem.eql(u8, normalized, "dragevent")) {
|
||||
const DragEvent = @import("event/DragEvent.zig");
|
||||
break :blk (try DragEvent.init("", null, frame)).asEvent();
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, normalized, "uievent") or std.mem.eql(u8, normalized, "uievents")) {
|
||||
const UIEvent = @import("event/UIEvent.zig");
|
||||
return (try UIEvent.init("", null, frame)).asEvent();
|
||||
}
|
||||
if (std.mem.eql(u8, normalized, "messageevent")) {
|
||||
const MessageEvent = @import("event/MessageEvent.zig");
|
||||
break :blk (try MessageEvent.init("", null, frame._page)).asEvent();
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, normalized, "focusevent") or std.mem.eql(u8, normalized, "focusevents")) {
|
||||
const FocusEvent = @import("event/FocusEvent.zig");
|
||||
return (try FocusEvent.init("", null, frame)).asEvent();
|
||||
}
|
||||
if (std.mem.eql(u8, normalized, "hashchangeevent")) {
|
||||
const HashChangeEvent = @import("event/HashChangeEvent.zig");
|
||||
break :blk (try HashChangeEvent.init("", null, frame)).asEvent();
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, normalized, "textevent") or std.mem.eql(u8, normalized, "textevents")) {
|
||||
const TextEvent = @import("event/TextEvent.zig");
|
||||
return (try TextEvent.init("", null, frame)).asEvent();
|
||||
}
|
||||
if (std.mem.eql(u8, normalized, "uievent") or std.mem.eql(u8, normalized, "uievents")) {
|
||||
const UIEvent = @import("event/UIEvent.zig");
|
||||
break :blk (try UIEvent.init("", null, frame)).asEvent();
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, normalized, "compositionevent")) {
|
||||
const CompositionEvent = @import("event/CompositionEvent.zig");
|
||||
return (try CompositionEvent.init("", null, frame)).asEvent();
|
||||
}
|
||||
if (std.mem.eql(u8, normalized, "focusevent") or std.mem.eql(u8, normalized, "focusevents")) {
|
||||
const FocusEvent = @import("event/FocusEvent.zig");
|
||||
break :blk (try FocusEvent.init("", null, frame)).asEvent();
|
||||
}
|
||||
|
||||
return error.NotSupported;
|
||||
if (std.mem.eql(u8, normalized, "textevent") or std.mem.eql(u8, normalized, "textevents")) {
|
||||
const TextEvent = @import("event/TextEvent.zig");
|
||||
break :blk (try TextEvent.init("", null, frame)).asEvent();
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, normalized, "compositionevent")) {
|
||||
const CompositionEvent = @import("event/CompositionEvent.zig");
|
||||
break :blk (try CompositionEvent.init("", null, frame)).asEvent();
|
||||
}
|
||||
|
||||
// Aliases the spec requires createEvent to support but whose
|
||||
// interfaces aren't implemented yet: return a plain Event so the
|
||||
// caller can at least initialize and dispatch it.
|
||||
if (std.mem.eql(u8, normalized, "beforeunloadevent") or
|
||||
std.mem.eql(u8, normalized, "devicemotionevent") or
|
||||
std.mem.eql(u8, normalized, "deviceorientationevent") or
|
||||
std.mem.eql(u8, normalized, "storageevent"))
|
||||
{
|
||||
log.info(.not_implemented, "createEvent interface", .{ .type = event_type });
|
||||
break :blk try Event.init("", null, frame._page);
|
||||
}
|
||||
|
||||
return error.NotSupported;
|
||||
};
|
||||
|
||||
// createEvent returns an uninitialized event: dispatching it before one
|
||||
// of the init*Event calls throws an InvalidStateError.
|
||||
event._initialized = false;
|
||||
return event;
|
||||
}
|
||||
|
||||
pub fn createTreeWalker(_: *const Document, root: *Node, what_to_show: ?js.Value, filter: ?DOMTreeWalker.FilterOpts, frame: *Frame) !*DOMTreeWalker {
|
||||
@@ -1265,6 +1311,7 @@ pub const JsApi = struct {
|
||||
}
|
||||
|
||||
pub const onselectionchange = bridge.accessor(Document.getOnSelectionChange, Document.setOnSelectionChange, .{});
|
||||
pub const onclick = bridge.accessor(Document.getOnClick, Document.setOnClick, .{});
|
||||
pub const URL = bridge.accessor(Document.getURL, null, .{});
|
||||
pub const location = bridge.accessor(Document.getLocation, Document.setLocation, .{});
|
||||
pub const documentURI = bridge.accessor(Document.getURL, null, .{});
|
||||
|
||||
@@ -50,6 +50,13 @@ _needs_retargeting: bool = false,
|
||||
_is_trusted: bool = false,
|
||||
_in_passive_listener: bool = false,
|
||||
_listeners_did_throw: bool = false, // IndexedDB needs to abort on callback throw
|
||||
// Per spec, events created via document.createEvent are not initialized
|
||||
// until one of the init*Event methods runs; dispatching one throws an
|
||||
// InvalidStateError. Events created any other way start initialized.
|
||||
_initialized: bool = true,
|
||||
// Time origin of the event's relevant global, captured at creation when
|
||||
// known; 0 means "use the accessing realm's origin" (see getTimeStamp).
|
||||
_time_origin: u64 = 0,
|
||||
|
||||
// There's a period of time between creating an event and handing it off to v8
|
||||
// where things can fail. If it does fail, we need to deinit the event. The timing
|
||||
@@ -109,9 +116,9 @@ pub fn initTrusted(typ: String, opts_: ?Options, page: *Page) !*Event {
|
||||
fn initWithTrusted(arena: Allocator, typ: String, opts_: ?Options, comptime trusted: bool) !*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;
|
||||
// Same (already coarsened) clock as the performance time origin, so the
|
||||
// timeStamp getter can report it relative to that origin.
|
||||
const time_stamp = @import("Performance.zig").highResTimestamp();
|
||||
|
||||
const event = try arena.create(Event);
|
||||
event.* = .{
|
||||
@@ -137,6 +144,7 @@ pub fn initEvent(
|
||||
return;
|
||||
}
|
||||
|
||||
self._initialized = true;
|
||||
self._type_string = try String.init(self._arena, event_string, .{});
|
||||
self._bubbles = bubbles orelse false;
|
||||
self._cancelable = cancelable orelse false;
|
||||
@@ -257,8 +265,12 @@ pub fn getEventPhase(self: *const Event) u8 {
|
||||
return @intFromEnum(self._event_phase);
|
||||
}
|
||||
|
||||
pub fn getTimeStamp(self: *const Event) u64 {
|
||||
return self._time_stamp;
|
||||
pub fn getTimeStamp(self: *const Event, exec: *js.Execution) f64 {
|
||||
const origin = if (self._time_origin != 0) self._time_origin else exec.performance()._time_origin;
|
||||
if (self._time_stamp <= origin) {
|
||||
return 0.0;
|
||||
}
|
||||
return @as(f64, @floatFromInt(self._time_stamp - origin)) / 1000.0;
|
||||
}
|
||||
|
||||
pub fn setTrusted(self: *Event) void {
|
||||
@@ -470,7 +482,14 @@ pub const JsApi = struct {
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
|
||||
pub const constructor = bridge.constructor(Event.init, .{});
|
||||
pub const constructor = bridge.constructor(struct {
|
||||
fn wrap(typ: []const u8, opts_: ?Options, exec: *js.Execution) !*Event {
|
||||
const event = try Event.init(typ, opts_, exec.page);
|
||||
// capture the realm's time
|
||||
event._time_origin = exec.performance()._time_origin;
|
||||
return event;
|
||||
}
|
||||
}.wrap, .{});
|
||||
pub const @"type" = bridge.accessor(Event.getType, null, .{});
|
||||
pub const bubbles = bridge.accessor(Event.getBubbles, null, .{});
|
||||
pub const cancelable = bridge.accessor(Event.getCancelable, null, .{});
|
||||
|
||||
@@ -69,6 +69,10 @@ pub fn dispatchEvent(self: *EventTarget, event: *Event, exec: *js.Execution) !bo
|
||||
if (event._event_phase != .none) {
|
||||
return error.InvalidStateError;
|
||||
}
|
||||
|
||||
if (!event._initialized) {
|
||||
return error.InvalidStateError;
|
||||
}
|
||||
event._is_trusted = false;
|
||||
|
||||
switch (exec.js.global) {
|
||||
|
||||
@@ -47,7 +47,7 @@ _delivery_scheduled: bool = false,
|
||||
|
||||
/// Get high-resolution timestamp in microseconds, rounded to 5μs increments
|
||||
/// to match browser behavior (prevents fingerprinting)
|
||||
fn highResTimestamp() u64 {
|
||||
pub fn highResTimestamp() u64 {
|
||||
const ts = datetime.timespec();
|
||||
const micros = @as(u64, @intCast(ts.sec)) * 1_000_000 + @as(u64, @intCast(@divTrunc(ts.nsec, 1_000)));
|
||||
// Round to nearest 5 microseconds (like Firefox default)
|
||||
|
||||
@@ -425,6 +425,21 @@ pub fn setOnScroll(self: *Window, setter: ?FunctionSetter) void {
|
||||
self._on_scroll = getFunctionFromSetter(setter);
|
||||
}
|
||||
|
||||
// Stored in the frame's attribute-listener map (like element and ShadowRoot
|
||||
// property handlers), which the dispatch propagation path consults for any
|
||||
// event target.
|
||||
pub fn getOnClick(self: *Window) ?js.Function.Global {
|
||||
return self._frame._event_target_attr_listeners.get(.{ .target = self.asEventTarget(), .handler = .onclick });
|
||||
}
|
||||
|
||||
pub fn setOnClick(self: *Window, setter: ?FunctionSetter) !void {
|
||||
if (getFunctionFromSetter(setter)) |cb| {
|
||||
try self._frame._event_target_attr_listeners.put(self._frame.arena, .{ .target = self.asEventTarget(), .handler = .onclick }, cb);
|
||||
} else {
|
||||
_ = self._frame._event_target_attr_listeners.remove(.{ .target = self.asEventTarget(), .handler = .onclick });
|
||||
}
|
||||
}
|
||||
|
||||
// The "window-reflecting body element event handler set" (HTML spec): these
|
||||
// event handlers of body and frameset elements are aliases for the Window's.
|
||||
// Returns the Window storage slot for the given content attribute name, or
|
||||
@@ -1159,6 +1174,7 @@ pub const JsApi = struct {
|
||||
pub const onfocus = bridge.accessor(Window.getOnFocus, Window.setOnFocus, .{});
|
||||
pub const onresize = bridge.accessor(Window.getOnResize, Window.setOnResize, .{});
|
||||
pub const onscroll = bridge.accessor(Window.getOnScroll, Window.setOnScroll, .{});
|
||||
pub const onclick = bridge.accessor(Window.getOnClick, Window.setOnClick, .{});
|
||||
pub const onmessage = bridge.accessor(Window.getOnMessage, Window.setOnMessage, .{});
|
||||
pub const onrejectionhandled = bridge.accessor(Window.getOnRejectionHandled, Window.setOnRejectionHandled, .{});
|
||||
pub const onunhandledrejection = bridge.accessor(Window.getOnUnhandledRejection, Window.setOnUnhandledRejection, .{});
|
||||
|
||||
@@ -79,6 +79,7 @@ pub fn initCompositionEvent(
|
||||
}
|
||||
|
||||
const arena = event._arena;
|
||||
event._initialized = true;
|
||||
event._type_string = try String.init(arena, typ, .{});
|
||||
event._bubbles = bubbles orelse false;
|
||||
event._cancelable = cancelable orelse false;
|
||||
|
||||
@@ -73,6 +73,7 @@ pub fn initCustomEvent(
|
||||
|
||||
// This function can only be called after the constructor has called.
|
||||
// So we assume proto is initialized already by constructor.
|
||||
self._proto._initialized = true;
|
||||
self._proto._type_string = try String.init(self._proto._arena, event_string, .{});
|
||||
self._proto._bubbles = bubbles orelse false;
|
||||
self._proto._cancelable = cancelable orelse false;
|
||||
|
||||
@@ -428,6 +428,7 @@ pub fn initKeyboardEvent(
|
||||
}
|
||||
|
||||
const arena = event._arena;
|
||||
event._initialized = true;
|
||||
event._type_string = try String.init(arena, typ, .{});
|
||||
event._bubbles = bubbles orelse false;
|
||||
event._cancelable = cancelable orelse false;
|
||||
|
||||
@@ -239,6 +239,7 @@ pub fn initMouseEvent(
|
||||
return;
|
||||
}
|
||||
|
||||
event._initialized = true;
|
||||
event._type_string = try String.init(event._arena, typ, .{});
|
||||
event._bubbles = bubbles orelse false;
|
||||
event._cancelable = cancelable orelse false;
|
||||
|
||||
@@ -83,6 +83,7 @@ pub fn initTextEvent(
|
||||
}
|
||||
|
||||
const arena = event._arena;
|
||||
event._initialized = true;
|
||||
event._type_string = try String.init(arena, typ, .{});
|
||||
event._bubbles = bubbles orelse false;
|
||||
event._cancelable = cancelable orelse false;
|
||||
|
||||
@@ -146,6 +146,7 @@ pub fn initUIEvent(
|
||||
return;
|
||||
}
|
||||
|
||||
event._initialized = true;
|
||||
event._type_string = try String.init(event._arena, typ, .{});
|
||||
event._bubbles = bubbles orelse false;
|
||||
event._cancelable = cancelable orelse false;
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const lp = @import("lightpanda");
|
||||
const js = @import("../../js/js.zig");
|
||||
|
||||
const EventTarget = @import("../EventTarget.zig");
|
||||
@@ -83,6 +84,20 @@ pub fn dispatch(self: *XMLHttpRequestEventTarget, comptime event_type: DispatchT
|
||||
);
|
||||
}
|
||||
|
||||
// Resolves the property event handler for the given event type, so that a
|
||||
// script-dispatched event (target.dispatchEvent) fires it like the internal
|
||||
// dispatch path does.
|
||||
pub fn inlineHandler(self: *const XMLHttpRequestEventTarget, typ: lp.String) ?js.Function.Global {
|
||||
if (typ.eql(comptime .wrap("abort"))) return self._on_abort;
|
||||
if (typ.eql(comptime .wrap("error"))) return self._on_error;
|
||||
if (typ.eql(comptime .wrap("load"))) return self._on_load;
|
||||
if (typ.eql(comptime .wrap("loadend"))) return self._on_load_end;
|
||||
if (typ.eql(comptime .wrap("loadstart"))) return self._on_load_start;
|
||||
if (typ.eql(comptime .wrap("progress"))) return self._on_progress;
|
||||
if (typ.eql(comptime .wrap("timeout"))) return self._on_timeout;
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn getOnAbort(self: *const XMLHttpRequestEventTarget) ?js.Function.Global {
|
||||
return self._on_abort;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user