mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-29 07:03:29 +00:00
Merge pull request #1041 from lightpanda-io/nikneym/keyboard-event
KeyboardEvent support
This commit is contained in:
@@ -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)) },
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
160
src/browser/events/keyboard_event.zig
Normal file
160
src/browser/events/keyboard_event.zig
Normal 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");
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
87
src/tests/events/keyboard.html
Normal file
87
src/tests/events/keyboard.html
Normal 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>
|
||||
Reference in New Issue
Block a user