Merge pull request #1041 from lightpanda-io/nikneym/keyboard-event

KeyboardEvent support
This commit is contained in:
Halil Durak
2025-09-12 13:57:39 +03:00
committed by GitHub
5 changed files with 306 additions and 19 deletions

View File

@@ -33,11 +33,20 @@ const AbortSignal = @import("../html/AbortController.zig").AbortSignal;
const CustomEvent = @import("custom_event.zig").CustomEvent;
const ProgressEvent = @import("../xhr/progress_event.zig").ProgressEvent;
const MouseEvent = @import("mouse_event.zig").MouseEvent;
const KeyboardEvent = @import("keyboard_event.zig").KeyboardEvent;
const ErrorEvent = @import("../html/error_event.zig").ErrorEvent;
const MessageEvent = @import("../dom/MessageChannel.zig").MessageEvent;
// Event interfaces
pub const Interfaces = .{ Event, CustomEvent, ProgressEvent, MouseEvent, ErrorEvent, MessageEvent };
pub const Interfaces = .{
Event,
CustomEvent,
ProgressEvent,
MouseEvent,
KeyboardEvent,
ErrorEvent,
MessageEvent,
};
pub const Union = generate.Union(Interfaces);
@@ -63,6 +72,7 @@ pub const Event = struct {
.mouse_event => .{ .MouseEvent = @as(*parser.MouseEvent, @ptrCast(evt)) },
.error_event => .{ .ErrorEvent = @as(*ErrorEvent, @ptrCast(evt)).* },
.message_event => .{ .MessageEvent = @as(*MessageEvent, @ptrCast(evt)).* },
.keyboard_event => .{ .KeyboardEvent = @as(*parser.KeyboardEvent, @ptrCast(evt)) },
};
}

View File

@@ -0,0 +1,160 @@
// Copyright (C) 2023-2024 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 log = @import("../../log.zig");
const builtin = @import("builtin");
const netsurf = @import("../netsurf.zig");
const Event = @import("event.zig").Event;
const JsObject = @import("../env.zig").JsObject;
// TODO: We currently don't have a UIEvent interface so we skip it in the prototype chain.
// https://developer.mozilla.org/en-US/docs/Web/API/UIEvent
const UIEvent = Event;
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent
pub const KeyboardEvent = struct {
pub const Self = netsurf.KeyboardEvent;
pub const prototype = *UIEvent;
pub const ConstructorOptions = struct {
key: []const u8 = "",
code: []const u8 = "",
location: netsurf.KeyboardEventOpts.LocationCode = .standard,
repeat: bool = false,
isComposing: bool = false,
// Currently not supported but we take as argument.
charCode: u32 = 0,
// Currently not supported but we take as argument.
keyCode: u32 = 0,
// Currently not supported but we take as argument.
which: u32 = 0,
ctrlKey: bool = false,
shiftKey: bool = false,
altKey: bool = false,
metaKey: bool = false,
};
pub fn constructor(event_type: []const u8, maybe_options: ?ConstructorOptions) !*netsurf.KeyboardEvent {
const options: ConstructorOptions = maybe_options orelse .{};
var event = try netsurf.keyboardEventCreate();
try netsurf.eventSetInternalType(@ptrCast(&event), .keyboard_event);
try netsurf.keyboardEventInit(
event,
event_type,
.{
.key = options.key,
.code = options.code,
.location = options.location,
.repeat = options.repeat,
.is_composing = options.isComposing,
.ctrl_key = options.ctrlKey,
.shift_key = options.shiftKey,
.alt_key = options.altKey,
.meta_key = options.metaKey,
},
);
return event;
}
// Returns the modifier state for given modifier key.
pub fn _getModifierState(self: *Self, key: []const u8) bool {
// Chrome and Firefox do case-sensitive match, here we prefer the same.
if (std.mem.eql(u8, key, "Alt")) {
return get_altKey(self);
}
if (std.mem.eql(u8, key, "AltGraph")) {
return (get_altKey(self) and get_ctrlKey(self));
}
if (std.mem.eql(u8, key, "Control")) {
return get_ctrlKey(self);
}
if (std.mem.eql(u8, key, "Shift")) {
return get_shiftKey(self);
}
if (std.mem.eql(u8, key, "Meta") or std.mem.eql(u8, key, "OS")) {
return get_metaKey(self);
}
// Special case for IE.
if (comptime builtin.os.tag == .windows) {
if (std.mem.eql(u8, key, "Win")) {
return get_metaKey(self);
}
}
// getModifierState() also accepts a deprecated virtual modifier named "Accel".
// event.getModifierState("Accel") returns true when at least one of
// KeyboardEvent.ctrlKey or KeyboardEvent.metaKey is true.
//
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/getModifierState#accel_virtual_modifier
if (std.mem.eql(u8, key, "Accel")) {
return (get_ctrlKey(self) or get_metaKey(self));
}
// TODO: Add support for "CapsLock", "ScrollLock".
return false;
}
// Getters.
pub fn get_altKey(self: *Self) bool {
return netsurf.keyboardEventKeyIsSet(self, .alt);
}
pub fn get_ctrlKey(self: *Self) bool {
return netsurf.keyboardEventKeyIsSet(self, .ctrl);
}
pub fn get_metaKey(self: *Self) bool {
return netsurf.keyboardEventKeyIsSet(self, .meta);
}
pub fn get_shiftKey(self: *Self) bool {
return netsurf.keyboardEventKeyIsSet(self, .shift);
}
pub fn get_isComposing(self: *Self) bool {
return self.is_composing;
}
pub fn get_location(self: *Self) u32 {
return self.location;
}
pub fn get_key(self: *Self) ![]const u8 {
return netsurf.keyboardEventGetKey(self);
}
pub fn get_repeat(self: *Self) bool {
return self.repeat;
}
};
const testing = @import("../../testing.zig");
test "Browser: Events.Keyboard" {
try testing.htmlRunner("events/keyboard.html");
}

