mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-08-02 10:47:15 -04:00
webapi: window.event shadow-tree rule; property handlers on direct dispatch
Fixes the 3 failing tests in WPT /dom/events/event-global.html (5/8 -> 8/8): - "window.event is undefined if the target is in a shadow tree" (and the window.onerror variant): per the DOM invoke algorithm, the window's current event is only set while invoking listeners whose invocation target is not in a shadow tree. window.event was set once for the whole dispatch; it is now set per invocation target (in dispatchPhase and around inline handler calls), left undefined for targets whose root is a shadow root. - "window.event is set to the current event, which is the event passed to dispatch (2)": script-dispatched events on non-node targets only ran addEventListener listeners. Property event handlers now fire too: XMLHttpRequestEventTarget resolves its on* fields by event type, and the Window's handler fields are consulted both when an event propagates to the window (getInlineHandler) and when one is dispatched directly on it. Coverage: /dom/events/event-global.html 5/8 -> 8/8. No regressions across /dom/events. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
committed by
Karl Seguin
parent
6df58c2318
commit
86f9059f3f
@@ -111,10 +111,30 @@ pub fn dispatchOpts(self: *EventManager, target: *EventTarget, event: *Event, co
|
||||
|
||||
switch (target._type) {
|
||||
.node => |node| try self.dispatchNode(node, event, opts),
|
||||
// Property event handlers (e.g. xhr.onload, window.onerror) fire for
|
||||
// script-dispatched events too, not only for the internal dispatch
|
||||
// paths which pass them explicitly.
|
||||
.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.
|
||||
@@ -284,6 +304,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 +355,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 +388,22 @@ fn dispatchNode(self: *EventManager, target: *Node, event: *Event, comptime opts
|
||||
}
|
||||
}
|
||||
|
||||
// Per spec ("invocation target in shadow tree"), window.event is left
|
||||
// undefined while invoking listeners whose target lives in a shadow tree.
|
||||
fn currentEventForTarget(target: *EventTarget, event: *Event) ?*Event {
|
||||
const ShadowRoot = @import("webapi/ShadowRoot.zig");
|
||||
switch (target._type) {
|
||||
.node => |n| {
|
||||
const root = n.getRootNode(.{});
|
||||
if (root.is(ShadowRoot) != null) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
const DispatchPhaseOpts = struct {
|
||||
capture_only: ?bool = null,
|
||||
apply_ignore: bool = false,
|
||||
@@ -376,6 +420,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 +526,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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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