Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -2038,7 +2038,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/noto_font_encoding.dart + ../../../flutter/LICENSE
Expand Down Expand Up @@ -4789,7 +4789,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/noto_font_encoding.dart
Expand Down
2 changes: 1 addition & 1 deletion lib/web_ui/lib/src/engine.dart
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,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/noto_font_encoding.dart';
Expand Down
2 changes: 2 additions & 0 deletions lib/web_ui/lib/src/engine/dom.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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);
Expand Down
1 change: 0 additions & 1 deletion lib/web_ui/lib/src/engine/initialization.dart
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,6 @@ Future<void> initializeEngineUi() async {
_initializationState = DebugEngineInitializationState.initializingUi;

RawKeyboard.initialize(onMacOs: operatingSystem == OperatingSystem.macOs);
MouseCursor.initialize();
ensureFlutterViewEmbedderInitialized();
_initializationState = DebugEngineInitializationState.initialized;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
//
Expand Down Expand Up @@ -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);
}
}
16 changes: 5 additions & 11 deletions lib/web_ui/lib/src/engine/platform_dispatcher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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));
});
Expand Down Expand Up @@ -585,7 +582,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
final Map<dynamic, dynamic> arguments = decoded.arguments as Map<dynamic, dynamic>;
switch (decoded.method) {
case 'activateSystemCursor':
MouseCursor.instance!.activateSystemCursor(arguments.tryString('kind'));
implicitView!.mouseCursor.activateSystemCursor(arguments.tryString('kind'));
}
return;

Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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.
Expand Down
19 changes: 18 additions & 1 deletion lib/web_ui/lib/src/engine/window.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that the class that represents all views in the Flutter Web Engine is EngineFlutterWindow (that's the type that was used to define the implicitView, for example), right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EngineFlutterWindow is a misnomer. It should be called EngineSingletonFlutterWindow (it actually extends ui.SingletonFlutterWindow). Views in the multi-view world shouldn't inherit from this singleton class. They should inherit directly from ui.FlutterView (and implement EngineFlutterView to satisfy all the web-specific needs).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's a follow up PR to rename this class to make it clear that it's a singleton: #45981

///
/// In addition to everything defined in [ui.FlutterView], this class adds
/// a few web-specific properties.
abstract class EngineFlutterView extends ui.FlutterView {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this interface here, but I don't think it needs to extends ui.FlutterView. In the class hierarchy, IIRC, EngineFlutterWindow already extends ui.FlutterView (through ui.SingletonFlutterWindow)

Also, I think this should be abstract interface class EngineFlutterView, if we can use that feature of Dart.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thought here was that EngineFlutterView will become the base for all window and non-window views. Other non-window views will directly extend EngineFlutterView since they don't need ui.SingletonFlutterWindow.

So the hierarchy becomes:

                         ui.FlutterView
               (dart:ui interface across platforms)
                                |
                                |
                       EngineFlutterView (new)
           (web-specific additions to the interface)
                /                               \
               /                                 \
     NonWindowFlutterView (future)          EngineFlutterWindow
(non-sintleton, non-window views)        (legacy singleton window)

Also, I think this should be abstract interface class EngineFlutterView, if we can use that feature of Dart.

We can! We already use it in platform_location.dart :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahhh, I gotcha. The advantage of the current version:

ui.View -> ui.Window -> WebWindow implements WebOnlyAccessors

Is that we're only extending the base classes defined by the core of the engine. In my mind that means that if there's an upcoming change in the base structure of classes in package:ui, it should be easier for ui to do changes to the base class, and for us to adapt to those changes?

(Of course, it depends on the changes, so our class extensions might not matter too much!)

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();
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -28,15 +27,13 @@ void testMain() {

expect(findGlassPane(), isNull);
expect(RawKeyboard.instance, isNull);
expect(MouseCursor.instance, isNull);
expect(KeyboardBinding.instance, isNull);
expect(PointerBinding.instance, isNull);

// Now UI should be taken over by Flutter.
await initializeEngineUi();
expect(findGlassPane(), isNotNull);
expect(RawKeyboard.instance, isNotNull);
expect(MouseCursor.instance, isNotNull);
expect(KeyboardBinding.instance, isNotNull);
expect(PointerBinding.instance, isNotNull);
});
Expand Down
99 changes: 99 additions & 0 deletions lib/web_ui/test/engine/mouse/cursor_test.dart
Original file line number Diff line number Diff line change
@@ -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<ui.DisplayFeature> 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();
}