View File

@@ -548,6 +548,7 @@ pub const EventType = enum(u8) {
abort_signal = 5,
xhr_event = 6,
message_event = 7,
keyboard_event = 8,
};
pub const MutationEvent = c.dom_mutation_event;
@@ -951,15 +952,44 @@ pub fn keyboardEventDestroy(evt: *KeyboardEvent) void {
c._dom_keyboard_event_destroy(evt);
}
const KeyboardEventOpts = struct {
key: []const u8,
code: []const u8,
pub fn keyboardEventKeyIsSet(
evt: *KeyboardEvent,
comptime key: enum { ctrl, alt, shift, meta },
) bool {
var is_set: bool = false;
const err = switch (key) {
.ctrl => c._dom_keyboard_event_get_ctrl_key(evt, &is_set),
.alt => c._dom_keyboard_event_get_alt_key(evt, &is_set),
.shift => c._dom_keyboard_event_get_shift_key(evt, &is_set),
.meta => c._dom_keyboard_event_get_meta_key(evt, &is_set),
};
// None of the earlier can fail.
std.debug.assert(err == c.DOM_NO_ERR);
return is_set;
}
pub const KeyboardEventOpts = struct {
key: []const u8 = "",
code: []const u8 = "",
location: LocationCode = .standard,
repeat: bool = false,
bubbles: bool = false,
cancelable: bool = false,
ctrl: bool = false,
alt: bool = false,
shift: bool = false,
meta: bool = false,
is_composing: bool = false,
ctrl_key: bool = false,
alt_key: bool = false,
shift_key: bool = false,
meta_key: bool = false,
pub const LocationCode = enum(u32) {
standard = c.DOM_KEY_LOCATION_STANDARD,
left = c.DOM_KEY_LOCATION_LEFT,
right = c.DOM_KEY_LOCATION_RIGHT,
numpad = c.DOM_KEY_LOCATION_NUMPAD,
mobile = 0x04, // Non-standard, deprecated.
joystick = 0x05, // Non-standard, deprecated.
};
};
pub fn keyboardEventInit(evt: *KeyboardEvent, typ: []const u8, opts: KeyboardEventOpts) !void {
@@ -972,13 +1002,13 @@ pub fn keyboardEventInit(evt: *KeyboardEvent, typ: []const u8, opts: KeyboardEve
null, // dom_abstract_view* ?
try strFromData(opts.key),
try strFromData(opts.code),
0, // location 0 == standard
opts.ctrl,
opts.shift,
opts.alt,
opts.meta,
false, // repease
false, // is_composiom
@intFromEnum(opts.location),
opts.ctrl_key,
opts.shift_key,
opts.alt_key,
opts.meta_key,
opts.repeat, // repease
opts.is_composing, // is_composiom
);
try DOMErr(err);
}

View File

@@ -942,10 +942,10 @@ pub const Page = struct {
.cancelable = true,
.key = kbe.key,
.code = kbe.code,
.alt = kbe.alt,
.ctrl = kbe.ctrl,
.meta = kbe.meta,
.shift = kbe.shift,
.alt_key = kbe.alt,
.ctrl_key = kbe.ctrl,
.meta_key = kbe.meta,
.shift_key = kbe.shift,
});
_ = try parser.elementDispatchEvent(element, @ptrCast(event));
}

