mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-08-02 10:47:15 -04:00
webapi: add BeforeUnload/Storage/DeviceMotion/DeviceOrientation/Touch events
Fixes all 21 failing tests in WPT /dom/nodes/Document-createEvent.https.html (258/279 -> 279/279), Document-createEvent-touchevent.window.html (0/3 -> 3/3) and, as a side effect, /dom/events/non-cancelable-when- passive/synthetic-events-cancelable.html (8/12 -> 12/12). - Five new event interfaces, following the existing patterns: BeforeUnloadEvent (returnValue, no constructor per spec), StorageEvent (key/oldValue/newValue/url + initStorageEvent; storageArea is always null), DeviceMotionEvent and DeviceOrientationEvent (nullable sensor values), and TouchEvent (UIEvent subclass with modifier keys and empty touch lists — there is no touch input source). - document.createEvent maps their aliases to real instances instead of plain Events, adds "touchevent", and drops the non-spec pluralized aliases CustomEvents/FocusEvents/TextEvents, which must throw NotSupportedError (the unit test asserting the old behavior is updated). - The "expose legacy touch event APIs" precondition requires ontouchstart & co. to exist: the four touch handlers are added to the global event handler set, HTMLElement and Document. Coverage: /dom/nodes/Document-createEvent.https.html 258/279 -> 279/279, Document-createEvent-touchevent.window.html 0/3 -> 3/3, synthetic-events-cancelable.html 8/12 -> 12/12. No regressions across /dom/events; 1002/1002 unit tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
committed by
Karl Seguin
parent
58d8aeb070
commit
7f04ee5f0a
@@ -1091,6 +1091,11 @@ pub const PageJsApis = flattenTypes(&.{
|
||||
@import("../webapi/event/PageTransitionEvent.zig"),
|
||||
@import("../webapi/event/PopStateEvent.zig"),
|
||||
@import("../webapi/event/HashChangeEvent.zig"),
|
||||
@import("../webapi/event/BeforeUnloadEvent.zig"),
|
||||
@import("../webapi/event/StorageEvent.zig"),
|
||||
@import("../webapi/event/DeviceMotionEvent.zig"),
|
||||
@import("../webapi/event/DeviceOrientationEvent.zig"),
|
||||
@import("../webapi/event/TouchEvent.zig"),
|
||||
@import("../webapi/event/UIEvent.zig"),
|
||||
@import("../webapi/event/MouseEvent.zig"),
|
||||
@import("../webapi/event/PointerEvent.zig"),
|
||||
|
||||
@@ -75,8 +75,10 @@
|
||||
const event2 = document.createEvent('CUSTOMEVENT');
|
||||
testing.expectEqual('', event2.type);
|
||||
|
||||
const event3 = document.createEvent('CustomEvents');
|
||||
testing.expectEqual('', event3.type);
|
||||
// per spec, the pluralized legacy alias is not supported
|
||||
let threw = false;
|
||||
try { document.createEvent('CustomEvents'); } catch (e) { threw = e.name === 'NotSupportedError'; }
|
||||
testing.expectEqual(true, threw);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -108,6 +108,24 @@ pub fn setOnClick(self: *Document, setter: ?Window.FunctionSetter, frame: *Frame
|
||||
}
|
||||
}
|
||||
|
||||
// Generates a getter/setter pair backed by the frame's attribute-listener
|
||||
// map, like onclick above, for other document event handler properties.
|
||||
fn handlerAccessor(comptime handler: @import("global_event_handlers.zig").Handler) type {
|
||||
return struct {
|
||||
pub fn get(self: *Document, frame: *Frame) ?js.Function.Global {
|
||||
return frame._event_target_attr_listeners.get(.{ .target = self.asEventTarget(), .handler = handler });
|
||||
}
|
||||
|
||||
pub fn set(self: *Document, setter: ?Window.FunctionSetter, frame: *Frame) !void {
|
||||
if (Window.getFunctionFromSetter(setter)) |cb| {
|
||||
try frame._event_target_attr_listeners.put(frame.arena, .{ .target = self.asEventTarget(), .handler = handler }, cb);
|
||||
} else {
|
||||
_ = frame._event_target_attr_listeners.remove(.{ .target = self.asEventTarget(), .handler = handler });
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub const Type = union(enum) {
|
||||
generic,
|
||||
html: *HTMLDocument,
|
||||
@@ -495,7 +513,7 @@ pub fn createEvent(_: *const Document, event_type: []const u8, frame: *Frame) !*
|
||||
break :blk try Event.init("", null, frame._page);
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, normalized, "customevent") or std.mem.eql(u8, normalized, "customevents")) {
|
||||
if (std.mem.eql(u8, normalized, "customevent")) {
|
||||
const CustomEvent = @import("event/CustomEvent.zig");
|
||||
break :blk (try CustomEvent.init("", null, frame._page)).asEvent();
|
||||
}
|
||||
@@ -535,12 +553,12 @@ pub fn createEvent(_: *const Document, event_type: []const u8, frame: *Frame) !*
|
||||
break :blk (try UIEvent.init("", null, frame)).asEvent();
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, normalized, "focusevent") or std.mem.eql(u8, normalized, "focusevents")) {
|
||||
if (std.mem.eql(u8, normalized, "focusevent")) {
|
||||
const FocusEvent = @import("event/FocusEvent.zig");
|
||||
break :blk (try FocusEvent.init("", null, frame)).asEvent();
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, normalized, "textevent") or std.mem.eql(u8, normalized, "textevents")) {
|
||||
if (std.mem.eql(u8, normalized, "textevent")) {
|
||||
const TextEvent = @import("event/TextEvent.zig");
|
||||
break :blk (try TextEvent.init("", null, frame)).asEvent();
|
||||
}
|
||||
@@ -550,16 +568,29 @@ pub fn createEvent(_: *const Document, event_type: []const u8, frame: *Frame) !*
|
||||
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);
|
||||
if (std.mem.eql(u8, normalized, "beforeunloadevent")) {
|
||||
const BeforeUnloadEvent = @import("event/BeforeUnloadEvent.zig");
|
||||
break :blk (try BeforeUnloadEvent.init("", null, frame)).asEvent();
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, normalized, "devicemotionevent")) {
|
||||
const DeviceMotionEvent = @import("event/DeviceMotionEvent.zig");
|
||||
break :blk (try DeviceMotionEvent.init("", null, frame)).asEvent();
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, normalized, "deviceorientationevent")) {
|
||||
const DeviceOrientationEvent = @import("event/DeviceOrientationEvent.zig");
|
||||
break :blk (try DeviceOrientationEvent.init("", null, frame)).asEvent();
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, normalized, "storageevent")) {
|
||||
const StorageEvent = @import("event/StorageEvent.zig");
|
||||
break :blk (try StorageEvent.init("", null, frame)).asEvent();
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, normalized, "touchevent")) {
|
||||
const TouchEvent = @import("event/TouchEvent.zig");
|
||||
break :blk (try TouchEvent.init("", null, frame)).asEvent();
|
||||
}
|
||||
|
||||
return error.NotSupported;
|
||||
@@ -1409,6 +1440,10 @@ pub const JsApi = struct {
|
||||
|
||||
pub const onselectionchange = bridge.accessor(Document.getOnSelectionChange, Document.setOnSelectionChange, .{});
|
||||
pub const onclick = bridge.accessor(Document.getOnClick, Document.setOnClick, .{});
|
||||
pub const ontouchstart = bridge.accessor(handlerAccessor(.ontouchstart).get, handlerAccessor(.ontouchstart).set, .{});
|
||||
pub const ontouchend = bridge.accessor(handlerAccessor(.ontouchend).get, handlerAccessor(.ontouchend).set, .{});
|
||||
pub const ontouchmove = bridge.accessor(handlerAccessor(.ontouchmove).get, handlerAccessor(.ontouchmove).set, .{});
|
||||
pub const ontouchcancel = bridge.accessor(handlerAccessor(.ontouchcancel).get, handlerAccessor(.ontouchcancel).set, .{});
|
||||
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, .{});
|
||||
|
||||
@@ -84,6 +84,10 @@ pub const Type = union(enum) {
|
||||
page_transition_event: *@import("event/PageTransitionEvent.zig"),
|
||||
pop_state_event: *@import("event/PopStateEvent.zig"),
|
||||
hash_change_event: *@import("event/HashChangeEvent.zig"),
|
||||
before_unload_event: *@import("event/BeforeUnloadEvent.zig"),
|
||||
storage_event: *@import("event/StorageEvent.zig"),
|
||||
device_motion_event: *@import("event/DeviceMotionEvent.zig"),
|
||||
device_orientation_event: *@import("event/DeviceOrientationEvent.zig"),
|
||||
ui_event: *@import("event/UIEvent.zig"),
|
||||
promise_rejection_event: *@import("event/PromiseRejectionEvent.zig"),
|
||||
submit_event: *@import("event/SubmitEvent.zig"),
|
||||
@@ -193,6 +197,10 @@ pub fn is(self: *Event, comptime T: type) ?*T {
|
||||
.page_transition_event => |e| return if (T == @import("event/PageTransitionEvent.zig")) e else null,
|
||||
.pop_state_event => |e| return if (T == @import("event/PopStateEvent.zig")) e else null,
|
||||
.hash_change_event => |e| return if (T == @import("event/HashChangeEvent.zig")) e else null,
|
||||
.before_unload_event => |e| return if (T == @import("event/BeforeUnloadEvent.zig")) e else null,
|
||||
.storage_event => |e| return if (T == @import("event/StorageEvent.zig")) e else null,
|
||||
.device_motion_event => |e| return if (T == @import("event/DeviceMotionEvent.zig")) e else null,
|
||||
.device_orientation_event => |e| return if (T == @import("event/DeviceOrientationEvent.zig")) e else null,
|
||||
.promise_rejection_event => |e| return if (T == @import("event/PromiseRejectionEvent.zig")) e else null,
|
||||
.submit_event => |e| return if (T == @import("event/SubmitEvent.zig")) e else null,
|
||||
.form_data_event => |e| return if (T == @import("event/FormDataEvent.zig")) e else null,
|
||||
|
||||
@@ -1227,6 +1227,38 @@ pub fn getOnToggle(self: *HtmlElement, frame: *Frame) !?js.Function.Global {
|
||||
return self.getAttributeFunction(.ontoggle, frame);
|
||||
}
|
||||
|
||||
pub fn setOnTouchCancel(self: *HtmlElement, callback: ?js.Function.Global, frame: *Frame) !void {
|
||||
return self.setAttributeListener(.ontouchcancel, callback, frame);
|
||||
}
|
||||
|
||||
pub fn getOnTouchCancel(self: *HtmlElement, frame: *Frame) !?js.Function.Global {
|
||||
return self.getAttributeFunction(.ontouchcancel, frame);
|
||||
}
|
||||
|
||||
pub fn setOnTouchEnd(self: *HtmlElement, callback: ?js.Function.Global, frame: *Frame) !void {
|
||||
return self.setAttributeListener(.ontouchend, callback, frame);
|
||||
}
|
||||
|
||||
pub fn getOnTouchEnd(self: *HtmlElement, frame: *Frame) !?js.Function.Global {
|
||||
return self.getAttributeFunction(.ontouchend, frame);
|
||||
}
|
||||
|
||||
pub fn setOnTouchMove(self: *HtmlElement, callback: ?js.Function.Global, frame: *Frame) !void {
|
||||
return self.setAttributeListener(.ontouchmove, callback, frame);
|
||||
}
|
||||
|
||||
pub fn getOnTouchMove(self: *HtmlElement, frame: *Frame) !?js.Function.Global {
|
||||
return self.getAttributeFunction(.ontouchmove, frame);
|
||||
}
|
||||
|
||||
pub fn setOnTouchStart(self: *HtmlElement, callback: ?js.Function.Global, frame: *Frame) !void {
|
||||
return self.setAttributeListener(.ontouchstart, callback, frame);
|
||||
}
|
||||
|
||||
pub fn getOnTouchStart(self: *HtmlElement, frame: *Frame) !?js.Function.Global {
|
||||
return self.getAttributeFunction(.ontouchstart, frame);
|
||||
}
|
||||
|
||||
pub fn setOnTransitionCancel(self: *HtmlElement, callback: ?js.Function.Global, frame: *Frame) !void {
|
||||
return self.setAttributeListener(.ontransitioncancel, callback, frame);
|
||||
}
|
||||
@@ -1773,6 +1805,10 @@ pub const JsApi = struct {
|
||||
pub const onsuspend = bridge.accessor(HtmlElement.getOnSuspend, HtmlElement.setOnSuspend, .{});
|
||||
pub const ontimeupdate = bridge.accessor(HtmlElement.getOnTimeUpdate, HtmlElement.setOnTimeUpdate, .{});
|
||||
pub const ontoggle = bridge.accessor(HtmlElement.getOnToggle, HtmlElement.setOnToggle, .{});
|
||||
pub const ontouchcancel = bridge.accessor(HtmlElement.getOnTouchCancel, HtmlElement.setOnTouchCancel, .{});
|
||||
pub const ontouchend = bridge.accessor(HtmlElement.getOnTouchEnd, HtmlElement.setOnTouchEnd, .{});
|
||||
pub const ontouchmove = bridge.accessor(HtmlElement.getOnTouchMove, HtmlElement.setOnTouchMove, .{});
|
||||
pub const ontouchstart = bridge.accessor(HtmlElement.getOnTouchStart, HtmlElement.setOnTouchStart, .{});
|
||||
pub const ontransitioncancel = bridge.accessor(HtmlElement.getOnTransitionCancel, HtmlElement.setOnTransitionCancel, .{});
|
||||
pub const ontransitionend = bridge.accessor(HtmlElement.getOnTransitionEnd, HtmlElement.setOnTransitionEnd, .{});
|
||||
pub const ontransitionrun = bridge.accessor(HtmlElement.getOnTransitionRun, HtmlElement.setOnTransitionRun, .{});
|
||||
|
||||
89
src/browser/webapi/event/BeforeUnloadEvent.zig
Normal file
89
src/browser/webapi/event/BeforeUnloadEvent.zig
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
|
||||
//
|
||||
// Francis Bouvier <francis@lightpanda.io>
|
||||
// Pierre Tachoire <pierre@lightpanda.io>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// 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 std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../../js/js.zig");
|
||||
const Frame = @import("../../Frame.zig");
|
||||
|
||||
const Event = @import("../Event.zig");
|
||||
|
||||
const String = lp.String;
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#the-beforeunloadevent-interface
|
||||
const BeforeUnloadEvent = @This();
|
||||
|
||||
_proto: *Event,
|
||||
_return_value: []const u8 = "",
|
||||
|
||||
const Options = Event.inheritOptions(BeforeUnloadEvent, struct {});
|
||||
|
||||
pub fn init(typ: []const u8, _opts: ?Options, frame: *Frame) !*BeforeUnloadEvent {
|
||||
const arena = try frame.getArena(.tiny, "BeforeUnloadEvent");
|
||||
errdefer frame.releaseArena(arena);
|
||||
const type_string = try String.init(arena, typ, .{});
|
||||
return initWithTrusted(arena, type_string, _opts, false, frame);
|
||||
}
|
||||
|
||||
pub fn initTrusted(typ: String, _opts: ?Options, frame: *Frame) !*BeforeUnloadEvent {
|
||||
const arena = try frame.getArena(.tiny, "BeforeUnloadEvent.trusted");
|
||||
errdefer frame.releaseArena(arena);
|
||||
return initWithTrusted(arena, typ, _opts, true, frame);
|
||||
}
|
||||
|
||||
fn initWithTrusted(arena: Allocator, typ: String, _opts: ?Options, trusted: bool, frame: *Frame) !*BeforeUnloadEvent {
|
||||
const opts = _opts orelse Options{};
|
||||
|
||||
const event = try frame._factory.event(
|
||||
arena,
|
||||
typ,
|
||||
BeforeUnloadEvent{
|
||||
._proto = undefined,
|
||||
},
|
||||
);
|
||||
|
||||
Event.populatePrototypes(event, opts, trusted);
|
||||
return event;
|
||||
}
|
||||
|
||||
pub fn asEvent(self: *BeforeUnloadEvent) *Event {
|
||||
return self._proto;
|
||||
}
|
||||
|
||||
pub fn getReturnValue(self: *const BeforeUnloadEvent) []const u8 {
|
||||
return self._return_value;
|
||||
}
|
||||
|
||||
pub fn setReturnValue(self: *BeforeUnloadEvent, value: []const u8, frame: *Frame) !void {
|
||||
self._return_value = try frame.dupeString(value);
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(BeforeUnloadEvent);
|
||||
|
||||
pub const Meta = struct {
|
||||
pub const name = "BeforeUnloadEvent";
|
||||
pub const prototype_chain = bridge.prototypeChain();
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
|
||||
// Per spec BeforeUnloadEvent has no constructor.
|
||||
pub const returnValue = bridge.accessor(BeforeUnloadEvent.getReturnValue, BeforeUnloadEvent.setReturnValue, .{});
|
||||
};
|
||||
96
src/browser/webapi/event/DeviceMotionEvent.zig
Normal file
96
src/browser/webapi/event/DeviceMotionEvent.zig
Normal file
@@ -0,0 +1,96 @@
|
||||
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
|
||||
//
|
||||
// Francis Bouvier <francis@lightpanda.io>
|
||||
// Pierre Tachoire <pierre@lightpanda.io>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// 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 std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../../js/js.zig");
|
||||
const Frame = @import("../../Frame.zig");
|
||||
|
||||
const Event = @import("../Event.zig");
|
||||
|
||||
const String = lp.String;
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
// https://w3c.github.io/deviceorientation/#devicemotionevent
|
||||
const DeviceMotionEvent = @This();
|
||||
|
||||
_proto: *Event,
|
||||
_interval: f64 = 0,
|
||||
|
||||
const DeviceMotionEventOptions = struct {
|
||||
interval: f64 = 0,
|
||||
};
|
||||
|
||||
const Options = Event.inheritOptions(DeviceMotionEvent, DeviceMotionEventOptions);
|
||||
|
||||
pub fn init(typ: []const u8, _opts: ?Options, frame: *Frame) !*DeviceMotionEvent {
|
||||
const arena = try frame.getArena(.tiny, "DeviceMotionEvent");
|
||||
errdefer frame.releaseArena(arena);
|
||||
const type_string = try String.init(arena, typ, .{});
|
||||
|
||||
const opts = _opts orelse Options{};
|
||||
const event = try frame._factory.event(
|
||||
arena,
|
||||
type_string,
|
||||
DeviceMotionEvent{
|
||||
._proto = undefined,
|
||||
._interval = opts.interval,
|
||||
},
|
||||
);
|
||||
|
||||
Event.populatePrototypes(event, opts, false);
|
||||
return event;
|
||||
}
|
||||
|
||||
pub fn asEvent(self: *DeviceMotionEvent) *Event {
|
||||
return self._proto;
|
||||
}
|
||||
|
||||
// There is no motion sensor: the acceleration and rotation members are null.
|
||||
pub fn getAcceleration(_: *const DeviceMotionEvent) ?bool {
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn getAccelerationIncludingGravity(_: *const DeviceMotionEvent) ?bool {
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn getRotationRate(_: *const DeviceMotionEvent) ?bool {
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn getInterval(self: *const DeviceMotionEvent) f64 {
|
||||
return self._interval;
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(DeviceMotionEvent);
|
||||
|
||||
pub const Meta = struct {
|
||||
pub const name = "DeviceMotionEvent";
|
||||
pub const prototype_chain = bridge.prototypeChain();
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
|
||||
pub const constructor = bridge.constructor(DeviceMotionEvent.init, .{});
|
||||
pub const acceleration = bridge.accessor(DeviceMotionEvent.getAcceleration, null, .{ .null_as_undefined = false });
|
||||
pub const accelerationIncludingGravity = bridge.accessor(DeviceMotionEvent.getAccelerationIncludingGravity, null, .{ .null_as_undefined = false });
|
||||
pub const rotationRate = bridge.accessor(DeviceMotionEvent.getRotationRate, null, .{ .null_as_undefined = false });
|
||||
pub const interval = bridge.accessor(DeviceMotionEvent.getInterval, null, .{});
|
||||
};
|
||||
104
src/browser/webapi/event/DeviceOrientationEvent.zig
Normal file
104
src/browser/webapi/event/DeviceOrientationEvent.zig
Normal file
@@ -0,0 +1,104 @@
|
||||
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
|
||||
//
|
||||
// Francis Bouvier <francis@lightpanda.io>
|
||||
// Pierre Tachoire <pierre@lightpanda.io>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// 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 std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../../js/js.zig");
|
||||
const Frame = @import("../../Frame.zig");
|
||||
|
||||
const Event = @import("../Event.zig");
|
||||
|
||||
const String = lp.String;
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
// https://w3c.github.io/deviceorientation/#deviceorientationevent
|
||||
const DeviceOrientationEvent = @This();
|
||||
|
||||
_proto: *Event,
|
||||
_alpha: ?f64 = null,
|
||||
_beta: ?f64 = null,
|
||||
_gamma: ?f64 = null,
|
||||
_absolute: bool = false,
|
||||
|
||||
const DeviceOrientationEventOptions = struct {
|
||||
alpha: ?f64 = null,
|
||||
beta: ?f64 = null,
|
||||
gamma: ?f64 = null,
|
||||
absolute: bool = false,
|
||||
};
|
||||
|
||||
const Options = Event.inheritOptions(DeviceOrientationEvent, DeviceOrientationEventOptions);
|
||||
|
||||
pub fn init(typ: []const u8, _opts: ?Options, frame: *Frame) !*DeviceOrientationEvent {
|
||||
const arena = try frame.getArena(.tiny, "DeviceOrientationEvent");
|
||||
errdefer frame.releaseArena(arena);
|
||||
const type_string = try String.init(arena, typ, .{});
|
||||
|
||||
const opts = _opts orelse Options{};
|
||||
const event = try frame._factory.event(
|
||||
arena,
|
||||
type_string,
|
||||
DeviceOrientationEvent{
|
||||
._proto = undefined,
|
||||
._alpha = opts.alpha,
|
||||
._beta = opts.beta,
|
||||
._gamma = opts.gamma,
|
||||
._absolute = opts.absolute,
|
||||
},
|
||||
);
|
||||
|
||||
Event.populatePrototypes(event, opts, false);
|
||||
return event;
|
||||
}
|
||||
|
||||
pub fn asEvent(self: *DeviceOrientationEvent) *Event {
|
||||
return self._proto;
|
||||
}
|
||||
|
||||
pub fn getAlpha(self: *const DeviceOrientationEvent) ?f64 {
|
||||
return self._alpha;
|
||||
}
|
||||
|
||||
pub fn getBeta(self: *const DeviceOrientationEvent) ?f64 {
|
||||
return self._beta;
|
||||
}
|
||||
|
||||
pub fn getGamma(self: *const DeviceOrientationEvent) ?f64 {
|
||||
return self._gamma;
|
||||
}
|
||||
|
||||
pub fn getAbsolute(self: *const DeviceOrientationEvent) bool {
|
||||
return self._absolute;
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(DeviceOrientationEvent);
|
||||
|
||||
pub const Meta = struct {
|
||||
pub const name = "DeviceOrientationEvent";
|
||||
pub const prototype_chain = bridge.prototypeChain();
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
|
||||
pub const constructor = bridge.constructor(DeviceOrientationEvent.init, .{});
|
||||
pub const alpha = bridge.accessor(DeviceOrientationEvent.getAlpha, null, .{ .null_as_undefined = false });
|
||||
pub const beta = bridge.accessor(DeviceOrientationEvent.getBeta, null, .{ .null_as_undefined = false });
|
||||
pub const gamma = bridge.accessor(DeviceOrientationEvent.getGamma, null, .{ .null_as_undefined = false });
|
||||
pub const absolute = bridge.accessor(DeviceOrientationEvent.getAbsolute, null, .{});
|
||||
};
|
||||
149
src/browser/webapi/event/StorageEvent.zig
Normal file
149
src/browser/webapi/event/StorageEvent.zig
Normal file
@@ -0,0 +1,149 @@
|
||||
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
|
||||
//
|
||||
// Francis Bouvier <francis@lightpanda.io>
|
||||
// Pierre Tachoire <pierre@lightpanda.io>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// 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 std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../../js/js.zig");
|
||||
const Frame = @import("../../Frame.zig");
|
||||
|
||||
const Event = @import("../Event.zig");
|
||||
|
||||
const String = lp.String;
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/webstorage.html#the-storageevent-interface
|
||||
const StorageEvent = @This();
|
||||
|
||||
_proto: *Event,
|
||||
_key: ?[]const u8 = null,
|
||||
_old_value: ?[]const u8 = null,
|
||||
_new_value: ?[]const u8 = null,
|
||||
_url: []const u8 = "",
|
||||
|
||||
const StorageEventOptions = struct {
|
||||
key: ?[]const u8 = null,
|
||||
oldValue: ?[]const u8 = null,
|
||||
newValue: ?[]const u8 = null,
|
||||
url: []const u8 = "",
|
||||
};
|
||||
|
||||
const Options = Event.inheritOptions(StorageEvent, StorageEventOptions);
|
||||
|
||||
pub fn init(typ: []const u8, _opts: ?Options, frame: *Frame) !*StorageEvent {
|
||||
const arena = try frame.getArena(.tiny, "StorageEvent");
|
||||
errdefer frame.releaseArena(arena);
|
||||
const type_string = try String.init(arena, typ, .{});
|
||||
return initWithTrusted(arena, type_string, _opts, false, frame);
|
||||
}
|
||||
|
||||
pub fn initTrusted(typ: String, _opts: ?Options, frame: *Frame) !*StorageEvent {
|
||||
const arena = try frame.getArena(.tiny, "StorageEvent.trusted");
|
||||
errdefer frame.releaseArena(arena);
|
||||
return initWithTrusted(arena, typ, _opts, true, frame);
|
||||
}
|
||||
|
||||
fn initWithTrusted(arena: Allocator, typ: String, _opts: ?Options, trusted: bool, frame: *Frame) !*StorageEvent {
|
||||
const opts = _opts orelse Options{};
|
||||
|
||||
const event = try frame._factory.event(
|
||||
arena,
|
||||
typ,
|
||||
StorageEvent{
|
||||
._proto = undefined,
|
||||
._key = if (opts.key) |k| try arena.dupe(u8, k) else null,
|
||||
._old_value = if (opts.oldValue) |v| try arena.dupe(u8, v) else null,
|
||||
._new_value = if (opts.newValue) |v| try arena.dupe(u8, v) else null,
|
||||
._url = try arena.dupe(u8, opts.url),
|
||||
},
|
||||
);
|
||||
|
||||
Event.populatePrototypes(event, opts, trusted);
|
||||
return event;
|
||||
}
|
||||
|
||||
pub fn asEvent(self: *StorageEvent) *Event {
|
||||
return self._proto;
|
||||
}
|
||||
|
||||
pub fn getKey(self: *const StorageEvent) ?[]const u8 {
|
||||
return self._key;
|
||||
}
|
||||
|
||||
pub fn getOldValue(self: *const StorageEvent) ?[]const u8 {
|
||||
return self._old_value;
|
||||
}
|
||||
|
||||
pub fn getNewValue(self: *const StorageEvent) ?[]const u8 {
|
||||
return self._new_value;
|
||||
}
|
||||
|
||||
pub fn getUrl(self: *const StorageEvent) []const u8 {
|
||||
return self._url;
|
||||
}
|
||||
|
||||
// The initiating Storage object is not tracked; always null.
|
||||
pub fn getStorageArea(_: *const StorageEvent) ?bool {
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn initStorageEvent(
|
||||
self: *StorageEvent,
|
||||
typ: []const u8,
|
||||
bubbles: ?bool,
|
||||
cancelable: ?bool,
|
||||
key: ?[]const u8,
|
||||
old_value: ?[]const u8,
|
||||
new_value: ?[]const u8,
|
||||
url: ?[]const u8,
|
||||
frame: *Frame,
|
||||
) !void {
|
||||
const event = self._proto;
|
||||
if (event._event_phase != .none) {
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
self._key = if (key) |k| try arena.dupe(u8, k) else null;
|
||||
self._old_value = if (old_value) |v| try arena.dupe(u8, v) else null;
|
||||
self._new_value = if (new_value) |v| try arena.dupe(u8, v) else null;
|
||||
self._url = if (url) |u| try arena.dupe(u8, u) else "";
|
||||
_ = frame;
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(StorageEvent);
|
||||
|
||||
pub const Meta = struct {
|
||||
pub const name = "StorageEvent";
|
||||
pub const prototype_chain = bridge.prototypeChain();
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
|
||||
pub const constructor = bridge.constructor(StorageEvent.init, .{});
|
||||
pub const key = bridge.accessor(StorageEvent.getKey, null, .{ .null_as_undefined = false });
|
||||
pub const oldValue = bridge.accessor(StorageEvent.getOldValue, null, .{ .null_as_undefined = false });
|
||||
pub const newValue = bridge.accessor(StorageEvent.getNewValue, null, .{ .null_as_undefined = false });
|
||||
pub const url = bridge.accessor(StorageEvent.getUrl, null, .{});
|
||||
pub const storageArea = bridge.accessor(StorageEvent.getStorageArea, null, .{ .null_as_undefined = false });
|
||||
pub const initStorageEvent = bridge.function(StorageEvent.initStorageEvent, .{});
|
||||
};
|
||||
124
src/browser/webapi/event/TouchEvent.zig
Normal file
124
src/browser/webapi/event/TouchEvent.zig
Normal file
@@ -0,0 +1,124 @@
|
||||
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
|
||||
//
|
||||
// Francis Bouvier <francis@lightpanda.io>
|
||||
// Pierre Tachoire <pierre@lightpanda.io>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// 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 std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../../js/js.zig");
|
||||
const Frame = @import("../../Frame.zig");
|
||||
|
||||
const Event = @import("../Event.zig");
|
||||
const UIEvent = @import("UIEvent.zig");
|
||||
|
||||
const String = lp.String;
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
// https://w3c.github.io/touch-events/#touchevent-interface
|
||||
// There is no touch input source: the touch lists are always empty.
|
||||
const TouchEvent = @This();
|
||||
|
||||
_proto: *UIEvent,
|
||||
_alt_key: bool = false,
|
||||
_meta_key: bool = false,
|
||||
_ctrl_key: bool = false,
|
||||
_shift_key: bool = false,
|
||||
|
||||
pub const TouchEventOptions = struct {
|
||||
altKey: bool = false,
|
||||
metaKey: bool = false,
|
||||
ctrlKey: bool = false,
|
||||
shiftKey: bool = false,
|
||||
};
|
||||
|
||||
pub const Options = Event.inheritOptions(
|
||||
TouchEvent,
|
||||
TouchEventOptions,
|
||||
);
|
||||
|
||||
pub fn init(typ: []const u8, _opts: ?Options, frame: *Frame) !*TouchEvent {
|
||||
const arena = try frame.getArena(.tiny, "TouchEvent");
|
||||
errdefer frame.releaseArena(arena);
|
||||
const type_string = try String.init(arena, typ, .{});
|
||||
|
||||
const opts = _opts orelse Options{};
|
||||
const event = try frame._factory.uiEvent(
|
||||
arena,
|
||||
type_string,
|
||||
TouchEvent{
|
||||
._proto = undefined,
|
||||
._alt_key = opts.altKey,
|
||||
._meta_key = opts.metaKey,
|
||||
._ctrl_key = opts.ctrlKey,
|
||||
._shift_key = opts.shiftKey,
|
||||
},
|
||||
);
|
||||
|
||||
Event.populatePrototypes(event, opts, false);
|
||||
return event;
|
||||
}
|
||||
|
||||
pub fn asEvent(self: *TouchEvent) *Event {
|
||||
return self._proto.asEvent();
|
||||
}
|
||||
|
||||
pub fn getTouches(_: *const TouchEvent) []const bool {
|
||||
return &.{};
|
||||
}
|
||||
|
||||
pub fn getTargetTouches(_: *const TouchEvent) []const bool {
|
||||
return &.{};
|
||||
}
|
||||
|
||||
pub fn getChangedTouches(_: *const TouchEvent) []const bool {
|
||||
return &.{};
|
||||
}
|
||||
|
||||
pub fn getAltKey(self: *const TouchEvent) bool {
|
||||
return self._alt_key;
|
||||
}
|
||||
|
||||
pub fn getMetaKey(self: *const TouchEvent) bool {
|
||||
return self._meta_key;
|
||||
}
|
||||
|
||||
pub fn getCtrlKey(self: *const TouchEvent) bool {
|
||||
return self._ctrl_key;
|
||||
}
|
||||
|
||||
pub fn getShiftKey(self: *const TouchEvent) bool {
|
||||
return self._shift_key;
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(TouchEvent);
|
||||
|
||||
pub const Meta = struct {
|
||||
pub const name = "TouchEvent";
|
||||
pub const prototype_chain = bridge.prototypeChain();
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
|
||||
pub const constructor = bridge.constructor(TouchEvent.init, .{});
|
||||
pub const touches = bridge.accessor(TouchEvent.getTouches, null, .{});
|
||||
pub const targetTouches = bridge.accessor(TouchEvent.getTargetTouches, null, .{});
|
||||
pub const changedTouches = bridge.accessor(TouchEvent.getChangedTouches, null, .{});
|
||||
pub const altKey = bridge.accessor(TouchEvent.getAltKey, null, .{});
|
||||
pub const metaKey = bridge.accessor(TouchEvent.getMetaKey, null, .{});
|
||||
pub const ctrlKey = bridge.accessor(TouchEvent.getCtrlKey, null, .{});
|
||||
pub const shiftKey = bridge.accessor(TouchEvent.getShiftKey, null, .{});
|
||||
};
|
||||
@@ -41,6 +41,7 @@ pub const Type = union(enum) {
|
||||
text_event: *@import("TextEvent.zig"),
|
||||
input_event: *@import("InputEvent.zig"),
|
||||
composition_event: *@import("CompositionEvent.zig"),
|
||||
touch_event: *@import("TouchEvent.zig"),
|
||||
};
|
||||
|
||||
pub const UIEventOptions = struct {
|
||||
@@ -90,6 +91,7 @@ pub fn is(self: *UIEvent, comptime T: type) ?*T {
|
||||
.text_event => |e| return if (T == @import("TextEvent.zig")) e else null,
|
||||
.input_event => |e| return if (T == @import("InputEvent.zig")) e else null,
|
||||
.composition_event => |e| return if (T == @import("CompositionEvent.zig")) e else null,
|
||||
.touch_event => |e| return if (T == @import("TouchEvent.zig")) e else null,
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -156,6 +156,10 @@ pub const Handler = enum(u7) {
|
||||
onsuspend,
|
||||
ontimeupdate,
|
||||
ontoggle,
|
||||
ontouchcancel,
|
||||
ontouchend,
|
||||
ontouchmove,
|
||||
ontouchstart,
|
||||
ontransitioncancel,
|
||||
ontransitionend,
|
||||
ontransitionrun,
|
||||
|
||||
Reference in New Issue
Block a user