System mouse cursor: Web (#17718)

Adds system mouse cursor to the web engine.
diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter
index fb970c8..1ef5628 100755
--- a/ci/licenses_golden/licenses_flutter
+++ b/ci/licenses_golden/licenses_flutter
@@ -443,6 +443,7 @@
 FILE: ../../../flutter/lib/web_ui/lib/src/engine/houdini_canvas.dart
 FILE: ../../../flutter/lib/web_ui/lib/src/engine/html_image_codec.dart
 FILE: ../../../flutter/lib/web_ui/lib/src/engine/keyboard.dart
+FILE: ../../../flutter/lib/web_ui/lib/src/engine/mouse_cursor.dart
 FILE: ../../../flutter/lib/web_ui/lib/src/engine/onscreen_logging.dart
 FILE: ../../../flutter/lib/web_ui/lib/src/engine/path_to_svg.dart
 FILE: ../../../flutter/lib/web_ui/lib/src/engine/picture.dart
diff --git a/lib/web_ui/lib/src/engine.dart b/lib/web_ui/lib/src/engine.dart
index 9baff43..93cf660 100644
--- a/lib/web_ui/lib/src/engine.dart
+++ b/lib/web_ui/lib/src/engine.dart
@@ -61,6 +61,7 @@
 part 'engine/houdini_canvas.dart';
 part 'engine/html_image_codec.dart';
 part 'engine/keyboard.dart';
+part 'engine/mouse_cursor.dart';
 part 'engine/onscreen_logging.dart';
 part 'engine/path_to_svg.dart';
 part 'engine/picture.dart';
@@ -216,6 +217,7 @@
   };
 
   Keyboard.initialize();
+  MouseCursor.initialize();
 }
 
 class _NullTreeSanitizer implements html.NodeTreeSanitizer {
diff --git a/lib/web_ui/lib/src/engine/mouse_cursor.dart b/lib/web_ui/lib/src/engine/mouse_cursor.dart
new file mode 100644
index 0000000..fcd9fc2
--- /dev/null
+++ b/lib/web_ui/lib/src/engine/mouse_cursor.dart
@@ -0,0 +1,45 @@
+// 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.
+
+// @dart = 2.6
+part of engine;
+
+/// Provides mouse cursor bindings, such as the `flutter/mousecursor` channel.
+class MouseCursor {
+  /// 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;
+
+  MouseCursor._() {}
+
+  // The kind values must be kept in sync with flutter's
+  // rendering/mouse_cursor.dart
+  static const Map<String, String> _kindToCssValueMap = <String, String>{
+    'none': 'none',
+    'basic': 'default',
+    'click': 'pointer',
+    'text': 'text',
+    'forbidden': 'not-allowed',
+    'grab': 'grab',
+    'grabbing': 'grabbing',
+  };
+  static String _mapKindToCssValue(String kind) {
+    return _kindToCssValueMap[kind] ?? 'default';
+  }
+
+  void activateSystemCursor(String kind) {
+    domRenderer.setElementStyle(
+      domRenderer.glassPaneElement,
+      'cursor',
+      _mapKindToCssValue(kind),
+    );
+  }
+}
diff --git a/lib/web_ui/lib/src/engine/window.dart b/lib/web_ui/lib/src/engine/window.dart
index 9d8afd9..a817b59 100644
--- a/lib/web_ui/lib/src/engine/window.dart
+++ b/lib/web_ui/lib/src/engine/window.dart
@@ -536,6 +536,16 @@
         textEditing.channel.handleTextInput(data, callback);
         return;
 
+      case 'flutter/mousecursor':
+        const MethodCodec codec = StandardMethodCodec();
+        final MethodCall decoded = codec.decodeMethodCall(data);
+        final Map<dynamic, dynamic> arguments = decoded.arguments;
+        switch (decoded.method) {
+          case 'activateSystemCursor':
+            MouseCursor.instance.activateSystemCursor(arguments['kind']);
+        }
+        return;
+
       case 'flutter/web_test_e2e':
         const MethodCodec codec = JSONMethodCodec();
         _replyToPlatformMessage(