View File

@@ -0,0 +1,87 @@
<script src="../testing.js"></script>
<script id=default>
let event = new KeyboardEvent("test", { key: "a" });
testing.expectEqual(true, event instanceof KeyboardEvent);
testing.expectEqual(true, event instanceof Event);
testing.expectEqual("test", event.type);
testing.expectEqual("a", event.key);
testing.expectEqual(0, event.location);
testing.expectEqual(false, event.repeat);
testing.expectEqual(false, event.isComposing);
testing.expectEqual(false, event.ctrlKey);
testing.expectEqual(false, event.shiftKey);
testing.expectEqual(false, event.metaKey);
testing.expectEqual(false, event.altKey);
</script>
<script id=getModifierState>
event = new KeyboardEvent("test", {
altKey: true,
shiftKey: true,
metaKey: true,
ctrlKey: true,
});
testing.expectEqual(true, event.getModifierState("Alt"));
testing.expectEqual(true, event.getModifierState("AltGraph"));
testing.expectEqual(true, event.getModifierState("Control"));
testing.expectEqual(true, event.getModifierState("Shift"));
testing.expectEqual(true, event.getModifierState("Meta"));
testing.expectEqual(true, event.getModifierState("OS"));
testing.expectEqual(true, event.getModifierState("Accel"));
</script>
<script id=keyDownListener>
event = new KeyboardEvent("keydown", { key: "z" });
let isKeyDown = false;
document.addEventListener("keydown", (e) => {
isKeyDown = true;
testing.expectEqual(true, e instanceof KeyboardEvent);
testing.expectEqual(true, e instanceof Event);
testing.expectEqual("z", event.key);
});
document.dispatchEvent(event);
testing.expectEqual(true, isKeyDown);
</script>
<script id=keyUpListener>
event = new KeyboardEvent("keyup", { key: "x" });
let isKeyUp = false;
document.addEventListener("keyup", (e) => {
isKeyUp = true;
testing.expectEqual(true, e instanceof KeyboardEvent);
testing.expectEqual(true, e instanceof Event);
testing.expectEqual("x", event.key);
});
document.dispatchEvent(event);
testing.expectEqual(true, isKeyUp);
</script>
<script id=keyPressListener>
event = new KeyboardEvent("keypress", { key: "w" });
let isKeyPress = false;
document.addEventListener("keypress", (e) => {
isKeyPress = true;
testing.expectEqual(true, e instanceof KeyboardEvent);
testing.expectEqual(true, e instanceof Event);
testing.expectEqual("w", event.key);
});
document.dispatchEvent(event);
testing.expectEqual(true, isKeyPress);
</script>