From 231aeddfd8663619d79f4a67a75f8ab093a16d52 Mon Sep 17 00:00:00 2001 From: Mouad Debbar Date: Fri, 18 Aug 2023 16:07:54 -0400 Subject: [PATCH 1/5] [web] De-singletonize MouseCursor for multi-view --- lib/web_ui/lib/src/engine/dom.dart | 2 + lib/web_ui/lib/src/engine/initialization.dart | 1 - lib/web_ui/lib/src/engine/mouse_cursor.dart | 26 ++--- .../lib/src/engine/platform_dispatcher.dart | 16 +-- lib/web_ui/lib/src/engine/window.dart | 19 +++- .../initialization/services_vs_ui_test.dart | 3 - lib/web_ui/test/engine/mouse_cursor_test.dart | 99 +++++++++++++++++++ 7 files changed, 131 insertions(+), 35 deletions(-) create mode 100644 lib/web_ui/test/engine/mouse_cursor_test.dart diff --git a/lib/web_ui/lib/src/engine/dom.dart b/lib/web_ui/lib/src/engine/dom.dart index 4a5ac6e540dea..403a4d99de6af 100644 --- a/lib/web_ui/lib/src/engine/dom.dart +++ b/lib/web_ui/lib/src/engine/dom.dart @@ -743,6 +743,7 @@ extension DomCSSStyleDeclarationExtension on DomCSSStyleDeclaration { set alignContent(String value) => setProperty('align-content', value); set textAlign(String value) => setProperty('text-align', value); set font(String value) => setProperty('font', value); + set cursor(String value) => setProperty('cursor', value); String get width => getPropertyValue('width'); String get height => getPropertyValue('height'); String get position => getPropertyValue('position'); @@ -807,6 +808,7 @@ extension DomCSSStyleDeclarationExtension on DomCSSStyleDeclaration { String get alignContent => getPropertyValue('align-content'); String get textAlign => getPropertyValue('text-align'); String get font => getPropertyValue('font'); + String get cursor => getPropertyValue('cursor'); @JS('getPropertyValue') external JSString _getPropertyValue(JSString property); diff --git a/lib/web_ui/lib/src/engine/initialization.dart b/lib/web_ui/lib/src/engine/initialization.dart index 5e94e5a78b77b..ed537132c98ca 100644 --- a/lib/web_ui/lib/src/engine/initialization.dart +++ b/lib/web_ui/lib/src/engine/initialization.dart @@ -226,7 +226,6 @@ Future initializeEngineUi() async { _initializationState = DebugEngineInitializationState.initializingUi; RawKeyboard.initialize(onMacOs: operatingSystem == OperatingSystem.macOs); - MouseCursor.initialize(); ensureFlutterViewEmbedderInitialized(); _initializationState = DebugEngineInitializationState.initialized; } diff --git a/lib/web_ui/lib/src/engine/mouse_cursor.dart b/lib/web_ui/lib/src/engine/mouse_cursor.dart index 0ccfbed03b129..7c89f51c07d7b 100644 --- a/lib/web_ui/lib/src/engine/mouse_cursor.dart +++ b/lib/web_ui/lib/src/engine/mouse_cursor.dart @@ -2,23 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'embedder.dart'; -import 'util.dart'; +import 'dom.dart'; +import 'window.dart'; -/// Provides mouse cursor bindings, such as the `flutter/mousecursor` channel. +/// Controls the mouse cursor in the given [view]. class MouseCursor { - MouseCursor._(); + MouseCursor(this.view); - /// Initializes the [MouseCursor] singleton. - /// - /// Use the [instance] getter to get the singleton after calling this method. - static void initialize() { - _instance ??= MouseCursor._(); - } - - /// The [MouseCursor] singleton. - static MouseCursor? get instance => _instance; - static MouseCursor? _instance; + final EngineFlutterView view; // Map from Flutter's kind values to CSS's cursor values. // @@ -61,15 +52,12 @@ class MouseCursor { 'zoomIn': 'zoom-in', 'zoomOut': 'zoom-out', }; + static String _mapKindToCssValue(String? kind) { return _kindToCssValueMap[kind] ?? 'default'; } void activateSystemCursor(String? kind) { - setElementStyle( - flutterViewEmbedder.flutterViewElement, - 'cursor', - _mapKindToCssValue(kind), - ); + view.rootElement.style.cursor = _mapKindToCssValue(kind); } } diff --git a/lib/web_ui/lib/src/engine/platform_dispatcher.dart b/lib/web_ui/lib/src/engine/platform_dispatcher.dart index de21b0cd6133e..66a74d3ce8c56 100644 --- a/lib/web_ui/lib/src/engine/platform_dispatcher.dart +++ b/lib/web_ui/lib/src/engine/platform_dispatcher.dart @@ -171,7 +171,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { /// * [PlatformDisptacher.views] for a list of all [FlutterView]s provided /// by the platform. @override - ui.FlutterView? get implicitView => viewData[kImplicitViewId]; + EngineFlutterWindow? get implicitView => viewData[kImplicitViewId] as EngineFlutterWindow?; /// A callback that is invoked whenever the platform's [devicePixelRatio], /// [physicalSize], [padding], [viewInsets], or [systemGestureInsets] @@ -505,10 +505,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { // TODO(a-wallen): As multi-window support expands, the pop call // will need to include the view ID. Right now only one view is // supported. - (viewData[kImplicitViewId]! as EngineFlutterWindow) - .browserHistory - .exit() - .then((_) { + implicitView!.browserHistory.exit().then((_) { replyToPlatformMessage( callback, codec.encodeSuccessEnvelope(true)); }); @@ -585,7 +582,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { final Map arguments = decoded.arguments as Map; switch (decoded.method) { case 'activateSystemCursor': - MouseCursor.instance!.activateSystemCursor(arguments.tryString('kind')); + implicitView!.mouseCursor.activateSystemCursor(arguments.tryString('kind')); } return; @@ -618,9 +615,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { // TODO(a-wallen): As multi-window support expands, the navigation call // will need to include the view ID. Right now only one view is // supported. - (viewData[kImplicitViewId]! as EngineFlutterWindow) - .handleNavigationMessage(data) - .then((bool handled) { + implicitView!.handleNavigationMessage(data).then((bool handled) { if (handled) { const MethodCodec codec = JSONMethodCodec(); replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); @@ -1231,8 +1226,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { /// requests from the embedder. @override String get defaultRouteName { - return _defaultRouteName ??= - (viewData[kImplicitViewId]! as EngineFlutterWindow).browserHistory.currentPath; + return _defaultRouteName ??= implicitView!.browserHistory.currentPath; } /// Lazily initialized when the `defaultRouteName` getter is invoked. diff --git a/lib/web_ui/lib/src/engine/window.dart b/lib/web_ui/lib/src/engine/window.dart index 47a1215fd1f9b..08c465d6444da 100644 --- a/lib/web_ui/lib/src/engine/window.dart +++ b/lib/web_ui/lib/src/engine/window.dart @@ -16,6 +16,8 @@ import 'package:ui/ui_web/src/ui_web.dart' as ui_web; import '../engine.dart' show DimensionsProvider, registerHotRestartListener, renderer; import 'display.dart'; import 'dom.dart'; +import 'embedder.dart'; +import 'mouse_cursor.dart'; import 'navigation/history.dart'; import 'platform_dispatcher.dart'; import 'services.dart'; @@ -29,8 +31,17 @@ const bool debugPrintPlatformMessages = false; /// The view ID for the implicit flutter view provided by the platform. const int kImplicitViewId = 0; +/// Represents all views in the Flutter Web Engine. +/// +/// In addition to everything defined in [ui.FlutterView], this class adds +/// a few web-specific properties. +abstract class EngineFlutterView extends ui.FlutterView { + MouseCursor get mouseCursor; + DomElement get rootElement; +} + /// The Web implementation of [ui.SingletonFlutterWindow]. -class EngineFlutterWindow extends ui.SingletonFlutterWindow { +class EngineFlutterWindow extends ui.SingletonFlutterWindow implements EngineFlutterView { EngineFlutterWindow(this.viewId, this.platformDispatcher) { platformDispatcher.viewData[viewId] = this; platformDispatcher.windowConfigurations[viewId] = const ViewConfiguration(); @@ -53,6 +64,12 @@ class EngineFlutterWindow extends ui.SingletonFlutterWindow { @override final EnginePlatformDispatcher platformDispatcher; + @override + late final MouseCursor mouseCursor = MouseCursor(this); + + @override + DomElement get rootElement => flutterViewEmbedder.flutterViewElement; + /// Handles the browser history integration to allow users to use the back /// button, etc. BrowserHistory get browserHistory { diff --git a/lib/web_ui/test/canvaskit/initialization/services_vs_ui_test.dart b/lib/web_ui/test/canvaskit/initialization/services_vs_ui_test.dart index 8f6d74cc09469..f4adbc5398af2 100644 --- a/lib/web_ui/test/canvaskit/initialization/services_vs_ui_test.dart +++ b/lib/web_ui/test/canvaskit/initialization/services_vs_ui_test.dart @@ -17,7 +17,6 @@ void testMain() { expect(findGlassPane(), isNull); expect(RawKeyboard.instance, isNull); - expect(MouseCursor.instance, isNull); expect(KeyboardBinding.instance, isNull); expect(PointerBinding.instance, isNull); @@ -28,7 +27,6 @@ void testMain() { expect(findGlassPane(), isNull); expect(RawKeyboard.instance, isNull); - expect(MouseCursor.instance, isNull); expect(KeyboardBinding.instance, isNull); expect(PointerBinding.instance, isNull); @@ -36,7 +34,6 @@ void testMain() { await initializeEngineUi(); expect(findGlassPane(), isNotNull); expect(RawKeyboard.instance, isNotNull); - expect(MouseCursor.instance, isNotNull); expect(KeyboardBinding.instance, isNotNull); expect(PointerBinding.instance, isNotNull); }); diff --git a/lib/web_ui/test/engine/mouse_cursor_test.dart b/lib/web_ui/test/engine/mouse_cursor_test.dart new file mode 100644 index 0000000000000..f603c4afadf66 --- /dev/null +++ b/lib/web_ui/test/engine/mouse_cursor_test.dart @@ -0,0 +1,99 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:test/bootstrap/browser.dart'; +import 'package:test/test.dart'; +import 'package:ui/src/engine.dart'; +import 'package:ui/ui.dart' as ui; + +void main() { + internalBootstrapBrowserTest(() => testMain); +} + +void testMain() { + group('$MouseCursor', () { + test('sets correct `cursor` style on root element', () { + final DomElement rootViewElement = createDomElement('div'); + final MockFlutterView view = MockFlutterView() + ..rootElement = rootViewElement; + final MouseCursor mouseCursor = MouseCursor(view); + + mouseCursor.activateSystemCursor('alias'); + expect(rootViewElement.style.cursor, 'alias'); + + mouseCursor.activateSystemCursor('move'); + expect(rootViewElement.style.cursor, 'move'); + + mouseCursor.activateSystemCursor('precise'); + expect(rootViewElement.style.cursor, 'crosshair'); + + mouseCursor.activateSystemCursor('resizeDownRight'); + expect(rootViewElement.style.cursor, 'se-resize'); + }); + + test('handles unknown cursor type', () { + final DomElement rootViewElement = createDomElement('div'); + final MockFlutterView view = MockFlutterView() + ..rootElement = rootViewElement; + final MouseCursor mouseCursor = MouseCursor(view); + + mouseCursor.activateSystemCursor('unknown'); + expect(rootViewElement.style.cursor, 'default'); + }); + }); +} + +class MockFlutterView implements EngineFlutterView { + @override + late DomElement rootElement; + + @override + double get devicePixelRatio => throw UnimplementedError(); + + @override + ui.Display get display => throw UnimplementedError(); + + @override + List get displayFeatures => throw UnimplementedError(); + + @override + ui.GestureSettings get gestureSettings => throw UnimplementedError(); + + @override + MouseCursor get mouseCursor => throw UnimplementedError(); + + @override + ViewPadding get padding => throw UnimplementedError(); + + @override + ui.Rect get physicalGeometry => throw UnimplementedError(); + + @override + ui.Size get physicalSize => throw UnimplementedError(); + + @override + ui.PlatformDispatcher get platformDispatcher => throw UnimplementedError(); + + @override + void render(ui.Scene scene) { + throw UnimplementedError(); + } + + @override + ViewPadding get systemGestureInsets => throw UnimplementedError(); + + @override + void updateSemantics(ui.SemanticsUpdate update) { + throw UnimplementedError(); + } + + @override + int get viewId => throw UnimplementedError(); + + @override + ViewPadding get viewInsets => throw UnimplementedError(); + + @override + ViewPadding get viewPadding => throw UnimplementedError(); +} From f4b93f7188360fe49b7e471e81211c56c325427f Mon Sep 17 00:00:00 2001 From: Mouad Debbar Date: Thu, 7 Sep 2023 11:08:19 -0400 Subject: [PATCH 2/5] create mouse/ folder --- ci/licenses_golden/licenses_flutter | 4 ++-- lib/web_ui/lib/src/engine.dart | 2 +- .../lib/src/engine/{mouse_cursor.dart => mouse/cursor.dart} | 4 ++-- .../engine/{mouse_cursor_test.dart => mouse/cursor_test.dart} | 0 4 files changed, 5 insertions(+), 5 deletions(-) rename lib/web_ui/lib/src/engine/{mouse_cursor.dart => mouse/cursor.dart} (97%) rename lib/web_ui/test/engine/{mouse_cursor_test.dart => mouse/cursor_test.dart} (100%) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 3ae421ecf59f3..f3a62d59d9b7f 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -2041,7 +2041,7 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/js_interop/js_typed_data.dart ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/key_map.g.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/keyboard_binding.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/layers.dart + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/mouse_cursor.dart + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/mouse/cursor.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/navigation/history.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/noto_font.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/onscreen_logging.dart + ../../../flutter/LICENSE @@ -4794,7 +4794,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/js_interop/js_typed_data.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/key_map.g.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/keyboard_binding.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/layers.dart -FILE: ../../../flutter/lib/web_ui/lib/src/engine/mouse_cursor.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/mouse/cursor.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/navigation/history.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/noto_font.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/onscreen_logging.dart diff --git a/lib/web_ui/lib/src/engine.dart b/lib/web_ui/lib/src/engine.dart index 1dcb01e019c18..2455cb8523521 100644 --- a/lib/web_ui/lib/src/engine.dart +++ b/lib/web_ui/lib/src/engine.dart @@ -112,7 +112,7 @@ export 'engine/js_interop/js_typed_data.dart'; export 'engine/key_map.g.dart'; export 'engine/keyboard_binding.dart'; export 'engine/layers.dart'; -export 'engine/mouse_cursor.dart'; +export 'engine/mouse/cursor.dart'; export 'engine/navigation/history.dart'; export 'engine/noto_font.dart'; export 'engine/onscreen_logging.dart'; diff --git a/lib/web_ui/lib/src/engine/mouse_cursor.dart b/lib/web_ui/lib/src/engine/mouse/cursor.dart similarity index 97% rename from lib/web_ui/lib/src/engine/mouse_cursor.dart rename to lib/web_ui/lib/src/engine/mouse/cursor.dart index 7c89f51c07d7b..1352d899c3342 100644 --- a/lib/web_ui/lib/src/engine/mouse_cursor.dart +++ b/lib/web_ui/lib/src/engine/mouse/cursor.dart @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dom.dart'; -import 'window.dart'; +import '../dom.dart'; +import '../window.dart'; /// Controls the mouse cursor in the given [view]. class MouseCursor { diff --git a/lib/web_ui/test/engine/mouse_cursor_test.dart b/lib/web_ui/test/engine/mouse/cursor_test.dart similarity index 100% rename from lib/web_ui/test/engine/mouse_cursor_test.dart rename to lib/web_ui/test/engine/mouse/cursor_test.dart From 567299e531cb1f4986d888956a0c22765a62da39 Mon Sep 17 00:00:00 2001 From: Mouad Debbar Date: Thu, 7 Sep 2023 14:46:43 -0400 Subject: [PATCH 3/5] fix import --- lib/web_ui/lib/src/engine/window.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/web_ui/lib/src/engine/window.dart b/lib/web_ui/lib/src/engine/window.dart index 08c465d6444da..a4c51060e05d5 100644 --- a/lib/web_ui/lib/src/engine/window.dart +++ b/lib/web_ui/lib/src/engine/window.dart @@ -17,7 +17,7 @@ import '../engine.dart' show DimensionsProvider, registerHotRestartListener, ren import 'display.dart'; import 'dom.dart'; import 'embedder.dart'; -import 'mouse_cursor.dart'; +import 'mouse/cursor.dart'; import 'navigation/history.dart'; import 'platform_dispatcher.dart'; import 'services.dart'; From b29c5f6f691b3255f03819669a63ac4c4e1ff055 Mon Sep 17 00:00:00 2001 From: Mouad Debbar Date: Wed, 13 Sep 2023 16:50:41 -0400 Subject: [PATCH 4/5] MouseCursor(rootElement) --- lib/web_ui/lib/src/engine/mouse/cursor.dart | 9 ++- lib/web_ui/lib/src/engine/window.dart | 2 +- lib/web_ui/test/engine/mouse/cursor_test.dart | 63 +------------------ 3 files changed, 7 insertions(+), 67 deletions(-) diff --git a/lib/web_ui/lib/src/engine/mouse/cursor.dart b/lib/web_ui/lib/src/engine/mouse/cursor.dart index 1352d899c3342..589891173b5fe 100644 --- a/lib/web_ui/lib/src/engine/mouse/cursor.dart +++ b/lib/web_ui/lib/src/engine/mouse/cursor.dart @@ -3,13 +3,12 @@ // found in the LICENSE file. import '../dom.dart'; -import '../window.dart'; -/// Controls the mouse cursor in the given [view]. +/// Controls the mouse cursor in the given [element]. class MouseCursor { - MouseCursor(this.view); + MouseCursor(this.element); - final EngineFlutterView view; + final DomElement element; // Map from Flutter's kind values to CSS's cursor values. // @@ -58,6 +57,6 @@ class MouseCursor { } void activateSystemCursor(String? kind) { - view.rootElement.style.cursor = _mapKindToCssValue(kind); + element.style.cursor = _mapKindToCssValue(kind); } } diff --git a/lib/web_ui/lib/src/engine/window.dart b/lib/web_ui/lib/src/engine/window.dart index a4c51060e05d5..d13e265c9ea87 100644 --- a/lib/web_ui/lib/src/engine/window.dart +++ b/lib/web_ui/lib/src/engine/window.dart @@ -65,7 +65,7 @@ class EngineFlutterWindow extends ui.SingletonFlutterWindow implements EngineFlu final EnginePlatformDispatcher platformDispatcher; @override - late final MouseCursor mouseCursor = MouseCursor(this); + late final MouseCursor mouseCursor = MouseCursor(rootElement); @override DomElement get rootElement => flutterViewEmbedder.flutterViewElement; diff --git a/lib/web_ui/test/engine/mouse/cursor_test.dart b/lib/web_ui/test/engine/mouse/cursor_test.dart index f603c4afadf66..f81012ba61efa 100644 --- a/lib/web_ui/test/engine/mouse/cursor_test.dart +++ b/lib/web_ui/test/engine/mouse/cursor_test.dart @@ -5,7 +5,6 @@ import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; import 'package:ui/src/engine.dart'; -import 'package:ui/ui.dart' as ui; void main() { internalBootstrapBrowserTest(() => testMain); @@ -15,9 +14,7 @@ void testMain() { group('$MouseCursor', () { test('sets correct `cursor` style on root element', () { final DomElement rootViewElement = createDomElement('div'); - final MockFlutterView view = MockFlutterView() - ..rootElement = rootViewElement; - final MouseCursor mouseCursor = MouseCursor(view); + final MouseCursor mouseCursor = MouseCursor(rootViewElement); mouseCursor.activateSystemCursor('alias'); expect(rootViewElement.style.cursor, 'alias'); @@ -34,66 +31,10 @@ void testMain() { test('handles unknown cursor type', () { final DomElement rootViewElement = createDomElement('div'); - final MockFlutterView view = MockFlutterView() - ..rootElement = rootViewElement; - final MouseCursor mouseCursor = MouseCursor(view); + final MouseCursor mouseCursor = MouseCursor(rootViewElement); mouseCursor.activateSystemCursor('unknown'); expect(rootViewElement.style.cursor, 'default'); }); }); } - -class MockFlutterView implements EngineFlutterView { - @override - late DomElement rootElement; - - @override - double get devicePixelRatio => throw UnimplementedError(); - - @override - ui.Display get display => throw UnimplementedError(); - - @override - List get displayFeatures => throw UnimplementedError(); - - @override - ui.GestureSettings get gestureSettings => throw UnimplementedError(); - - @override - MouseCursor get mouseCursor => throw UnimplementedError(); - - @override - ViewPadding get padding => throw UnimplementedError(); - - @override - ui.Rect get physicalGeometry => throw UnimplementedError(); - - @override - ui.Size get physicalSize => throw UnimplementedError(); - - @override - ui.PlatformDispatcher get platformDispatcher => throw UnimplementedError(); - - @override - void render(ui.Scene scene) { - throw UnimplementedError(); - } - - @override - ViewPadding get systemGestureInsets => throw UnimplementedError(); - - @override - void updateSemantics(ui.SemanticsUpdate update) { - throw UnimplementedError(); - } - - @override - int get viewId => throw UnimplementedError(); - - @override - ViewPadding get viewInsets => throw UnimplementedError(); - - @override - ViewPadding get viewPadding => throw UnimplementedError(); -} From 1b949ec2796cb82818c0333eed996d220f754c70 Mon Sep 17 00:00:00 2001 From: Mouad Debbar Date: Wed, 13 Sep 2023 16:50:52 -0400 Subject: [PATCH 5/5] abstract interface --- lib/web_ui/lib/src/engine/window.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/web_ui/lib/src/engine/window.dart b/lib/web_ui/lib/src/engine/window.dart index d13e265c9ea87..0dd502def1627 100644 --- a/lib/web_ui/lib/src/engine/window.dart +++ b/lib/web_ui/lib/src/engine/window.dart @@ -35,7 +35,7 @@ const int kImplicitViewId = 0; /// /// In addition to everything defined in [ui.FlutterView], this class adds /// a few web-specific properties. -abstract class EngineFlutterView extends ui.FlutterView { +abstract interface class EngineFlutterView extends ui.FlutterView { MouseCursor get mouseCursor; DomElement get rootElement; }