mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[web] Don't send keyboard events from text fields to flutter (flutter/engine#13699)
This commit is contained in:
parent
8cb106f918
commit
c7ab18cec2
@ -60,6 +60,7 @@ class Chrome extends Browser {
|
||||
'--window-size=$kMaxScreenshotWidth,$kMaxScreenshotHeight', // When headless, this is the actual size of the viewport
|
||||
'--disable-extensions',
|
||||
'--disable-popup-blocking',
|
||||
// Indicates that the browser is in "browse without sign-in" (Guest session) mode.
|
||||
'--bwsi',
|
||||
'--no-first-run',
|
||||
'--no-default-browser-check',
|
||||
|
||||
@ -4,6 +4,23 @@
|
||||
|
||||
part of engine;
|
||||
|
||||
/// Contains a whitelist of keys that must be sent to Flutter under all
|
||||
/// circumstances.
|
||||
///
|
||||
/// When keys are pressed in a text field, we generally don't want to send them
|
||||
/// to Flutter. This list of keys is the exception to that rule. Keys in this
|
||||
/// list will be sent to Flutter even if pressed in a text field.
|
||||
///
|
||||
/// A good example is the "Tab" and "Shift" keys which are used by the framework
|
||||
/// to move focus between text fields.
|
||||
const List<String> _alwaysSentKeys = <String>[
|
||||
'Alt',
|
||||
'Control',
|
||||
'Meta',
|
||||
'Shift',
|
||||
'Tab',
|
||||
];
|
||||
|
||||
/// Provides keyboard bindings, such as the `flutter/keyevent` channel.
|
||||
class Keyboard {
|
||||
/// Initializes the [Keyboard] singleton.
|
||||
@ -50,6 +67,10 @@ class Keyboard {
|
||||
static const JSONMessageCodec _messageCodec = JSONMessageCodec();
|
||||
|
||||
void _handleHtmlEvent(html.KeyboardEvent event) {
|
||||
if (_shouldIgnoreEvent(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_shouldPreventDefault(event)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
@ -66,6 +87,20 @@ class Keyboard {
|
||||
_messageCodec.encodeMessage(eventData), _noopCallback);
|
||||
}
|
||||
|
||||
/// Whether the [Keyboard] class should ignore the given [html.KeyboardEvent].
|
||||
///
|
||||
/// When this method returns true, it prevents the keyboard event from being
|
||||
/// sent to Flutter.
|
||||
bool _shouldIgnoreEvent(html.KeyboardEvent event) {
|
||||
// Keys in the [_alwaysSentKeys] list should never be ignored.
|
||||
if (_alwaysSentKeys.contains(event.key)) {
|
||||
return false;
|
||||
}
|
||||
// Other keys should be ignored if triggered on a text field.
|
||||
return event.target is html.Element &&
|
||||
HybridTextEditing.isEditingElement(event.target);
|
||||
}
|
||||
|
||||
bool _shouldPreventDefault(html.KeyboardEvent event) {
|
||||
switch (event.key) {
|
||||
case 'Tab':
|
||||
|
||||
@ -17,6 +17,8 @@ void _emptyCallback(dynamic _) {}
|
||||
///
|
||||
/// They are assigned once during the creation of the DOM element.
|
||||
void _setStaticStyleAttributes(html.HtmlElement domElement) {
|
||||
domElement.classes.add(HybridTextEditing.textEditingClass);
|
||||
|
||||
final html.CssStyleDeclaration elementStyle = domElement.style;
|
||||
elementStyle
|
||||
..whiteSpace = 'pre-wrap'
|
||||
@ -534,6 +536,14 @@ class HybridTextEditing {
|
||||
return _defaultEditingElement;
|
||||
}
|
||||
|
||||
/// A CSS class name used to identify all elements used for text editing.
|
||||
@visibleForTesting
|
||||
static const String textEditingClass = 'flt-text-editing';
|
||||
|
||||
static bool isEditingElement(html.Element element) {
|
||||
return element.classes.contains(textEditingClass);
|
||||
}
|
||||
|
||||
/// Requests that [customEditingElement] is used for managing text editing state
|
||||
/// instead of the hidden default element.
|
||||
///
|
||||
|
||||
@ -13,6 +13,17 @@ import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group('Keyboard', () {
|
||||
/// Used to save and restore [ui.window.onPlatformMessage] after each test.
|
||||
ui.PlatformMessageCallback savedCallback;
|
||||
|
||||
setUp(() {
|
||||
savedCallback = ui.window.onPlatformMessage;
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
ui.window.onPlatformMessage = savedCallback;
|
||||
});
|
||||
|
||||
test('initializes and disposes', () {
|
||||
expect(Keyboard.instance, isNull);
|
||||
Keyboard.initialize();
|
||||
@ -204,6 +215,12 @@ void main() {
|
||||
test('prevents default when "Tab" is pressed', () {
|
||||
Keyboard.initialize();
|
||||
|
||||
int count = 0;
|
||||
ui.window.onPlatformMessage = (String channel, ByteData data,
|
||||
ui.PlatformMessageResponseCallback callback) {
|
||||
count += 1;
|
||||
};
|
||||
|
||||
final html.KeyboardEvent event = dispatchKeyboardEvent(
|
||||
'keydown',
|
||||
key: 'Tab',
|
||||
@ -211,14 +228,78 @@ void main() {
|
||||
);
|
||||
|
||||
expect(event.defaultPrevented, isTrue);
|
||||
expect(count, 1);
|
||||
|
||||
Keyboard.instance.dispose();
|
||||
});
|
||||
|
||||
test('ignores keyboard events triggered on text fields', () {
|
||||
Keyboard.initialize();
|
||||
|
||||
int count = 0;
|
||||
ui.window.onPlatformMessage = (String channel, ByteData data,
|
||||
ui.PlatformMessageResponseCallback callback) {
|
||||
count += 1;
|
||||
};
|
||||
|
||||
useTextEditingElement((html.Element element) {
|
||||
final html.KeyboardEvent event = dispatchKeyboardEvent(
|
||||
'keydown',
|
||||
key: 'SomeKey',
|
||||
code: 'SomeCode',
|
||||
target: element,
|
||||
);
|
||||
|
||||
expect(event.defaultPrevented, isFalse);
|
||||
expect(count, 0);
|
||||
});
|
||||
|
||||
Keyboard.instance.dispose();
|
||||
});
|
||||
|
||||
test('the "Tab" key should never be ignored', () {
|
||||
Keyboard.initialize();
|
||||
|
||||
int count = 0;
|
||||
ui.window.onPlatformMessage = (String channel, ByteData data,
|
||||
ui.PlatformMessageResponseCallback callback) {
|
||||
count += 1;
|
||||
};
|
||||
|
||||
useTextEditingElement((html.Element element) {
|
||||
final html.KeyboardEvent event = dispatchKeyboardEvent(
|
||||
'keydown',
|
||||
key: 'Tab',
|
||||
code: 'Tab',
|
||||
target: element,
|
||||
);
|
||||
|
||||
expect(event.defaultPrevented, isTrue);
|
||||
expect(count, 1);
|
||||
});
|
||||
|
||||
Keyboard.instance.dispose();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
typedef ElementCallback = void Function(html.Element element);
|
||||
|
||||
void useTextEditingElement(ElementCallback callback) {
|
||||
final html.InputElement input = html.InputElement();
|
||||
input.classes.add(HybridTextEditing.textEditingClass);
|
||||
|
||||
try {
|
||||
html.document.body.append(input);
|
||||
callback(input);
|
||||
} finally {
|
||||
input.remove();
|
||||
}
|
||||
}
|
||||
|
||||
html.KeyboardEvent dispatchKeyboardEvent(
|
||||
String type, {
|
||||
html.EventTarget target,
|
||||
String key,
|
||||
String code,
|
||||
bool repeat = false,
|
||||
@ -227,6 +308,8 @@ html.KeyboardEvent dispatchKeyboardEvent(
|
||||
bool isControlPressed = false,
|
||||
bool isMetaPressed = false,
|
||||
}) {
|
||||
target ??= html.window;
|
||||
|
||||
final Function jsKeyboardEvent =
|
||||
js_util.getProperty(html.window, 'KeyboardEvent');
|
||||
final List<dynamic> eventArgs = <dynamic>[
|
||||
@ -239,12 +322,13 @@ html.KeyboardEvent dispatchKeyboardEvent(
|
||||
'altKey': isAltPressed,
|
||||
'ctrlKey': isControlPressed,
|
||||
'metaKey': isMetaPressed,
|
||||
'bubbles': true,
|
||||
'cancelable': true,
|
||||
}
|
||||
];
|
||||
final html.KeyboardEvent event =
|
||||
js_util.callConstructor(jsKeyboardEvent, js_util.jsify(eventArgs));
|
||||
html.window.dispatchEvent(event);
|
||||
target.dispatchEvent(event);
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user