mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-09-23 04:55:23 -04:00
Merge pull request #3414 from navidemad/fix-a54-beforeinput-cancelable
webapi: make trusted beforeinput cancelable
This commit is contained in:
3 files changed
+155
-5
No files matched your search
@@ -0,0 +1,66 @@
|
||||
<!DOCTYPE html>
|
||||
<script src="../testing.js"></script>
|
||||
<script id=constructorDefaults>
|
||||
|
||||
{
|
||||
// Constructed events follow the EventInit dictionary defaults.
|
||||
let evt = new InputEvent('beforeinput');
|
||||
testing.expectEqual(true, evt instanceof InputEvent);
|
||||
testing.expectEqual(true, evt instanceof UIEvent);
|
||||
testing.expectEqual(false, evt.bubbles);
|
||||
testing.expectEqual(false, evt.cancelable);
|
||||
testing.expectEqual(false, evt.composed);
|
||||
testing.expectEqual(null, evt.data);
|
||||
testing.expectEqual('', evt.inputType);
|
||||
testing.expectEqual(false, evt.isComposing);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<script id=constructorOptions>
|
||||
|
||||
{
|
||||
let evt = new InputEvent('beforeinput', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
composed: true,
|
||||
data: 'x',
|
||||
inputType: 'insertText',
|
||||
isComposing: true,
|
||||
});
|
||||
testing.expectEqual(true, evt.bubbles);
|
||||
testing.expectEqual(true, evt.cancelable);
|
||||
testing.expectEqual(true, evt.composed);
|
||||
testing.expectEqual('x', evt.data);
|
||||
testing.expectEqual('insertText', evt.inputType);
|
||||
testing.expectEqual(true, evt.isComposing);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<script id=constructorCancelable>
|
||||
|
||||
{
|
||||
// preventDefault() only takes effect on a cancelable event.
|
||||
let cancelable = new InputEvent('beforeinput', {cancelable: true});
|
||||
cancelable.preventDefault();
|
||||
testing.expectEqual(true, cancelable.defaultPrevented);
|
||||
|
||||
let plain = new InputEvent('beforeinput');
|
||||
plain.preventDefault();
|
||||
testing.expectEqual(false, plain.defaultPrevented);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<script id=createEvent>
|
||||
|
||||
{
|
||||
// document.createEvent leaves the flags unset until initEvent runs.
|
||||
let evt = document.createEvent('InputEvent');
|
||||
testing.expectEqual(true, evt instanceof InputEvent);
|
||||
testing.expectEqual(false, evt.bubbles);
|
||||
testing.expectEqual(false, evt.cancelable);
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -80,11 +80,18 @@ fn initWithTrusted(arena: *lp.Arena, typ: String, _opts: ?Options, trusted: bool
|
||||
|
||||
Event.populatePrototypes(event, opts, trusted);
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Element/input_event
|
||||
const rootevt = event._proto._proto;
|
||||
rootevt._bubbles = true;
|
||||
rootevt._cancelable = false;
|
||||
rootevt._composed = true;
|
||||
if (trusted) {
|
||||
// Browser-generated input events bubble and are composed. `beforeinput`
|
||||
// is cancelable — cancelling it vetoes the edit — while `input` is
|
||||
// dispatched after the fact and is not:
|
||||
// https://w3c.github.io/uievents/#event-type-beforeinput
|
||||
// https://w3c.github.io/uievents/#event-type-input
|
||||
// Synthetic ones follow the EventInit dictionary defaults.
|
||||
const rootevt = event._proto._proto;
|
||||
rootevt._bubbles = true;
|
||||
rootevt._cancelable = typ.eqlSlice("beforeinput");
|
||||
rootevt._composed = true;
|
||||
}
|
||||
|
||||
// Hold a ref on the DataTransfer (when present) for this event's lifetime;
|
||||
// released in deinit. Almost always null for input events, but keeps the
|
||||
@@ -146,3 +153,8 @@ pub const JsApi = struct {
|
||||
pub const inputType = bridge.accessor(InputEvent.getInputType, null, .{});
|
||||
pub const isComposing = bridge.accessor(InputEvent.getIsComposing, null, .{});
|
||||
};
|
||||
|
||||
const testing = @import("../../../testing.zig");
|
||||
test "WebApi: InputEvent" {
|
||||
try testing.htmlRunner("event/input.html", .{});
|
||||
}
|
||||
@@ -406,3 +406,75 @@ test "cdp.input: dispatchKeyEvent Tab runs sequential focus navigation" {
|
||||
});
|
||||
try testing.expect((try ls.local.compileAndRun("document.activeElement.id === 'b1'", null)).isTrue());
|
||||
}
|
||||
|
||||
test "cdp.input: dispatchKeyEvent beforeinput can veto the edit" {
|
||||
var ctx = try testing.context();
|
||||
defer ctx.deinit();
|
||||
|
||||
const bc = try ctx.loadBrowserContext(.{});
|
||||
const page = try bc.session.createPage();
|
||||
const frame = page.frame().?;
|
||||
try frame.navigate("http://localhost:9582/src/browser/tests/mcp_actions.html", .{
|
||||
.reason = .address_bar,
|
||||
.kind = .{ .push = null },
|
||||
});
|
||||
try testing.waitForPage(bc);
|
||||
|
||||
var ls: lp.js.Local.Scope = undefined;
|
||||
frame.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
// A listener that rejects every keystroke, the way masked-input libraries
|
||||
// do. Records what it saw so we can assert the event was cancelable.
|
||||
_ = try ls.local.compileAndRun(
|
||||
\\const inp = document.getElementById('inp');
|
||||
\\inp.value = 'ab';
|
||||
\\inp.focus();
|
||||
\\window.seen = [];
|
||||
\\inp.addEventListener('beforeinput', (e) => {
|
||||
\\ window.seen.push(['beforeinput', e.cancelable].join(':'));
|
||||
\\ e.preventDefault();
|
||||
\\ window.seen.push(['defaultPrevented', e.defaultPrevented].join(':'));
|
||||
\\});
|
||||
\\inp.addEventListener('input', () => window.seen.push('input'));
|
||||
, null);
|
||||
|
||||
try ctx.processMessage(.{
|
||||
.id = 1,
|
||||
.method = "Input.dispatchKeyEvent",
|
||||
.params = .{ .type = "keyDown", .key = "c", .text = "c" },
|
||||
});
|
||||
|
||||
// The veto held: no character inserted and no `input` event followed.
|
||||
try testing.expect((try ls.local.compileAndRun(
|
||||
\\inp.value === 'ab' &&
|
||||
\\window.seen.join(',') === 'beforeinput:true,defaultPrevented:true'
|
||||
, null)).isTrue());
|
||||
|
||||
// Backspace goes through the same pre-edit gate.
|
||||
try ctx.processMessage(.{
|
||||
.id = 2,
|
||||
.method = "Input.dispatchKeyEvent",
|
||||
.params = .{ .type = "keyDown", .key = "Backspace", .code = "Backspace" },
|
||||
});
|
||||
try testing.expect((try ls.local.compileAndRun("inp.value === 'ab'", null)).isTrue());
|
||||
|
||||
// Without the veto, the edit goes through and `input` is dispatched — and
|
||||
// `input` itself is not cancelable.
|
||||
_ = try ls.local.compileAndRun(
|
||||
\\const clone = inp.cloneNode(true);
|
||||
\\inp.replaceWith(clone);
|
||||
\\clone.focus();
|
||||
\\window.seen = [];
|
||||
\\clone.addEventListener('input', (e) => window.seen.push(['input', e.cancelable].join(':')));
|
||||
, null);
|
||||
|
||||
try ctx.processMessage(.{
|
||||
.id = 3,
|
||||
.method = "Input.dispatchKeyEvent",
|
||||
.params = .{ .type = "keyDown", .key = "c", .text = "c" },
|
||||
});
|
||||
try testing.expect((try ls.local.compileAndRun(
|
||||
\\clone.value === 'abc' && window.seen.join(',') === 'input:false'
|
||||
, null)).isTrue());
|
||||
}
|
||||
Reference in new issue
Block a user