From 8edd3c000a390234d0b09b2965c2b2a6a0315daa Mon Sep 17 00:00:00 2001 From: joshualitt Date: Tue, 4 Apr 2023 09:53:13 -0700 Subject: [PATCH] [web] Migrate EventListener's to JS types. (#40566) --- .../src/engine/canvaskit/canvaskit_api.dart | 4 +- .../lib/src/engine/canvaskit/surface.dart | 15 ++-- lib/web_ui/lib/src/engine/dom.dart | 28 +++++-- .../html/debug_canvas_reuse_overlay.dart | 3 +- .../lib/src/engine/html_image_codec.dart | 4 +- .../lib/src/engine/keyboard_binding.dart | 24 +++--- .../engine/navigation/js_url_strategy.dart | 4 +- .../src/engine/navigation/url_strategy.dart | 3 +- lib/web_ui/lib/src/engine/picture.dart | 5 +- .../lib/src/engine/platform_dispatcher.dart | 14 ++-- .../lib/src/engine/pointer_binding.dart | 26 +++---- lib/web_ui/lib/src/engine/raw_keyboard.dart | 5 +- .../lib/src/engine/safe_browser_api.dart | 43 +---------- .../src/engine/semantics/incrementable.dart | 3 +- .../lib/src/engine/semantics/scrollable.dart | 2 +- .../engine/semantics/semantics_helper.dart | 5 +- .../lib/src/engine/semantics/tappable.dart | 3 +- .../lib/src/engine/semantics/text_field.dart | 15 ++-- .../text_editing/composition_aware_mixin.dart | 15 ++-- .../src/engine/text_editing/text_editing.dart | 76 +++++++++---------- .../full_page_dimensions_provider.dart | 3 +- .../embedding_strategy.dart | 3 +- lib/web_ui/test/engine/routing_test.dart | 2 +- lib/web_ui/test/engine/text_editing_test.dart | 5 +- lib/web_ui/test/engine/window_test.dart | 2 +- .../drawing/draw_vertices_golden_test.dart | 2 +- .../web_engine_tester/lib/static/host.dart | 10 +-- 27 files changed, 146 insertions(+), 178 deletions(-) diff --git a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart index 4aee9ba55ee9c..884f38f24290e 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart @@ -3915,8 +3915,8 @@ Future _downloadCanvasKitJs(String url) { canvasKitLoadCompleter.complete(false); } - loadCallback = allowInterop(loadEventHandler); - errorCallback = allowInterop(errorEventHandler); + loadCallback = createDomEventListener(loadEventHandler); + errorCallback = createDomEventListener(errorEventHandler); canvasKitScript.addEventListener('load', loadCallback); canvasKitScript.addEventListener('error', errorCallback); diff --git a/lib/web_ui/lib/src/engine/canvaskit/surface.dart b/lib/web_ui/lib/src/engine/canvaskit/surface.dart index 80ef7e1da8c4d..76719d68dcea8 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/surface.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/surface.dart @@ -2,13 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:js_interop'; + import 'package:ui/ui.dart' as ui; import '../browser_detection.dart'; import '../configuration.dart'; import '../dom.dart'; import '../platform_dispatcher.dart'; -import '../safe_browser_api.dart'; import '../util.dart'; import '../window.dart'; import 'canvas.dart'; @@ -67,7 +68,7 @@ class Surface { /// We must cache this function because each time we access the tear-off it /// creates a new object, meaning we won't be able to remove this listener /// later. - void Function(DomEvent)? _cachedContextLostListener; + DomEventListener? _cachedContextLostListener; /// A cached copy of the most recently created `webglcontextrestored` /// listener. @@ -75,7 +76,7 @@ class Surface { /// We must cache this function because each time we access the tear-off it /// creates a new object, meaning we won't be able to remove this listener /// later. - void Function(DomEvent)? _cachedContextRestoredListener; + DomEventListener? _cachedContextRestoredListener; SkGrContext? _grContext; int? _glContext; @@ -268,7 +269,7 @@ class Surface { htmlCanvas!.style.transform = 'translate(0, -${offset}px)'; } - void _contextRestoredListener(DomEvent event) { + JSVoid _contextRestoredListener(DomEvent event) { assert( _contextLost, 'Received "webglcontextrestored" event but never received ' @@ -280,7 +281,7 @@ class Surface { event.preventDefault(); } - void _contextLostListener(DomEvent event) { + JSVoid _contextLostListener(DomEvent event) { assert(event.target == htmlCanvas, 'Received a context lost event for a disposed canvas'); final SurfaceFactory factory = SurfaceFactory.instance; @@ -344,8 +345,8 @@ class Surface { // notification. When we receive this notification we force a new context. // // See also: https://www.khronos.org/webgl/wiki/HandlingContextLost - _cachedContextRestoredListener = allowInterop(_contextRestoredListener); - _cachedContextLostListener = allowInterop(_contextLostListener); + _cachedContextRestoredListener = createDomEventListener(_contextRestoredListener); + _cachedContextLostListener = createDomEventListener(_contextLostListener); htmlCanvas.addEventListener( 'webglcontextlost', _cachedContextLostListener, diff --git a/lib/web_ui/lib/src/engine/dom.dart b/lib/web_ui/lib/src/engine/dom.dart index abd107d3cb81a..4019ab8d7f3df 100644 --- a/lib/web_ui/lib/src/engine/dom.dart +++ b/lib/web_ui/lib/src/engine/dom.dart @@ -39,7 +39,7 @@ import 'browser_detection.dart'; /// `JSUndefined`. extension ObjectToJSAnyExtension on Object { JSAny get toJSAnyShallow { - if (isWasm) { + if (isWasm) { return toJSAnyDeep; } else { // TODO(joshualitt): remove this cast when we reify JS types on JS @@ -334,6 +334,13 @@ extension DomEventTargetExtension on DomEventTarget { } } + @JS('addEventListener') + external JSVoid _addEventListener3( + JSString type, DomEventListener listener, JSAny options); + void addEventListenerWithOptions(String type, DomEventListener listener, + Map options) => + _addEventListener3(type.toJS, listener, options.toJSAnyDeep); + @JS('removeEventListener') external JSVoid _removeEventListener1( JSString type, DomEventListener listener); @@ -356,7 +363,14 @@ extension DomEventTargetExtension on DomEventTarget { bool dispatchEvent(DomEvent event) => _dispatchEvent(event).toDart; } -typedef DomEventListener = void Function(DomEvent event); +typedef DartDomEventListener = JSVoid Function(DomEvent event); + +@JS() +@staticInterop +class DomEventListener {} + +DomEventListener createDomEventListener(DartDomEventListener listener) => + listener.toJS as DomEventListener; @JS() @staticInterop @@ -2260,10 +2274,10 @@ extension DomMediaQueryListExtension on DomMediaQueryList { bool get matches => _matches.toDart; @JS('addListener') - external JSVoid addListener(JSFunction? listener); + external JSVoid addListener(DomEventListener? listener); @JS('removeListener') - external JSVoid removeListener(JSFunction? listener); + external JSVoid removeListener(DomEventListener? listener); } @JS() @@ -2829,8 +2843,10 @@ extension DomScreenOrientationExtension on DomScreenOrientation { // remove the listener. Caller is still responsible for calling [allowInterop] // on the listener before creating the subscription. class DomSubscription { - DomSubscription(this.target, String typeString, this.listener) - : type = typeString.toJS { + DomSubscription( + this.target, String typeString, DartDomEventListener dartListener) + : type = typeString.toJS, + listener = createDomEventListener(dartListener) { target._addEventListener1(type, listener); } diff --git a/lib/web_ui/lib/src/engine/html/debug_canvas_reuse_overlay.dart b/lib/web_ui/lib/src/engine/html/debug_canvas_reuse_overlay.dart index 644a53aa57f5b..348b92914d671 100644 --- a/lib/web_ui/lib/src/engine/html/debug_canvas_reuse_overlay.dart +++ b/lib/web_ui/lib/src/engine/html/debug_canvas_reuse_overlay.dart @@ -65,7 +65,8 @@ class DebugCanvasReuseOverlay { ..append( createDomHTMLButtonElement() ..text = 'Reset' - ..addEventListener('click', (_) => _reset()), + ..addEventListener('click', + createDomEventListener((_) => _reset())), ), ), ); diff --git a/lib/web_ui/lib/src/engine/html_image_codec.dart b/lib/web_ui/lib/src/engine/html_image_codec.dart index f1f184b527c26..212d45942fd71 100644 --- a/lib/web_ui/lib/src/engine/html_image_codec.dart +++ b/lib/web_ui/lib/src/engine/html_image_codec.dart @@ -87,7 +87,7 @@ class HtmlCodec implements ui.Codec { // on the main thread, and may cause dropped framed. late DomEventListener errorListener; DomEventListener? loadListener; - errorListener = allowInterop((DomEvent event) { + errorListener = createDomEventListener((DomEvent event) { if (loadListener != null) { imgElement.removeEventListener('load', loadListener); } @@ -95,7 +95,7 @@ class HtmlCodec implements ui.Codec { completer.completeError(event); }); imgElement.addEventListener('error', errorListener); - loadListener = allowInterop((DomEvent event) { + loadListener = createDomEventListener((DomEvent event) { if (chunkCallback != null) { chunkCallback!(100, 100); } diff --git a/lib/web_ui/lib/src/engine/keyboard_binding.dart b/lib/web_ui/lib/src/engine/keyboard_binding.dart index 443f22f2dfd8c..7a851b16c2deb 100644 --- a/lib/web_ui/lib/src/engine/keyboard_binding.dart +++ b/lib/web_ui/lib/src/engine/keyboard_binding.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:js_interop'; + import 'package:meta/meta.dart'; import 'package:ui/ui.dart' as ui; import 'package:web_locale_keymap/web_locale_keymap.dart' as locale_keymap; @@ -11,7 +13,6 @@ import 'browser_detection.dart'; import 'dom.dart'; import 'key_map.g.dart'; import 'platform_dispatcher.dart'; -import 'safe_browser_api.dart'; import 'semantics.dart'; typedef _VoidCallback = void Function(); @@ -100,13 +101,13 @@ ValueGetter _cached(ValueGetter body) { class KeyboardBinding { KeyboardBinding._() { - _addEventListener('keydown', allowInterop((DomEvent domEvent) { + _addEventListener('keydown', (DomEvent domEvent) { final FlutterHtmlKeyboardEvent event = FlutterHtmlKeyboardEvent(domEvent as DomKeyboardEvent); - return _converter.handleEvent(event); - })); - _addEventListener('keyup', allowInterop((DomEvent event) { - return _converter.handleEvent(FlutterHtmlKeyboardEvent(event as DomKeyboardEvent)); - })); + _converter.handleEvent(event); + }); + _addEventListener('keyup', (DomEvent event) { + _converter.handleEvent(FlutterHtmlKeyboardEvent(event as DomKeyboardEvent)); + }); } /// The singleton instance of this object. @@ -138,18 +139,17 @@ class KeyboardBinding { ); final Map _listeners = {}; - void _addEventListener(String eventName, DomEventListener handler) { - dynamic loggedHandler(DomEvent event) { + void _addEventListener(String eventName, DartDomEventListener handler) { + JSVoid loggedHandler(DomEvent event) { if (_debugLogKeyEvents) { print(event.type); } if (EngineSemanticsOwner.instance.receiveGlobalEvent(event)) { - return handler(event); + handler(event); } - return null; } - final DomEventListener wrappedHandler = allowInterop(loggedHandler); + final DomEventListener wrappedHandler = createDomEventListener(loggedHandler); assert(!_listeners.containsKey(eventName)); _listeners[eventName] = wrappedHandler; domWindow.addEventListener(eventName, wrappedHandler, true); diff --git a/lib/web_ui/lib/src/engine/navigation/js_url_strategy.dart b/lib/web_ui/lib/src/engine/navigation/js_url_strategy.dart index ee1e7c017bd70..a76dfd7c44c4a 100644 --- a/lib/web_ui/lib/src/engine/navigation/js_url_strategy.dart +++ b/lib/web_ui/lib/src/engine/navigation/js_url_strategy.dart @@ -14,7 +14,7 @@ typedef _PathGetter = String Function(); typedef _StateGetter = Object? Function(); -typedef _AddPopStateListener = ui.VoidCallback Function(DomEventListener); +typedef _AddPopStateListener = ui.VoidCallback Function(DartDomEventListener); typedef _StringToString = String Function(String); @@ -47,7 +47,7 @@ abstract class JsUrlStrategy { extension JsUrlStrategyExtension on JsUrlStrategy { /// Adds a listener to the `popstate` event and returns a function that, when /// invoked, removes the listener. - external ui.VoidCallback addPopStateListener(DomEventListener fn); + external ui.VoidCallback addPopStateListener(DartDomEventListener fn); /// Returns the active path in the browser. external String getPath(); diff --git a/lib/web_ui/lib/src/engine/navigation/url_strategy.dart b/lib/web_ui/lib/src/engine/navigation/url_strategy.dart index 6c211e2fd7d27..0d49ed64fc495 100644 --- a/lib/web_ui/lib/src/engine/navigation/url_strategy.dart +++ b/lib/web_ui/lib/src/engine/navigation/url_strategy.dart @@ -35,8 +35,7 @@ class HashUrlStrategy extends ui_web.UrlStrategy { @override ui.VoidCallback addPopStateListener(ui_web.PopStateListener fn) { - final DomEventListener wrappedFn = - allowInterop((DomEvent event) => fn((event as DomPopStateEvent).state)); + final DomEventListener wrappedFn = createDomEventListener(fn); _platformLocation.addPopStateListener(wrappedFn); return () => _platformLocation.removePopStateListener(wrappedFn); } diff --git a/lib/web_ui/lib/src/engine/picture.dart b/lib/web_ui/lib/src/engine/picture.dart index 3b9083ff04231..59a1a70aef9cc 100644 --- a/lib/web_ui/lib/src/engine/picture.dart +++ b/lib/web_ui/lib/src/engine/picture.dart @@ -10,7 +10,6 @@ import 'dom.dart'; import 'html/bitmap_canvas.dart'; import 'html/recording_canvas.dart'; import 'html_image_codec.dart'; -import 'safe_browser_api.dart'; import 'util.dart'; /// An implementation of [ui.PictureRecorder] backed by a [RecordingCanvas]. @@ -74,13 +73,13 @@ class EnginePicture implements ui.Picture { // Ignoring the returned futures from onError and onLoad because we're // communicating through the `onImageLoaded` completer. late final DomEventListener errorListener; - errorListener = allowInterop((DomEvent event) { + errorListener = createDomEventListener((DomEvent event) { onImageLoaded.completeError(event); imageElement.removeEventListener('error', errorListener); }); imageElement.addEventListener('error', errorListener); late final DomEventListener loadListener; - loadListener = allowInterop((DomEvent event) { + loadListener = createDomEventListener((DomEvent event) { onImageLoaded.complete(HtmlImage( imageElement, width, diff --git a/lib/web_ui/lib/src/engine/platform_dispatcher.dart b/lib/web_ui/lib/src/engine/platform_dispatcher.dart index a0128a0c71383..efe4496e8bd06 100644 --- a/lib/web_ui/lib/src/engine/platform_dispatcher.dart +++ b/lib/web_ui/lib/src/engine/platform_dispatcher.dart @@ -43,8 +43,8 @@ class HighContrastSupport { /// Reference to css media query that indicates whether high contrast is on. final DomMediaQueryList _highContrastMediaQuery = domWindow.matchMedia(_highContrastMediaQueryString); - late final JSFunction _onHighContrastChangeListener = - _onHighContrastChange.toJS; + late final DomEventListener _onHighContrastChangeListener = + createDomEventListener(_onHighContrastChange); bool get isHighContrastEnabled => _highContrastMediaQuery.matches; @@ -799,11 +799,11 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { } updateLocales(); // First time, for good measure. _onLocaleChangedSubscription = - DomSubscription(domWindow, 'languagechange', allowInterop((DomEvent _) { + DomSubscription(domWindow, 'languagechange', (DomEvent _) { // Update internal config, then propagate the changes. updateLocales(); invokeOnLocaleChanged(); - })); + }); } /// Removes the [_onLocaleChangedSubscription]. @@ -1029,7 +1029,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { /// A callback that is invoked whenever [_brightnessMediaQuery] changes value. /// /// Updates the [_platformBrightness] with the new user preference. - JSFunction? _brightnessMediaQueryListener; + DomEventListener? _brightnessMediaQueryListener; /// Set the callback function for listening changes in [_brightnessMediaQuery] value. void _addBrightnessMediaQueryListener() { @@ -1037,12 +1037,12 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { ? ui.Brightness.dark : ui.Brightness.light); - _brightnessMediaQueryListener = (DomEvent event) { + _brightnessMediaQueryListener = createDomEventListener((DomEvent event) { final DomMediaQueryListEvent mqEvent = event as DomMediaQueryListEvent; _updatePlatformBrightness( mqEvent.matches! ? ui.Brightness.dark : ui.Brightness.light); - }.toJS; + }); _brightnessMediaQuery.addListener(_brightnessMediaQueryListener); } diff --git a/lib/web_ui/lib/src/engine/pointer_binding.dart b/lib/web_ui/lib/src/engine/pointer_binding.dart index 8b035e8398723..23dafa64e6626 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:js_interop'; import 'dart:math' as math; import 'package:meta/meta.dart'; @@ -74,7 +75,7 @@ class SafariPointerEventWorkaround { static SafariPointerEventWorkaround instance = SafariPointerEventWorkaround(); void workAroundMissingPointerEvents() { - domDocument.addEventListener('touchstart', allowInterop((DomEvent event) {})); + domDocument.addEventListener('touchstart', createDomEventListener((DomEvent event) {})); } } @@ -204,10 +205,10 @@ class _Listener { factory _Listener.register({ required String event, required DomEventTarget target, - required DomEventListener handler, + required DartDomEventListener handler, bool capture = false, }) { - final DomEventListener jsHandler = allowInterop((DomEvent event) => handler(event)); + final DomEventListener jsHandler = createDomEventListener(handler); final _Listener listener = _Listener._( event: event, target: target, @@ -223,15 +224,14 @@ class _Listener { factory _Listener.registerNative({ required String event, required DomEventTarget target, - required DomEventListener handler, + required DomEventListener jsHandler, bool capture = false, bool passive = false, }) { - final Object eventOptions = createPlainJsObject({ + final Map eventOptions = { 'capture': capture, 'passive': passive, - }); - final DomEventListener jsHandler = allowInterop((DomEvent event) => handler(event)); + }; final _Listener listener = _Listener._( event: event, target: target, @@ -239,7 +239,7 @@ class _Listener { useCapture: capture, isNative: true, ); - addJsEventListener(target, event, jsHandler, eventOptions); + target.addEventListenerWithOptions(event, jsHandler, eventOptions); return listener; } @@ -253,7 +253,7 @@ class _Listener { void unregister() { if (isNative) { - removeJsEventListener(target, event, handler, useCapture); + target.removeEventListener(event, handler, useCapture); } else { target.removeEventListener(event, handler, useCapture); } @@ -306,10 +306,10 @@ abstract class _BaseAdapter { void addEventListener( DomEventTarget target, String eventName, - DomEventListener handler, { + DartDomEventListener handler, { bool useCapture = true, }) { - dynamic loggedHandler(DomEvent event) { + JSVoid loggedHandler(DomEvent event) { if (_debugLogPointerEvents) { if (domInstanceOfString(event, 'PointerEvent')) { final DomPointerEvent pointerEvent = event as DomPointerEvent; @@ -495,11 +495,11 @@ mixin _WheelEventListenerMixin on _BaseAdapter { return data; } - void _addWheelEventListener(DomEventListener handler) { + void _addWheelEventListener(DartDomEventListener handler) { _listeners.add(_Listener.registerNative( event: 'wheel', target: glassPaneElement, - handler: (DomEvent event) => handler(event), + jsHandler: createDomEventListener(handler), )); } diff --git a/lib/web_ui/lib/src/engine/raw_keyboard.dart b/lib/web_ui/lib/src/engine/raw_keyboard.dart index 6157bd7477c85..b2e2edc7cf7df 100644 --- a/lib/web_ui/lib/src/engine/raw_keyboard.dart +++ b/lib/web_ui/lib/src/engine/raw_keyboard.dart @@ -9,18 +9,17 @@ import '../engine.dart' show registerHotRestartListener; import 'dom.dart'; import 'keyboard_binding.dart'; import 'platform_dispatcher.dart'; -import 'safe_browser_api.dart'; import 'services.dart'; /// Provides keyboard bindings, such as the `flutter/keyevent` channel. class RawKeyboard { RawKeyboard._(this._onMacOs) { - _keydownListener = allowInterop((DomEvent event) { + _keydownListener = createDomEventListener((DomEvent event) { _handleHtmlEvent(event); }); domWindow.addEventListener('keydown', _keydownListener); - _keyupListener = allowInterop((DomEvent event) { + _keyupListener = createDomEventListener((DomEvent event) { _handleHtmlEvent(event); }); domWindow.addEventListener('keyup', _keyupListener); diff --git a/lib/web_ui/lib/src/engine/safe_browser_api.dart b/lib/web_ui/lib/src/engine/safe_browser_api.dart index bceb7c9d5b81d..ec9a4723b37b8 100644 --- a/lib/web_ui/lib/src/engine/safe_browser_api.dart +++ b/lib/web_ui/lib/src/engine/safe_browser_api.dart @@ -27,16 +27,6 @@ import 'vector_math.dart'; export 'package:js/js.dart' show allowInterop; -/// Creates JavaScript object populated with [properties]. -/// -/// This is equivalent to writing `{}` in plain JavaScript. -Object createPlainJsObject([Map? properties]) { - if (properties != null) { - return js_util.jsify(properties) as Object; - } else { - return js_util.newObject(); - } -} /// Returns true if [object] has property [name], false otherwise. /// @@ -80,37 +70,6 @@ Future promiseToFuture(Object jsPromise) { /// A function that receives a benchmark [value] labeleb by [name]. typedef OnBenchmark = void Function(String name, double value); -/// Adds an event [listener] of type [type] to the [target]. -/// -/// [eventOptions] supply additional configuration parameters. -/// -/// This is different from [DomElement.addEventListener] in that the listener -/// is added as a plain JavaScript function, as opposed to a Dart function. -/// -/// To remove the listener, call [removeJsEventListener]. -void addJsEventListener(Object target, String type, Function listener, Object eventOptions) { - js_util.callMethod( - target, - 'addEventListener', [ - type, - listener, - eventOptions, - ] - ); -} - -/// Removes an event listener that was added using [addJsEventListener]. -void removeJsEventListener(Object target, String type, Function listener, Object eventOptions) { - js_util.callMethod( - target, - 'removeEventListener', [ - type, - listener, - eventOptions, - ] - ); -} - /// Parses a string [source] into a double. /// /// Uses the JavaScript `parseFloat` function instead of Dart's [double.parse] @@ -1076,7 +1035,7 @@ class OffScreenCanvas { if (offScreenCanvas != null) { offScreenCanvas!.convertToBlob().then((DomBlob value) { final DomFileReader fileReader = createDomFileReader(); - fileReader.addEventListener('load', allowInterop((DomEvent event) { + fileReader.addEventListener('load', createDomEventListener((DomEvent event) { completer.complete( js_util.getProperty(js_util.getProperty(event, 'target'), 'result'), ); diff --git a/lib/web_ui/lib/src/engine/semantics/incrementable.dart b/lib/web_ui/lib/src/engine/semantics/incrementable.dart index 59f43fa2e0fe1..5cba2db7b1695 100644 --- a/lib/web_ui/lib/src/engine/semantics/incrementable.dart +++ b/lib/web_ui/lib/src/engine/semantics/incrementable.dart @@ -6,7 +6,6 @@ import 'package:ui/ui.dart' as ui; import '../dom.dart'; import '../platform_dispatcher.dart'; -import '../safe_browser_api.dart'; import 'semantics.dart'; /// Adds increment/decrement event handling to a semantics object. @@ -25,7 +24,7 @@ class Incrementable extends RoleManager { _element.type = 'range'; _element.setAttribute('role', 'slider'); - _element.addEventListener('change', allowInterop((_) { + _element.addEventListener('change', createDomEventListener((_) { if (_element.disabled!) { return; } diff --git a/lib/web_ui/lib/src/engine/semantics/scrollable.dart b/lib/web_ui/lib/src/engine/semantics/scrollable.dart index 38f07c71e91fb..cafd8cbfff134 100644 --- a/lib/web_ui/lib/src/engine/semantics/scrollable.dart +++ b/lib/web_ui/lib/src/engine/semantics/scrollable.dart @@ -121,7 +121,7 @@ class Scrollable extends RoleManager { }; semanticsObject.owner.addGestureModeListener(_gestureModeListener); - _scrollListener = allowInterop((_) { + _scrollListener = createDomEventListener((_) { _recomputeScrollPosition(); }); semanticsObject.element.addEventListener('scroll', _scrollListener); diff --git a/lib/web_ui/lib/src/engine/semantics/semantics_helper.dart b/lib/web_ui/lib/src/engine/semantics/semantics_helper.dart index 1e3fffae4d176..eae0e1bcd098b 100644 --- a/lib/web_ui/lib/src/engine/semantics/semantics_helper.dart +++ b/lib/web_ui/lib/src/engine/semantics/semantics_helper.dart @@ -8,7 +8,6 @@ import 'package:meta/meta.dart'; import '../browser_detection.dart'; import '../dom.dart'; -import '../safe_browser_api.dart'; import 'semantics.dart'; /// The maximum [semanticsActivationAttempts] before we give up waiting for @@ -180,7 +179,7 @@ class DesktopSemanticsEnabler extends SemanticsEnabler { // Only listen to "click" because other kinds of events are reported via // PointerBinding. - placeholder.addEventListener('click', allowInterop((DomEvent event) { + placeholder.addEventListener('click', createDomEventListener((DomEvent event) { tryEnableSemantics(event); }), true); @@ -374,7 +373,7 @@ class MobileSemanticsEnabler extends SemanticsEnabler { // Only listen to "click" because other kinds of events are reported via // PointerBinding. - placeholder.addEventListener('click', allowInterop((DomEvent event) { + placeholder.addEventListener('click', createDomEventListener((DomEvent event) { tryEnableSemantics(event); }), true); diff --git a/lib/web_ui/lib/src/engine/semantics/tappable.dart b/lib/web_ui/lib/src/engine/semantics/tappable.dart index 311b5b77a00c6..1141ad77336f7 100644 --- a/lib/web_ui/lib/src/engine/semantics/tappable.dart +++ b/lib/web_ui/lib/src/engine/semantics/tappable.dart @@ -6,7 +6,6 @@ import 'package:ui/ui.dart' as ui; import '../dom.dart'; import '../platform_dispatcher.dart'; -import '../safe_browser_api.dart'; import 'semantics.dart'; /// Listens to HTML "click" gestures detected by the browser. @@ -44,7 +43,7 @@ class Tappable extends RoleManager { if (semanticsObject.hasAction(ui.SemanticsAction.tap) && !semanticsObject.hasFlag(ui.SemanticsFlag.isTextField)) { if (_clickListener == null) { - _clickListener = allowInterop((_) { + _clickListener = createDomEventListener((_) { if (semanticsObject.owner.gestureMode != GestureMode.browserGestures) { return; diff --git a/lib/web_ui/lib/src/engine/semantics/text_field.dart b/lib/web_ui/lib/src/engine/semantics/text_field.dart index abf29dd9d6cfd..ae2cec9394cb5 100644 --- a/lib/web_ui/lib/src/engine/semantics/text_field.dart +++ b/lib/web_ui/lib/src/engine/semantics/text_field.dart @@ -9,7 +9,6 @@ import '../browser_detection.dart'; import '../dom.dart'; import '../embedder.dart'; import '../platform_dispatcher.dart'; -import '../safe_browser_api.dart'; import '../text_editing/text_editing.dart'; import 'semantics.dart'; @@ -139,13 +138,13 @@ class SemanticsTextEditingStrategy extends DefaultTextEditingStrategy { // Subscribe to text and selection changes. subscriptions.add( - DomSubscription(activeDomElement, 'input', allowInterop(handleChange))); + DomSubscription(activeDomElement, 'input', handleChange)); subscriptions.add( DomSubscription(activeDomElement, 'keydown', - allowInterop(maybeSendAction))); + maybeSendAction)); subscriptions.add( DomSubscription(domDocument, 'selectionchange', - allowInterop(handleChange))); + handleChange)); preventDefaultForMouseEvents(); } @@ -298,7 +297,7 @@ class TextField extends RoleManager { void _initializeForBlink() { _initializeEditableElement(); activeEditableElement.addEventListener('focus', - allowInterop((DomEvent event) { + createDomEventListener((DomEvent event) { if (semanticsObject.owner.gestureMode != GestureMode.browserGestures) { return; } @@ -339,14 +338,14 @@ class TextField extends RoleManager { num? lastPointerDownOffsetY; semanticsObject.element.addEventListener('pointerdown', - allowInterop((DomEvent event) { + createDomEventListener((DomEvent event) { final DomPointerEvent pointerEvent = event as DomPointerEvent; lastPointerDownOffsetX = pointerEvent.clientX; lastPointerDownOffsetY = pointerEvent.clientY; }), true); semanticsObject.element.addEventListener('pointerup', - allowInterop((DomEvent event) { + createDomEventListener((DomEvent event) { final DomPointerEvent pointerEvent = event as DomPointerEvent; if (lastPointerDownOffsetX != null) { @@ -396,7 +395,7 @@ class TextField extends RoleManager { semanticsObject.element.removeAttribute('role'); activeEditableElement.addEventListener('blur', - allowInterop((DomEvent event) { + createDomEventListener((DomEvent event) { semanticsObject.element.setAttribute('role', 'textbox'); activeEditableElement.remove(); SemanticsTextEditingStrategy._instance?.deactivate(this); diff --git a/lib/web_ui/lib/src/engine/text_editing/composition_aware_mixin.dart b/lib/web_ui/lib/src/engine/text_editing/composition_aware_mixin.dart index 7ba45ad442f5b..c33a72bfde378 100644 --- a/lib/web_ui/lib/src/engine/text_editing/composition_aware_mixin.dart +++ b/lib/web_ui/lib/src/engine/text_editing/composition_aware_mixin.dart @@ -2,8 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:js_interop'; + import '../dom.dart'; -import '../safe_browser_api.dart'; import 'text_editing.dart'; /// Provides default functionality for listening to HTML composition events. @@ -30,11 +31,11 @@ mixin CompositionAwareMixin { static const String _kCompositionEnd = 'compositionend'; late final DomEventListener _compositionStartListener = - allowInterop(_handleCompositionStart); + createDomEventListener(_handleCompositionStart); late final DomEventListener _compositionUpdateListener = - allowInterop(_handleCompositionUpdate); + createDomEventListener(_handleCompositionUpdate); late final DomEventListener _compositionEndListener = - allowInterop(_handleCompositionEnd); + createDomEventListener(_handleCompositionEnd); /// The currently composing text in the `domElement`. /// @@ -55,17 +56,17 @@ mixin CompositionAwareMixin { domElement.removeEventListener(_kCompositionEnd, _compositionEndListener); } - void _handleCompositionStart(DomEvent event) { + JSVoid _handleCompositionStart(DomEvent event) { composingText = null; } - void _handleCompositionUpdate(DomEvent event) { + JSVoid _handleCompositionUpdate(DomEvent event) { if (domInstanceOfString(event, 'CompositionEvent')) { composingText = (event as DomCompositionEvent).data; } } - void _handleCompositionEnd(DomEvent event) { + JSVoid _handleCompositionEnd(DomEvent event) { composingText = null; } diff --git a/lib/web_ui/lib/src/engine/text_editing/text_editing.dart b/lib/web_ui/lib/src/engine/text_editing/text_editing.dart index 94e66d8b6e698..b451d8b0b0b01 100644 --- a/lib/web_ui/lib/src/engine/text_editing/text_editing.dart +++ b/lib/web_ui/lib/src/engine/text_editing/text_editing.dart @@ -194,7 +194,7 @@ class EngineAutofillForm { formElement.noValidate = true; formElement.method = 'post'; formElement.action = '#'; - formElement.addEventListener('submit', allowInterop((DomEvent e) { + formElement.addEventListener('submit', createDomEventListener((DomEvent e) { e.preventDefault(); })); @@ -304,7 +304,7 @@ class EngineAutofillForm { final DomElement element = elements![key]!; subscriptions.add( DomSubscription(element, 'input', - allowInterop((DomEvent e) { + (DomEvent e) { if (items![key] == null) { throw StateError( 'AutofillInfo must have a valid uniqueIdentifier.'); @@ -312,7 +312,7 @@ class EngineAutofillForm { final AutofillInfo autofillInfo = items![key]!; handleChange(element, autofillInfo); } - }))); + })); } keys.forEach(addSubscriptionForKey); @@ -1231,23 +1231,23 @@ abstract class DefaultTextEditingStrategy with CompositionAwareMixin implements // Subscribe to text and selection changes. subscriptions.add(DomSubscription(activeDomElement, 'input', - allowInterop(handleChange))); + handleChange)); subscriptions.add(DomSubscription(activeDomElement, 'keydown', - allowInterop(maybeSendAction))); + maybeSendAction)); subscriptions.add(DomSubscription(domDocument, 'selectionchange', - allowInterop(handleChange))); + handleChange)); activeDomElement.addEventListener('beforeinput', - allowInterop(handleBeforeInput)); + createDomEventListener(handleBeforeInput)); addCompositionEventHandlers(activeDomElement); // Refocus on the activeDomElement after blur, so that user can keep editing the // text field. subscriptions.add(DomSubscription(activeDomElement, 'blur', - allowInterop((_) { activeDomElement.focus(); }))); + (_) { activeDomElement.focus(); })); preventDefaultForMouseEvents(); } @@ -1420,19 +1420,19 @@ abstract class DefaultTextEditingStrategy with CompositionAwareMixin implements /// flickering during selection by mouse. void preventDefaultForMouseEvents() { subscriptions.add( - DomSubscription(activeDomElement, 'mousedown', allowInterop((_) { + DomSubscription(activeDomElement, 'mousedown', (_) { _.preventDefault(); - }))); + })); subscriptions.add( - DomSubscription(activeDomElement, 'mouseup', allowInterop((_) { + DomSubscription(activeDomElement, 'mouseup', (_) { _.preventDefault(); - }))); + })); subscriptions.add( - DomSubscription(activeDomElement, 'mousemove', allowInterop((_) { + DomSubscription(activeDomElement, 'mousemove', (_) { _.preventDefault(); - }))); + })); } } @@ -1525,25 +1525,25 @@ class IOSTextEditingStrategy extends GloballyPositionedTextEditingStrategy { // Subscribe to text and selection changes. subscriptions.add(DomSubscription(activeDomElement, 'input', - allowInterop(handleChange))); + handleChange)); subscriptions.add(DomSubscription(activeDomElement, 'keydown', - allowInterop(maybeSendAction))); + maybeSendAction)); subscriptions.add(DomSubscription(domDocument, 'selectionchange', - allowInterop(handleChange))); + handleChange)); activeDomElement.addEventListener('beforeinput', - allowInterop(handleBeforeInput)); + createDomEventListener(handleBeforeInput)); addCompositionEventHandlers(activeDomElement); // Position the DOM element after it is focused. subscriptions.add(DomSubscription(activeDomElement, 'focus', - allowInterop((_) { + (_) { // Cancel previous timer if exists. _schedulePlacement(); - }))); + })); _addTapListener(); @@ -1567,14 +1567,14 @@ class IOSTextEditingStrategy extends GloballyPositionedTextEditingStrategy { // input field was activated. If the time is too short, we re-focus the // input element. subscriptions.add(DomSubscription(activeDomElement, 'blur', - allowInterop((_) { + (_) { final bool isFastCallback = blurWatch.elapsed < _blurFastCallbackInterval; if (windowHasFocus && isFastCallback) { activeDomElement.focus(); } else { owner.sendTextConnectionClosedToFrameworkIfAny(); } - }))); + })); } @override @@ -1610,7 +1610,7 @@ class IOSTextEditingStrategy extends GloballyPositionedTextEditingStrategy { /// [_positionInputElementTimer] timer is restarted. The element will be /// placed to its correct position after [_delayBeforePlacement]. void _addTapListener() { - subscriptions.add(DomSubscription(activeDomElement, 'click', allowInterop((_) { + subscriptions.add(DomSubscription(activeDomElement, 'click', (_) { // Check if the element is already positioned. If not this does not fall // under `The user was using the long press, now they want to enter text // via keyboard` journey. @@ -1621,7 +1621,7 @@ class IOSTextEditingStrategy extends GloballyPositionedTextEditingStrategy { // Re-configure the timer to place the element. _schedulePlacement(); } - }))); + })); } void _schedulePlacement() { @@ -1676,24 +1676,24 @@ class AndroidTextEditingStrategy extends GloballyPositionedTextEditingStrategy { // Subscribe to text and selection changes. subscriptions.add( - DomSubscription(activeDomElement, 'input', allowInterop(handleChange))); + DomSubscription(activeDomElement, 'input', handleChange)); subscriptions.add( DomSubscription(activeDomElement, 'keydown', - allowInterop(maybeSendAction))); + maybeSendAction)); subscriptions.add( DomSubscription(domDocument, 'selectionchange', - allowInterop(handleChange))); + handleChange)); activeDomElement.addEventListener('beforeinput', - allowInterop(handleBeforeInput)); + createDomEventListener(handleBeforeInput)); addCompositionEventHandlers(activeDomElement); subscriptions.add( DomSubscription(activeDomElement, 'blur', - allowInterop((_) { + (_) { if (windowHasFocus) { // Chrome on Android will hide the onscreen keyboard when you tap outside // the text box. Instead, we want the framework to tell us to hide the @@ -1703,7 +1703,7 @@ class AndroidTextEditingStrategy extends GloballyPositionedTextEditingStrategy { } else { owner.sendTextConnectionClosedToFrameworkIfAny(); } - }))); + })); } @override @@ -1742,14 +1742,14 @@ class FirefoxTextEditingStrategy extends GloballyPositionedTextEditingStrategy { // Subscribe to text and selection changes. subscriptions.add( - DomSubscription(activeDomElement, 'input', allowInterop(handleChange))); + DomSubscription(activeDomElement, 'input', handleChange)); subscriptions.add( DomSubscription( - activeDomElement, 'keydown', allowInterop(maybeSendAction))); + activeDomElement, 'keydown', maybeSendAction)); activeDomElement.addEventListener('beforeinput', - allowInterop(handleBeforeInput)); + createDomEventListener(handleBeforeInput)); addCompositionEventHandlers(activeDomElement); @@ -1770,16 +1770,16 @@ class FirefoxTextEditingStrategy extends GloballyPositionedTextEditingStrategy { DomSubscription( activeDomElement, 'keyup', - allowInterop((DomEvent event) { + (DomEvent event) { handleChange(event); - }))); + })); // In Firefox the context menu item "Select All" does not work without // listening to onSelect. On the other browsers onSelectionChange is // enough for covering "Select All" functionality. subscriptions.add( DomSubscription( - activeDomElement, 'select', allowInterop(handleChange))); + activeDomElement, 'select', handleChange)); // Refocus on the activeDomElement after blur, so that user can keep editing the // text field. @@ -1787,9 +1787,9 @@ class FirefoxTextEditingStrategy extends GloballyPositionedTextEditingStrategy { DomSubscription( activeDomElement, 'blur', - allowInterop((_) { + (_) { _postponeFocus(); - }))); + })); preventDefaultForMouseEvents(); } diff --git a/lib/web_ui/lib/src/engine/view_embedder/dimensions_provider/full_page_dimensions_provider.dart b/lib/web_ui/lib/src/engine/view_embedder/dimensions_provider/full_page_dimensions_provider.dart index f99ce2ebb125c..0a88acc57d62f 100644 --- a/lib/web_ui/lib/src/engine/view_embedder/dimensions_provider/full_page_dimensions_provider.dart +++ b/lib/web_ui/lib/src/engine/view_embedder/dimensions_provider/full_page_dimensions_provider.dart @@ -4,7 +4,6 @@ import 'dart:async'; -import 'package:js/js.dart'; import 'package:ui/src/engine/browser_detection.dart'; import 'package:ui/src/engine/dom.dart'; import 'package:ui/src/engine/window.dart'; @@ -33,7 +32,7 @@ class FullPageDimensionsProvider extends DimensionsProvider { _domResizeSubscription = DomSubscription( resizeEventTarget, 'resize', - allowInterop(_onVisualViewportResize), + _onVisualViewportResize, ); } diff --git a/lib/web_ui/lib/src/engine/view_embedder/embedding_strategy/embedding_strategy.dart b/lib/web_ui/lib/src/engine/view_embedder/embedding_strategy/embedding_strategy.dart index b811153a4ba97..8a3e401816bf9 100644 --- a/lib/web_ui/lib/src/engine/view_embedder/embedding_strategy/embedding_strategy.dart +++ b/lib/web_ui/lib/src/engine/view_embedder/embedding_strategy/embedding_strategy.dart @@ -5,7 +5,6 @@ import 'package:meta/meta.dart'; import 'package:ui/src/engine/dom.dart'; -import 'package:ui/src/engine/safe_browser_api.dart'; import 'package:ui/src/engine/view_embedder/hot_restart_cache_handler.dart'; import 'custom_element_embedding_strategy.dart'; @@ -65,7 +64,7 @@ mixin _ContextMenu { /// Listener for contextmenu events that prevents the browser's context menu /// from being shown. - final DomEventListener _disablingContextMenuListener = allowInterop((DomEvent event) { + final DomEventListener _disablingContextMenuListener = createDomEventListener((DomEvent event) { event.preventDefault(); }); diff --git a/lib/web_ui/test/engine/routing_test.dart b/lib/web_ui/test/engine/routing_test.dart index dd940cd802103..aa1d572102079 100644 --- a/lib/web_ui/test/engine/routing_test.dart +++ b/lib/web_ui/test/engine/routing_test.dart @@ -53,7 +53,7 @@ void testMain() { final JsUrlStrategy jsUrlStrategy = JsUrlStrategy( getPath: allowInterop(() => '/initial'), getState: allowInterop(() => state), - addPopStateListener: allowInterop((DomEventListener listener) => allowInterop(() {})), + addPopStateListener: allowInterop((DartDomEventListener listener) => allowInterop(() {})), prepareExternalUrl: allowInterop((String value) => ''), pushState: allowInterop((Object? newState, String title, String url) { expect(newState is Map, true); diff --git a/lib/web_ui/test/engine/text_editing_test.dart b/lib/web_ui/test/engine/text_editing_test.dart index 5c84bbcdaa3e0..8577a9487f509 100644 --- a/lib/web_ui/test/engine/text_editing_test.dart +++ b/lib/web_ui/test/engine/text_editing_test.dart @@ -13,7 +13,6 @@ import 'package:ui/src/engine.dart' show flutterViewEmbedder; import 'package:ui/src/engine/browser_detection.dart'; import 'package:ui/src/engine/dom.dart'; import 'package:ui/src/engine/initialization.dart'; -import 'package:ui/src/engine/safe_browser_api.dart'; import 'package:ui/src/engine/services.dart'; import 'package:ui/src/engine/text_editing/autofill_hint.dart'; import 'package:ui/src/engine/text_editing/input_type.dart'; @@ -903,7 +902,7 @@ Future testMain() async { defaultTextEditingRoot.querySelector('form')! as DomHTMLFormElement; final Completer submittedForm = Completer(); formElement.addEventListener( - 'submit', allowInterop((DomEvent event) => + 'submit', createDomEventListener((DomEvent event) => submittedForm.complete(true))); const MethodCall clearClient = MethodCall('TextInput.clearClient'); @@ -956,7 +955,7 @@ Future testMain() async { defaultTextEditingRoot.querySelector('form')! as DomHTMLFormElement; final Completer submittedForm = Completer(); formElement.addEventListener( - 'submit', allowInterop((DomEvent event) => + 'submit', createDomEventListener((DomEvent event) => submittedForm.complete(true))); // Clear client is not called. The used requested context to be finalized. diff --git a/lib/web_ui/test/engine/window_test.dart b/lib/web_ui/test/engine/window_test.dart index 623f141510352..8c78d94283729 100644 --- a/lib/web_ui/test/engine/window_test.dart +++ b/lib/web_ui/test/engine/window_test.dart @@ -437,7 +437,7 @@ Future testMain() async { test('dispatches browser event on flutter/service_worker channel', () async { final Completer completer = Completer(); domWindow.addEventListener('flutter-first-frame', - allowInterop((DomEvent e) => completer.complete())); + createDomEventListener((DomEvent e) => completer.complete())); final Zone innerZone = Zone.current.fork(); innerZone.runGuarded(() { diff --git a/lib/web_ui/test/html/drawing/draw_vertices_golden_test.dart b/lib/web_ui/test/html/drawing/draw_vertices_golden_test.dart index 8a1a10ccb568d..4790ff9bd4e43 100644 --- a/lib/web_ui/test/html/drawing/draw_vertices_golden_test.dart +++ b/lib/web_ui/test/html/drawing/draw_vertices_golden_test.dart @@ -397,7 +397,7 @@ Future createTestImage({int width = 50, int height = 40}) { ctx.fill(); final DomHTMLImageElement imageElement = createDomHTMLImageElement(); final Completer completer = Completer(); - imageElement.addEventListener('load', allowInterop((DomEvent event) { + imageElement.addEventListener('load', createDomEventListener((DomEvent event) { completer.complete(HtmlImage(imageElement, width, height)); })); imageElement.src = js_util.callMethod(canvas, 'toDataURL', []); diff --git a/web_sdk/web_engine_tester/lib/static/host.dart b/web_sdk/web_engine_tester/lib/static/host.dart index 3db4d5bf334da..e6ac2e288988a 100644 --- a/web_sdk/web_engine_tester/lib/static/host.dart +++ b/web_sdk/web_engine_tester/lib/static/host.dart @@ -184,7 +184,7 @@ MultiChannel _connectToServer() { final DomWebSocket webSocket = createDomWebSocket(_currentUrl.queryParameters['managerUrl']!); final StreamChannelController controller = StreamChannelController(sync: true); - webSocket.addEventListener('message', allowInterop((DomEvent message) { + webSocket.addEventListener('message', createDomEventListener((DomEvent message) { final String data = (message as DomMessageEvent).data as String; controller.local.sink.add(jsonDecode(data)); })); @@ -221,7 +221,7 @@ StreamChannel _connectToIframe(String url, int id) { _domSubscriptions[id] = domSubscriptions; _streamSubscriptions[id] = streamSubscriptions; domSubscriptions.add(DomSubscription(domWindow, 'message', - allowInterop((DomEvent event) { + (DomEvent event) { final DomMessageEvent message = event as DomMessageEvent; // A message on the Window can theoretically come from any website. It's // very unlikely that a malicious site would care about hacking someone's @@ -249,13 +249,13 @@ StreamChannel _connectToIframe(String url, int id) { // loading the test. controller.local.sink.add(message.data['data']); } - }))); + })); channel.port1.start(); domSubscriptions.add(DomSubscription(channel.port1, 'message', - allowInterop((DomEvent message) { + (DomEvent message) { controller.local.sink.add((message as DomMessageEvent).data['data']); - }))); + })); streamSubscriptions.add(controller.local.stream.listen((dynamic message) async { await readyCompleter.future;