未验证 提交 a9ef2410 编写于 作者: M Mouad Debbar 提交者: GitHub

[web] Don't send keyboard events from text fields to flutter (#13699)

上级 6c763bb5
......@@ -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;
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册